The Flyweight Design Pattern is a structural design pattern that aims to reduce memory usage by sharing common data among similar objects. This pattern is particularly useful when an application needs to create many objects that are similar but not identical, as it allows the system to reuse existing objects rather than creating new ones, thereby optimizing memory usage.
The Flyweight pattern achieves this by separating the
object's intrinsic state (shared data) from its extrinsic state (unique data).
The intrinsic state is stored in the flyweight object, and the extrinsic state
is passed in when needed.
Key Concepts:
- Flyweight:
The object that stores the intrinsic (shared) state.
- Intrinsic
State: Data that can be shared between many objects (e.g., properties
that do not change between objects).
- Extrinsic
State: Data that is unique to a specific object and may change between
different objects (e.g., properties that vary).
- Flyweight
Factory: A factory that manages the flyweights and ensures that
objects are shared when possible.
Example of Flyweight Pattern in JavaScript
Let's consider a scenario where we are managing a large
number of characters for a word processor. Each character might have a few
shared properties (e.g., font, size, color), but each character has a unique
position (extrinsic state).
Without Flyweight Pattern (Naive Approach):
class Character {
constructor(char,
font, size, color) {
this.char =
char;
this.font =
font;
this.size =
size;
this.color
= color;
}
display(position)
{
console.log(`Character:
${this.char}, Position: ${position}, Font: ${this.font}, Size: ${this.size},
Color: ${this.color}`);
}
}
// Creating separate objects for every character
let characters = [
new Character("A",
"Arial", 12, "red"),
new Character("B",
"Arial", 12, "blue"),
new Character("A",
"Arial", 12, "red"),
new Character("C",
"Arial", 12, "green"),
];
characters.forEach((char, index) => char.display(index));
This approach could be inefficient because many of the Character
objects have the same properties (e.g., font, size, and color) but are stored
as separate instances, wasting memory.
With Flyweight Pattern (Optimized Approach):
To optimize the memory usage, we can implement the Flyweight
Pattern, where common data is shared, and only unique data is stored
separately.
// Flyweight Class: Represents the shared state
(intrinsic state)
class CharacterFlyweight {
constructor(font,
size, color) {
this.font =
font;
this.size =
size;
this.color
= color;
}
display(char,
position) {
console.log(`Character:
${char}, Position: ${position}, Font: ${this.font}, Size: ${this.size}, Color: ${this.color}`);
}
}
// Flyweight Factory: Manages the shared flyweights
class CharacterFactory {
constructor() {
this.flyweights
= {};
}
getFlyweight(font,
size, color) {
const key =
`${font}-${size}-${color}`;
if (!this.flyweights[key])
{
this.flyweights[key]
= new CharacterFlyweight(font, size, color);
}
return this.flyweights[key];
}
}
// Extrinsic state: Position of each character
let characterFactory = new CharacterFactory();
let characters = [
{ char: "A",
font: "Arial", size: 12, color: "red", position: 0 },
{ char: "B",
font: "Arial", size: 12, color: "blue", position: 1 },
{ char: "A",
font: "Arial", size: 12, color: "red", position: 2 },
{ char: "C",
font: "Arial", size: 12, color: "green", position: 3 },
];
// Using the factory to get flyweights and share common
states
characters.forEach(character => {
let flyweight =
characterFactory.getFlyweight(character.font, character.size, character.color);
flyweight.display(character.char,
character.position);
});
Explanation:
- CharacterFlyweight:
The CharacterFlyweight class represents the intrinsic state of a
character, which can be shared. This includes the font, size, and color.
These properties are common and do not change between characters.
- CharacterFactory:
The CharacterFactory class ensures that we only create one instance of a CharacterFlyweight
for each unique combination of font, size, and color. It uses the
combination of these three properties as a key to store and retrieve the
flyweight objects.
- Extrinsic
State: The position of the character is an extrinsic property that is
unique for each instance and is passed to the display method when needed.
- Client
Code: The client creates a list of characters, and instead of creating
a new Character object for each one, it uses the CharacterFactory to get a
flyweight. Only the position (extrinsic state) is passed to the flyweight
when displaying the character.
Advantages of the Flyweight Pattern:
- Memory
Efficiency: By sharing common data between objects, memory usage is
reduced significantly. This is especially useful when you have large
numbers of similar objects.
- Performance
Improvement: Creating fewer objects means less overhead for object
creation and management. The Flyweight pattern can improve performance
when dealing with large numbers of objects.
- Centralized
Management: The factory ensures that the flyweights are managed and
reused properly, providing a clean interface for object creation.
Disadvantages of the Flyweight Pattern:
- Complexity:
The Flyweight pattern introduces some complexity because it requires
separating intrinsic and extrinsic states. This can make the code harder
to understand and maintain.
- Not
Always Applicable: The Flyweight pattern is only useful when there are
many similar objects that can share intrinsic data. If the objects are
unique in many aspects, the pattern may not provide significant benefits.
- Extrinsic
State Management: The client code must explicitly manage the extrinsic
state (e.g., the position of characters in the above example), which could
increase the complexity.
When to Use the Flyweight Pattern:
- When
your system needs to handle large numbers of similar objects, and you want
to reduce memory usage.
- When
you have objects with similar states that can be shared across many
instances.
- When
you need to manage large sets of data (e.g., characters in a word
processor, graphical elements in a game) where much of the data can be
shared.
Conclusion:
The Flyweight Design Pattern is an effective solution
for reducing memory usage when dealing with many similar objects. By separating
shared and unique states, and reusing existing objects, the Flyweight pattern
helps optimize both memory and performance. However, it is best suited for
cases where many objects share common data, and managing the extrinsic state
does not introduce significant complexity.
No comments:
Post a Comment