La belleza de los patrones de diseño 54-Modo Flyweight (Parte 1): ¿Cómo usar el modo Flyweight para optimizar el uso de memoria de los editores de texto?

54 | Modo Flyweight (Parte 1): ¿Cómo usar el modo Flyweight para optimizar el uso de memoria de los editores de texto?

En la última lección, hablamos sobre el modo de composición. El modo de combinación no se usa comúnmente y se usa principalmente en escenarios donde los datos se pueden representar como una estructura de árbol y se pueden resolver mediante algoritmos transversales de árbol. Hoy, aprendamos un patrón de uso menos común, Patrón de diseño de peso mosca . Este es el último patrón estructural que aprenderemos.

Similar a todos los demás patrones de diseño, el principio y la implementación del patrón Flyweight son muy simples. Hoy os lo explicaré a través de dos ejemplos prácticos de juegos de mesa y editores de texto. Además, también hablaré sobre sus diferencias y conexiones con singletons, cachés y pools de objetos. En la próxima lección, lo llevaré a analizar la aplicación del modo flyweight en Java Integer y String.

Sin más preámbulos, ¡comencemos oficialmente el estudio de hoy!

Principio e implementación del modo Flyweight

El llamado "yuan volador", como sugiere su nombre, es una unidad compartida. El propósito del patrón de peso mosca es reutilizar objetos y ahorrar memoria, siempre que el objeto de peso mosca sea un objeto inmutable.

Específicamente, cuando hay una gran cantidad de objetos repetidos en un sistema, si estos objetos repetidos son objetos inmutables, podemos usar el patrón flyweight para diseñar los objetos como flyweights y solo mantener una instancia en la memoria para varias referencias de código. Esto puede reducir la cantidad de objetos en la memoria y ahorrar memoria. De hecho, no solo se pueden diseñar los mismos objetos como pesos mosca, sino que para objetos similares, también podemos extraer las mismas partes (campos) de estos objetos y diseñarlos como pesos mosca, de modo que una gran cantidad de objetos similares puedan hacer referencia a estos. pesos mosca

Aquí explicaré un poco, el "objeto inmutable" en la definición significa que una vez que se inicializa a través del constructor, su estado (variables miembro o atributos del objeto) no se modificará nuevamente. Por lo tanto, los objetos inmutables no pueden exponer ningún método como set() que modifique el estado interno. La razón por la que se requiere que el peso ligero sea un objeto inmutable es porque será compartido y utilizado por múltiples códigos, para evitar que una parte del código modifique el peso ligero y afecte a otros códigos que lo usan.

A continuación, explicamos el modo de peso mosca a través de un ejemplo simple.

Supongamos que estamos desarrollando un juego de mesa (como el ajedrez). Hay miles de "salas" en una sala de juegos, y cada habitación corresponde a una partida de ajedrez. El juego de ajedrez debe guardar los datos de cada pieza de ajedrez, tales como: tipo de pieza de ajedrez (general, fase, soldado, cañón, etc.), color de la pieza de ajedrez (cuadrado rojo, cuadrado negro) y la posición de la pieza de ajedrez en el juego. Usando estos datos, podemos mostrar un tablero completo al jugador. El código específico es el siguiente. Entre ellos, la clase ChessPiece representa una pieza de ajedrez y la clase ChessBoard representa un juego de ajedrez, que almacena la información de 30 piezas de ajedrez en el ajedrez.

public class ChessPiece {//棋子
  private int id;
  private String text;
  private Color color;
  private int positionX;
  private int positionY;

  public ChessPiece(int id, String text, Color color, int positionX, int positionY) {
    this.id = id;
    this.text = text;
    this.color = color;
    this.positionX = positionX;
    this.positionY = positionX;
  }

  public static enum Color {
    RED, BLACK
  }

  // ...省略其他属性和getter/setter方法...
}

public class ChessBoard {//棋局
  private Map<Integer, ChessPiece> chessPieces = new HashMap<>();

  public ChessBoard() {
    init();
  }

  private void init() {
    chessPieces.put(1, new ChessPiece(1, "車", ChessPiece.Color.BLACK, 0, 0));
    chessPieces.put(2, new ChessPiece(2,"馬", ChessPiece.Color.BLACK, 0, 1));
    //...省略摆放其他棋子的代码...
  }

  public void move(int chessPieceId, int toPositionX, int toPositionY) {
    //...省略...
  }
}

Para registrar la situación actual del ajedrez en cada habitación, necesitamos crear un objeto de juego ChessBoard para cada habitación. Debido a que hay miles de salas en la sala de juegos (de hecho, hay muchas salas de juegos con millones de personas en línea al mismo tiempo), guardar tantos objetos del juego consumirá mucha memoria. ¿Hay alguna forma de ahorrar memoria?

En este momento, el modo de peso mosca puede resultar útil. Al igual que la implementación de ahora, habrá una gran cantidad de objetos similares en la memoria. La identificación, el texto y el color de estos objetos similares son todos iguales, a excepción de positionX y positionY. De hecho, podemos separar los atributos de identificación, texto y color de las piezas de ajedrez, diseñarlas como clases independientes y usarlas como pesos mosca para reutilizar múltiples tableros de ajedrez. De esta forma, el tablero de ajedrez solo necesita registrar la información de posición de cada pieza de ajedrez. La implementación del código específico es la siguiente:

// 享元类
public class ChessPieceUnit {
  private int id;
  private String text;
  private Color color;

  public ChessPieceUnit(int id, String text, Color color) {
    this.id = id;
    this.text = text;
    this.color = color;
  }

  public static enum Color {
    RED, BLACK
  }

  // ...省略其他属性和getter方法...
}

public class ChessPieceUnitFactory {
  private static final Map<Integer, ChessPieceUnit> pieces = new HashMap<>();

  static {
    pieces.put(1, new ChessPieceUnit(1, "車", ChessPieceUnit.Color.BLACK));
    pieces.put(2, new ChessPieceUnit(2,"馬", ChessPieceUnit.Color.BLACK));
    //...省略摆放其他棋子的代码...
  }

  public static ChessPieceUnit getChessPiece(int chessPieceId) {
    return pieces.get(chessPieceId);
  }
}

public class ChessPiece {
  private ChessPieceUnit chessPieceUnit;
  private int positionX;
  private int positionY;

  public ChessPiece(ChessPieceUnit unit, int positionX, int positionY) {
    this.chessPieceUnit = unit;
    this.positionX = positionX;
    this.positionY = positionY;
  }
  // 省略getter、setter方法
}

public class ChessBoard {
  private Map<Integer, ChessPiece> chessPieces = new HashMap<>();

  public ChessBoard() {
    init();
  }

  private void init() {
    chessPieces.put(1, new ChessPiece(
            ChessPieceUnitFactory.getChessPiece(1), 0,0));
    chessPieces.put(1, new ChessPiece(
            ChessPieceUnitFactory.getChessPiece(2), 1,0));
    //...省略摆放其他棋子的代码...
  }

  public void move(int chessPieceId, int toPositionX, int toPositionY) {
    //...省略...
  }
}

En la implementación del código anterior, usamos la clase de fábrica para almacenar en caché la información de ChessPieceUnit (es decir, id, texto, color). La ChessPieceUnit obtenida a través de la clase de fábrica es el peso mosca. Todos los objetos ChessBoard comparten estos 30 objetos ChessPieceUnit (porque solo hay 30 piezas en el ajedrez). Antes de usar el modo Flyweight, para registrar 10 000 partidas de ajedrez, necesitamos crear un objeto ChessPieceUnit con 300 000 (30*10 000) piezas. Usando el modo de peso mosca, solo necesitamos crear 30 objetos de peso mosca para compartir todos los juegos de ajedrez, lo que ahorra mucho memoria.

Ahora que el principio del modo de peso ligero está terminado, resumamos su estructura de código. De hecho, la implementación de su código es muy simple, principalmente a través del modo de fábrica.En la clase de fábrica, se usa un mapa para almacenar en caché los objetos de disfrute creados para lograr el propósito de reutilización.

Aplicación del modo flyweight en el editor de texto.

Después de comprender el principio y la implementación del modo de peso ligero, veamos otro ejemplo, que se proporciona en el título del artículo: ¿Cómo usar el modo de peso ligero para optimizar el uso de memoria del editor de texto?

Puede pensar en el editor de texto mencionado aquí como Word para Office. Sin embargo, para simplificar los antecedentes de los requisitos, asumimos que este editor de texto solo implementa funciones de edición de texto y no incluye funciones de edición complejas, como imágenes y tablas. Para el editor de texto simplificado, si queremos representar un archivo de texto en memoria, solo necesitamos registrar dos partes de información, el texto y el formato, el formato incluye información como la fuente, el tamaño y el color del texto.

Aunque en la redacción de documentos reales, generalmente formateamos el texto según el tipo de texto (título, cuerpo...), el título es un formato, el cuerpo es otro formato, y así sucesivamente. Sin embargo, en teoría, podemos establecer un formato diferente para cada texto en el archivo de texto. Para lograr una configuración de formato tan flexible, y la implementación del código no es demasiado complicada, tratamos cada texto como un objeto independiente e incluimos su información de formato en él. Un ejemplo de código específico es el siguiente:

public class Character {//文字
  private char c;

  private Font font;
  private int size;
  private int colorRGB;

  public Character(char c, Font font, int size, int colorRGB) {
    this.c = c;
    this.font = font;
    this.size = size;
    this.colorRGB = colorRGB;
  }
}

public class Editor {
  private List<Character> chars = new ArrayList<>();

  public void appendCharacter(char c, Font font, int size, int colorRGB) {
    Character character = new Character(c, font, size, colorRGB);
    chars.add(character);
  }
}

En el editor de texto, cada vez que escribimos un texto, llamaremos al método appendCharacter() en la clase Editor para crear un nuevo objeto Carácter y guardarlo en la matriz de caracteres. Si hay decenas de miles, cientos de miles o cientos de miles de texto en un archivo de texto, entonces necesitamos almacenar tantos objetos de caracteres en la memoria. ¿Hay alguna manera de ahorrar un poco de memoria?

De hecho, en un archivo de texto no se utilizan demasiados formatos de fuente, después de todo, es poco probable que alguien configure cada texto en un formato diferente. Por lo tanto, para el formato de fuente, podemos diseñarlo como un peso mosca, de modo que se puedan compartir y usar diferentes textos. De acuerdo con esta idea de diseño, refactorizamos el código anterior. El código refactorizado se ve así:

public class CharacterStyle {
  private Font font;
  private int size;
  private int colorRGB;

  public CharacterStyle(Font font, int size, int colorRGB) {
    this.font = font;
    this.size = size;
    this.colorRGB = colorRGB;
  }

  @Override
  public boolean equals(Object o) {
    CharacterStyle otherStyle = (CharacterStyle) o;
    return font.equals(otherStyle.font)
            && size == otherStyle.size
            && colorRGB == otherStyle.colorRGB;
  }
}

public class CharacterStyleFactory {
  private static final List<CharacterStyle> styles = new ArrayList<>();

  public static CharacterStyle getStyle(Font font, int size, int colorRGB) {
    CharacterStyle newStyle = new CharacterStyle(font, size, colorRGB);
    for (CharacterStyle style : styles) {
      if (style.equals(newStyle)) {
        return style;
      }
    }
    styles.add(newStyle);
    return newStyle;
  }
}

public class Character {
  private char c;
  private CharacterStyle style;

  public Character(char c, CharacterStyle style) {
    this.c = c;
    this.style = style;
  }
}

public class Editor {
  private List<Character> chars = new ArrayList<>();

  public void appendCharacter(char c, Font font, int size, int colorRGB) {
    Character character = new Character(c, CharacterStyleFactory.getStyle(font, size, colorRGB));
    chars.add(character);
  }
}

Modo Flyweight vs singleton, caché, grupo de objetos

En la explicación anterior, mencionamos las palabras "compartir", "caché" y "reutilizar" muchas veces, entonces, ¿cuál es la diferencia entre ellas y los conceptos de singleton, caché y conjunto de objetos? Hagamos una comparación sencilla.

Primero veamos la diferencia entre el patrón de peso mosca y el singleton.

En el modo singleton, una clase solo puede crear un objeto, mientras que en el modo Flyweight, una clase puede crear múltiples objetos y cada objeto es compartido por múltiples referencias de código. De hecho, el patrón Flyweight es algo similar a la variante del singleton mencionada anteriormente: instancias múltiples.

También hemos mencionado muchas veces antes que para distinguir entre los dos patrones de diseño, no podemos simplemente mirar la implementación del código, sino mirar la intención del diseño, es decir, el problema a resolver. Aunque existen muchas similitudes entre el modo flyweight y las instancias múltiples desde el punto de vista de la implementación del código, son completamente diferentes desde el punto de vista de la intención del diseño. El modo Flyweight se usa para reutilizar objetos y ahorrar memoria, mientras que el modo de múltiples instancias se usa para limitar la cantidad de objetos.

Veamos la diferencia entre el modo flyweight y el caché.

En la implementación del patrón Flyweight, usamos la clase de fábrica para "almacenar en caché" los objetos creados. El "caché" aquí en realidad significa "almacenamiento", que es diferente de lo que solemos llamar "caché de base de datos", "caché de CPU" y "caché de MemCache". El caché del que solemos hablar es principalmente para mejorar la eficiencia del acceso, no para reutilizar.

Finalmente, veamos la diferencia entre el modo flyweight y el grupo de objetos.

Los grupos de objetos, los grupos de conexiones (como los grupos de conexiones de bases de datos), los grupos de subprocesos, etc. también se pueden reutilizar, entonces, ¿cuál es la diferencia entre ellos y el modo Flyweight?

Es posible que esté familiarizado con los grupos de conexiones y los grupos de subprocesos, pero no con los grupos de objetos, por lo que aquí explico brevemente los grupos de objetos. En un lenguaje de programación como C++, la gestión de la memoria es responsabilidad del programador. Para evitar la fragmentación de la memoria causada por la creación y publicación frecuente de objetos, podemos solicitar previamente un espacio de memoria continua, que es el conjunto de objetos que se menciona aquí. Cada vez que se crea un objeto, sacamos directamente un objeto inactivo del grupo de objetos para su uso. Después de que se usa el objeto, se vuelve a colocar en el grupo de objetos para su posterior reutilización en lugar de liberarse directamente.

Aunque los grupos de objetos, los grupos de conexiones, los grupos de subprocesos y los modos Flyweight son todos para su reutilización, si observamos más de cerca la palabra "reutilizar", las tecnologías de agrupación, como los grupos de objetos, los grupos de conexiones y los grupos de subprocesos. La "reutilización" de "reutilizar" y "reutilizar" en el modo Flyweight son en realidad conceptos diferentes.

"Reutilizar" en la tecnología de agrupación puede entenderse como "reutilizar", el objetivo principal es ahorrar tiempo (como tomar una conexión del grupo de bases de datos sin volver a crearla). En cualquier momento, cada objeto, conexión e hilo no se utilizará en varios lugares, sino que será utilizado exclusivamente por un usuario. Una vez que se complete el uso, se devolverá al grupo y será reutilizado por otros usuarios. La "reutilización" en modo Flyweight puede entenderse como un "uso compartido", que es compartido por todos los usuarios a lo largo del ciclo de vida, y el objetivo principal es ahorrar espacio.

revisión clave

Bueno, eso es todo por el contenido de hoy. Resumamos y revisemos el contenido que necesita dominar.

1. El principio del modo Flyweight

El llamado "yuan volador", como sugiere su nombre, es una unidad compartida. El propósito del patrón de peso mosca es reutilizar objetos y ahorrar memoria, siempre que el objeto de peso mosca sea un objeto inmutable. Específicamente, cuando hay una gran cantidad de objetos duplicados en un sistema, podemos usar el patrón flyweight para diseñar objetos como flyweights, y solo mantener una instancia en la memoria para varias referencias de código, lo que puede reducir la memoria. La cantidad de objetos para ahorrar memoria. . De hecho, no solo se pueden diseñar los mismos objetos como pesos mosca, sino que para objetos similares, también podemos extraer las mismas partes (campos) de estos objetos y diseñarlos como pesos mosca, de modo que esta gran cantidad de objetos similares puedan referirse a estos pesos mosca

2. Realización del modo de peso mosca

La implementación del código del modo de disfrute es muy simple, principalmente a través del modo de fábrica.En la clase de fábrica, se usa un Mapa o Lista para almacenar en caché los objetos de disfrute creados para lograr el propósito de reutilización.

3. Modo Flyweight VS singleton, caché, grupo de objetos

También hemos mencionado muchas veces antes que para distinguir entre los dos patrones de diseño, no podemos simplemente mirar la implementación del código, sino mirar la intención del diseño, es decir, el problema a resolver. La diferencia aquí no es una excepción.

Podemos resumir la diferencia entre ellos en pocas palabras. La aplicación del patrón singleton es para garantizar que el objeto sea globalmente único. El modo Flyweight se aplica para realizar la reutilización de objetos y ahorrar memoria. El almacenamiento en caché es para mejorar la eficiencia del acceso, no para multiplexar. La "reutilización" en la tecnología de pooling se entiende como "reutilización", principalmente para ahorrar tiempo.

discusión en clase

  1. En el ejemplo del ajedrez y los juegos de cartas, ¿es necesario diseñar ChessPiecePosition como peso mosca?
  2. En el ejemplo de un editor de texto, llamar al método getStyle() de la clase CharacterStyleFactory requiere recorrer y buscar en la matriz de estilos, y la búsqueda transversal lleva mucho tiempo. ¿Se puede optimizar?

Bienvenido a dejar un mensaje y compartir sus pensamientos conmigo. Si obtienes algo, puedes compartir este artículo con tus amigos.

Supongo que te gusta

Origin blog.csdn.net/fegus/article/details/130498806
Recomendado
Clasificación