Pattern Type: Structural
What Problem Does it Solve?
Straight from the Go4 book, the intent of the flyweight pattern is to “use sharing to support large numbers of fine-grained objects efficiently.”
So, a flyweight pattern helps us design situations where we have extremely large numbers of objects. There are a couple common examples you’ll find on the Internet and in software engineering books: painting large numbers of character objects in a document editing application (Go4) or rendering large numbers of trees in a landscape on the screen. As you can imagine, a given document can contain tens of thousands of characters; a forest in, say, an Elder Scrolls game such as Skyrim could contain huge numbers of trees and other foliage.
In software, it is useful to use objects to represent these objects. But consider the memory-related implications of creating huge numbers of objects. Creating 1,000 separate copies of the tree object suddenly seems extremely wasteful. Each tree could have the exact same attributes, like name, color, and texture.
Intrinsic and Extrinsic State
Part of the problem we have identified is that these trees contain information that is present in all other trees (at least in this simplified example). No matter where inside a forest you render a tree, it still has the same name, color, and texture. This sort of information, encapsulated by an object, is called intrinsic state. The more general description of intrinsic state is that it consists of information that is independent of context.
As you may expect, extrinsic state is dependent on context. In the tree example, this could include information about where you’re placing each tree. This information is not the same for all of your tree objects.
Now, going back to the description of flyweight’s intent from Go4, we are beginning to build a framework for what flyweights can do for us. We want to “use sharing.” Intrinsic state is the stuff we are able to share across all of our objects, while extrinsic state cannot be shared.
The Flyweight Solution
The flyweight is the object that holds our intrinsic state. We’re still creating thousands of character objects, or tree objects, or whatever object you’re creating thousands of. But each of these objects uses significantly less memory because the intrinsic state is sourced from a single location: the flyweight. Instead of storing name, color, and texture inside each tree object, this information is pulled from the single flyweight.
Many implementations of the flyweight solve this problem by introducing a map that contains the set of flyweights. Say we have 15-20 different types of trees; this would mean 15-20 different flyweights, each stored inside a map. We’ll see an example of this soon.
Example – World of Warcraft Whelps
In order to better visualize how a flyweight is used, we’ll utilize a fresh example (though it’s pretty analogous to the previously mentioned examples). Lets say you’re playing some vanilla WoW with your friends and you enter a large cavern. Suddenly a huge swarm of whelps spawns.
Lets analyze the whelp situation from a software engineer’s perspective. It’s pretty likely that we have a whelp object representing each one of these renderings on the screen. There’s a pretty good chance that if your computer is on the low end of RAM-oriented hardware, your gaming experience will slow down significantly at this moment.
There could be many complex reasons for this, but one very plausible explanation is the memory requirements of creating an object for each of these whelps! As a savvy developer, you want to ease the burden on the poor gamer’s machine, and you know that certain information about all of these whelps is common for every single one of them. They all have the same sprite – or graphical representation. They have the same speed attributes and names, and probably other information. There’s a lot of intrinsic state that we can source from a single location.
Keep in mind that there is also a significant amount of extrinsic state that the flyweight will not help us with. This might include the x, y, and z coordinates of each whelp, their current health points available, and other information. All of this would constantly need updating and recomputing; therefore, the flyweight may ease RAM burdens by reducing the size of the objects, but the CPU demands of thousands of whelp objects will still be relatively high.
As you can see below, we’ll use a factory pattern to help us encapsulate the generation of flyweights (here, the flyweight is WhelpType).
Here’s a simple Java representation of the WhelpFactory. It’s important that each flyweight be immutable, so we cache the created WhelpTypes and only create new ones when necessary, otherwise we reference the already-existing ones. This is how we source the Whelp’s intrinsic details from a single location.
Here is the WhelpType code; this is the flyweight object encapsulating all of the intrinsic state that we want to share across all whelps of the same type.
Notice that it does not encapsulate any information about a whelp’s location; this would be extrinsic state, which does not belong in the flyweight because it cannot be shared across all whelps. The extrinsic state is encapsulated inside each actual whelp object, as seen below.
Finally, we need a client object that utilizes the flyweight when it creates a whelp. Here, this would be the whelp “swarm” object, which gives us a method for the creation of whelps, “spawnWhelp.” It uses the factory to obtain the whelp type, and adds this to the constructor parameters for a whelp object.
- Flyweights are a RAM-oriented performance improvement. They are really only necessary for cases where you’re creating thousands of similar objects. Even the whelp example might be a stretch; it’s unlikely there will be enough whelps to justify a flyweight, unless the intrinsic state uses lots of space.
- Flyweights increase code complexity, and so should only be used when RAM considerations are very important.
- As mentioned earlier, there will still be CPU-intensive aspects that flyweight will not address. Constantly recomputing whelp coordinates, for example, would not be solved by a flyweight. More generally, any operations concerning the object’s extrinsic state will still be a burden on CPU resources.
I used a few fantastic resources to pull information about flyweights. My Whelp example is heavily influenced from https://refactoring.guru/design-patterns/flyweight
Additionally, my primary research came from the classic book Design Patterns: Elements of Reusable Object-Oriented Software (Go4).