// Purpose. Flyweight design pattern // // Discussion. Trying to use objects at very low levels of granularity is // nice, but the overhead may be prohibitive. Flyweight suggests removing the // non-shareable state from the class, and having the client supply it when // methods are called. This places more responsibility on the client, but, // considerably fewer instances of the Flyweight class are now created. // Sharing of these instances is facilitated by introducing a Factory class // that maintains a "cache" of existing Flyweights. class Gazillion { private static int num = 0; private int row, col; public Gazillion( int maxPerRow ) { row = num / maxPerRow; col = num % maxPerRow; num++; } void report() { System.out.print( " " + row + col ); } } public class FlyweightDemo { public static final int ROWS = 6, COLS = 10; public static void main( String[] args ) { Gazillion[][] matrix = new Gazillion[ROWS][COLS]; for (int i=0; i < ROWS; i++) for (int j=0; j < COLS; j++) matrix[i][j] = new Gazillion( COLS ); for (int i=0; i < ROWS; i++) { for (int j=0; j < COLS; j++) matrix[i][j].report(); System.out.println(); } } } // 00 01 02 03 04 05 06 07 08 09 // 10 11 12 13 14 15 16 17 18 19 // 20 21 22 23 24 25 26 27 28 29 // 30 31 32 33 34 35 36 37 38 39 // 40 41 42 43 44 45 46 47 48 49 // 50 51 52 53 54 55 56 57 58 59 // In this refactoring, the "row" state is considered shareable (within each // row anyways), and the "col" state has been externalized (it is supplied by // the client when report() is called). class Gazillion { private int row; public Gazillion( int theRow ) { row = theRow; System.out.println( "ctor: " + row ); } void report( int theCol ) { System.out.print( " " + row + theCol ); } } class Factory { private Gazillion[] pool; public Factory( int maxRows ) { pool = new Gazillion[maxRows]; } public Gazillion getFlyweight( int theRow ) { if (pool[theRow] == null) pool[theRow] = new Gazillion( theRow ); return pool[theRow]; } } public class FlyweightDemo { public static final int ROWS = 6, COLS = 10; public static void main( String[] args ) { Factory theFactory = new Factory( ROWS ); for (int i=0; i < ROWS; i++) { for (int j=0; j < COLS; j++) theFactory.getFlyweight( i ).report( j ); System.out.println(); } } } // ctor: 0 // 00 01 02 03 04 05 06 07 08 09 // ctor: 1 // 10 11 12 13 14 15 16 17 18 19 // ctor: 2 // 20 21 22 23 24 25 26 27 28 29 // ctor: 3 // 30 31 32 33 34 35 36 37 38 39 // ctor: 4 // 40 41 42 43 44 45 46 47 48 49 // ctor: 5 // 50 51 52 53 54 55 56 57 58 59 // Purpose. Flyweight design pattern // 1. Identify shareable state (intrinsic) and non-shareable state (extrinsic) // 2. Create a Factory that can return an existing object or a new object // 3. The client must use the Factory instead of "new" to request objects // 4. The client (or a third party) must provide/compute the extrinsic state import java.awt.*; import java.awt.event.*; class FlyweightFactory { private static java.util.Map ht = new java.util.TreeMap(); private static int sharedButtons = 0; private static ButtonListener bl = new ButtonListener(); public static Button makeButton( String num ) { Button btn; if (ht.containsKey( num )) { // 1. Identify intrinsic state (Button label) // 2. Return an existing object [The same Button cannot be added // multiple times to a container, and, Buttons cannot be cloned. // So - this is only simulating the sharing that the Flyweight // pattern provides.] btn = new Button( ((Button)ht.get(num)).getLabel() ); sharedButtons++; } else { // 2. Return a new object btn = new Button( num ); ht.put( num, btn ); } btn.addActionListener( bl ); return btn; } public static void report() { System.out.print( "new Buttons - " + ht.size() + ", \"shared\" Buttons - " + sharedButtons + ", " ); for (java.util.Iterator it = ht.keySet().iterator(); it.hasNext(); ) System.out.print( it.next() + " " ); System.out.println(); } } class ButtonListener implements ActionListener { public void actionPerformed( ActionEvent e) { Button btn = (Button) e.getSource(); java.awt.Component[] btns = btn.getParent().getComponents(); int i = 0; for ( ; i < btns.length; i++) if (btn == btns[i]) break; // 4. A third party must compute the extrinsic state (x and y) // (the Button label is intrinsic state) System.out.println( "label-" + e.getActionCommand() + " x-" + i/10 + " y-" + i%10 ); // 1. Identify extrinsic state } } // (Button location) public class FlyweightDemo { public static void main( String[] args ) { java.util.Random rn = new java.util.Random(); Frame frame = new Frame( "Flyweight Demo" ); frame.addWindowListener( new WindowAdapter() { public void windowClosing( WindowEvent e ) { System.exit( 0 ); } } ); frame.setLayout( new GridLayout( 10, 10 ) ); // 1. Identify shareable and non-shareable state // shareable - Button label, non-shareable - Button location for (int i=0; i < 10; i++) for (int j=0; j < 10; j++) // 3. The client must use the Factory to request objects frame.add( FlyweightFactory.makeButton( Integer.toString( rn.nextInt(15) ) ) ); frame.pack(); frame.setVisible( true ); FlyweightFactory.report(); } } // new Buttons - 15, "shared" Buttons - 85, 0 1 10 11 12 13 14 2 3 4 5 6 7 8 9 // label-0 x-1 y-1 // label-0 x-2 y-2 // label-0 x-6 y-5 // label-0 x-2 y-5 // label-0 x-9 y-0 // label-1 x-9 y-9 // label-1 x-2 y-8 // label-1 x-5 y-8 // label-1 x-3 y-1 // Purpose. heavyweight ColorBoxes ==> ColorBox Flyweights and a Factory // (1 thread per ColorBox) of pooled HandlerThreads // // Discussion. Creating a thread for each ColorBox is a much more straight- // forward approach, but it doesn't scale when dozens of ColorBoxes are // created. Sharing a "pool" of threads across the collection of ColorBoxes // requires more thought to set-up, but does not saturate "system resources" // like the former approach does. // // In the implementation below, each ColorBox "wraps" itself with a Thread // object. The Thread object provides all the "threading functionality magic" // and simply calls ColorBox's run() method when it is promoted from the // "ready" state to the "running" state. When each Thread/ColorBox is swapped // into the CPU, it causes the ColorBox part of itself to change its color and // then graciously gives up the CPU [by calling sleep()] so that other Threads // may run. // // In the ThreadPool implementation, after the ColorBoxes are set-up, the // ThreadPool creates and starts 8 HandlerThreads. When a HandlerThread is // swapped into the CPU, it gets a random ColorBox object from ThreadPool's // private Vector, tells the ColorBox to change its color, and graciously // returns to the "asleep" state. // // "You can typically make your threaded applications run FASTER by inserting // calls to sleep() (with reasonably long durations)." This definitely contri- // butes to the perception that Threads are a "black art". Not enough calls: // monopolization of the CPU. Not enough duration: time expiration interrupt // events interrupt the running thread before it can finish useful work. import java.awt.*; class ColorBox extends Canvas implements Runnable { private int pause; private Color curColor = getColor(); private static Color[] colors = { Color.black, Color.blue, Color.cyan, Color.darkGray, Color.gray, Color.green, Color.lightGray, Color.red, Color.magenta, Color.orange, Color.pink, Color.white, Color.yellow }; public ColorBox( int p ) { pause = p; new Thread( this ).start(); } private static Color getColor() { return colors[ (int)(Math.random() * 1000) % colors.length ]; } public void run() { while (true) { curColor = getColor(); repaint(); try { Thread.sleep( pause ); } catch( InterruptedException e ) { } } } public void paint( Graphics g ) { g.setColor( curColor ); g.fillRect( 0, 0, getWidth(), getHeight() ); } } public class ColorBoxes { public static void main( String[] args ) { int size = 8, pause = 10; if (args.length > 0) size = Integer.parseInt( args[0] ); if (args.length > 1) pause = Integer.parseInt( args[1] ); Frame f = new FrameClose( "ColorBoxes - 1 thread per ColorBox" ); f.setLayout( new GridLayout( size, size ) ); for (int i=0; i < size*size; i++) f.add( new ColorBox( pause ) ); f.setSize( 500, 400 ); f.setVisible( true ); } } // D:> java ColorBoxes 18 50 // produces 324 boxes/threads and 50 millisecond sleep() // Purpose. 8 shared HandlerThreads in a ThreadPool. The ColorBox class // has now become a Flyweight: the color changing and painting capability // remains "intrinsic", and the threaded behavior has been made "extrinsic". // The ThreadPool class plays the role of the Factory. As ColorBox objects // are created, they register themselves with the ThreadPool object. The // latter launches 8 "handler" threads. When each thread is swapped into // the CPU, it selects a random Flyweight from the ThreadPool's cache, and // asks the object to changeColor(). import java.awt.*; import java.util.Vector; class ColorBox2 extends Canvas { private Color curColor = getColor(); private static Color[] colors = { Color.black, Color.blue, Color.cyan, Color.darkGray, Color.gray, Color.green, Color.lightGray, Color.red, Color.magenta, Color.orange, Color.pink, Color.white, Color.yellow }; public ColorBox2( ThreadPool2 tp ) { tp.register( this ); } private static Color getColor() { return colors[ (int)(Math.random() * 1000) % colors.length ]; } public void changeColor() { curColor = getColor(); repaint(); } public void paint( Graphics g ) { g.setColor( curColor ); g.fillRect( 0, 0, getWidth(), getHeight() ); } } class ThreadPool2 { private final int NUM_THREADS = 8; private Vector cboxes = new Vector(); private int pause; class HandlerThread extends Thread { public void run() { while (true) { ((ColorBox2) cboxes.elementAt( (int)(Math.random()*1000) % cboxes.size() )).changeColor(); try { Thread.sleep( pause ); } catch( InterruptedException e ) { } } } } public ThreadPool2( int p ) { pause = p; } public void register( ColorBox2 r ) { cboxes.addElement( r ); } public void start() { for (int i=0; i < NUM_THREADS; i++) new HandlerThread().start(); } } public class ColorBoxes2 { public static void main( String[] args ) { int size = 8, pause = 10; if (args.length > 0) size = Integer.parseInt( args[0] ); if (args.length > 1) pause = Integer.parseInt( args[1] ); ThreadPool2 tp = new ThreadPool2( pause ); Frame f = new FrameClose( "ColorBoxes2 - 8 shared HandlerThreads" ); f.setLayout( new GridLayout( size, size ) ); for (int i=0; i < size*size; i++) f.add( new ColorBox2( tp ) ); f.setSize( 500, 400 ); f.setVisible( true ); tp.start(); } } // D:> java ColorBoxes 18 50 // produces 324 boxes, 8 threads, and 50 millisecond sleep() // performance is very much improved