Flyweight Design Pattern

The Flyweight Design Pattern is a structural design pattern that reduces the number of objects created, improving memory efficiency and performance. It does so by sharing objects that are identical or similar instead of creating new ones.

Essentially,Flyweight pattern seeks to reduce memory usage by sharing common state and separating intrinsic and extrinsic states.

Key Concepts:

  1. Intrinsic State: Shared between objects, and it is stored in the Flyweight object.

  2. Extrinsic State: Varies between objects and is passed by the client or caller during method calls.

Structure of the Flyweight Pattern:

  1. Flyweight interface: Defines the interface that is used by the client code.

  2. Concrete Flyweight: Implements the Flyweight interface, containing the intrinsic state.

  3. Flyweight factory: Manages a pool of Flyweight objects. It ensures that a shared instance is returned when needed.

  4. Client: Maintains reference to the Flyweight objects and uses them as needed.

Let's look at an example of how the Flyweight Design Pattern can be applied to Marker Points in a Geographic Information System (GIS).

Scenario:

In a GIS application, we might need to display a large number of marker points (such as cities, landmarks, or Points of Interests) on a map. Each marker point has a location (latitude and longitude) and potentially other attributes (e.g., name, description, etc.).

However, marker points might share common properties, such as icons, colors, or categories (e.g., all hospitals have the same icon and color). Instead of creating a new object for each marker point (which would be memory expensive if there are thousands of points), we can use the Flyweight pattern to share the common attributes (intrinsic state) and only store the unique attributes (extrinsic state) like position, label, etc.

Now we will have a look at the code snippets below.

Step 1: Create the flyweight interface

export interface MarkerPoint {
  icon: string;
  color: string;
  display(x: number, y: number): void;
}

Step 2: Concrete implementation of the interface

import { MarkerPoint } from "./Markerpoint";

//concrete implementation of the flyweight interface
export class ConcreteMarkerPoint implements MarkerPoint {
  icon: string;
  color: string;

  constructor(icon: string, color: string) {
    this.icon = icon;
    this.color = color;
  }

  //display method with extrinsic state (coordinates)
  display(x: number, y: number): void {
    console.log(
      `Marker at (${x} ${y}) with icon: ${this.icon} and color: $this.color}`
    );
  }
}

Step 3: Flyweight factory to manage shared markerpoint objects

import { ConcreteMarkerPoint } from "./ConcreteMarkerPoint";
import { MarkerPoint } from "./Markerpoint";

//flyweight factory to manage shared markerpoint objects
export class MarkerPointFactory {
  private readonly markerPoints: Map<string, MarkerPoint> = new Map();

  //get or create a shared markerpoint
  getmarkerPoint(icon: string, color: string): MarkerPoint {
    const key = `${icon}-${color}`;

    if (!this.markerPoints.has(key)) {
      //create a new marker point if it doesn't exist in the pool
      this.markerPoints.set(key, new ConcreteMarkerPoint(icon, color)); //reurn shared instance
    }

    return this.markerPoints.get(key)!;
  }
}

Step 4: Client using the Flyweight

import { MarkerPointFactory } from "./MarkerPointFactory";

export function main() {
  const factory = new MarkerPointFactory();

  //extrinsic state - coordinates and unique information

  const marker1 = factory.getmarkerPoint("garden-icon", "green");
  marker1.display(10, 30); //display the marker at position 10,30

  const marker2 = factory.getmarkerPoint("hospital-icon", "red");
  marker2.display(11, 32); //display the marker at position 11,32

  const marker3 = factory.getmarkerPoint("school-icon", "blue");
  marker3.display(12, 33); //display the marker at position 12,33

  const marker4 = factory.getmarkerPoint("garden-icon", "green");
  marker4.display(16, 49); //display the marker at position 16,49
}
main();

Explanation

  1. Create the MarkerPoint interface - define the contract for marker points with intrinsic and extrinsic properties.

  2. ConcreteMarkerPoint class - Implements the MarkerPoint interface, holding the shared intrinsic state (icon, color), and contains the display() method, which uses the extrinsic state (coordinates x, y).

  3. MarkerPointFactory class: Manages a pool of MarkerPoint objects. If a marker with the same icon and color already exists, it will return the existing object (reusing the intrinsic state).

  4. main.ts: The client code that requests shared marker points from the factory and displays them at specific coordinates.

Compile and run main.ts to see the output in the console.

Notes:

We are using the Map data structure to share the MarkerPoint objects ensuring that each unique combination of icon and color results only in one instance.

Reuse of Shared Objects: As shown in the above code snippet for step 4, the garden-icon with green color is reused in both marker1 and marker4, which saves memory and avoids creating identical objects.

Advantages :

  • Memory Efficiency: By reusing MarkerPoint objects that have the same intrinsic state (icon and color), memory consumption is reduced.

  • Improved Performance: The system doesn't need to repeatedly create new marker objects with the same shared properties, thus optimising the performance.

Disadvantages:

Complexity: The pattern adds complexity in managing the intrinsic and extrinsic states separately. In some cases, it may be overkill if the marker points have a small number of variations.

Real-World Use Case:

In a GIS system displaying thousands of points (e.g., locations of restaurants, parks, schools, hospitals, etc.) on a map, you can avoid creating a separate object for every marker. For instance, all "hospitals" might have the same icon and color, and Flyweight can help share that common data, saving memory and improving performance.

Summary:

  • The Flyweight pattern is useful for reducing memory usage by sharing common state across objects.

  • It divides the state of objects into intrinsic (shared) and extrinsic (unique) states.

  • The Flyweight Factory ensures shared objects are reused, reducing the creation of identical objects.

The GITHUB code link is here

1
Subscribe to my newsletter

Read articles from Ganesh Rama Hegde directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ganesh Rama Hegde
Ganesh Rama Hegde

Passionate Developer | Code Whisperer | Innovator Hi there! I'm a senior software developer with a love for all things tech and a knack for turning complex problems into elegant, scalable solutions. Whether I'm diving deep into TypeScript, crafting seamless user experiences in React Native, or exploring the latest in cloud computing, I thrive on the thrill of bringing ideas to life through code. I’m all about creating clean, maintainable, and efficient code, with a strong focus on best practices like the SOLID principles. My work isn’t just about writing code; it’s about crafting digital experiences that resonate with users and drive impact. Beyond the code editor, I’m an advocate for continuous learning, always exploring new tools and technologies to stay ahead in this ever-evolving field. When I'm not coding, you'll find me blogging about my latest discoveries, experimenting with side projects, or contributing to open-source communities. Let's connect, share knowledge, and build something amazing together!