In my brief career in software development thus far, I have seen a lot of “WTF” code, that is, code that deserves to be posted to The Daily WTF. Some of this code was admittedly developed by myself and upon reviewing it a few months after it was written, I secretly wondered what I’d been thinking.
This isn’t going to be an indictment of bad programming; in fact, I think it’s good if you can look back at your old code and see where it could be improved. Such a process suggests that you are continually self-improving, a skill crucial in software development. Besides, all of us have made a mistake or two at times when we were stressed, tired or just plain not thinking straight.
However, there’s one mistake that I’ve seen that I think warrants bringing to light, and that is the misuse of the Flyweight pattern.
Who wants to be a Flyweight?
Flyweight is typically used to describe one of the smaller weight classes in boxing or other fighting sports. This “minimal” aspect is what is shared with the design pattern of the same name. Simply put, a Flyweight object is one that reduces memory use by sharing common data with other objects. Despite this plain definition, implementing the Flyweight pattern can be tricky.
Perhaps this is why I have seen examples like this: (Java pseudo-code below; may not compile, but you shouldn’t use it anyways)
public class WidgetWithManyFields() {
private Data field1;
private String field2;
private int field3;
// A lot more fields...
private SomeOtherData fieldN;
// Getters and setters...
}
Now, obviously the memory footprint of WidgetWithManyFields
can be quite large, and since not all aspects of an application will need access to all data fields, it was decided that a “Flyweight” was needed:
public class WidgetFlyweight() {
// Only these fields are needed.
private Data field1;
private String field2;
public WidgetFlyweight() {
// Default constructor.
}
// Constructor to make one from the regular widget class.
public WidgetFlyweight(WidgetWithManyFields widget) {
this.field1 = widget.getField1();
this.field2 = widget.getField2();
}
// Getters and setters...
}
This isn’t really the Flyweight pattern at all. In fact, I don’t even know if it is a pattern at all. It might be considered something like the Proxy pattern, if the “Flyweight” class contained an instance of the regular class. But I don’t really know.
So what is a Flyweight?
Consider the example of a document that can have images embedded in it. There might be multiple copies of the same image present in the document, but each copy would be sized and positioned differently within the document.
In this case, you wouldn’t want to load and store the data in memory for multiple copies of the same image as that would be wasteful. However, each instance of the image displayed in the document might be formatted or positioned differently. How might this be done?
Firstly, some assumptions:
- An image is uniquely identified by some resource path.
- The underlying image data does not change during the lifetime of the application.
With these assumptions, we can define three classes that allow us to implement the Flyweight pattern.
Firstly, an ImageData
class that encapsulates the actual image data. There should be only one canonical instance of this class for each unique resource path. Because of this, we can pool these objects for reuse.
However, the ImageData
objects won’t be directly used by other parts of the application. Instead, we create an ImageFlyweight
class that is manipulated. Each instance contains a reference to a canonical ImageData
object and also stores information about how to format and position the image.
In this way, there can be multiple ImageFlyweight
instances that reference the same image and hence the same ImageData
instance, but each instance would define separate formatting and positioning details.
Tying everything together is a factory (ImageFlyweightFactory
) that maintains the pool and is the access point for getting instances of ImageFlyweight
.
Below is the code: (Sorry, it’s a lot of code to throw at you at once, but I didn’t feel like breaking it down into separate chunks, and you can just copy & paste it into your favourite IDE for inspection/compilation)
/**
* Copyright (c) 2012 Peter Chng, http://unitstep.net/
*/
package net.unitstep.examples.flyweight;
import java.util.HashMap;
import java.util.Map;
/**
* In order for the Flyweight Pattern to be effective, ImageFlyweight instances
* should only be obtained via ImageFlyweightFactory.getImageFlyweight().
*
* This ensures that for each unique resource path, there is only one instance
* of the backing ImageData existing in the application.
*
* @author Peter Chng
*/
public class ImageFlyweightFactory {
private Map<String, ImageData> imageDataPool =
new HashMap<String, ImageData>();
public ImageFlyweight getImageFlyweight(final String resourcePath) {
// This will return a new ImageFlyweight object each time; however, the
// backing ImageData might be shared across multiple ImageFlyweight
// instances.
return new ImageFlyweight(this.getImageData(resourcePath));
}
private ImageData getImageData(final String resourcePath) {
ImageData imageData = this.imageDataPool.get(resourcePath);
if (null == imageData) {
imageData = new ImageData(resourcePath);
this.imageDataPool.put(resourcePath, imageData);
}
return imageData;
}
/**
* @return the current count of ImageData instances in the pool; only for
* testing purposes.
*/
public int getImageDataPoolCount() {
return this.imageDataPool.size();
}
/**
* Will contain the data representing an image loaded from some resource, i.e.
* the file system.
*
* This is a private inner class because it should never need to be used
* externally by callers. It is considered an implementation detail.
*
* We assume that the resource path is the uniquely-identifying aspect of an
* image and that the underlying image resource/data will not change over the
* lifetime of the application.
*
* Thus, only one instance of the ImageData class is needed for each image
* uniquely identified by its resource path.
*
* @author Peter Chng
*/
private class ImageData {
private final byte[] data;
private final String resourcePath;
public ImageData(final String resourcePath) {
this.resourcePath = resourcePath;
// Image data would be loaded here based on the resource path supplied.
// For brevity, it's not really done.
this.data = new byte[] {};
}
public byte[] getData() {
// Note: If we really intend to make this class immutable, we should
// return a defensive copy instead so that callers cannot modify the
// data stored in this instance.
return this.data;
}
public String getResourcePath() {
return resourcePath;
}
// Note: Not strictly necessary to override equals() and hashCode() for this
// example, but it's done to indicate we only consider the resource path
// in determining equality.
@Override
public boolean equals(final Object object) {
if (null == object) {
return false;
}
if (object == this) {
return true;
}
if (object.getClass() != this.getClass()) {
return false;
}
return this.resourcePath.equals(((ImageData) object).getResourcePath());
}
@Override
public int hashCode() {
return this.resourcePath.hashCode();
}
}
/**
* The ImageFlyweight object contains a reference to a canonical ImageData
* object containing the actual image data we wish to render.
*
* By making this a static inner class of {@link ImageFlyweightFactory} and
* the constructor private, instantiation of this class can be controlled and
* limited to only the {@link ImageFlyweightFactory}. Callers MUST obtain an
* instance of the ImageFlyweight through the factory and not by direct
* instantiation.
*
* It also contains other properties that will affect the rendering of the
* image in the application, such as height, width and position.
*
* Reusing the same ImageData object across different ImageFlyweight instances
* allows us to display the same image in different ways within the
* application, without having to load (or store in memory) the image data
* multiple times.
*
* @author Peter Chng
*/
public static class ImageFlyweight {
private final ImageData imageData;
private int height;
private int width;
private int positionX;
private int positionY;
private ImageFlyweight(final ImageData imageData) {
this.imageData = imageData;
}
public byte[] getData() {
return this.imageData.getData();
}
// Getters/setters for height, width, positionX, positionY...
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getPositionX() {
return positionX;
}
public void setPositionX(int positionX) {
this.positionX = positionX;
}
public int getPositionY() {
return positionY;
}
public void setPositionY(int positionY) {
this.positionY = positionY;
}
}
}
Everything is contained within the ImageFlyweightFactory
class, because the ImageData
class does not need to be visible to outsiders and callers should not be able to instantiate ImageFlyweight
instances on their own.
With this code, we have a simple test harness to verify whether it’s working:
/**
* Copyright (c) 2012 Peter Chng, http://unitstep.net/
*/
package net.unitstep.examples.flyweight;
import net.unitstep.examples.flyweight.ImageFlyweightFactory.ImageFlyweight;
import org.apache.log4j.Logger;
/**
* @author Peter Chng
*/
public class ImageFlyweightTest {
private static final Logger LOGGER =
Logger.getLogger(ImageFlyweightTest.class);
public static void main(final String[] args) {
final ImageFlyweightFactory factory = new ImageFlyweightFactory();
final String resourcePath1 = "/path/to/images/someImage.png";
final String resourcePath2 = "/path/to/images/anotherImage.png";
final ImageFlyweight image1 = factory.getImageFlyweight(resourcePath1);
displayImageDataCountInPool(factory);
final ImageFlyweight image2 = factory.getImageFlyweight(resourcePath2);
displayImageDataCountInPool(factory);
// Should not create an new ImageData instance in the pool.
final ImageFlyweight image3 = factory.getImageFlyweight(resourcePath1);
displayImageDataCountInPool(factory);
}
private static void displayImageDataCountInPool(
final ImageFlyweightFactory factory) {
LOGGER.debug("Current number of ImageData instances: "
+ factory.getImageDataPoolCount());
}
}
Running the code yields the following results:
DEBUG ImageFlyweightTest - Current number of ImageData instances: 1 DEBUG ImageFlyweightTest - Current number of ImageData instances: 2 DEBUG ImageFlyweightTest - Current number of ImageData instances: 2
The key point is that after the third ImageFlyweight
object is created, the count in the ImageData
pool does not increase since the same image has already been “loaded”.
Other examples
Note that Java itself implements something similar to the Flyweight pattern for Strings; this is known as string interning and many other languages support this feature as well.
Basically, because Strings are immutable, Java can store each distinct value in a pool and then reuse these instances when appropriate. As an example, the following code displays “EQUAL”:
String string1 = "A test of the string intern pool.";
String string2 = "A test of the string intern pool.";
// Note that we are comparing object identity, NOT equality.
if (string1 == string2) {
System.out.println("EQUAL");
} else {
System.out.println("NOT EQUAL");
}
Note that this doesn’t work if you directly create a String using the new
keyword.
Conclusion
I know that this was a fairly contrived example (aren’t they all?), but I hope it provided the basics of the Flyweight pattern to readers. There are a lot of holes and I don’t suggest you directly copy this example for production code, but instead learn the skills to effectively develop the pattern on your own.
As always, I welcome questions or comments and especially corrections if I’ve made a mistake! Thanks for reading!