TypeScript Flyweight Design Pattern Example

1. Definition

The Flyweight pattern is a structural design pattern that aims to optimize objects by sharing as much data as possible with other similar objects; it is a way to use objects in large numbers when a simple repeated representation would use an unacceptable amount of memory.

2. Problem Statement

Sometimes, applications may need thousands of similar objects that share a lot of common data. Creating individual objects for each can be highly memory intensive and inefficient.

3. Solution

Use a common data structure to share among multiple objects rather than storing data in each object. The Flyweight pattern divides an object's state into intrinsic and extrinsic states. The intrinsic state is shared and stored in the Flyweight object. The extrinsic state is stored or computed by client objects and passed to the Flyweight when needed.

4. Real-World Use Cases

1. Text editors that deal with character rendering. Each character's style and glyph data can be shared among all instances of that character.

2. Games that require large numbers of almost identical objects, like forest landscapes with thousands of trees.

5. Implementation Steps

1. Divide object properties into intrinsic (shared) and extrinsic (external) states.

2. Create a factory that caches and reuses instances of flyweight objects based on their intrinsic state.

6. Implementation in TypeScript

// Define the Flyweight object
interface Flyweight {
    operation(extrinsicState: number): void;
}
// Concrete Flyweight class
class ConcreteFlyweight implements Flyweight {
    private intrinsicState: string;
    constructor(intrinsicState: string) {
        this.intrinsicState = intrinsicState;
    }
    operation(extrinsicState: number): void {
        console.log(`ConcreteFlyweight: Intrinsic State = ${this.intrinsicState}, Extrinsic State = ${extrinsicState}`);
    }
}
// Flyweight Factory class
class FlyweightFactory {
    private flyweights: { [key: string]: ConcreteFlyweight } = {};
    getFlyweight(key: string): ConcreteFlyweight {
        if (!this.flyweights[key]) {
            this.flyweights[key] = new ConcreteFlyweight(key);
        }
        return this.flyweights[key];
    }
}
// Usage:
const factory = new FlyweightFactory();
const flyweightA = factory.getFlyweight('A');
const flyweightB = factory.getFlyweight('B');
// Both calls will return the same instance
console.log(flyweightA === factory.getFlyweight('A'));  // true
flyweightA.operation(1);
flyweightB.operation(2);

Output:

true
ConcreteFlyweight: Intrinsic State = A, Extrinsic State = 1
ConcreteFlyweight: Intrinsic State = B, Extrinsic State = 2

Explanation:

The Flyweight pattern promotes sharing to support a large number of fine-grained objects efficiently. 

In the TypeScript example, intrinsic state is stored within the ConcreteFlyweight and is shared. Extrinsic state is provided by the client when an operation is called. The FlyweightFactory ensures that objects are shared/reused.

7. When to use?

The Flyweight pattern is useful when:

1. An application uses a large number of objects, which makes a significant impact on memory.

2. Object's state can be externalized, and most of the state can be made extrinsic.

3. The application doesn't rely on object identity. Flyweights are shared, so identity checks will return true for conceptually distinct objects.


Comments