21
NovFlyweight Design Pattern: An Easy Way
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:
- Flyweight Interface or Class
- Concrete Flyweight Classes
- Flyweight Factory
- 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.
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.
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
- 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.
- 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.