Description
Flyweight pattern is
one of the fundamental design patterns described in the volume
“Design Patterns: elements of reusable object-oriented software”.
It falls in the category
of structural patterns.
Suppose that, in
your app, you have to create and use many objects, and this process
requires a lot of memory and/or computational resources. The purpose
is to reduce the proliferation of objects as much as possible.Solution. In a class
we can identify two different states:
- internal state: internal state corresponds to the features of the object that are constant, regardless of the context in which it is used; the internal state can therefore be shared (there is no need to create different objects with the same internal state: we can re-use the already existing ones);
- external state: external state contains the features of the object that depend on the context in which the object is created and used.
Implementation
In the following
example we create 20 circles of different colors (5 different colors)
with random location and size; using the Flyweight pattern we can
create only 5 objects at most, corresponding to the 5 different
colors (the color represents here the internal state of the object).
Flyweight
public interface Shape {
void draw(int x, int y, int size);
}
This interface
represents a simple geometric shape with a draw method to draw
itself. The draw method takes some parameters (x, y, size)
corresponding to the external state of the object (parameters set “on
the fly” by the client when needed).
When defining the
Flyweight interface you therefore have to find out the parameters
corresponding to the external state of the object: in our simplified
example the location and size of each shape.
ConcreteFlyweight
public class Circle implements Shape {
private String color;
public Circle(String color){
this.color = color;
}
@Override
public void draw(int x, int y, int size) {
System.out.println("Circle: Draw() [Color : " + color + ", x : " + x + ", y :" + y + ", radius :" + size);
}
}
Circle implements
Shape interface and represents a specific geometric shape: a circle.
Each circle has a color (set with the constructor) and implements the
draw method taking x, y and size as parameters.
Notice that Circle
has an instance variable, color, which is the internal state of the
object (the part of the object that can be shared).
The external state, depending on the context (x, y and size), is set by the client “on the fly” when the circle must be drawn and passed as a parameter to the draw method.
The external state, depending on the context (x, y and size), is set by the client “on the fly” when the circle must be drawn and passed as a parameter to the draw method.
When defining the
ConcreteFlyweight you therefore have to identify the parameters
corresponding to the internal state of the object; these parameters
are stored inside the object itself so that they can be shared.
FlyweightFactory
public class ShapeFactory {
private static final HashMap circleMap = new HashMap();
public static Shape getCircle(String color) {
Circle circle = (Circle)circleMap.get(color);
if(circle == null) {
circle = new Circle(color);
circleMap.put(color, circle);
System.out.println("Creating circle of color : " + color);
}
return circle;
}
}
ShapeFactory is a factory
class creating Circle objects requested by the clients through its
getCircle(String color) method.
This class keeps an
internal HashMap of Circle objects, where the key is the color of the
circle.
The getCircle(String
color) method tries to retrieve an already existing Circle from the
HashMap with the requested color. If a circle of the specified color
already exists in the HashMap, getCircle simply returns it (it is not
created anew); otherwise a new Circle object is created, put in the
HashMap and returned to the client.
The discriminating element
for deciding whether to create a new circle or not is the color, that
is the internal state of the object.
Internal state of
the object. Parameters that don’t
vary with the context in which the object is used (in our example,
the color). These parameters can be shared: there is no need to
create a new object if the internal state stays the same.
External state of
the object. Parameters that vary with
the context in which the object is used (in our example, the location
and size of each circle). These parameters are set by the client “on
the fly” before invoking the required method (in our example, the
draw method).
Client
public class FlyweightPatternDemo {
private static final String colors[] = { "Red", "Green", "Blue", "White", "Black" };
public static void main(String[] args) {
for(int i=0; i < 20; ++i) {
Circle circle = (Circle)ShapeFactory.getCircle(getRandomColor());
circle.draw(getRandomX(),getRandomY(),100);
}
}
private static String getRandomColor() {
return colors[(int)(Math.random()*colors.length)];
}
private static int getRandomX() {
return (int)(Math.random()*100 );
}
private static int getRandomY() {
return (int)(Math.random()*100);
}
}
In our example the client
draws 20 circles with different location (x, y) and size, randomly
chosen. Notice, however, that we don’t create 20 different objects
but 5 at most! Circles of the same color are infact retrieved from
the HashMap; the external state (x, y and size) is simply set by the
client before invoking the draw method.
The discriminating
element for deciding whether to create a new circle or not is the
color: if the color varies a new object must be created; if the
location and size vary we can re-use a circle of the same color and
set these parameters by invoking the draw method.
The client doesn’t
create the objects directly, but uses the factory class ShapeFactory,
which is responsible for creating a new object or retrieving an
already existing one.
Notice that the external
state of the object (x, y and size) is not stored inside the object
itself, but managed and set by the client.
Structure
UML Diagram |
Notice the
UnsharedConcreteFlyweight class. UnsharedConcreteFlyweight is a class
whose state must not be shared (unlike the ConcreteFlyweight); at the
same time UnsharedConcreteFlyweight uses the same interface of the
Flyweight (in our example, a draw method with x, y and size as
parameters).
Consider as an example a
text editor software:
- ConcreteFlyweight: in a text editor software the ConcreteFlyweight is a single Character, whose internal state must be shared (for example the letter represented by that charcter, ‘a’, ‘r’, ‘d’, etc.);
- UnsharedConcreteFlyweight: a Row is a good example of an UnsharedConcreteFlyweight. Each row must be drawn according to a specific external state (size, font, etc.): it therefore implements the Flyweight interface. At the same time there is not an internal state that can be shared (each row is different from each other). UnsharedConcreteFlyweight usually have Flyweight objects as children: a Row is composed by different Characters (that are Flyweight objects).
Insights
Applications
Web browsers generally use this pattern to display images in a web page. Suppose that the same image must be displayed in the same page in different locations and sizes. The Flyweight pattern can be used here to avoid donwloading the same image more times from the web (internal state: the image to display; external state: location and size).
War game. An object of type "soldier" must be created many times because an army is composed of different soldiers. However some features are common (graphic representation, movements, etc.), while others vary depending on the context (location, health, etc.). The Flyweight pattern can be applied here to avoid the proliferation on unnecesary objects.
Text editor. In a text editor app we could use an object, let's call it Character, to represent each character in the document. Each character has some properties, like font, size, the character displayed, etc. A document contains a lot of characters, so this approach causes un unnecesary proliferation of objects. To avoid this we could apply the Flyweight pattern as follows:
- internal state: the internal state is the character displayed ('a', 'b', 'c', etc.). For example, if the document contains several letters 'a' we don't have to create an object for each one; instead we can re-use the same object as previously described;
- external state: the external state could be the font, the size, the location of each character.
State and Strategy pattern
State and Strategy objects are generally implemented as Flyweights.
Singleton pattern
In our example of the Flyweight pattern ConcreteFlyweights are created using a factory class. For each type of shape, that is a shape with a given color, only one instance is created: the same instance is re-used when necessary.
This is an example of application of the Singleton pattern.