Flyweight Design Pattern

Flyweight design pattern falls under the structural design pattern category. Sometimes, our applications hit slow performances. This can be due to various reasons. Instantiating many amounts of heavy objects can be one of the very common reasons for the low system performance. This can be rectified by the flyweight pattern successfully. Flyweight pattern introduces a new object called ‘Flyweight’ to avoid the creation of a large number of heavy and high memory consumption objects. This can be a kind of basic caching mechanism to reduce the overhead.

GoF Definition,

Facilitates the reuse of many fine-grained objects, making the utilization of large numbers of objects more efficient.

In this context, fine-grained objects are the objects that consume large memory and heavy for the pool. Flyweight object act as the one shareable object. Thus, that shareable object takes the place of a large number of fine-grained objects efficiently. In this mechanism, the system requires to store only the flyweight object by sharing common parts of object state among multiple objects. Thus, RAM does not need to store all the other similar heavy objects and can save huge performance overhead.

Feature/Factors to Satisfy

Following basic factors will determine whether your application is in need of the flyweight pattern.

  • Thousands of new instantiation occurrences of a memory intensive heavy object
  • The objects created via the heavy object have very similar features in every instantiation occurrences
  • The object pool contains many similar objects and two objects from the pool don’t differ highly.
  • The object creation consumes high memory usage causing interruptions in program execution.
  • Objects have the shareable ability

What is Flyweight Object?

This is the core component of the flyweight design pattern. It provides the full solution for the problem being addressed by this pattern. Flyweight is an object that can be used in place of a number of similar heavyweight objects and it efficiently shares the common information present in the similar object pool. Flyweight object must have shareable features unless it cannot be used as a template representor for similar objects. Flyweight can be used in multiple contexts simultaneously due to this shareable nature. In that case, it acts as an independent object in each context.

It is important to note that flyweight objects are immutable. That is they cannot be modified after the construction occurred. This is to preserve the template nature of the flyweight object in order to support the duplication role. This is achieved via keeping the state of the objects constant.

Let’s briefly identify the state details of the flyweight object.

Two States of Flyweight Design Pattern

State of the flyweight object is a crucial part in designing the solutions out of the flyweight design pattern. The main objective of the flyweight pattern is to reduce the memory load by sharing objects. This is mainly achieved by separating the object properties into two states. Each flyweight object can be divided into two categories as state-independent and state-dependent aka ‘Intrinsic’ and ‘Extrinsic’ respectively.

Intrinsic State

This state contains data that are unchangeable and independent of the context of the flyweight object. Those data can be stored permanently inside the flyweight object. This makes the flyweight object shareable. The intrinsic data are stateless and generally remains unchanged. This feature gives the ability to replicate the flyweight object properties among other similar objects. It’s important to note that flyweight objects should receive their intrinsic state only via constructor parameters not exposing setters or public fields.

Extrinsic State

The extrinsic state represents the context-dependent nature of the flyweight object. This state contains the properties and data applied or calculated on runtime. Thus, those data are not stored in the memory. Since the extrinsic state is context dependent and variant, those objects cannot be shared. Therefore, the client objects are responsible for passing the extrinsic state related data to the flyweight object when needed. Extrinsic state data can be passed to the flyweight object through arguments.

In short, it’s best to consider how each data behaves inside the object when creating the flyweight object. That is we must store unchangeable (intrinsic) within the flyweight object while passing the volatile data (extrinsic) on the fly. This behaviour of the pattern saves us a lot of memory and make the execution far efficient.

Real World Examples of Flyweight Design Pattern

  • Alphabet characters of a text editor

Let’s just focus on the alphabet characters for this example. Text editors have 26 distinct letters to write words. The editor has to instantiate all 26 letters repeatedly. For instance, when typing ‘Birthday Baby’, there are three occurrences of the letter ‘b’. If instantiate three different characters ‘O’ objects separately it is a complete waste of time, effort and memory because it is the same object base. Only the extrinsic effect of letter ‘b’ has changed. That is one occurrence it is bold-capital ‘b’ and next it is just capital while the final is simple ‘b’. To overcome repeated heavy object creation, we can use a flyweight object. Keeping the base of letter ‘b’ as intrinsic and other styles as extrinsic data. Take a look at Word Processor

  • Public-switched telephone system

There are several resources that have to be shared among subscribers in this type of telephone networks. The pool contains thousands of resources to cater the subscriber requests. Although, the subscriber is unaware of the number of resources in the pool. The usual resources in a public switched telephone system are dial tone generators, ringing generators and digital resources. When assessing the behaviours of each resource we can find intrinsic and extrinsic data and later can apply flyweight pattern without creating new objects for each subscriber request. Read more here.

  • JDBC connection pool

This is a fine tech level example of a flyweight behaviour. JDBC connection pool contains a collection of pre-created and cached database connections. Because the process of creating a database connection in an application is an expensive and time-consuming process. Moreover, in most applications, there are thousands of database transactions occurring every day. Catering this requirement cost huge performance overhead if try to create new connections at every request. Instead, connections are created ahead of time like flyweight objects and depending on the user request type extrinsic parameters added on the fly and cater the user need.  May be interesting to you: How JDBC Resources and Connection Pools Work Together

  • Browser component loading and caching

Modern web browsers use this technique to prevent loading the same images twice. When the browser loads a web page, it traverses through all images on that page. The browser loads all new images from the Internet and places them the internal cache. For already loaded images, a flyweight object is created, which has some unique and extrinsic data like position within the page, but everything else (intrinsic) is referenced to the cached one. More about browser component loading and caching – https://stackoverflow.com/questions/11751041/will-browser-download-image-twice-if-its-used-in-both-an-images-src-as-well-as

Class Diagram for Flyweight Design Pattern

UML Class Diagram - Flyweight Design Pattern
Standard Class Diagram for Flyweight Design Pattern

Components of Flyweight Design Pattern

  1. Flyweight Interface (IFlyweight):
    • The Flyweight Interface serves as the blueprint for creating concrete flyweight classes.
    • It typically includes a method that accepts extrinsic data as arguments, enabling concrete flyweights to incorporate external information when needed.
    • This interface may also be implemented as an abstract class, providing a common foundation for concrete flyweights.
  2. FlyweightFactory:
    • The FlyweightFactory class is responsible for managing the creation and retrieval of flyweight objects.
    • Instead of clients directly instantiating flyweight objects, they rely on the factory to handle object creation and management.
    • The factory maintains an internal pool of existing flyweight objects in memory.
    • When a client requests a flyweight object, the factory checks if a matching object already exists in the pool.
    • If a match is found, a reference to the existing object is returned; otherwise, a new object is created, added to the pool, and then returned to the client.
    • The FlyweightFactory optimizes memory usage by promoting the sharing of flyweight objects among clients.
  3. ConcreteFlyweight:
    • ConcreteFlyweight is a concrete implementation of the Flyweight Interface.
    • It encapsulates intrinsic state, which consists of data that is unchangeable and common across multiple instances of the same concrete flyweight class.
    • Objects of this type are typically stateless and shareable, allowing multiple clients to use the same ConcreteFlyweight object in various contexts.
  4. UnsharedFlyweight:
    • UnsharedFlyweight is an alternative concrete implementation of the Flyweight Interface.
    • It provides flexibility by allowing the creation of flyweight objects that are not shareable.
    • Unlike shared flyweights, UnsharedFlyweight objects may have mutable, context-specific state that varies between instances.
  5. Client:
    • The Client represents the end-user or application that interacts with the Flyweight Design Pattern.
    • Clients request flyweight objects from the FlyweightFactory when they need to perform tasks.
    • They are shielded from the inner workings of the pattern and are primarily concerned with using flyweights to accomplish specific objectives.
    • By leveraging the FlyweightFactory, clients can efficiently manage and reuse flyweight objects, conserving memory and resources.

Step-by-Step Implementation of Flyweight Pattern

  1. Identify Memory Intensive Objects:
    • Observe your application to identify where high memory usage occurs, particularly due to a large number of objects.
    • Focus on a specific class or object type that significantly contributes to memory usage.
  2. Distinguish Intrinsic and Extrinsic States:
    • In the identified object, separate the state into intrinsic (shareable) and extrinsic (non-shareable) parts.
    • Intrinsic state is independent of the object’s context and can be shared (e.g., object’s immutable data).
    • Extrinsic state depends on and varies with the object’s context (e.g., the object’s current state in a particular situation).
  3. Define the Flyweight Interface:
    • Create a Flyweight interface that declares methods for interacting with the intrinsic state.
    • This interface will be the blueprint for creating concrete Flyweight objects.
  4. Implement Concrete Flyweight Classes:
    • Create one or more concrete classes implementing the Flyweight interface.
    • These classes will manage the intrinsic state and ensure it is shared effectively.
  5. Implement Non-Shareable Flyweight Classes (Optional):
    • If there are non-shareable components, implement classes to handle these specific parts.
    • These classes deal with the extrinsic state that cannot be shared among objects.
  6. Create a Flyweight Factory:
    • Implement a Flyweight Factory class that manages Flyweight objects.
    • The factory should create new Flyweights when needed or return existing instances if a matching Flyweight already exists.
  7. Utilize the Flyweight Factory in Client Code:
    • Modify the client code to request Flyweight objects from the factory instead of creating new instances.
    • The client should provide the extrinsic state to the Flyweight objects when using them.
  8. Optimize Object Creation and Management:
    • Ensure the factory efficiently manages Flyweight objects, possibly using data structures like hash maps for quick lookups.
    • Handle the lifecycle of Flyweight objects, ensuring they are created, shared, and disposed of efficiently.

Factory Method Pattern Usage inside Flyweight Design Pattern

As you can see in the class diagram, there is a FlyweightFactory class in the pattern implementation. This is to avoid the unnecessary heavy object creation from the scratch. Pattern maintains an object pool to cater object requests. The client will directly call the factory to get the required object. Then, factory checks whether the required object is in the object pool. If the object exists within the pool, the factory will return it with the added features (extrinsic) on the fly. If the requested object is not inside the pool, the FlyweightFactory class will create a new intrinsic state object, add it to the pool, and return to the client with the requested extrinsic state. Each new instance should be created by the FlyweightFactory since it is the place of management and shareability happens with clients

Code Example

Let’s assume a factory that manufactures teacups. There are about four types of teacups. They differ only by shape and colour. The ingredients and quality are the same. The factory manufactures around 1000 items in one batch for one type of teacup. In this case, creating 4000 teacups from the scratch is an inefficient task. Instead, we can use the Flyweight pattern to save the time and effort. Let’s see the suitable components for the teacups example.

TeacupBaseAbstractClass

This class contains the base methods for creating a teacup. Like adding initial moulding ingredients, mixing formula and applying suitable temperature and time for making the base mixture.

TeacupFactory

This class performs the creation and management of teacups and it’s repository. This is the main component that decides whether to create a brand new cup mould or use the existing template and use for cup creation.

ConcreteTeacup

This class include the methods to create different shape and colour teacups derived from the abstract class.

Customer

Any teacup type requester who wants to have all types of teacups.

Code Samples

TeacupBaseAbstractClass.java

ConcreteTeacup.java

TeacupFactory.java

Customer.java

When to Use the Flyweight Design Pattern

The Flyweight design pattern is most suitable in scenarios where:

  1. Extensive Use of Objects: The application creates and manages a large number of objects.
  2. Repetitive Object Creation: The same heavy objects are frequently created, leading to inefficiencies.
  3. Memory Constraints: The application faces memory allocation issues, especially due to the large number of objects.
  4. Opportunity for Object Sharing: A significant portion of object groups can be replaced by a smaller number of shared objects, without compromising functionality.

Advantages of the Flyweight Design Pattern

  • Reduced Memory Usage: Significantly lowers memory consumption by sharing common parts of object states.
  • Enhanced Data Caching: Facilitates more efficient data caching, leading to quicker response times.
  • Improved Performance: Reduces the computational overhead associated with managing numerous heavy objects, thereby enhancing overall performance.

Drawbacks of the Flyweight Design Pattern

  • Garbage Collection Challenges: The pattern may hinder efficient garbage collection as shared objects are not easily disposable and may linger in memory longer than necessary.

Related Design Patterns

The Flyweight pattern intersects and interacts with several other design patterns:

  1. Factory Pattern: Flyweight commonly employs the Factory pattern for object creation. Learn more about Factory Pattern.
  2. Comparison with Singleton Pattern: While similar to the Singleton pattern in terms of object reuse, Flyweight differs significantly. Singleton involves a single mutable instance per system, while Flyweight typically involves multiple immutable instances, each representing different states.
  3. State and Strategy Patterns: Objects in State and Strategy patterns can often be implemented as Flyweight objects, demonstrating the versatility of the Flyweight pattern.
  4. Contrast with Facade Pattern: Flyweight focuses on creating multiple fine-grained objects for efficiency, whereas the Facade pattern emphasizes a single interface to a complex subsystem, showing a contrasting approach to managing object complexity.

Conclusion

The Flyweight pattern offers a sophisticated solution for optimizing resource utilization in scenarios with extensive object creation and memory usage. While it enhances performance and reduces memory footprint, its application should be carefully considered due to the potential complexities it introduces, particularly in terms of garbage collection and overall system architecture.

As usual we have uploaded the code used in this article to github. You can find it here.

5 thoughts on “Flyweight Design Pattern”

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.