design pattern

Structural Design Patterns: Flyweight

Dive into the exciting world of the flyweight design pattern in today’s article, as we continue looking at Structural design patterns throughout our extensive Guide to Software Design Patterns series. The flyweight design pattern, named after the boxing weight class of the same name, is intended to be just as agile and adaptive as those nimble athletes. Flyweight gains this agility by minimizing memory and computational usage by sharing and reusing objects.

In this article we’ll explore the flyweight pattern in more detail, looking at both a real world and fully-functional C# code example to help illustrate how the pattern can best be used, so let’s get to it!

In the Real World

Virtually any instance of reuse that we might encounter in our day-to-day lives would arguably constitute a form of the flyweight pattern. However, since one major principle of the pattern is the notion of “sharing” — and since I personally just returned a movie rental yesterday — the immediate example that comes to my mind is physical movie rental services like the ubiquitous Redbox.

In case you aren’t aware, Redbox is a DVD-rental service that provides little red kiosks, plopped outside storefronts and the like, each packed with an assortment of movies and console games that can be rented for a day at the cost of a few bucks. Select your movie, swipe your credit card, and the machine spits out your Blu-ray or DVD to take home. If can get home fast enough, you can possibly binge-watch 50 Shades of Grey eleven times during your 24-hour rental period, before you’ll need to rush back to a Redbox kiosk and return the now well-worn disc.

Movie tastes aside, the Redbox business model (and the entire concept of movie rentals, for the matter), is a great real world example of the flyweight design pattern. Movie studios and distributors don’t want to print an excess of discs, but they don’t want an availability shortage either. Since discs are generally quite cheap to print and distribute, most companies err on the side of excess. This is where Redbox (and similar services) carve out their entire business model: Scooping up those excess discs on the cheap and renting them out to viewers who don’t mind stopping by a kiosk to grab a movie that isn’t available on streaming services.

Since each Redbox kiosk may only contain a handful of copies of a specific movie, most individual discs will be rented and viewed by many people over the course of their lifespan. The distributor and/or Redbox only need a relatively small library of discs inside a single kiosk to facilitate rentals from many, many individuals. The ability to “reuse” discs between different renters, as well as sharing discs between customers and across other kiosks, is exactly what the flyweight pattern aims to accomplish.

How It Works In Code

Even though I used movies as a real world example of the flyweight design pattern, we’re going to use more literary examples in our code sample. As usual, let’s start with the full code below, then we’ll dig into it a bit more afterward:

As briefly mentioned in the introduction, the overall purpose of the flyweight pattern is to make it easier to reuse objects whenever possible, ideally saving both memory and processor time. To assist with this goal, a typical flyweight pattern implementation consists of the following three components:

  • Flyweight interface: This interface defines the basic members of all flyweight objects.
  • ConcreteFlyweight class: A class that implements the Flyweight interface. Must be shareable.
  • FlyweightFactory class: Handles all flyweight object sharing. Can retrieve existing flyweight objects, or create new ones when necessary, usually through a shared collection.

While we won’t show an example of it here, another common type of object is the optional UnsharedFlyweight:

  • (Optional) UnsharedFlyweight class: Also implements the Flyweight interface, but these do not require sharing.

For our example code above we’re using these fundamental concepts to build the Library class, which behaves as our FlyweightFactory. We add IPublication objects, like Books and GraphicNovels, to our Library collection, sharing all these objects in a Dictionary collection.

We start by defining a few helper classes specific to our example. Author, Illustrator, and Publisher are not part of the flyweight design pattern, but they’re included to flesh out the code and illustrate something closer to a production example. The same goes for the PublicationType enumeration:

Next, we get to the first major component of our flyweight pattern, the IPublication interface, which acts as our Flyweight interface and defines a few properties:

We now need a few ConcreteFlyweight classes in the mix, so here we define our Book and GraphicNovel classes, both of which implement the IPublication interface:

In this case, Book and GraphicNovel contain slightly different property signatures, which is a bit more realistic. Regardless, the point is that we can define as many ConcreteFlyweight classes as we need to, and within our FlyweightFactory class we’ll actually differentiate between them when we need to handle sharing logic.

Speaking of the FlyweightFactory class, now we finally declare our own in the form of the Library class:

While this may appear a little complicated, we’ll break down the fundamental components of the Library class and you’ll see that there’s really not a lot going on at all.

A fundamental aspect of the flyweight pattern is the ability to share and reuse objects, so our factory needs a collection, or some other means of tracking all flyweight objects. Therefore, we begin with the Publications property:

We’re using a Dictionary here to store our collection, which makes it easy to use a complex key value (a three-part tuple, in this case) that can be associated with each IPublication object value.

The GetPublication() method is where most of the magic happens. In this example we’re using this method to perform all sharing, reuse, and creation logic, but obviously we could split this logic up if necessary. Again, we need a way to uniquely identify our IPublication (flyweight) objects, so we use the same three-value tuple as the key, then immediately create a new IPublication instance variable. We’ll use this variable throughout the method logic to hold either the new or existing object.

Next, when implementing reusability we won’t want to create objects that already exist, so we first check if the Publications collection property contains the key parameter. If the key exists in the collection we simply assign the existing object to the local publication value:

On the other hand, if key doesn’t exist in the collection we probably need to create a new object instance and add it to the collection. For this example we’re using the third value of our tuple key to store the PublicationType of the object. Therefore, we perform a switch() using that third item of the tuple, and try to find a match of either PublicationType.Book or PublicationType.GraphicNovel, both of which our method can handle:

If we’re dealing with a valid PublicationType our code creates a new instance of the respective object. Note: In this example only some of the arguments passed to new Book() and new GraphicNovel() are dynamic (e.g. obtained from the key tuple), while the remaining arguments are hard-coded. Obviously, this is a poor practice in a real-world application, but creating a five- or six-part tuple is a bit of a hassle, so I decided to leave it as is for now.

Lastly, since the local publication variable within this else block scope was assigned to the newly-generated IPublication instance, we need to Add() it to the shared Publication collection.

Now that we’re all set up let’s try actually using our Library flyweight configuration. We always begin by creating a new FlyweightFactory class instance (Library, in this case), and then use the GetPublication() method to create (or retrieve) object instances. In our first example here, we start by creating a new book and a new graphic novel, then we attempt to GetPublication() using the same key tuple values we passed in the first call:

We end the example with an output from library.GetPublicationCount, which simply retrieves the quantity of IPublication objects stored in the library. Since our second Book retrieval attempt uses the same values as the first, if our code is working correctly we’d expect to see only 2 publications in the collection, since the second Book call should be a retrieval of an existing record. However, our output actually shows that the second Book call also created a new instance, thereby giving us 3 objects in the collection:

Keen observers will probably already notice the problem: The Author value we’re passing into our tuple creation is always a new Author() instance in this example. Even though the string Name property value of the Author is the same in both cases, the underlying Author object is different, and therefore, the generated key that is used for comparison within the Library.GetPublication() method differs.

The solution is to explicitly pass the same instance of Author to both our Book retrieval attempts, which we do here in Example2():

Executing the above code now gives us the output we’d expect: The first call to Book and GraphicNovel both create new object instances, while the second identical Book call performs a retrieval of the existing book object, resulting in only 2 total publications in the collection:

Just for fun we’ve also included a final example illustrating what might happen if we pass a key to the retrieval method that doesn’t exist in the collection, but also cannot be used to create a new object instance either. To accomplish this we’ve added a third PublicationType enumeration, but the Library.GetPublication() method is only built to handle Book or GraphicNovel, throwing an ArgumentException if another type is used:

Sure enough, executing the above example method throws an exception our way, as expected:

This is just a small taste of what can be accomplished with the flyweight design pattern, but hopefully it helped to illustrate the agility and potential for resource savings that this pattern can provide. Check out more design patterns in our ongoing series over here!