The flyweight structural pattern allows you to represent more items in your application as class instances without using up all your computer memory.
It does this with four steps:
- Separate the data members in your classes that you’re considering for this pattern into intrinsic and extrinsic state. Intrinsic state is common for all instances and belongs with shared instances of the class. Extrinsic state is anything that’s unique to each instance and can’t be shared.
- Use either a factory or a modified singleton to create a pool of shared objects where there’s just one object of each type that’s participating in this pattern. There should only be one instance of each class with the same intrinsic state.
- Pass the extrinsic state to each class method that needs it. You’ll likely have several data members that need to be kept separate like this and they can all be bundled into a context that gets passed to each method.
- And try to calculate the extrinsic state as much as possible. This will lead to even more memory savings.
This pattern shows you how to save a lot of memory when dealing with many instances of your classes. It’s likely to allow you to represent items as class instances that you wouldn’t have been able to consider before. Listen to the full episode or you can also read the full transcript below.
The basic description says that this pattern allows you to share large numbers of objects efficiently.
In the adventure game, there’s probably going to be lots of trees outside. And for that matter, there’ll be lots of rocks, boulders, and shrubs. What about in a town? There’s the obvious buildings. But what about all the windows and doors? What about all the tables and chairs? Almost everything in the game could be considered to be defined by a class.
The problem with this is that if you try to instantiate all these objects, you’ll quickly run out of memory. Whenever you create a new instance of a class, you need dedicated memory to hold all that class’s member data. The methods are shared so at least that much is good. You don’t have to allocate memory for all the method instructions for each instance of a class. Just the member data.
This pattern explains how you can do even better. That means it now becomes feasible to represent all the game elements with their own class and create an instance for each occurrence. Got a forest with a hundred thousand trees? With this pattern, you can now treat each one of those trees as if they were unique instances. Notice, I’m saying as if. No design pattern including this one, can magically give your computer unlimited resources. Even if the application itself deals with magic. It just doesn’t work that way.
Design patterns will show you clever ways to think about solutions to problems that developers face all the time.
What you need to do in order to use this pattern is first figure out if there’s any data in your classes that could be shared among many or all instances. Then for the data that has to be unique, is there any way it could be calculated instead of stored in memory.
I hope you see the tradeoff we’re making here. Most engineering problems have multiple solutions that trade one aspect for another. Maybe weight gets traded for cost. What this pattern does is trade storage or memory requirements for some extra runtime calculations. This means your program will run a little slower but the benefit is that your user will appreciate the details that are now possible in that forest.
I’ll explain exactly how the flyweight pattern works right after this message from our sponsor.
( Message from Sponsor )
Let’s take the specific example of how to represent all the doors in a town. Most games will have separate towns with a theme or a general appearance that’s common to all the buildings. This is another one of those things that mirrors real life. Just visit a typical housing complex where a builder has constructed maybe 50 houses and chances are that they’ll look a lot alike. Maybe they have different colors but this is where the phrase “cookie cutter house” comes from. It looks like all the houses were stamped from the same mold.
We can make use of this in a game too. A typical town might only have 2 types of doors, normal single doors and slightly larger double doors. A door class needs to know how big it is, right? It should have width and height dimensions which will probably both be integers. And it’ll probably have a color or a texture. And it’ll have a state so each door knows if it’s open or closed. these are some basic data members that each door will need in order to provide proper object-oriented behavior. But does each door really need it’s own copy of all this? No. And that’s where you can save some space initially. And in just a moment, I’ll explain how you can save even more space.
We’ll start by having someplace we can go to get a new instance of a door. This could be a factory or you could implement my modified singleton pattern. Check out episodes 59 and 60 for more information. The key is that when you ask for a new door, you get a reference to a door that’s already been instantiated if this is the second or subsequent request. This means that for our town of maybe 30 buildings with doors not just on the outside but inside too, there could be hundreds of doors that we need to create. But in reality, there will only be two. One will be the small single door, and the other will be the larger double door.
Each door will only contain data members that are common. So the size dimensions and texture will be stored in each door. How much memory have we already saved? Let’s forget about the texture for a moment and just look at the two integers spread across 200 doors. Two integers at 4 bytes each is 8 bytes and for 200 hundred doors, would have required 1600 bytes. But since we only have two doors, we only need 16 bytes. And this is just for the doors! Now a sixteen hundred bytes may not seem like a lot of memory but like I said, this is just for the doors. And still, that’s sixteen hundred bytes that you can now put to use for something else. And all it takes is a little knowledge about a well known software pattern.
The data that stays with each object like the door dimensions is called intrinsic state. This is information that is part of the object and doesn’t change. It can be shared very easy.
But some data needs to change from one instance to another. This is called extrinsic state and can’t be shared. In this example, some doors might be open and some might be closed. We can’t share this information because that would just be weird. Imagine walking into a town and opening a door and every single door opened at the same time. So some data just needs to be specific and if there are 200 doors, then there needs to be 200 individual flags representing which doors are open and which are closed, right?
So we’ve already save a lot of memory just by sharing the intrinsic data, now let me explain how this pattern goes into overdrive and allows you to save a lot of the extrinsic state as well.
Just think about these doors for a moment. Would you expect to walk into a town and find them randomly open and closed? Maybe if these were barn doors. But normal doors, even the big double doors will normally be closed.
This means we don’t need to actually remember if each and every door is open or closed. We can calculate the extrinsic state instead. That’s the other half of this pattern. Try to first identify and share all the intrinsic data. Then calculate as much extrinsic data as possible.
One way to do this would be to store references to just those doors that are open. And it’ll make sense to keep this list small by closing doors again when the hero isn’t looking. If that’s not possible, then consider storing ranges of doors instead of individual doors. What do I mean by this? Well, if five doors are open in a row, then just remember the first and last of these doors and whether or not they are all open or closed. This is where you might have to do a bit of calculating but the tradeoff is definitely worth it.
This means that the door class won’t have enough information anymore to work independently. You’ll need to tell each door whether it’s open or closed each time you call one of the door methods that need this information. Since this is fairly critical information for a door to know about, you’ll probably need to pass this state in every call. This is called the context and the pattern describes how you can put the extrinsic state together into a bundled context that you can then pass to the door methods.
What this is really doing is getting away from full object-oriented data encapsulation. We’re breaking the encapsulation by removing some of the data from the class and putting it in the context. Be careful with this pattern because overusing it can turn an object-oriented design into a procedural design. This is the sort of thing that C++ is supposed to make better. And it does but with a cost. When that cost becomes too high, the flyweight pattern can help bring it back.