Flyweight

Flyweight Pattern

Introduction

The Flyweight pattern is a structural design pattern that focuses on sharing object instances to reduce memory usage and improve performance for applications with a large number of objects.

Purpose

  • To reduce the memory footprint and increase performance by sharing as much data as possible with related objects.
  • To use sharing to support a large number of fine-grained objects efficiently.

Use Cases

  • When an application uses a large number of objects that have part of their internal state in common where the other part of the state can vary.
  • When the overhead of object storage is too high due to a high quantity of objects.

Advantages/Disadvantages

Advantages:

  • Reduces memory usage and object creation overhead.
  • Improves application performance due to fewer memory allocations.

Disadvantages:

  • Increases complexity by introducing layers of shared state management.
  • Can lead to a trade-off between time and space where saving memory space might result in additional time overhead.

Implementation in JavaScript

javascript
	class Flyweight {
	  constructor(sharedState) {
	    this.sharedState = sharedState;
	  }
	
	  operation(uniqueState) {
	    return `Flyweight: Displaying shared (${JSON.stringify(
	      this.sharedState,
	    )}) and unique (${JSON.stringify(uniqueState)}) state.`;
	  }
	}
	
	class FlyweightFactory {
	  constructor() {
	    this.flyweights = {};
	  }
	
	  getFlyweight(sharedState) {
	    const key = JSON.stringify(sharedState);
	    if (!(key in this.flyweights)) {
	      console.log('FlyweightFactory: Creating new flyweight.');
	      this.flyweights[key] = new Flyweight(sharedState);
	    } else {
	      console.log('FlyweightFactory: Reusing existing flyweight.');
	    }
	    return this.flyweights[key];
	  }
	}
	
	// Usage
	const factory = new FlyweightFactory();
	
	const flyweight1 = factory.getFlyweight({ a: 1, b: 2 });
	const flyweight2 = factory.getFlyweight({ a: 1, b: 2 });
	
	console.log(flyweight1.operation({ c: 3 }));
	console.log(flyweight2.operation({ d: 4 }));

Practical Example

Consider a backend system for a book library where each book object might have repetitive data (like genre, author). We can use Flyweight to share common data among different book instances.

javascript
	class Book {
	  constructor(title, author, genre) {
	    this.title = title;
	    this.author = author;
	    this.genre = genre; // Shared state
	  }
	}
	
	class BookFactory {
	  constructor() {
	    this.books = {};
	  }
	
	  getBook(genre) {
	    if (!this.books[genre]) {
	      this.books[genre] = new Book(null, null, genre);
	    }
	    return this.books[genre];
	  }
	}
	
	// Usage
	const bookFactory = new BookFactory();
	
	const thrillerBook1 = bookFactory.getBook('Thriller');
	thrillerBook1.title = 'Book One';
	thrillerBook1.author = 'Author A';
	
	const thrillerBook2 = bookFactory.getBook('Thriller');
	thrillerBook2.title = 'Book Two';
	thrillerBook2.author = 'Author B';
	
	console.log(thrillerBook1, thrillerBook2); // Both books share the same genre instance

In this example, BookFactory creates a flyweight Book object for each genre. When a new book is created, it shares the genre flyweight if it already exists. This pattern efficiently manages memory for large numbers of book objects with shared data.