Flyweight Design Pattern: An Easy Way

Flyweight Design Pattern: An Easy Way

18 Sep 2024
Intermediate
71.2K Views
29 min read
Learn with an interactive course and practical hands-on labs

⭐ .NET Design Patterns Course: Design Patterns in C# Online Training

Flyweight Design Pattern

Flyweight Design Pattern is a structural design pattern of Gangs of Four Design pattern which is used to reduce memory usage when a program needs to create a large number of similar objects. It achieves this by sharing common data between objects rather than storing duplicate information. The pattern is useful when many objects share the same intrinsic properties but differ in extrinsic properties, which can be passed from the outside.

In the Design Pattern tutorial, we will explore what is the flyweight design pattern?, the components of flyweight design factory, the structure of flyweight design factory, example of flyweight design factory,the Implementation of the flyweight design factory, application of flyweight design factory, advantages, and many more.

What is a Flyweight Design Pattern?

The Flyweight Design Pattern is a structured pattern that shares objects in similar or identical states to reduce memory use and enhance performance. It is especially helpful when working with several items that may be combined into shared flyweight objects to lower the total memory footprint. Some key concepts of flyweight design pattern:

  • Object Reuse:Flyweight items are reused from a pool of shared objects, improving performance and reducing memory consumption by avoiding repeated creation.
  • Shared State:Objects are split into shared intrinsic state and variable extrinsic state, with the latter passed to flyweight objects as needed.
  • Factory for Flyweights: A factory class handles the creation and reuse of flyweight objects, providing shared instances or creating new ones only when necessary.
Read More:
Different Types of Design Patterns
Different Types of Software Design Principles

Components of Flyweight Design Pattern

There are four major components of the flyweight design pattern that are as follows:

Components of Flyweight Design Pattern

  1. Flyweight Interface or Class
  2. Concrete Flyweight Classes
  3. Flyweight Factory
  4. Client

1. Flyweight Interface or Class

The Flyweight Interface defines common methods for all flyweight objects and handles extrinsic state passed by the client. It ensures a consistent way to manage shared intrinsic state and supports efficient object reuse.

You should know:
Intrinsic state: The part of the object that is shared among many instances.
Extrinsic state: The part of the object that is unique to each instance.

2. Concrete Flyweight Classes

Concrete Flyweight Classes implement the Flyweight interface and store the intrinsic (shared) state that is common across multiple objects. They manage and provide this shared state while allowing clients to pass extrinsic (context-specific) data as needed.

3. Flyweight Factory

The Flyweight Factory manages the creation and reuse of flyweight objects, ensuring that shared instances are used whenever possible. It checks if an existing flyweight can be reused or creates a new one if necessary.

4. Client

In the Flyweight Design Pattern, the Client uses flyweight objects and provides the extrinsic (context-specific) state required for their operation. The client requests flyweights from the factory and passes necessary data to them during runtime.

Read More:
Prototype Design Pattern
Strategy Design Pattern
Chain of Responsibility Design Pattern

Structure of Flyweight Design Pattern

  • The Flyweight Design Pattern is an optimization. Before implementing it, make sure your software doesn't have the RAM consumption issue caused by a large number of comparable objects in memory at once. Verify that there are no other viable solutions for this issue.
  • The part of the original object's state that other instances may share is stored in the Flyweight class. There are several applications for a single flyweight item. Intrinsic state is the state that is kept inside a flyweight. Extrinsic refers to the state that is passed to the flyweight's procedures.
  • In the flyweight class, the original object's behavior is often retained. In this scenario, the person calling the flyweight's method also has to supply the necessary extrinsic state bits as arguments. However, if the behavior is transferred to the context class, the connected flyweight would just be used as a data object.
  • The Context Class holds the extrinsic (context-specific) state that varies between flyweight objects. This class is responsible for providing this data to the flyweight objects at runtime, ensuring that the shared intrinsic state remains consistent while accommodating the unique needs of each instance.
  • The client computes or stores flyweights' extrinsic state. The client views a flyweight as a template object that may be customized at runtime by providing certain contextual information as arguments to its functions.

Structure of Flyweight Design Pattern

Implementation of Flyweight Design Pattern

Here are the key steps to implement to implement Flyweight Design Pattern:

  • Create a Flyweight Interface or Abstract Class: This will declare a method that flyweight objects can use.
  • Create Concrete Flyweight Classes: These classes implement the flyweight interface and store the intrinsic state that can be shared.
  • Create a Flyweight Factory: This factory will manage the flyweight objects and ensure that objects are shared instead of being instantiated repeatedly.
  • Client Class: The client code will provide the extrinsic state and interact with the flyweight objects.

Real-World Example of Flyweight Design Pattern

Example- Text Formatting in a Word Processor

Imagine a word processor like Microsoft Word where you might need to render millions of characters on the screen. Each character can have attributes like font type, font size, and color. Instead of creating a new object for every character with these attributes (which would take up a lot of memory), the Flyweight pattern allows shared objects for characters that have the same attributes.

Let’s assume you have a document with a mix of characters like 'A', 'B', and 'C' in various fonts and styles.

  • Intrinsic State: The characters (like 'A', 'B', 'C') can be shared. For example, you don’t need separate objects for every 'A' in the document. They all share the same intrinsic state (glyphs or fonts).
  • Extrinsic State: The position of the character in the document is stored separately for each instance. This would be passed to the object when needed.
 using System;
using System.Collections.Generic;

// Flyweight Class
class CharacterFlyweight
{
    private readonly char symbol;  // Intrinsic state

    public CharacterFlyweight(char symbol)
    {
        this.symbol = symbol;
    }

    public void Display(int size, string color, int position)  // Extrinsic state
    {
        Console.WriteLine($"Character: {symbol} | Font size: {size} | Color: {color} | Position: {position}");
    }
}

// Flyweight Factory to manage shared objects
class FlyweightFactory
{
    private readonly Dictionary<char, CharacterFlyweight> characters = new Dictionary<char, CharacterFlyweight>();

    public CharacterFlyweight GetCharacter(char symbol)
    {
        if (!characters.ContainsKey(symbol))
        {
            characters[symbol] = new CharacterFlyweight(symbol);
        }
        return characters[symbol];
    }
}

// Client Code
class FlyweightExample
{
    static void Main(string[] args)
    {
        FlyweightFactory factory = new FlyweightFactory();

        CharacterFlyweight charA = factory.GetCharacter('A');
        charA.Display(12, "Red", 1);  // Extrinsic state (size, color, position)

        CharacterFlyweight charB = factory.GetCharacter('B');
        charB.Display(14, "Blue", 2);

        // Reusing the same 'A' object
        CharacterFlyweight charA2 = factory.GetCharacter('A');
        charA2.Display(12, "Green", 3);
    }
}

# Python code here
# Flyweight Class
class CharacterFlyweight:
    """Flyweight class that represents shared characters."""
    def __init__(self, symbol):
        self.symbol = symbol  # Intrinsic state

    def display(self, size, color, position):
        """Display method to show extrinsic state."""
        print(f"Character: {self.symbol} | Font size: {size} | Color: {color} | Position: {position}")


# Flyweight Factory to manage shared objects
class FlyweightFactory:
    """Flyweight Factory to manage shared objects."""
    def __init__(self):
        self.characters = {}  # Dictionary to store shared character objects

    def get_character(self, symbol):
        """Return the shared character object or create one if it doesn't exist."""
        if symbol not in self.characters:
            self.characters[symbol] = CharacterFlyweight(symbol)
        return self.characters[symbol]


# Client Code
if __name__ == "__main__":
    factory = FlyweightFactory()

    charA = factory.get_character('A')
    charA.display(12, "Red", 1)  # Extrinsic state (size, color, position)

    charB = factory.get_character('B')
    charB.display(14, "Blue", 2)

    # Reusing the same 'A' object
    charA2 = factory.get_character('A')
    charA2.display(12, "Green", 3)

            
import java.util.HashMap;
import java.util.Map;

// Flyweight Class
class CharacterFlyweight {
    private final char symbol;  // Intrinsic state

    public CharacterFlyweight(char symbol) {
        this.symbol = symbol;
    }

    public void display(int size, String color, int position) {  // Extrinsic state
        System.out.println("Character: " + symbol + " | Font size: " + size + 
                           " | Color: " + color + " | Position: " + position);
    }
}

// Flyweight Factory to manage shared objects
class FlyweightFactory {
    private final Map<Character, CharacterFlyweight> characters = new HashMap<>();

    public CharacterFlyweight getCharacter(char symbol) {
        if (!characters.containsKey(symbol)) {
            characters.put(symbol, new CharacterFlyweight(symbol));
        }
        return characters.get(symbol);
    }
}

// Client Code
public class FlyweightExample {
    public static void main(String[] args) {
        FlyweightFactory factory = new FlyweightFactory();

        CharacterFlyweight charA = factory.getCharacter('A');
        charA.display(12, "Red", 1);  // Extrinsic state (size, color, position)

        CharacterFlyweight charB = factory.getCharacter('B');
        charB.display(14, "Blue", 2);

        // Reusing the same 'A' object
        CharacterFlyweight charA2 = factory.getCharacter('A');
        charA2.display(12, "Green", 3);
    }
}

// Flyweight Class
class CharacterFlyweight {
    constructor(symbol) {
        this.symbol = symbol;  // Intrinsic state
    }

    display(size, color, position) {  // Extrinsic state
        console.log(`Character: ${this.symbol} | Font size: ${size} | Color: ${color} | Position: ${position}`);
    }
}

// Flyweight Factory to manage shared objects
class FlyweightFactory {
    constructor() {
        this.characters = new Map();  // Map to store shared character objects
    }

    getCharacter(symbol) {
        if (!this.characters.has(symbol)) {
            this.characters.set(symbol, new CharacterFlyweight(symbol));
        }
        return this.characters.get(symbol);
    }
}

// Client Code
const factory = new FlyweightFactory();

const charA = factory.getCharacter('A');
charA.display(12, "Red", 1);  // Extrinsic state (size, color, position)

const charB = factory.getCharacter('B');
charB.display(14, "Blue", 2);

// Reusing the same 'A' object
const charA2 = factory.getCharacter('A');
charA2.display(12, "Green", 3);

            
// Flyweight Class
class CharacterFlyweight {
    private readonly symbol: string;  // Intrinsic state

    constructor(symbol: string) {
        this.symbol = symbol;
    }

    public display(size: number, color: string, position: number): void {  // Extrinsic state
        console.log(`Character: ${this.symbol} | Font size: ${size} | Color: ${color} | Position: ${position}`);
    }
}

// Flyweight Factory to manage shared objects
class FlyweightFactory {
    private readonly characters: Map<string, CharacterFlyweight> = new Map();  // Map to store shared character objects

    public getCharacter(symbol: string): CharacterFlyweight {
        if (!this.characters.has(symbol)) {
            this.characters.set(symbol, new CharacterFlyweight(symbol));
        }
        return this.characters.get(symbol)!;  // The non-null assertion operator ensures TypeScript knows the value will be present
    }
}

// Client Code
const factory = new FlyweightFactory();

const charA = factory.getCharacter('A');
charA.display(12, "Red", 1);  // Extrinsic state (size, color, position)

const charB = factory.getCharacter('B');
charB.display(14, "Blue", 2);

// Reusing the same 'A' object
const charA2 = factory.getCharacter('A');
charA2.display(12, "Green", 3);  

Output

Character: A | Font size: 12 | Color: Red | Position: 1
Character: B | Font size: 14 | Color: Blue | Position: 2
Character: A | Font size: 12 | Color: Green | Position: 3

Explanation

In this example,

  • Flyweight Class: Represents a character with a fixed state (symbol) and displays it with variable properties.
  • Flyweight Factory: Manages and reuses instances of CharacterFlyweight to minimize memory usage.
  • Client Code: Uses the factory to obtain and display characters with different properties, reusing instances where possible.
  • Reusability: Demonstrates efficient memory use by sharing character instances across different contexts.

Applications of Flyweight Design Factory

  • Text Editors: Sharing character objects while formatting (font, size, color) is handled as extrinsic data.
  • Graphics and UI Systems: Reusing shape and icon objects with different positions or states.
  • Gaming: Sharing game objects like trees or enemies while passing unique attributes like position or health.
  • Data Caching Systems: Reusing cached objects such as images or database records.
  • Document Printing Systems: Sharing font and graphic objects for printing multiple times with different sizes or styles.
  • Network Connections: Connection pooling by sharing protocols and configurations.
  • Reporting Systems: Reusing graph layouts while passing in different data points.
  • Geospatial Applications (Map Rendering): Sharing shapes of geographical entities while using unique locations and sizes.

When to use Flyweight Design Factory

  • Large Number of Similar Objects: When an application needs to manage a massive number of objects that share similar characteristics (e.g., characters in a text editor, icons in a UI).
  • Memory Constraints: When memory usage is a concern, objects can be shared to reduce memory consumption.
  • Object Creation Overhead: When creating or initializing objects, it is costly, and sharing them optimizes performance.
  • Repetitive Data: When many objects share intrinsic properties (e.g., shape, texture, or character form), but differ in extrinsic properties (e.g., position, color, or state).
  • Cache Optimization: When you want to avoid reloading or recreating the same object frequently, like caching graphical elements or database records.
  • Performance Improvement: Optimizing performance in rendering systems, games, or network connections by reducing the object footprint.

When not to use Flyweight Design Pattern

  • Objects Have No Shared State: If the objects in your system do not share common intrinsic data, there is no benefit in using Flyweight.
  • Objects Are Complex and Mutable: If the objects have a lot of unique, mutable states (extrinsic data), Flyweight might add unnecessary complexity and reduce performance.
  • Low Object Count: If the system does not have a large number of similar objects, the overhead of managing the shared state can outweigh the benefits.
  • Simpler Solutions Exist: If simpler patterns (e.g., object pooling or lazy initialization) can solve the problem, Flyweight may add unnecessary complexity.
  • Thread-Safety Concerns: Sharing objects can lead to concurrency issues in a multi-threaded environment, where shared objects may be modified simultaneously.

Advantages of Flyweight Design Factory

  • Reduces memory usage by sharing common objects.
  • Improves performance by minimizing object creation.
  • Efficient management of resources like memory and CPU.
  • Increases scalability in large systems with many objects.
  • Promotes object reusability and reduces redundancy.
  • Separates shared (intrinsic) and unique (extrinsic) object properties clearly.

Disadvantages of Flyweight Design Factory

  • Increased complexity in managing shared and unique states.
  • Harder to maintain as managing an extrinsic state can become cumbersome.
  • It is not suitable for objects with many unique attributes.
  • This can lead to thread-safety issues in multi-threaded environments.
  • It may introduce performance overhead if extrinsic data is frequently recalculated or passed around.
  • Less flexibility due to shared intrinsic state, which limits individual object modifications.

Flyweight Design Patterns' Relations with Other Patterns

The Flyweight Design Pattern interacts with and complements several other design patterns. Here’s how it relates to some key patterns:

  • Singleton Pattern: Often used with Flyweight to ensure that only one instance of the Flyweight Factory exists, centralizing control and object management.
  • Factory Method Pattern:This pattern works in conjunction with Flyweight by providing a way to create Flyweight objects. The Factory Method can delegate the responsibility of object creation to the Flyweight Factory.
  • Prototype Pattern: Can be used to clone Flyweight objects. While Flyweight focuses on sharing objects, Prototype can help in creating copies of these shared objects efficiently.
  • Object Pool Pattern: Similar to Flyweight, an Object Pool aims to manage a limited number of objects to reduce overhead. However, while an Object Pool focuses on reusing objects of the same type, a Flyweight focuses on sharing intrinsic states among similar objects.
  • Decorator Pattern: This pattern can be used with Flyweight to add additional features to Flyweight objects dynamically. Decorators can provide additional functionality without modifying the original Flyweight objects.
  • Proxy Pattern: Useful for controlling access to Flyweight objects. A Proxy can be employed to manage access and ensure that Flyweight objects are used efficiently.
  • Composite Pattern: Often combined with Flyweight to handle tree structures efficiently. Flyweight can be used to share components of composite structures, reducing the memory footprint for large hierarchies.
Read More:
Understanding MVC, MVP, and MVVM Design Patterns
Top 50 Java Design Patterns Interview Questions and Answers
.Net Design Patterns Interview Questions, You Must Know!
Most Frequently Asked Software Architect Interview Questions and Answers
Conclusion

In conclusion, the Flyweight Design Pattern is a powerful tool for optimizing memory usage and improving performance in systems with a large number of similar objects. It achieves this by sharing common parts of objects (intrinsic state) and allowing unique properties (extrinsic state) to be passed separately. Also, consider our ScholarhatSoftware Architecture and Design Certification Training for a better understanding of other Java concepts.

FAQs

Flyweight reduces memory usage by sharing intrinsic data across many objects instead of creating separate instances for each. The extrinsic state, which varies between objects, is passed in externally at runtime, reducing duplication. 

 Systems that deal with a large number of repeated, similar objects, such as: 
  • Graphics rendering: Games or graphical user interfaces. 
  • Text processing: Text editors or document formatting systems. 
  • Geospatial mapping: Applications that render large maps with repeated entities like roads or buildings. 

 Yes, Flyweight can be used alongside: 
  • Object pooling to reuse objects efficiently. 
  • Lazy initialization to delay object creation until absolutely necessary. 
  • Caching strategies to store and retrieve Flyweight objects more effectively. 

In the Flyweight pattern, the shared intrinsic state means multiple objects may appear identical. Object uniqueness is handled by the extrinsic state passed at runtime, which customizes the Flyweight object for a specific context. 

Flyweight works best with immutable (unchanging) shared data. If dynamic attributes are frequently changing, managing the extrinsic state separately can become complex, reducing the effectiveness of the pattern. 
Share Article
About Author
Shailendra Chauhan (Microsoft MVP, Founder & CEO at Scholarhat by DotNetTricks)

Shailendra Chauhan is the Founder and CEO at ScholarHat by DotNetTricks which is a brand when it comes to e-Learning. He provides training and consultation over an array of technologies like Cloud, .NET, Angular, React, Node, Microservices, Containers and Mobile Apps development. He has been awarded Microsoft MVP 9th time in a row (2016-2024). He has changed many lives with his writings and unique training programs. He has a number of most sought-after books to his name which has helped job aspirants in cracking tough interviews with ease.
Accept cookies & close this