Flyweight Design Pattern


Some programs require to have a large number of objects that have some shared state among them. Consider for example a game of war, were there is a large number of soldier objects; a soldier object maintain the graphical representation of a soldier, soldier behavior such as motion, and firing weapons, in addition soldier’s health and location on the war terrain. Creating a large number of soldier objects is a necessity however it would incur a huge memory cost. Note that although the representation and behavior of a soldier is the same their health and location can vary greatly.


Why Fly Weight?

The intent of this pattern is to use sharing to support a large number of objects that have part of their internal state in common where the other part of state can vary.

Where to use “Flyweight Design Pattern”?
Following reasons to choose flyweight design pattern,
  • When we need to create large number of objects.
  • Memory Constraints due to large number ob object creation.
  • When most of the object attributes can be made external and shared.
  • The application must not mandate unique objects, as after implementation same object will be used repeatedly.
  • Its better when extrinsic state can be computed rather than stored.
Fly Weight is about sharing memory of object where needed. If we have a class star and we need to create an application for universe so if we create the object for each star in universe then think about the application performance so what a fly weight pattern can do here and how to implement the Design pattern.

The object which we are going to create in high number should be analyzed before going for flyweight. Idea is to create lesser number of objects by reusing the same objects. Create smaller groups of objects and they should be reused by sharing. Closely look at objects properties and they can be segregated as two types intrinsic and extrinsic. Sharing is judged with respect to a context. Lets take the example of editors.

Consider a simple text editor where we can use only alphabet set A to Z. If we are going to create 100 page document using this editor we may have 200000 (2000 X 100) characters (assuming 2000 characters / page). Without flyweight we will create 200000 objects to have fine grained control. With such fine control, every character can have its own characteristics like color, font, size, etc. How do we apply flyweight here?

Flyweight Implementation

The object with intrinsic state is called flyweight object. When we implement flyweight we create concrete objects and have the intrinsic state stored in that. To create those concrete objects we will have factory and that is called Flyweight factory. This factory is to ensure that the objects are shared and we don’t end up creating duplicate objects.

Let us take an example scenario of drawing. We need to draw different geometrical shapes like rectangles and ovals in huge number. Every shape may vary in colour, size, fill type, font used. For implementation sake lets limit our shapes to two rectangle and oval. Every shape will be accompanied by a label which directly maps it with the shape. That is all rectangles will have label as ‘R’ and all ovals will have label as ‘O’.

Now our flyweight will have intrinsic state as label only. Therefore we will have only two flyweight objects. The varying properties colour, size, fill type and font will be extrinsic. We will have a flyweight factory that will maintain the two flyweight objects and distribute to client accordingly. There will be an interface for the flyweights to implement so that we will have a common blueprint and that is the flyweight interface.

Client code will use random number generators to create extrinsic properties. We are not storing the extrinsic properties anywhere, we will calculate on the fly and pass it. Use of random number generator is for convenience.

// Flyweight object interface
public interface CoffeeOrder {
    public void serveCoffee(CoffeeOrderContext context);
}

// ConcreteFlyweight object that creates ConcreteFlyweight
public class CoffeeFlavor implements CoffeeOrder {
    private String flavor;

    public CoffeeFlavor(String newFlavor) {
        this.flavor = newFlavor;
    }

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

    public void serveCoffee(CoffeeOrderContext context) {
        System.out.println(“Serving Coffee flavor ” + flavor + ” to table number ” + context.getTable());
    }
}

public class CoffeeOrderContext {
   private int tableNumber;

   public CoffeeOrderContext(int tableNumber) {
       this.tableNumber = tableNumber;
   }

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

import java.util.HashMap;
import java.util.Map;

//FlyweightFactory object
public class CoffeeFlavorFactory {
    private Map<String, CoffeeFlavor> flavors = new HashMap<String, CoffeeFlavor>();

    public CoffeeFlavor getCoffeeFlavor(String flavorName) {
        CoffeeFlavor flavor = flavors.get(flavorName);
        if (flavor == null) {
            flavor = new CoffeeFlavor(flavorName);
            flavors.put(flavorName, flavor);
        }
        return flavor;
    }

    public int getTotalCoffeeFlavorsMade() {
        return flavors.size();
    }
}

public class TestFlyweight {
   /** The flavors ordered. */
   private static CoffeeFlavor[] flavors = new CoffeeFlavor[100];
   /** The tables for the orders. */
   private static CoffeeOrderContext[] tables = new CoffeeOrderContext[100];
   private static int ordersMade = 0;
   private static CoffeeFlavorFactory flavorFactory;

   public static void takeOrders(String flavorIn, int table) {
       flavors[ordersMade] = flavorFactory.getCoffeeFlavor(flavorIn);
       tables[ordersMade++] = new CoffeeOrderContext(table);
   }

   public static void main(String[] args) {
       flavorFactory = new CoffeeFlavorFactory();

       takeOrders(“Cappuccino”, 2);
       takeOrders(“Frappe”, 1);
       takeOrders(“Frappe”, 1);
       takeOrders(“Xpresso”, 1);
       takeOrders(“Frappe”, 897);
       takeOrders(“Cappuccino”, 97);
       takeOrders(“Cappuccino”, 97);
       takeOrders(“Frappe”, 3);
       takeOrders(“Xpresso”, 3);
       takeOrders(“Cappuccino”, 3);
       takeOrders(“Xpresso”, 96);
       takeOrders(“Frappe”, 552);
       takeOrders(“Cappuccino”, 121);
       takeOrders(“Xpresso”, 121);

       for (int i = 0; i < ordersMade; ++i) {
           flavors[i].serveCoffee(tables[i]);
       }
       System.out.println(” “);
       System.out.println(“Count of CoffeeFlavor objects made: ” +      
       flavorFactory.getTotalCoffeeFlavorsMade());
   }
}