Wednesday, 5 March 2008

FlyWeight Design Pattern in Java

Today we will discuss another GoF Structural Pattern "FlyWeight".

Overview:
The reusable and variable parts of a class are broken into two classes to save resources.

Wikipedia Says:
A flyweight is an object that minimizes memory occupation by sharing as much data as possible with other similar objects.

Intent:
-> Use Sharing to support large numbers of fine-grained objects efficiently.[GoF,p195]
-> The Motif GUI Strategy of replacing heavy-weight widgets with light-weight gadgets.

Problem:
Designing objects down to the lowest levels of system "granularity" provides optimal flexibility,but can be unacceptably expensive in terms of performance and memory usgae.

Structure Summary:
1) Choose a class from which so many instances will be created that performance will suffer.
2) Identify the state associated with the class that will not vary from one instance to another,and the state that is peculiar to each individual instance.
3) State that is peculiar i.e cannot be shared will be maintained and supplied by client.
4) Hundreds of objects can now be exercised by caching and reusing a few "flyweight" objects.

Abstract Factory,Singleton and Template Method patterns fall under this category.Flyweight pattern is often combined with the Composite Pattern to implement a logically hierarchical structure in terms of a directed-acyclic graph with shared lead nodes.Flyweight is a strategy in which you keep a pool of objects available and create references into the pool of objects for particular views.It uses the idea of canonical objects.A canonical object is a single representative object that represents all other objects for particular type.

Discussion:
Flyweight pattern describes how to share objects to allow their use at fine granularities without prohibitive cost.Each "Flyweight" object is divided into 2 pieces.The state dependent(extrensic) part and the state-independent(intrinsic) part.Intrinsic state is stored(shared) in the Flyweight object.Extrensic state is stored or computed by client objects,and passed to the Flyweight when its operations are invoked.Flyweights are shared objects and that using them can result in substantial performance gains.Flyweights are typically instantiated by a flyweight factory that creates a limited number of flyweights and doles them out,one at a time to clients.For example, you might have a pool of line objects that know how to draw lines. In that case, the flyweight factory could create one line object for each line color, such as one object for white lines and another for blue lines. Those lines, which are flyweights, get reused whenever you draw white or blue lines. If you have a drawing with 1,000 white lines and 6,000 blue lines, only two lines—instead of 7,000—are actually instantiated. In java strings specified at compile-time are flyweights—strings that contain the same character sequence are shared. That sharing can greatly reduce memory footprints, and therefore, increase performance. Strings computed at runtime, are not flyweights by default; however, you can force the issue with the String.intern() which returns flyweights for strings computed at runtime.


Structure:
Flyweights are stored in a Factory's repository.The client restrains herself from creating Flyweighs directly,and requests them from the Factory.Each Flyweight cannot stand on its own.Any Attributes that would make sharing impossible must be supplied by the client whenever a request is made of the Flyweight.If the context lends itself to "economy of scale"(i.e the client can easily compute or look-up the necessary attributes),then the Flyweight pattern offers appropriate leverage.

Example:
The Flyweight uses sharing to support large numbers of objects efficiently. The public switched telephone network is an example of a Flyweight. There are several resources such as dial tone generators, ringing generators, and digit receivers that must be shared between all subscribers. A subscriber is unaware of how many resources are in the pool when he or she lifts the handset to make a call. All that matters to subscribers is that a dial tone is provided, digits are received, and the call is completed. [Michael Duell, "Non-software examples of software design patterns", Object Magazine, Jul 97, p54]

CheckList:
1) Ensure the object overhead is an issue needing attention,and the client of the class is able and willing to absorb responsibility realignment.
2) Divide the target class's state into: shareable(intrinsic) state,and non-shareable(extrensic) state.
3) Remove the non-shareable state from the class attributes,and add it to the calling argument list of affected methods.
4) Create a factory that can cache and reuse existing class instances.
5) The client must use the Factory instead of the new operator to request objects.
6) The client(or a third party) must look-up or compute the non-shareable state,and supply that state to class methods.

Flyweight Considerations:
The effectiveness of Flyweight pattern as a caching mechanism depends heavily on certain characteristics of the data we are caching.

-> Application uses a large number of objects.
-> Storage(Memory) Cost is high to replicate this large number of multiple users.
-> Either the objects are immutable or their state can be made external.
-> Relatively few shared objects may replace many groups of objects.
-> Application does not depend on object identity.While the user may think they are getting a unique object, they actually have a reference from the cache.

The flyweight design pattern is not recommended when the objects in the cache change rapidly or unexpectedly.

Summary:
According to Neal Ford
"Adding caching to an application isn't a guaranteed way to improve performance. You should determine that an improvement is needed before you add the extra complexity to the application. To determine whether it is needed and if it is working, you should measure the actual performance of the application. It is possible to degrade your application by adding more complexity. Remember the quote from Donald Knuth: "We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil ."


Rules Of Thumb:
Whereas flyweight shows how to make lots of little objects,Facade shows how to make a single object represent an entire subsystem.[GoF,p138]

Flyweight is often combined with Composite to implement shared leaf nodes.[GoF,p206]

Terminal symbols within Interpreter's abstract syntax tree can be shared with Flyweight. [GoF. p255]

Flyweights explain when and how State objects can be shared.[GoF,p313]

Below is an example implemented using "FlyWeight" Pattern.


package patterns;

class TeaOrderContext {
int tableNumber;

TeaOrderContext(int tableNumber) {
this.tableNumber=tableNumber;
}

public int getTable() {
return this.tableNumber;
}
}

abstract class TeaOrder {
public abstract void serveTea(TeaOrderContext teaOrderContext);
}

class TeaFlavor extends TeaOrder {
String teaFlavor;

TeaFlavor(String teaFlavor) {
this.teaFlavor=teaFlavor;
}

public String getFlavor() {
return this.teaFlavor;
}

public void serveTea(TeaOrderContext teaOrderContext) {
System.out.println("Serving Tea Flavor " + teaFlavor + "to table number " + teaOrderContext.getTable());
}
}

class TeaFlavorFactory {
TeaFlavor[] flavors=new TeaFlavor[10];

int teasMade=0;

public TeaFlavor getTeaFlavor(String flavorToGet) {
if(teasMade >0) {
for(int i=0;i<teasMade;i++) {
if(flavorToGet.equals(flavors[i].getFlavor())) {
return flavors[i];
}
}
}
flavors[teasMade]=new TeaFlavor(flavorToGet);
return flavors[teasMade++];
}

public int getTotalTeaFlavorsMade() { return teasMade; }
}

public class FlyWeightPattern {

static TeaFlavor[] flavors=new TeaFlavor[100];

static TeaOrderContext[] tables=new TeaOrderContext[100];

static int ordersMade=0;

static TeaFlavorFactory teaFlavorFactory;

static void takeOrders(String flavorIn,int table) {
flavors[ordersMade]=teaFlavorFactory.getTeaFlavor(flavorIn);
tables[ordersMade++]=new TeaOrderContext(table);
}

public static void main(String[] args) {
teaFlavorFactory=new TeaFlavorFactory();
takeOrders("chai",2);
takeOrders("earl grey",2);

for(int i=0;i<ordersMade;i++) {
flavors[i].serveTea(tables[i]);
}

System.out.println("Total teaFlavor objects made: " + teaFlavorFactory.getTotalTeaFlavorsMade());
}
}



In this example a single class could have held both the tea flavor and table number.

Table number is less limited, so it goes into the context.

The factory is responsible for only making one instance of each flyweight.

Hope this explanation helps if any.

No comments: