Flyweight Design Pattern
Structural: Flyweight Pattern
What is the Flyweight Design Pattern?
View Answer:
This pattern's objects are as follows:
Client -- Example code: Computer
- calls into FlyweightFactory to obtain flyweight objects
FlyweightFactory -- In example code: FlyweightFactory
- creates and manages flyweight objects
- If a flyweight is required and one does not exist, it constructs one.
- stores newly created flyweights for future requests
Flyweight -- In example code: Flyweight
- preserves intrinsic data for use throughout the application
In JavaScript, the Flyweight pattern can be implemented using a shared factory that produces objects sharing common properties. Here is an example using a simple game scenario where there are a number of soldiers, each can belong to one of several different factions.
class Soldier {
constructor(name, faction, weapon) {
this.name = name;
this.faction = faction;
this.weapon = weapon;
}
}
class Faction {
constructor(name) {
this.name = name;
}
}
class SoldierFactory {
constructor() {
this.factions = {};
}
createFaction(name) {
let faction = this.factions[name];
if (!faction) {
faction = new Faction(name);
this.factions[name] = faction;
}
return faction;
}
createSoldier(name, factionName, weapon) {
const faction = this.createFaction(factionName);
return new Soldier(name, faction, weapon);
}
}
// Client code
const factory = new SoldierFactory();
const soldier1 = factory.createSoldier('John', 'Red', 'Sword');
const soldier2 = factory.createSoldier('Bob', 'Red', 'Bow');
const soldier3 = factory.createSoldier('Alex', 'Blue', 'Sword');
console.log(soldier1, soldier2, soldier3);
In this example, the Faction
objects are the "flyweights". Even though there may be a large number of Soldier
objects, there's only one Faction
object for each distinct faction name. The SoldierFactory
creates and manages the Faction
objects, ensuring that they are shared appropriately.
The client code creates Soldier
objects via the factory. If two soldiers belong to the same faction, they will share the same Faction
object. This can save a significant amount of memory if there are a large number of soldiers and a small number of factions.
The Flyweight pattern belongs to which design pattern family?
View Answer:
What types of objects are involved in the Flyweight Pattern?
View Answer:
In the Flyweight pattern, there are generally two types of objects involved:
Flyweight objects: These are the shared objects that contain the common data. The aim is to use these objects to minimize memory use.
Context objects: These are the objects which, along with flyweight objects, represent the original system objects. These objects contain the extrinsic state, which is the information that varies between the system objects.
Here's an example to illustrate this:
// The 'flyweight' object
class Color {
constructor(name) {
this.name = name;
}
}
// The 'flyweight' factory
class ColorFactory {
constructor() {
this.colors = {};
}
create(name) {
let color = this.colors[name];
if (!color) {
color = new Color(name);
this.colors[name] = color;
}
return color;
}
}
// The 'context' object
class Ball {
constructor(colorName, radius, factory) {
this.radius = radius;
this.color = factory.create(colorName); // Reference to a flyweight color object
}
draw() {
console.log(`Drawing a ${this.color.name} ball with radius ${this.radius}`);
}
}
// Client code
const factory = new ColorFactory();
const ball1 = new Ball('Red', 5, factory);
const ball2 = new Ball('Blue', 3, factory);
const ball3 = new Ball('Red', 7, factory); // reuses 'Red' color from the first ball
ball1.draw();
ball2.draw();
ball3.draw();
In this example, the Color
objects are the flyweights, which are created and managed by the ColorFactory
. The Ball
objects are the context objects. Each Ball
has a radius
property, which is part of its unique (extrinsic) state, and a color
property, which is a reference to a shared (intrinsic) Color
object. The ColorFactory
ensures that each unique color name is associated with exactly one Color
object, saving memory when there are many Ball
objects with the same color.
When should the Flyweight Pattern be used?
View Answer:
This pattern most commonly gets found in network programs or word processors, and it can be used in internet browsers to prevent the same images from loading. The flyweight pattern enables image caching. As a result, only new images are loaded from the Web when a web page loads, while existing ones get extracted from the cache.
What are some of the advantages of employing the Flyweight pattern?
View Answer:
What are some of the disadvantages of employing the Flyweight pattern?
View Answer:
Drawbacks of the Flyweight Pattern.
- When certain context data needs to be regenerated each time a flyweight method gets called, you may be sacrificing RAM for CPU cycles.
- The code becomes noticeably more complex with the Flyweight Pattern.
- New colleagues get perplexed as to why an entity's state gets partitioned.
Are there any alternatives to using the Flyweight pattern?
View Answer:
class ObjectPool {
constructor() {
this._pool = [];
}
acquire() {
return this._pool.length > 0 ? this._pool.pop() : new ExpensiveObject();
}
release(obj) {
this._pool.push(obj);
}
}
class ExpensiveObject {
constructor() {
this.data = 'Expensive Data';
}
}
// Client code
const pool = new ObjectPool();
const obj1 = pool.acquire();
const obj2 = pool.acquire();
pool.release(obj1);
const obj3 = pool.acquire(); // Reuses obj1, doesn't create a new object
In this example, ObjectPool
manages a pool of ExpensiveObject
instances. When an object is requested (acquire
), it either returns an existing object from the pool, or creates a new one if the pool is empty. When an object is done being used (release
), it's returned to the pool for future reuse. This can save time and memory if ExpensiveObject
is costly to create.
In what cases is the Flyweight Design Pattern most beneficial?
View Answer:
How does the Flyweight Pattern reduce memory usage?
View Answer:
What are the two states in a Flyweight pattern?
View Answer:
How does Flyweight handle intrinsic and extrinsic states?
View Answer:
The Flyweight pattern separates intrinsic and extrinsic states.
- Intrinsic state is stored in the Flyweight objects, shared across multiple context objects.
- Extrinsic state is stored outside of the Flyweight and typically within the context objects.
Here's an example of how this might be handled:
class TreeType {
constructor(name, color) {
this.name = name; // intrinsic state
this.color = color; // intrinsic state
}
display(age, x, y) { // extrinsic state passed as arguments
console.log(`Displaying a ${age}-year-old ${this.name} tree of color ${this.color} at (${x}, ${y})`);
}
}
class TreeTypeFactory {
constructor() {
this.treeTypes = {};
}
getTreeType(name, color) {
let type = this.treeTypes[name + color];
if (!type) {
type = new TreeType(name, color);
this.treeTypes[name + color] = type;
}
return type;
}
}
class Tree {
constructor(x, y, age, treeType) {
this.x = x; // extrinsic state
this.y = y; // extrinsic state
this.age = age; // extrinsic state
this.treeType = treeType; // reference to a flyweight treeType object
}
display() {
this.treeType.display(this.age, this.x, this.y);
}
}
// Client code
const factory = new TreeTypeFactory();
const appleTreeType = factory.getTreeType('Apple', 'Green');
const orangeTreeType = factory.getTreeType('Orange', 'Orange');
const tree1 = new Tree(1, 2, 10, appleTreeType);
const tree2 = new Tree(2, 4, 7, orangeTreeType);
const tree3 = new Tree(3, 6, 12, appleTreeType); // reuses appleTreeType from tree1
tree1.display();
tree2.display();
tree3.display();
In this example, TreeType
objects are the flyweights with intrinsic states (name and color). Each Tree
object maintains its own extrinsic state (x, y, and age) and references a shared TreeType
. The TreeTypeFactory
ensures each unique combination of name and color has exactly one TreeType
object.