079
creational

Flyweight

Reference Wikipedia ↗
Flyweight — class diagram
Plate 079 class diagram

The Flyweight pattern is a structural design pattern that aims to minimize memory usage or computational costs by sharing as much data as possible between similar objects. It achieves this by separating the object state into intrinsic and extrinsic parts. Intrinsic state is shared and immutable, held within the flyweight object itself, while extrinsic state is unique to each object and passed to the flyweight when needed.

This pattern is particularly useful when dealing with a large number of objects that contain redundant information. By sharing the common, intrinsic state, you significantly reduce the memory footprint. It’s often employed in applications like text editors, graphics editors, or game development, where numerous similar objects (characters, graphical elements, etc.) need to be managed efficiently.

Usage

  • Text Editors/Word Processors: Representing characters in a document. Each character might have a different font, size, and color (extrinsic state), while the glyph data for the character itself is shared (intrinsic state).
  • Game Development: Managing game entities like trees, bushes, or rocks. Many instances of these entities might share the same visual model and properties (intrinsic state), while their position, rotation, and state (alive/destroyed) are specific to each instance (extrinsic state).
  • Database Connection Pooling: Sharing database connections across multiple requests. The connection details are intrinsic, while the specific query and result set are extrinsic.
  • Image Sprites: In web development, combining many small images into a single larger image (sprite) and using CSS to display only the required portion. The sprite image is intrinsic, and its position and size within the page is extrinsic.

Examples

  1. Java’s String Pool: Java internally uses a flyweight-like mechanism with the String pool. When you create a string literal, the JVM first checks if a string with the same value already exists in the pool. If it does, it returns a reference to the existing string; otherwise, it creates a new one and adds it to the pool. This avoids creating duplicate string objects with identical content, saving memory.

  2. React’s useMemo hook: While not a direct implementation of the Flyweight pattern, useMemo serves a similar purpose by memoizing the result of a function. If the dependencies of the function remain unchanged between renders, useMemo returns the cached result instead of re-executing the function. This shares computational effort and the resulting object between render cycles, effectively making it a lightweight, shared resource.

Specimens

14 implementations
Specimen 079.01 Dart View specimen ↗

The Flyweight pattern aims to minimize memory usage by sharing objects that are similar. It separates an object’s state into intrinsic (shared) and extrinsic (unique) parts. Intrinsic state is stored in the flyweight object, while extrinsic state is passed to the flyweight when needed. This is particularly useful when dealing with a large number of similar objects.

This Dart example demonstrates the Flyweight pattern with Character as the flyweight. The Character stores the intrinsic character itself (the glyph). Text holds references to these shared Character objects and manages the extrinsic state – the color and position of each character instance. The CharacterFactory ensures only one instance of each character exists, providing the flyweight functionality. This approach is idiomatic Dart due to its object-oriented nature and use of classes and factories for managing object creation and state.

// Flyweight interface
abstract class Character {
  void display(String context, int x, int y);
}

// Concrete Flyweight
class ConcreteCharacter implements Character {
  final String character;

  ConcreteCharacter(this.character);

  @override
  void display(String context, int x, int y) {
    print('Character: $character, Context: $context, Position: ($x, $y)');
  }
}

// Flyweight Factory
class CharacterFactory {
  final Map<String, Character> _characters = {};

  Character getCharacter(String character) {
    if (!_characters.containsKey(character)) {
      _characters[character] = ConcreteCharacter(character);
    }
    return _characters[character]!;
  }
}

// Flyweight User (Client)
class Text {
  final CharacterFactory characterFactory;
  final List<Character> characters = [];
  final List<String> contexts = [];
  final List<int> xPositions = [];
  final List<int> yPositions = [];

  Text(this.characterFactory);

  void addCharacter(String character, String context, int x, int y) {
    final charFlyweight = characterFactory.getCharacter(character);
    characters.add(charFlyweight);
    contexts.add(context);
    xPositions.add(x);
    yPositions.add(y);
  }

  void display() {
    for (int i = 0; i < characters.length; i++) {
      characters[i].display(contexts[i], xPositions[i], yPositions[i]);
    }
  }
}

void main() {
  final factory = CharacterFactory();
  final text = Text(factory);

  text.addCharacter('a', 'sentence1', 10, 20);
  text.addCharacter('b', 'sentence1', 30, 20);
  text.addCharacter('a', 'sentence2', 10, 40);
  text.addCharacter('c', 'sentence2', 50, 40);

  text.display();
}