Flyweight


Definition

Make instances of classes on the fly to improve performance efficiently, like individual characters or icons on the screen.

Where to use & benefits

Examples

In order to share an object, we may declare an interface and an intrinsic state through which flyweights can receive and act on it. If you want to show a file system with folders to show the directories or subdirectories, you don't need to load all the files or directories at one loading time. You may show the upper level folders first. If the user clicks a folder, then load its subdirectories and files. The shared trigger is mouse-clicked. The composite pattern may be combined to define the flyweight system.

class Folder {
  void draw(..) {}
}
class FolderFactory {
   ...
   if (selected) {
      return aFolder;
   else
      return aFile;
   ...
}

...

To show how to use flyweight to reduce object creation, we will make a program to draw 1000 circles with 6 different colors. Before we customize it to a flyweight design, it is coded as follows:

import java.awt.*;
import java.awt.Color;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

class Test extends JFrame{
   private static final Color colors[] = { Color.red, Color.blue,
                                           Color.yellow, Color.orange,
                                           Color.black,  Color.white };
   private static final int WIDTH_ = 400,
                            HEIGHT = 400,
                            NUMBER_OF_CIRCLES = 1000;

   public Test() {
      Container contentPane = getContentPane();
      
      JButton button = new JButton("Draw Circle");
      final JPanel  panel  = new JPanel();

      contentPane.add(panel,  BorderLayout.CENTER);
      contentPane.add(button, BorderLayout.SOUTH);
      setSize(WIDTH,HEIGHT);
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setVisible(true);

      button.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent event) {
            Graphics g = panel.getGraphics();

            for(int i=0; i < NUMBER_OF_CIRCLES; ++i) {
               g.setColor(getRandomColor());
               int r = getRandomR();
               g.drawOval(getRandomX(), getRandomY(), r, r);
            }
         }
      });
   }

   private int getRandomX() {
      return (int)(Math.random()*WIDTH );
   }
   private int getRandomY() {
      return (int)(Math.random()*HEIGHT);
   }
   private int getRandomR() {
      return (int)(Math.random()*(HEIGHT/10));
   }
   private Color getRandomColor() {
      return colors[(int)(Math.random()*colors.length)];
   }
   public static void main(String[] args) {
      Test test = new Test();
   }   
}
Copy, paste above code, and run it to see the functionality.

 C:\ Command Prompt
 
C:\> java Test

Note that the above program doesn't take advantage of reusability of OOP. We will customize it and make a Circle object, so the Circle object can be reused.

class Circle {
   private Color color;

   public Circle(Color color) {
      this.color = color;
   }
   public void draw(Graphics g, int x, int y, int r) {
      g.setColor(color);
      g.drawOval(x, y, r, r);
   }
}

Then we rewrite the program. It is possible for people to rewrite with Circle object in the following way:

import java.awt.*;
import java.awt.Color;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

class Test extends JFrame{
   private static final Color colors[] = { Color.red, Color.blue,
                                           Color.yellow, Color.orange,
                                           Color.black,  Color.white };
   private static final int WIDTH = 400,
                            HEIGHT = 400,
                            NUMBER_OF_CIRCLES = 1000;

   public Test() {
      Container contentPane = getContentPane();
      
      JButton button = new JButton("Draw Circle");
      final JPanel  panel  = new JPanel();

      contentPane.add(panel,  BorderLayout.CENTER);
      contentPane.add(button, BorderLayout.SOUTH);
      setSize(WIDTH ,HEIGHT);
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setVisible(true);

      button.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent event) {
            Graphics g = panel.getGraphics();

            for(int i=0; i < NUMBER_OF_CIRCLES; ++i) {
               Circle circle = new Circle(getRandomColor());
               circle.draw(g, getRandomX(), getRandomY(), getRandomR());//1000 object created.
            }
         }
      });
   }

   private int getRandomX() {
      return (int)(Math.random()*WIDTH );
   }
   private int getRandomY() {
      return (int)(Math.random()*HEIGHT);
   }
   private int getRandomR() {
      return (int)(Math.random()*(HEIGHT/10));
   }
   private Color getRandomColor() {
      return colors[(int)(Math.random()*colors.length)];
   }
   public static void main(String[] args) {
      Test test = new Test();
   }   
}

From the above code, you may note that 1000 circle object has been created. It is memory consuming.

To improve it, we will create a CircleFactory class to customize it by using flyweight design pattern. Since we just draw circle with different colors, we can store color info in a hashmap. If a circle has been drawn, the new circle will be checked with color. If the circle with the same color has been found in the hashmap, the circle will share the instance which is picked up from the hashmap instead of creating a new one. We will reuse the object with different state, that is to say we will share the instance and draw the circle with different start position and radius on the fly.

class CircleFactory {
   //store color
   private static final HashMap circleByColor = new HashMap();

   public static Circle getCircle(Color color) {
      Circle circle = (Circle)circleByColor.get(color);

      if(circle == null) {
         circle = new Circle(color);
         circleByColor.put(color, circle);
         System.out.println("Creating " + color + " circle");//see how many objects we create on command line
      }
      return circle;
   }
}

So our test program will be coded as follows:

import java.awt.*;
import java.util.HashMap;
import java.awt.Color;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

class Test extends JFrame{
   private static final Color colors[] = { Color.red, Color.blue,
                                           Color.yellow, Color.orange,
                                           Color.black,  Color.white };
   private static final int WIDTH = 400,
                            HEIGHT = 400,
                            NUMBER_OF_CIRCLES = 1000;

   public Test() {
      Container contentPane = getContentPane();
      
      JButton button = new JButton("Draw Circle");
      final JPanel  panel  = new JPanel();

      contentPane.add(panel,  BorderLayout.CENTER);
      contentPane.add(button, BorderLayout.SOUTH);
      setSize(WIDTH,HEIGHT);
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setVisible(true);

      button.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent event) {
            Graphics g = panel.getGraphics();
            for(int i=0; i < NUMBER_OF_CIRCLES; ++i) {
                Circle circle = CircleFactory.getCircle(getRandomColor());
                circle.draw(g, getRandomX(), getRandomY(),getRandomR());
               //Since we have 6 different colors, we have 6 objects created.
            }
          }
      });
   }
   public static void main(String[] args) {
      Test test = new Test();
   }
   private int getRandomX() {
      return (int)(Math.random()*WIDTH );
   }
   private int getRandomY() {
      return (int)(Math.random()*HEIGHT);
   }
   private int getRandomR() {
      return (int)(Math.random()*(HEIGHT/10));
   }
   private Color getRandomColor() {
      return colors[(int)(Math.random()*colors.length)];
   }
}

class CircleFactory {
   private static final HashMap circleByColor = new HashMap();

   public static Circle getCircle(Color color) {
      Circle circle = (Circle)circleByColor.get(color);

      if(circle == null) {
         circle = new Circle(color);
         circleByColor.put(color, circle);
         System.out.println("Creating " + color + " circle");
      }
      return circle;
   }
}

class Circle {
   private Color color;

   public Circle(Color color) {
      this.color = color;
   }
   public void draw(Graphics g, int x, int y, int r) {
      g.setColor(color);
      g.drawOval(x, y, r, r);
   }
}

Copy, paste above code and run it. You will see the printout from the command line, that you only have 6 objects created, not 1000 objects because you only have 6 colors. Such a big reduction of object creation will improve your program performance dramatically.

 C:\ Command Prompt
 
C:\> java Test
Creating java.awt.Color[r=255,g=0,b=0] circle
Creating java.awt.Color[r=0,g=0,b=0] circle
Creating java.awt.Color[r=255,g=200,b=0] circle
Creating java.awt.Color[r=255,g=255,b=0] circle
Creating java.awt.Color[r=0,g=0,b=255] circle
Creating java.awt.Color[r=255,g=255,b=255] circle

Flyweight design is effective with instantiating a large amount of small and fine-grained classes by combining with factory design pattern.

If you have jdk1.5 installed, you may need to use a tool to check if you save the memory by running your commands as follows:

 C:\ Command Prompt
 
C:\> java -Dcom.sun.management.jmxremote Test

And open another console as follows:

 C:\ Command Prompt
 
C:\> jconsole

The jconsole tool will hook up your program Test to give your statistic numbers about your program, such as threads, heap, memory, VM, etc.

String class is designed with Flyweight design pattern. It has similar structure as above example. When you create a string constant, such constant is stored in a pool. When the second string is created, it will be checked to see if it has been created. If it is true, the second string instance will be picked up from the string pool instead of creating a new one. This is why the following code makes sense, but bothers many people.

String s1 = "hello";
String s2 = "hello"; //store in a string pool.
String s3 = new String("hello");

System.out.println(s1==s2); //true, share the same memmory address 
System.out.println(s1==s3); //false

Return to top