Definition

The Flyweight pattern describes how to share objects to allow their use at fine granularity without prohibitive cost. It conserves memory by pooling large numbers of fine-grained objects efficiently.

Each "flyweight" object is divided into two pieces: the state-dependent (extrinsic) part, and the state-independent (intrinsic) part. Intrinsic state is stored (shared) in the Flyweight object. Extrinsic state is stored or computed by client objects and passed to the Flyweight when its operations are invoked.

Shared flyweight objects are immutable, that is, they cannot be changed as they represent the characteristics that are shared with other objects.

An example of the Flyweight Pattern is within the JavaScript engine itself which maintains a list of immutable strings that are shared across the application.

The Flyweight pattern falls under the structural patterns category.

Use Cases

  • Use sharing to support large numbers of fine-grained objects efficiently.
  • The Motif GUI strategy of replacing heavy-weight widgets with light-weight gadgets.

Example

The Flyweight uses sharing to support large numbers of objects efficiently. Modern web browsers use this technique to prevent loading same images twice. When a browser loads a web page, it traverses through all images on that page. The browser loads all new images from the Internet and places them the internal cache. For already loaded images, a flyweight object is created, which has some unique data like position within the page, but everything else is referenced to the cached one.

Let's take a look at an example from Addy Osmani's "Learning JavaScript Design Patterns" book which is about implementing a system to manage all of the books in a library.

The vital meta-data for each book could be:

  • ID
  • Title
  • Author
  • Genre
  • Page count
  • Publisher ID
  • ISBN

We'll have the following properties to keep track of which member has checked out a particular book, the date they've checked it out on as well as the expected date of return:

  • checkoutDate
  • checkoutMember
  • dueReturnDate
  • availability

Each book would thus be represented as follows, prior to any optimization using the Flyweight pattern:

var Book = function(
  id,
  title,
  author,
  genre,
  pageCount,
  publisherID,
  ISBN,
  checkoutDate,
  checkoutMember,
  dueReturnDate,
  availability
) {
  this.id = id;
  this.title = title;
  this.author = author;
  this.genre = genre;
  this.pageCount = pageCount;
  this.publisherID = publisherID;
  this.ISBN = ISBN;
  this.checkoutDate = checkoutDate;
  this.checkoutMember = checkoutMember;
  this.dueReturnDate = dueReturnDate;
  this.availability = availability;
};

Book.prototype = {
  getTitle: function() {
    return this.title;
  },

  getAuthor: function() {
    return this.author;
  },

  getISBN: function() {
    return this.ISBN;
  },

  // For brevity, other getters are not shown
  updateCheckoutStatus: function(
    bookID,
    newStatus,
    checkoutDate,
    checkoutMember,
    newReturnDate
  ) {
    this.id = bookID;
    this.availability = newStatus;
    this.checkoutDate = checkoutDate;
    this.checkoutMember = checkoutMember;
    this.dueReturnDate = newReturnDate;
  },

  extendCheckoutPeriod: function(bookID, newReturnDate) {
    this.id = bookID;
    this.dueReturnDate = newReturnDate;
  },

  isPastDue: function(bookID) {
    var currentDate = new Date();
    return currentDate.getTime() > Date.parse(this.dueReturnDate);
  }
};

This probably works fine initially for small collections of books, however as the library expands to include a more substantial inventory with multiple versions and copies of each book available, we may find the management system running slower and slower over time. Using thousands of book objects may overwhelm the available memory, but we can optimize our system using the Flyweight pattern to improve this.

We can now separate our data into intrinsic and extrinsic states as follows: data relevant to the book object (title, author etc.) is intrinsic while the checkout data (checkoutMember, dueReturnDate etc.) is considered extrinsic. Effectively this means that only one Book object is required for each combination of book properties. It's still a considerable quantity of objects, but significantly fewer than we had previously.

The following single instance of our book meta-data combinations will be shared among all of the copies of a book with a particular title.

// Flyweight optimized version
var Book = function(title, author, genre, pageCount, publisherID, ISBN) {
  this.title = title;
  this.author = author;
  this.genre = genre;
  this.pageCount = pageCount;
  this.publisherID = publisherID;
  this.ISBN = ISBN;
};

As we can see, the extrinsic states have been removed. Everything to do with library check-outs will be moved to a manager, and as the object data is now segmented, a factory can be used for instantiation.