//
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