Skip to content

Flyweight Pattern

Intent

The Flyweight pattern uses sharing to support large numbers of fine-grained objects efficiently. A flyweight is a shared object that can be used in multiple contexts simultaneously, acting as an independent object in each context.

Problem

When your application needs to create a huge number of similar objects, memory consumption can become prohibitive:

  • Creating millions of objects consumes too much memory
  • Most of the object's state can be shared between objects
  • Creating and destroying objects is expensive
  • Objects differ only in a small portion of their state

For example, in a text editor, each character might have properties like font, size, color, and style. If you store these properties in every character object, a document with 1 million characters would consume enormous amounts of memory, even though most characters share the same formatting.

Solution

The Flyweight pattern suggests separating an object's state into two parts:

  1. Intrinsic state - The state that is shared across many objects and doesn't change
  2. Extrinsic state - The state that varies between objects and is context-dependent

The flyweight stores only the intrinsic state, making it shareable. Extrinsic state is stored or computed by client objects and passed to the flyweight when operations are invoked. This dramatically reduces memory consumption when many objects share the same intrinsic state.

Structure

Flyweight Pattern Diagram 0

Participants

  • Flyweight - Declares an interface for operations with extrinsic state - Stores intrinsic state (shared) - Must be shareable - In our example: Flyweight class with sharedState_
  • ConcreteFlyweight - Implements the Flyweight interface - Stores intrinsic state - Must be shareable across contexts - In our example: Flyweight implementation
  • FlyweightFactory - Creates and manages flyweight objects - Ensures flyweights are shared properly - Returns existing flyweights when requested - In our example: FlyweightFactory class
  • Client - Maintains references to flyweights - Computes or stores extrinsic state - Passes extrinsic state to flyweight operations - In our example: Context struct and client code

Implementation

Flyweight Class

class Flyweight {
private:
    std::string sharedState_;  // Intrinsic state

public:
    explicit Flyweight(const std::string& sharedState)
        : sharedState_(sharedState) {}

    std::string getSharedState() const {
        return sharedState_;
    }

    void operation(const std::string& uniqueState) const {
        std::cout << "Flyweight: Displaying shared (" << sharedState_
                  << ") and unique (" << uniqueState << ") state." << std::endl;
    }
};

FlyweightFactory Class

class FlyweightFactory {
private:
    std::unordered_map<std::string, std::shared_ptr<Flyweight>> flyweights_;

public:
    std::shared_ptr<Flyweight> getFlyweight(const std::string& sharedState) {
        std::string key = sharedState;

        auto it = flyweights_.find(key);
        if (it != flyweights_.end()) {
            std::cout << "Reusing existing flyweight." << std::endl;
            return it->second;
        } else {
            std::cout << "Creating new flyweight." << std::endl;
            auto flyweight = std::make_shared<Flyweight>(sharedState);
            flyweights_[key] = flyweight;
            return flyweight;
        }
    }
};

Context Usage

1
2
3
4
5
6
7
8
struct Context {
    std::shared_ptr<Flyweight> flyweight;  // Shared intrinsic state
    std::string uniqueState;                // Extrinsic state

    void operation() const {
        flyweight->operation(uniqueState);
    }
};

Usage Example

int main() {
    FlyweightFactory factory;
    std::vector<Context> contexts;

    // Shared state: "brand-model-color"
    auto fw1 = factory.getFlyweight("Toyota-Camry-white");
    contexts.emplace_back(fw1, "Alice-AJ5678");  // Unique state

    // Reuses the same flyweight
    auto fw2 = factory.getFlyweight("Toyota-Camry-white");
    contexts.emplace_back(fw2, "Bob-BW9012");

    // Creates a new flyweight
    auto fw3 = factory.getFlyweight("BMW-M5-red");
    contexts.emplace_back(fw3, "John-JS1234");

    // Use all contexts
    for (const auto& context : contexts) {
        context.operation();
    }

    return 0;
}

Applicability

Use the Flyweight pattern when:

  • An application uses a large number of objects - Memory cost is high - Example: Text editor with millions of characters
  • Storage costs are high because of the sheer quantity of objects - Most object state can be made extrinsic - Example: Game with thousands of particles
  • Many groups of objects can be replaced by relatively few shared objects - Objects are similar and differ only in extrinsic state - Example: Forest with many trees of the same type
  • The application doesn't depend on object identity - Flyweights are shared, so identity checks will fail - Can't use object identity (pointer comparison)

Consequences

Benefits

  1. Reduced memory consumption - Share intrinsic state across many objects - Can save 90%+ memory in optimal cases - Critical for large-scale applications

  2. Improved performance - Fewer objects to create and destroy - Better cache locality (shared objects) - Reduced garbage collection pressure

  3. Scalability - Can handle much larger numbers of objects - Enables features that would be impractical otherwise - Example: Rendering millions of particles

Drawbacks

  1. Increased complexity - Need to separate intrinsic and extrinsic state - Requires careful design - More code to maintain

  2. Runtime costs - Computing/passing extrinsic state adds overhead - Factory lookup has cost - May offset memory savings in some cases

  3. No object identity - Can't rely on pointer comparison - Shared objects can't be modified - Must be immutable (or carefully synchronized)

  4. Thread safety concerns - Shared flyweights must be thread-safe - Factory access may need synchronization - More complex in multi-threaded environments

Intrinsic vs Extrinsic State

Intrinsic State (Shared)

Characteristics: - Stored in the flyweight - Independent of context - Shareable across objects - Immutable (shouldn't change)

Examples: - Character formatting: font, size, color, style - Tree properties: texture, model, color - Particle properties: sprite, animation

Extrinsic State (Unique)

Characteristics: - Stored in context or computed - Depends on flyweight's context - Varies per object - Can change

Examples: - Character properties: position in document - Tree properties: position, scale, rotation - Particle properties: position, velocity

Real-world Examples

  1. Text Editors - Intrinsic: Font, size, color, style (shared across characters) - Extrinsic: Position, character value - Savings: 90%+ memory for large documents

  2. Game Engines - Particle Systems - Intrinsic: Sprite, texture, animation - Extrinsic: Position, velocity, lifetime - Enables millions of particles simultaneously

  3. Game Engines - Terrain - Intrinsic: Tile texture, type, properties - Extrinsic: Position in world - Massive terrain maps with minimal memory

  4. Graphics - Icons/Sprites - Intrinsic: Image data, sprite sheet - Extrinsic: Screen position, scale, rotation - Reuse same image in multiple places

  5. String Pooling - Intrinsic: String content - Extrinsic: References to string - Java/C# string interning uses this

  6. UI Frameworks - Styles - Intrinsic: Font, colors, margins, padding - Extrinsic: Component position, text content - Share styles across many components

Implementation Considerations

1. Managing Extrinsic State

Client is responsible for:

1
2
3
// Client stores or computes extrinsic state
std::string extrinsicState = computeExtrinsicState();
flyweight->operation(extrinsicState);

2. Removing Flyweights

Consider when flyweights should be removed: - Never (simplest, works if set is bounded) - Reference counting (remove when no longer used) - Garbage collection (automatic cleanup)

3. Sharing Flyweights

Factory ensures sharing:

1
2
3
4
5
6
7
8
auto getFlyweight(const std::string& key) {
    if (flyweights_.count(key)) {
        return flyweights_[key];  // Reuse
    }
    auto fw = std::make_shared<Flyweight>(key);
    flyweights_[key] = fw;  // Store for future reuse
    return fw;
}

4. Immutability

Flyweights should be immutable: - Prevents one context from affecting others - Enables safe sharing - Thread-safe by default

5. Factory Variations

Different factory approaches:

1
2
3
4
5
6
7
// Lazy creation (create on demand)
auto getFlyweight(key) { /*...*/ }

// Eager creation (create all upfront)
FlyweightFactory(initialStates) { /*...*/ }

// Hybrid (common ones upfront, others on demand)

Flyweight vs Similar Patterns

Flyweight vs Singleton

Aspect Flyweight Singleton
Number Many instances (one per unique intrinsic state) Single instance
Purpose Memory optimization through sharing Single point of access
State Immutable shared state Can have mutable state
Factory Factory manages pool Not typically needed

Flyweight vs Prototype

Aspect Flyweight Prototype
Creation Share existing objects Clone/copy objects
Identity Same object used in multiple contexts Each context has separate copy
Memory Saves memory through sharing Each copy consumes memory
Mutability Immutable Can be mutable

Flyweight vs State

Aspect Flyweight State
Purpose Share structure Vary behavior
Mutability Immutable State can change
Quantity Many flyweights Typically few states

Sample Output

=== Flyweight Pattern Demo ===

Client: Creating FlyweightFactory with initial data:
FlyweightFactory: I have 3 initial flyweights:
  - Chevrolet-Camaro-pink
  - Mercedes-C300-black
  - BMW-M5-red

--- Adding cars with existing flyweights ---
Client: Adding a car to database.
FlyweightFactory: Reusing existing flyweight.

--- Adding cars that create new flyweights ---
Client: Adding a car to database.
FlyweightFactory: Can't find a flyweight, creating new one.

Client: Adding a car to database.
FlyweightFactory: Reusing existing flyweight.

FlyweightFactory: I have 4 flyweights:
  - BMW-M5-red
  - Mercedes-C300-black
  - Toyota-Camry-white
  - Chevrolet-Camaro-pink

--- Memory Analysis ---
Total contexts (car records): 5
Unique flyweights (shared state): 4
Memory saved: Instead of storing full state in 5 objects,
we only store 4 flyweights plus 5 unique states.

=== Real-world Scenario ===

Scenario: Text Editor with Character Formatting
- Intrinsic state (shared): font, size, color, style
- Extrinsic state (unique): position, character

Without Flyweight:
- Document with 1,000,000 characters
- Each character stores: font, size, color, style, position, char
- Memory: ~100 bytes × 1,000,000 = ~100 MB

With Flyweight:
- 1,000,000 characters share ~100 flyweights (font combinations)
- Each flyweight stores: font, size, color, style (~50 bytes)
- Each character stores: position, char, flyweight pointer (~10 bytes)
- Memory: (100 × 50) + (1,000,000 × 10) = ~10 MB
- Memory savings: 90%!

Key Takeaways

  1. Flyweight optimizes memory by sharing common state across many objects
  2. Separates state into intrinsic (shared) and extrinsic (unique) parts
  3. Factory manages and ensures proper sharing of flyweights
  4. Flyweights must be immutable to be safely shared
  5. Can achieve 90%+ memory savings in optimal scenarios
  6. Critical for applications with millions of similar objects
  7. Commonly used in game engines, text editors, and graphics systems
  8. Trade-off: Saves memory but adds complexity and runtime overhead
  9. Cannot rely on object identity since objects are shared
  10. Factory pattern is typically used to manage flyweight instances