Managing Application Resources with the Flyweight Pattern

Addy Osmani | June 22, 2011

 

In design patterns, the flyweight is considered one of the most useful classical solutions for code that's repetitive, slow and inefficient - for example: situations where we might create large numbers of similar objects.

It's of particular use in JavaScript where code that's complex in nature may easily use all of the available memory, causing a number of performance issues - however it's been quite underused in recent years. Given how reliant we are on JavaScript for the applications of today, both performance and scalability are often paramount and this pattern (when applied correctly) can assist with improving both.

To give you some quick historical context, the pattern is named after the boxing weight class that includes fighters weighing less than 112lb - Poncho Villa being the most famous fighter in this division. It derives from this weight classification as it refers to the small amount of weight (memory) used.

Now that our lengthy history lesson is over, let's get back to software engineering!.

Flyweights are an approach to taking several similar objects and placing that shared information into a single external object or structure. The general idea is that (in theory) this reduces the resources required to run an overall application. The flyweight is also a structural pattern, meaning that it aims to assist with both the structure of your objects and the relationships between them.

So, how do we apply it to JavaScript?

There are two ways in which the Flyweight pattern can be applied. The first is on the data-layer, where we deal with the concept of large quantities of similar objects stored in memory. The second is on the DOM-layer where the flyweight can be used as a central event-manager to avoid attaching event handlers to every child element in a parent container you wish to have some similar behaviour.

As the data-layer is where the flyweight pattern is most used traditionally, we'll take a look at this first.

Flyweight and the data layer

For this application, there are a few more concepts around the classical flyweight pattern that we need to be aware of. In the Flyweight pattern there's a concept of two states - intrinsic and extrinsic. Intrinsic information may be required by internal methods in your objects which they absolutely can't function without. Extrinsic information can however be removed and stored externally.

Objects with the same intrinsic data can be replaced with a single shared object, created by a factory method, meaning we're able to reduce the overall quantity of objects down significantly. The benefit of this is that we're able to keep an eye on objects that have already been instantiated so that new copies are only ever created should the intrinsic state differ from the object we already have.

We use a manager to handle the extrinsic states. How this is implemented can vary, however as Dustin Diaz correctly points out in Pro JavaScript Design patterns, one approach to this to have the manager object contain a central database of the extrinsic states and the flyweight objects which they belong to.

Converting code to use the Flyweight pattern

Let's now demonstrate some of these concepts using the idea of a system to manage all of the books in a library. The important meta-data for each book could probably be broken down as follows:

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

We'll also require 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:

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;   

    },

/*other getters not shown for brevity*/

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 larger inventory with multiple versions and copies of each book available, you'll 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 whilst 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 you 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's data is now segmented, a factory can be used for instantiation.

A Basic Factory

Let's now define a very basic factory. What we're going to have it do is perform a check to see if a book with a particular title has been previously created inside the system. If it has, we'll return it. If not, a new book will be created and stored so that it can be accessed later. This makes sure that we only create a single copy of each unique intrinsic piece of data:

/*Book Factory singleton */

var BookFactory = (function(){

    var existingBooks = {};

    return{

        createBook: function(title, author, genre,pageCount,publisherID,ISBN){

        /*Find out if a particular book meta-data combination has been created before*/

            var existingBook = existingBooks[ISBN];

            if(existingBook){

                    return existingBook;

                }else{

                /*if not, let's create a new instance of it and store it*/

                var book = new Book(title, author, genre,pageCount,publisherID,ISBN);

                existingBooks[ISBN] =  book;

                return book;

            }

        }

    }   

})();

Managing the extrinsic states

Next, we need to store the states that were removed from the Book objects somewhere - luckily a manager (which we'll be defining as a singleton) can be used to encapsulate them. Combinations of a Book object and the library member that's checked them out will be called Book records. Our manager will be storing both and will also include checkout related logic we stripped out during our flyweight optimization of the Book class.

/*BookRecordManager singleton*/

var BookRecordManager = (function(){

    var bookRecordDatabase = {};

    return{

        /*add a new book into the library system*/

        addBookRecord: function(id, title, author, genre,pageCount,publisherID,ISBN, checkoutDate, checkoutMember, dueReturnDate, availability){

            var book = bookFactory.createBook(title, author, genre,pageCount,publisherID,ISBN);

             bookRecordDatabase[id] ={

                checkoutMember: checkoutMember,

                checkoutDate: checkoutDate,

                dueReturnDate: dueReturnDate,

                availability: availability,

                book: book;

                

            };

        },

     updateCheckoutStatus: function(bookID, newStatus, checkoutDate, checkoutMember,     newReturnDate){

         var record = bookRecordDatabase[bookID];

         record.availability = newStatus;

         record.checkoutDate = checkoutDate;

         record.checkoutMember = checkoutMember;

         record.dueReturnDate = newReturnDate;

    },

    extendCheckoutPeriod: function(bookID, newReturnDate){

        bookRecordDatabase[bookID].dueReturnDate = newReturnDate;   

    },

    isPastDue: function(bookID){

        var currentDate = new Date();

        return currentDate.getTime() > Date.parse(bookRecordDatabase[bookID].dueReturnDate);   

    }       

 };

})();

The result of these changes is that all of the data that's been extracted from the Book 'class' is now being stored in an attribute of the BookManager singleton (BookDatabase) which is considerable more efficient than the large number of objects we were previously using. Methods related to book checkouts are also now based here as they deal with data that's extrinsic rather than intrinsic.

This process does add a little complexity to our final solution, however it's a small concern when compared to the performance issues that have been tackled.

The Flyweight pattern and the DOM

In JavaScript, functions are effectively object descriptors and all functions are also JavaScript objects internally. The goal of the pattern here is thus to make triggering objects have little to no responsibility for the actions they perform and to instead abstract this responsibility up to a global manager. One of the best metaphors for describing the pattern was written by Gary Chisholm and it goes a little like this:

Try to think of the flyweight in terms of a pond. A fish opens its mouth (the event), bubbles raise to the surface (the bubbling) a fly sitting on the top flies away when the bubble reaches the surface (the action). In this example you can easily transpose the fish opening its mouth to a button being clicked, the bubbles as the bubbling effect and the fly flying away to some function being run'.

As jQuery is accepted as one of the best options for DOM-manipulation and selection, we'll be using it for our DOM-related examples.

Example 1: Centralized event handling

For our first example, consider scenarios where you may have a number of similar elements or structures on a page that share similar behaviour when a user-action is performed against them.

In JavaScript, there's a known bubbling effect in the language so that if an element such as a link or button is clicked, that event is bubbled up to the parent, informing them that something lower down the tree has been clicked. We can use this effect to our advantage.

Normally what you might do when constructing your own accordion component, menu or other list-based widget is bind a click event to each link element in the parent container. Instead of binding the click to multiple elements, we can easily attach a flyweight to the top of our container which can listen for events coming from below. These can then be handled using as simple or as complex logic as is required.

The benefit here is that we're converting many independent objects into a few shared ones (potentially saving on memory), similar to what we were doing with our first JavaScript example.

As the types of components mentioned often have the same repeating markup for each section (e.g. each section of an accordion), there's a good chance the behaviour of each element that may be clicked is going to be quite similar and relative to similar classes nearby. We'll use this information to construct a very basic accordion using the flyweight below.

A stateManager namespace is used here encapsulate our flyweight logic whilst jQuery is used to bind the initial click to a container div. In order to ensure that no other logic on the page is attaching similar handles to the container, an unbind event is first applied.

Now to establish exactly what child element in the container is clicked, we make use of a target check which provides a reference to the element that was clicked, regardless of its parent. We then use this information to handle the click event without actually needing to bind the event to specific children when our page loads.

<div id="container">

    <div class="toggle" href="#">More Info (Address)

        <span class="info">

            This is more information

        </span></div>

    <div class="toggle" href="#">Even More Info (Map)

        <span class="info">

           <iframe src="https://www.map-generator.net/extmap.php?name=London&amp;address=london%2C%20england&amp;width=500&amp;height=400&amp;maptype=map&amp;zoom=14&amp;hl=en&amp;t=1307912275" width="500" height="400" marginwidth="0" marginheight="0" frameborder="0" scrolling="no"></iframe>

        </span>

    </div>

</div>
stateManager = {

    fly: function(){

        var self =  this;

        $('#container').unbind().bind("click", function(e){

            var target = $(e.originalTarget || e.srcElement);

            if(target.is("div.toggle")){

                self.handleClick(target);

            }

        });        

    },

    

    handleClick: function(elem){

        elem.find('span').toggle('slow');

    }

}

stateManager.fly();

Example 2: Using the Flyweight for Performance Gains

In our second example, we'll reference some useful performance gains you can get from applying the flyweight pattern to jQuery.

James Padolsey previously wrote a post called '76 bytes for faster jQuery' where he reminds us of an important point: every time jQuery fires off a callback, regardless of type (filter, each, event handler), you're able to access the function's context (the DOM element related to it) via the this keyword.

Unfortunately, many of us have become used to the idea of wrapping this in $() or jQuery(), which means that a new instance of jQuery is constructed every time.

Rather than doing this:

$('div').bind('click', function(){

  console.log('You clicked: ' + $(this).attr('id'));

});

you should avoid using the DOM element to create a jQuery object (with the overhead that comes with it) and just use the DOM element itself like this:

$('div').bind('click', function(){

  console.log('You clicked: ' + this.id);

});

Now with respect to redundant wrapping, where possible with jQuery's utility methods, it's better to use jQuery.N as opposed to jQuery.fn.N where N represents a utility such as each. Because not all of jQuery's methods have corresponding single-node functions, Padolsey devised the idea of jQuery.single.

The idea here is that a single jQuery object is created and used for each call to jQuery.single (effectively meaning only one jQuery object is ever created). The implementation for this can be found below and is a flyweight as we're consolidating multiple possible objects into a more central singular structure.

jQuery.single = (function(o){

 

    var collection = jQuery([1]);

    return function(element) {

 

        // Give collection the element:

        collection[0] = element;

 

         // Return the collection:

        return collection;

 

    };

 }());

An example of this in action with chaining is:

$('div').bind('click', function(){

    var html = jQuery.single(this).next().html(); 

    console.log(html);

 });

Note that although we may believe that simply caching our jQuery code may offer just as equivalent performance gains, Padolsey claims that $.single() is still worth using and can perform better. That's not to say don't apply any caching at all, just be mindful that this approach can assist. For further details about $.single, I recommend reading Padolsey's full post.

Conclusions

Although we've just touched the surface of what can be done with the Flyweight pattern it has a number of advantages that make it worth trying out. If you would like to give it a go yourself, please feel free to play around with the exercise listed below.

Exercise

A typical blog will have two types of comments - standard comments and trackbacks. For any blog that grows popular over time, this can easily build up to a large number of objects to allocate if strictly working within JavaScript. The complexity of this increases further when you take into account the number of actions that can be performed on each type of comment - replies, ratings, edits and so on.

Let us say a blog post has 150 standard comments and 140 trackbacks. Rather than attempting to hold and manage 290 comment objects, we'll only use two. A StandardComment object and a TrackbackComment object. A factory may be used to handle these with a single base Comment or StandardComment 'class' depending on your preference.

Each set of markup (e.g. div) that represents a regular comment would have a comment property that references back the StandardComment object. When the site owner or a visitor thus clicks on a reply or rating inside the div, regardless of whether the element clicked on is a StandardComment or a TrackbackComment, we can call comment.Like() or comment.reply() on them. How would you craft a solution using the Flyweight pattern to solve this using either vanilla JavaScript, jQuery or a combination of the two?

Reference and further reading:

Pro JavaScript Design Patterns - Ross Harmes & Dustin Diaz

About the Author

Addy Osmani is a User Interface Developer from London, England. An avid blogger, he has a passion for encouraging best practices in client-side development, in particular with respect to JavaScript and jQuery. He enjoys evangelizing the latter of these and is on the jQuery Bug triage, API and front-end teams. Addy works at AOL where he's a JavaScript developer on their European Products & Innovation team.

Find Addy on: