Design Patterns in Action(6)- Flyweight
A series of programming design patterns illustration with examples with JavaScript/Python
Simply put, the Flyweight pattern is a Structural Design Pattern that help reduce memory load by minimising the number of objects created of the same type. It does so by extracting the extrinsic part (state dependent)of the object into a shared Flyweight object and separating that from the intrinsic (state independent) part of the object.
These Flyweight objects are hashed by key and stored in a hash table. When the object first gets created, it is put into the hash table, subsequent call of the object by key will retrieve the item from the table instead of creating the new one.
One important thing to note that is the Flyweight object is immutable, meaning they only store extrinsic values at creation. Any call to intrinsic parts will be done by the client dynamically passing in the intrinsic parameters to the Flyweight. This requires the Flyweight object to open for intrinsic operations.
- FlyweightFactory : creates and manages flyweight objects by a hash table
- Flyweight : maintains intrinsic state to be shared and open for operation with extrinsic state.
The benefits of Flyweight pattern are:
- More efficient memory usage.
- Better caching and improved performance. As objects’ extrinsic attributes are stored in hash table, this is easier to cache and retrieve.
However, this does bring the cons such as increased run-time costs from retrieving objects and computing extrinsic state.
Here is an example with Python:
import abc
class BreadFlyweightFactory:
def __init__(self):
self._flyweights = {}
def get_bread_flyweight(self, flour_kind, kind):
try:
flyweight = self._flyweights[kind]
except KeyError:
flyweight = ConcreteBreadFlyweight(flour_kind, kind)
self._flyweights[kind] = flyweight
return flyweight
class BreadFlyweightInterface(metaclass=abc.ABCMeta):
def __init__(self):
self.intrinsic_state = None
@abc.abstractmethod
def wait_for_bake(self, extrinsic_state):
pass
@abc.abstractmethod
def wait_for_fry(self, extrinsic_state):
pass
@abc.abstractmethod
def wait_for_toast(self, extrinsic_state):
pass
class ConcreteBreadFlyweight(BreadFlyweightInterface):
def __init__(self, kind=None, flour_kind="white"):
self.kind = kind
self.flour_kind = flour_kind
def wait_for_bake(self, min):
return f"{self.kind} is waiting for bake in {min}!"
def wait_for_fry(self, min):
return f"{self.kind} is waiting for fry in {min}!"
def wait_for_toast(self, min):
return f"{self.kind} is waiting for toast in {min}!"
bread_factory = BreadFlyweightFactory()
sourdough = bread_factory.get_bread_flyweight("sourdough")
sourdough.wait_for_toast(30)
And another example with JavaScript:
class BreadFlyweightFactory {
private flyweights: Record<string, BreadFlyweight> = {};
constructor() {}
public get_bread_flyweight(flour_kind, kind): BreadFlyweight {
let bread;
if (kind in this.flyweights) {
bread = this.flyweights[kind];
} else {
this.flyweights[kind] = new BreadFlyweight(flour_kind, kind);
bread = this.flyweights[kind];
}
return bread;
}
}
class BreadFlyweight {
public kind: string;
public flour_kind: string;
constructor(flour_kind, kind) {
this.kind = kind;
this.flour_kind = flour_kind;
}
public wait_for_bake(min) {
return `${this.kind} is waiting for bake in ${min}!`;
}
public wait_for_fry(min) {
return `${this.kind} is waiting for fry in ${min}!`;
}
public wait_for_toast(min) {
return `${this.kind} is waiting for taost in ${min}!`;
}
}
const bread_factory = new BreadFlyweightFactory();
const sourDough = bread_factory.get_bread_flyweight("wholemeal", "sourdough");
sourDough.wait_for_toast(30);
That’s it! Happy Reading!