The Flyweight pattern is used to create lots of small, related objects without invoking a lot of overhead work in doing so, thereby improving performance and maintainability.
The idea is that each Flyweight object has two pieces:
- The intrinsic state, which is stored within the Flyweight object itself, and
- The extrinsic state, which is stored or calculated by other components.
The Flyweight pattern allows many instances of an object to share their intrinsic state and thereby reduce the cost associated with creating them.
NOTE: This post is part of a series demonstrating software design patterns using C# and .NET. The patterns are taken from the book Design Patterns by the Gang of Four. Check out the other posts in the series!
The Rundown
- Type: Structural
- Useful? 1/5 (Nope)
- Good For: Creating lots of instances of the same set of objects and thereby improving performance.
- Example Code: On GitHub
The Participants
- The Flyweight declares an interface through which flyweights can receive and act upon extrinsic state.
- The ConcreteFlyweight objects implement the Flyweight interface and may be sharable. Any state stored by these objects must be intrinsic to the object.
- The FlyweightFactory creates and manages flyweight objects, while also ensuring that they are shared properly. When the FlyweightFactory is asked to create an object, it either uses an existing instance of that object or creates a new one if no existing one exists.
- The Client maintains a reference to flyweights and computes or stores the extrinsic state of said flyweights.
A Delicious Example
To model the Flyweight pattern, let's think about sliders.
For those of you who might not be familiar with the term "slider", a slider is a small hamburger, typically only 3 or 4 inches in diameter. They're often used as party snacks, but can also be a meal unto themselves.
At any rate, let's imagine that we need to create a whole bunch of these sliders for our fictional restaurant; this is a good model for Flyweight.
First, let's build a Slider
abstract class (the Flyweight participant):
/// <summary>
/// The Flyweight class
/// </summary>
abstract class Slider
{
protected string Name;
protected string Cheese;
protected string Toppings;
protected decimal Price;
public abstract void Display(int orderTotal);
}
The Slider
class has properties for Name
, Cheese
, Toppings
, and Price
(all of which are part of the intrinsic state of these objects), and an abstract method Display()
which will display the details of that slider.
Now we need our ConcreteFlyweight objects. Let's build three: one each for BaconMaster
, VeggieSlider
, and BBQKing
:
/// <summary>
/// A ConcreteFlyweight class
/// </summary>
class BaconMaster : Slider
{
public BaconMaster()
{
Name = "Bacon Master";
Cheese = "American";
Toppings = "lots of bacon";
Price = 2.39m;
}
public override void Display(int orderTotal)
{
Console.WriteLine("Slider #" + orderTotal + ": " + Name + " - topped with " + Cheese + " cheese and " + Toppings + "! $" + Price.ToString());
}
}
/// <summary>
/// A ConcreteFlyweight class
/// </summary>
class VeggieSlider : Slider
{
public VeggieSlider()
{
Name = "Veggie Slider";
Cheese = "Swiss";
Toppings = "lettuce, onion, tomato, and pickles";
Price = 1.99m;
}
public override void Display(int orderTotal)
{
Console.WriteLine("Slider #" + orderTotal
+ ": " + Name + " - topped with "
+ Cheese + " cheese and "
+ Toppings + "! $" + Price.ToString());
}
}
/// <summary>
/// A ConcreteFlyweight class
/// </summary>
class BBQKing : Slider
{
public BBQKing()
{
Name = "BBQ King";
Cheese = "American";
Toppings = "Onion rings, lettuce, and BBQ sauce";
Price = 2.49m;
}
public override void Display(int orderTotal)
{
Console.WriteLine("Slider #" + orderTotal + ": "
+ Name + " - topped with "
+ Cheese + " cheese and "
+ Toppings + "! $" + Price.ToString());
}
}
Note that the ConcreteFlyweight classes are very similar to one another: they all have the same properties. This is critical to using Flyweight: all of the related objects must have similar definitions.
Finally, we need our FlyweightFactory participant, which will create Flyweight objects. The Factory will store a collection of already-created sliders, and any time another slider of the same type needs to be created, the Factory will use the already-created one rather than creating a brand-new one.
/// <summary>
/// The FlyweightFactory class
/// </summary>
class SliderFactory
{
private Dictionary<char, Slider> _sliders =
new Dictionary<char, Slider>();
public Slider GetSlider(char key)
{
Slider slider = null;
//If we've already created one of the
//requested type of slider, just use that.
if (_sliders.ContainsKey(key))
{
slider = _sliders[key];
}
else //Otherwise, create a brand new instance of the slider.
{
switch (key)
{
case 'B': slider = new BaconMaster(); break;
case 'V': slider = new VeggieSlider(); break;
case 'Q': slider = new BBQKing(); break;
}
_sliders.Add(key, slider);
}
return slider;
}
}
All of this comes together in our Main()
(which is also our Client participant). Let's pretend we are an order system and we need to take orders for these sliders; the patron can order as many kinds of sliders as she or he wants.
static void Main(string[] args)
{
// Build a slider order using patron's input
Console.WriteLine("Please enter your slider order (use characters B, V, Z with no spaces):");
var order = Console.ReadLine();
char[] chars = order.ToCharArray();
SliderFactory factory = new SliderFactory();
int orderTotal = 0;
//Get the slider from the factory
foreach (char c in chars)
{
orderTotal++;
Slider character = factory.GetSlider(c);
character.Display(orderTotal);
}
Console.ReadKey();
}
When we run the app, we enter as many of those characters as we like to order as many sliders as we like.
Looking at the screenshot above, the FlyweightFactory will have only created new sliders for orders 1, 3, and 4, with every other order being a copy of those objects. This is the power of Flyweight: you can theoretically improve performance by only instantiating new objects on first creation.
Will I Ever Use This Pattern?
Probably not. In theory, this pattern could improve performance, but in practice it's limited to scenarios where you find yourself creating a lot of objects from one or more templates.
Further, the entire point of this pattern is to improve performance, and as I've written about before performance is not an issue until you can prove that it is, so while refactoring to this pattern may be useful in some extreme circumstances, for most people and most projects the overhead and complexity of the Flyweight pattern outweigh the benefits.
In my opinion, if you need to create lots of instances of an object, you'd be better off using something like the previously-discussed Prototype pattern rather than Flyweight.
Summary
The Flyweight pattern strives to improve performance by creating lots of objects from a small set of "template" objects, where those objects are the same or very similar to all their other instances. In practice, though, the usefulness of this pattern is limited, and you might be better off using Prototype. That said, if anyone has a different opinion on the benefits of Flyweight, I'd love to hear about it, so share in the comments!
Happy Coding!