When memory matters (and it kinda matters everywhere), the flyweight pattern is super… fly! It’s used way more often than you might realize and can help you as well…
You’d have to be a little crazy — or extremely dedicated (it’s a fine line) — to draw every single tree in an animated forest by hand. Copying and pasting maybe 3 three in different sizes and locations is a heck of a lot easier and doesn’t suffer too much by comparison. Best of all, it appeals to our laziness, which is, after all one of the three virtues of great programmers.
Doing stuff like this in 3D games and animation is even more important. Everything on the screen has wireframes, polygons, color and texture. And if you’re building a forest, those attributes equate to a lot of memory. Quite beyond simply running out of memory, there’s the question of getting hundreds of these things from memory to the graphics system for rendering — not just once, but 60 times every second so that the scene renders smoothly.
The solution is the same as lazy animation. Send a couple of model objects — objects that hold the shape, polygon surface, and texture to the graphics device — then send a hundred individual tree objects that specify different colors, locations, and sizes.
In graphics programming this is “indexed rendering.” Some heavy-ish wireframes are stored in the graphics system and referenced by index for lightning quick rendering.
The rest of the programming world knows this approach as the Flyweight pattern. Let’s have a look…
…At cells in a spreadsheet. The flyweight lessens memory requirements for a large number of similar objects. There are a lot of cell objects in spreadsheets.
And those cell objects tend to be surprisingly… heavy. You need to be able to set values, obviously. You need to be able to down arrow… a couple of times. And then set values again.
Even more interesting are left and right arrow keys. Normally a right arrow navigates to a new cell. Then you can hit Enter to start editing a cell. But… once you’re editing, arrow keys just move the cursor around inside the cell instead of navigating. But a down arrow always navigates.
It’s pretty crazy how many rules there are for editing cells in a spreadsheet. Once you recognize that there are so many rules, what is not surprising is how much code is involved.
You need properties to mark a cell active, to hold the current value, to position the cursor within the cell, to specify the current editing mode, and lots more. Cells have to handle arrow keys, and Enter, and regular character inputs. There is help, formula wizards, and events to notify others when things change.
That’s a lot — even for a simple 10×10 spreadsheet. What is this going to look like with hundreds of cells? Thousands? Something’s gotta give.
One of those somethings is the Flyweight Pattern.
So what’s the tree wireframe equivalent in this example? It’s probably all the stuff having to do with editing cell contents — handling arrow and enter and regular key presses. Because no matter which cell we’re talking about, editing works pretty much… uh, exactly the same in every cell. So instead of sharing the same code, let’s share.. a single
CellEditor will handle arrow keys — moving the cursor within the cell or generating navigation events depending on the cell’s editing mode. The cell editor can also handle typing. And help and formula wizards.
What’s left back in
Cell are things specific to individual cells – the value of the cell, a boolean indicating if this cell is the currently active cell in the spreadsheet, and things of that nature.
To gain access to the editing features,
Cell needs an instance of the
CellEditor. And it delegates editing methods and properties to this editor. In Dart, we get cheap delegation via
noSuchMethod(). When the method or property isn’t in this class,
noSuchMethod() can delegate it to
editor (using mirrors). And boom! Our spreadsheet is still working.
But we’re not flyweighting just yet. Each
Cell has its own copy of
CellEditor. So we’ve still got the same memory pressure on our hands. Instead of 100 cell editors, we just want one that gets shared by all cells.
So, convert the private
_editor property to a static class property — instead of one editor for each instance of
Cell, there will be one for the entire class. That… almost works.
What this out of range error means is that our shared cell editor is broadcasting single navigation events to all 100 cells in our worksheet. Only the currently active cell should be broadcasting events, so we redefine the stream of events to do just that.
With that, we have a working flyweight. The very lightweight
Cell class acts as the Flyweight factory — creating a
CellEditor as needed and delegating the heavy lifting of editing to that shared instance. That one shared instance of
CellEditor — the one that is reused by all cells in the spreadsheet — is the Concrete Flyweight — the wireframe tree model equivalent. And the editable interface that they both implement is the Flyweight itself.
The flyweight pattern is a nifty little win for memory challenged systems. This doesn’t have to be thousands of objects either, just enough to save appreciable memory.
We’re well beyond the days when 640K of memory was enough for anybody. But memory still matters in web pages, mobile apps, and… well, just about everywhere. And where memory matters, the super flyweight will be there.