link: Structural patterns
Flyweight Pattern
Overview
Also known as: Cache
Abstract
Flyweight is a structural design pattern that lets you fit more objects into the available amount of RAM by sharing common parts of state between multiple objects instead of keeping all of the data in each object.
Content
Intent
Problem
After developing a video game with a realistic particle system featuring bullets, missiles, and shrapnel, you encountered a critical issue when testing it on a friend’s computer. While the game ran smoothly on your machine, it crashed after a few minutes of gameplay on your friend’s less powerful computer. Upon investigation, it was revealed that the crashes were caused by insufficient RAM due to the excessive memory consumption of the particle system. Each particle, such as bullets and missiles, was represented by a separate object containing significant amounts of data. As the intensity of the gameplay increased, the creation of new particles led to a rapid depletion of available RAM, resulting in system crashes.
Solution
Upon examining the
Particle
class, it’s apparent that certain fields likecolor
andsprite
consume excessive memory and store redundant data across all particles. For example, every bullet in the game shares the same color and sprite.However, attributes like coordinates, movement vector, and speed are unique to each particle and change dynamically during gameplay, representing the particle’s context.
The Flyweight pattern offers a solution by separating extrinsic state (varying data) from intrinsic state (constant data) within objects. Extrinsic state is moved out of the object and passed to methods as needed, while intrinsic state remains within the object.
Implementing this pattern significantly reduces the number of objects required, as they only differ in intrinsic state, which typically has fewer variations than extrinsic state.
For instance, in our game, segregating extrinsic state from the
Particle
class would result in only three distinct flyweight objects needed to represent all particles: bullets, missiles, and shrapnel.To store extrinsic state, a container object like the main
Game
object can be used, managing arrays for coordinates, vectors, speeds, and references to specific flyweight objects representing particles. Alternatively, a separate context class can be created for a more elegant solution with reduced memory overhead.
Ensuring immutability of flyweight objects is crucial, as they are shared across multiple contexts. Flyweight objects should initialize their state via constructor parameters and avoid exposing setters or public fields.
Additionally, a flyweight factory method can be employed for easy access to flyweight objects, managing a pool of existing objects and creating new ones as needed. This method can be placed within a flyweight container, a separate factory class, or even inside the flyweight class itself, depending on design preferences.
Structure
-
Problem Identification:
- Before implementing the Flyweight pattern, ensure that your program encounters memory consumption issues due to a large number of similar objects in memory simultaneously. Confirm that this problem cannot be effectively addressed through other means.
-
Flyweight Class:
- The Flyweight class stores the portion of an object’s state that can be shared among multiple instances. It encapsulates intrinsic state, which remains constant across different contexts. Extrinsic state, specific to each context, is passed to the flyweight’s methods.
-
Context Class:
- The Context class holds the extrinsic state, which varies across all original objects. When paired with a flyweight object, the context represents the complete state of the original object.
-
Behavior Handling:
- Typically, the behavior of the original object is retained in the flyweight class. When invoking a flyweight’s method, appropriate extrinsic state bits must be passed as method parameters. Alternatively, behavior can be delegated to the context class, treating the linked flyweight as a data object.
-
Client:
- The Client calculates or maintains the extrinsic state of flyweights. From the client’s viewpoint, a flyweight acts as a template object that can be configured at runtime by providing contextual data to its methods.
-
Flyweight Factory:
- The Flyweight Factory manages a pool of existing flyweights. Clients interact with the factory to obtain flyweight instances, passing intrinsic state details. The factory searches for existing flyweights matching the specified criteria and returns them if found, or creates new instances if necessary.
Applicability
Use the Flyweight pattern in scenarios where your program must accommodate an extensive array of objects that strain available memory resources. The effectiveness of this pattern hinges on its careful application, particularly in situations where:
- High Volume of Similar Objects:
- Your application necessitates the creation of a vast quantity of objects, each sharing common characteristics or states.
- Memory Exhaustion on Target Device:
- The proliferation of these objects consumes the entirety of available Random Access Memory (RAM) on the target computing device.
- Duplicate State Extraction:
- Objects contain redundant or duplicate states that can be abstracted and shared across multiple instances, thereby alleviating the burden on memory resources.
How to Implement
- Divide State:
- Segregate the fields of the class intended to be a flyweight into two categories:
- Intrinsic State: Fields containing immutable data that remains consistent across multiple objects.
- Extrinsic State: Fields containing contextual data unique to individual objects.
- Segregate the fields of the class intended to be a flyweight into two categories:
- Immutable Intrinsic State:
- Ensure that fields representing the intrinsic state remain immutable. Initialize them exclusively within the class constructor.
- Method Refactoring:
- Review methods utilizing fields of the extrinsic state. Introduce new parameters for each extrinsic state field within the method and utilize these parameters instead of direct field access.
- Factory Class (Optional):
- Consider creating a factory class to oversee the management of flyweight objects. This factory should verify the existence of an equivalent flyweight before instantiating a new one. Clients should interact exclusively with this factory to obtain flyweight instances, specifying the desired intrinsic state as parameters.
- Extrinsic State Management:
- Clients must maintain or compute values corresponding to the extrinsic state (context) to effectively invoke methods of flyweight objects. To enhance convenience, consider relocating the extrinsic state, along with the field referencing the flyweight, to a separate context class.
Pros and Cons
Advantages
RAM Efficiency: Significant reduction in memory usage, particularly beneficial when dealing with a vast number of similar objects.
Disadvantages
CPU Overhead: There might be a trade-off between memory and CPU cycles, especially when recalculating contextual data for flyweight methods.
Increased Complexity: Implementation complexity increases due to the separation of entity state into intrinsic and extrinsic components. This complexity can potentially confuse new team members.
Relations with Other Patterns
-
Composite Pattern: Shared leaf nodes in a Composite tree can be implemented as Flyweights to conserve memory by avoiding duplicate storage of intrinsic state.
-
Facade Pattern: While Flyweight focuses on minimizing memory usage by sharing common state across multiple objects, Facade simplifies interaction with a complex subsystem by providing a unified interface. They address different concerns but can be used together to improve system design.
-
Singleton Pattern: Flyweight and Singleton share similarities in terms of reducing memory usage, but they have fundamental differences. While Singleton restricts instantiation of a class to a single instance, Flyweight allows multiple instances with different intrinsic states. Additionally, Singleton instances can be mutable, whereas Flyweight objects are immutable.
Examples
This code exemplifies the Flyweight pattern, which is used to minimize memory usage and improve performance by sharing as much data as possible between similar objects. Here’s a breakdown of the components:
Components of the Implementation
TreeType: Represents a portion of the state shared among multiple tree objects. It stores data such as name, color, and texture. The
Draw
method is responsible for rendering the tree type at specified coordinates.TreeFactory: Decides whether to reuse existing flyweight instances or create new ones. It maintains a dictionary of tree types to avoid duplication.
Tree: Represents a tree object with intrinsic attributes (position) and an extrinsic attribute (reference to a tree type). The
Draw
method utilizes the tree type’s attributes to render the tree.Forest: Manages a collection of trees, handling their creation and drawing. It utilizes the
TreeFactory
to obtain tree types and creates tree objects accordingly.The
Main
method demonstrates the real-world usage of theForest
class by planting trees of different types and drawing the entire forest. Additionally, a conceptual example (ConceptualExample
) is mentioned but not implemented in the provided code.
C# Example - GitHub
Server Side
Client Side
Summary
Cheat Sheet
Flyweight Pattern Cheat Sheet Purpose:
- Reduces the cost of creating and manipulating a large number of similar objects.
- Saves memory by sharing as much data as possible with other similar objects (intrinsic state).
Components:
- Flyweight: Interface through which flyweights can receive and act on extrinsic states.
- ConcreteFlyweight: Implements the Flyweight interface and stores intrinsic state. These objects must be sharable.
- FlyweightFactory: Creates and manages flyweight objects and ensures that flyweights are shared properly. When a client requests a flyweight, the factory either uses an existing instance or creates a new one.
- Client: Maintains the extrinsic state and passes it to the flyweight objects for processing.
Usage:
- Use when there are many instances of objects that are similar in structure but vary in state.
- Use when reducing memory footprint is a priority.
Benefits:
- Greatly reduces the number of objects that need to be created, reducing memory usage and increasing application performance.
- Centralizes state management in shared objects, simplifying data management across the system.
Common Scenarios:
- Character objects in word processors.
- Tree and forest simulations where many trees share the same data (species, textures).