The Flyweight pattern

This article was originally published in VSJ, which is now part of Developer Fusion.
Having worked on object oriented systems throughout my career the advantages of objects seem obvious. As well as increasing the flexibility of software, they are easy to understand – after all, we encounter objects in everyday life. This accessibility makes them easy to explain to non-technical team members.

Object instantiation, however, can be expensive both in terms of memory and performance. In systems that have lots of objects we may find ourselves compromising our model because of this. Wouldn’t it would be great if we could have the advantages of objects without the disadvantages? In this article we are going to examine a pattern that overcomes these disadvantages by sharing objects.

Scenario

Consider a game as an example of an application that uses thousands, if not hundreds of thousands, of objects in real time. We want to create a rich environment and interact with it in as realistic a manner as possible. To this end we want to model everything as an object, including scenery. However, with both man-made and organic scenery it is easy to imagine how the proliferation of these objects would quickly affect memory and degrade performance. This overhead may make our game design unfeasible.

To illustrate this, imagine we are developing a simple maze game. Our game maze has bricks that have different behaviours. Our initial design has only two types; solid and breakable bricks as shown in Figure 1.

Figure 1
Figure 1

Though simple, this example will still encounter problems – it doesn’t scale because the number of objects increases with the size of the maze. We need a way to manage the proliferation of fine grained objects.

Flyweight

The Flyweight pattern can be used to reduce the number of objects created. It does this by enabling us to share object instances. To do this we delegate instantiation to a factory class that contains a pool of all the possible objects, as shown in Figure 2.

Figure 2
Figure 2

This is simple to implement in C#:

public static class MazeFactory
{
	private static IDictionary
		<WallType, Wall> wallPool =
		new Dictionary<WallType,Wall>();
	public static Wall GetWall(
		WallType key)
	{
		Wall wall;
		if (wallPool.ContainsKey(key))
			wall = wallPool[key];
		else
		{
			Wall = CreateWall(key);
			// omitted for brevity
			wallPool.Add(key,wall);
		}
		return wall;
	}
}
For object sharing to work we must ensure all object creation is performed by the MazeFactory class. We can do this by making the constructors visible only inside the MazeFactory assembly.
public abstract class Wall
{
	protected Wall()
	{ }
}
public class Solid : Wall
{
	internal Solid() : base()
	{ }
}
public class Breakable : Wall
{
	internal Breakable() : base()
	{ }
}
When we call the GetWall() factory method with a key we get a newly created object the first time and a reference to the previously created instance thereafter. Our model now scales as the number of objects no longer depend on the size of the maze. We will only ever have as many instances as we have keys.

Comparing flyweights

Applications that need to compare object instances can’t use this technique. Consider the example client code below:
class MazeGame {
	static void Main(string[] args) {
		Wall s=MazeFactory.GetEvent(
			WallType.Solid);
		Wall s1 = MazeFactory.GetEvent(
			WallType.Breakable);
		Wall s2 = MazeFactory.GetEvent(
			WallType.Solid);
		Console.WriteLine(s==b);
		Console.WriteLine(s==s1);
		}
	}
The second comparison returns positive because we are returning a reference to the variables. This is a small restriction considering the benefits of using flyweights.

Independent state

However, there is a bigger disadvantage; objects encapsulate data and act independently of each other. By sharing object instances like this we have lost this ability – if we change a property on s, it will be reflected in s2. We need a mechanism to allow these objects to have independent object state.

Imagine our Wall objects have a Mass, Size and Colour that differs throughout our maze. We can’t include this on the base class or the concrete classes because this information is dependant on the instance of the class, and we are sharing instances from our object pool. Flyweights solve this by removing all non-shared state and putting it in an external data structure. This can then be passed to the object as method parameters when required. The Gang of Four refer to non-shared state as extrinsic state (GoF), whereas shared state is referred to as intrinsic state.

This can be demonstrated by implementing some collision detection. The abstract class acts as the interface for all our concrete classes, so let’s implement it:

public enum WallSize
{ Small, Medium, Large }
public class WallConfiguration
{ public WallSize Size=WallSize.Medium; }
public abstract class Wall
{
	WallConfiguration config = new WallConfiguration();
	protected Wall() { }
	public bool Intersects(Point p)
	{	// use intrinsic state.
		return false; }
	public bool Intersects(Point p,WallConfiguration externalState)
	{	// override instrinsic state and use extrinsic state.
		return false; }
}
We have added a new enumeration for different wall sizes and a WallConfiguration class to store this external state. We have a new method on our abstract class. The Intersects method determines if our wall object occupies a given point. We have supplied two implementations; one will use the internal state and the other will override this and use the extrinsic state supplied.

Of course, this solution relies on there being fewer possible extrinsic states than there would have been wall objects had we not used flyweights. This is possible in our example because there would be a limited number of variations. Additionally, wall objects with similar attributes may be next to each other or possibly there would be a few more common wall configurations than another. Predictable data allows us to use an efficient data structure for storage (Run Length for example).

Real World Flyweights

Although flyweights reduce the number of objects, they also introduce additional complexity because we have to manage non-shared state externally. This does place some restrictions on their use.

Flyweights are only useful for objects whose non-shared state can be externally managed or better yet calculated, and when the sharing of objects greatly reduces the number of instances created. For example, it is common to implement Strategy and State patterns as flyweights. One of the consequences associated with these patterns is the proliferation of fine grained objects. They are also good candidates for flyweights because they have a limited number of permutations.

Conclusion

This article has introduced the Flyweight pattern and described how it can be used to reduce the proliferation of fine grained objects by sharing object instances. This requires us to identify shared and non shared state and implement non-shared state in an external data structure.


Garfield Moore has been working as a freelance consultant for over ten years, specialising in object oriented design and agile methods.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“It works on my machine.” - Anonymous