Gang of Four Structural pattern: Flyweight
Structural Pattern
A fine-grained instance used for efficient sharing. Use sharing to support large numbers of fine-grained objects efficiently. A flyweight is a shared object that can be used in multiple contexts simultaneously. The flyweight acts as an independent object in each context — it’s indistinguishable from an instance of the object that’s not shared.
When there are many instances of a class which use the same data across many instances then that data should be factored out into a read only instance of a class that allows the data to be shared between many instances.
To get the code for this example:
git clone https://github.com/spotadev/gangoffour.git
In src/main/java navigate to this package:
com.javaspeak.designpatterns.go4.structural.flyweight
You can run the code from the main method of:
FlyweightApplication
In this example we are drawing shapes on a canvas at different locations. Some of the data for a given shape is the same. For example a square looks the same. Other data could be different; for example the location of a square on the canvas.
We have factored out the information about rendering a square or triangle into a flyweight object which can be referenced by many CanvasElements wishing to draw a square or triangle. The CanvasElements reference the shape they are interested in and provide unique data such as the location to draw the shape at.
When there are many references to the same flyweight objects this can substantially reduce memory requirements.
package com.javaspeak.designpatterns.go4.structural.flyweight;
/**
* Text book description:
* <ul>
* Flyweight: A fine-grained instance used for efficient sharing. Use sharing to support
* large numbers of fine-grained objects efficiently. A flyweight is a shared object that can
* be used in multiple contexts simultaneously. The flyweight acts as an independent object
* in each context — it’s indistinguishable from an instance of the object that’s not shared.
* </ul>
* When there are many instances of a class which use the same data across many instances then
* that data should be factored out into a read only instance of a class that allows the data to
* be shared between many instances.
* <p>
* In this example we are drawing shapes on a canvas at different locations. Some of the data for
* a given shape is the same. For example a square looks the same. Other data could be different;
* for example the location of a square on the canvas.
* <p>
* We have factored out the information about rendering a square or triangle into a flyweight
* object which can be referenced by many CanvasElements wishing to draw a square or triangle.
* The CanvasElements reference the shape they are interested in and provide unique data such as
* the location to draw the shape at.
* <p>
* When there are many references to the same flyweight objects this can substantially reduce
* memory requirements.
* <p>
* @author John Dickerson - 22 Feb 2020
*/
public class FlyweightApplication {
/**
* Adds CanvasElements to the canvas and then renders them by printing to System.out. The
* adding of the CanvasElements to the canvas involves writing individual pixels from the canvas
* Shapes to a multi dimensional array.
*/
public void drawCanvas() {
Canvas canvas = new CanvasImpl();
canvas.addCanvasElement( new CanvasElement( ShapeCache.getSquare(), 0, 0 ) );
canvas.addCanvasElement( new CanvasElement( ShapeCache.getSquare(), 0, 6 ) );
canvas.addCanvasElement( new CanvasElement( ShapeCache.getTriangle(), 6, 0 ) );
canvas.addCanvasElement( new CanvasElement( ShapeCache.getTriangle(), 6, 6 ) );
canvas.render();
}
public static void main( String[] args ) {
FlyweightApplication application = new FlyweightApplication();
application.drawCanvas();
}
}
package com.javaspeak.designpatterns.go4.structural.flyweight;
/**
* CanvasElements are added to this canvas and then rendered to System.out
*
* @author John Dickerson - 23 Feb 2020
*/
public interface Canvas {
/**
* Add a CanvasElement to the canvas
*
* @param canvasElement
*/
public void addCanvasElement( CanvasElement canvasElement );
/**
* Render all the CanvasElements on the Canvas to System.out
*/
public void render();
}
package com.javaspeak.designpatterns.go4.structural.flyweight;
/**
* @author John Dickerson - 23 Feb 2020
*/
/**
* References Shape. Shape is a flyweight object which can be used by many CanvasElement instances.
* The implementation for Shape encapsulates the data required to render the underlying shape.
* Shape does not specify the coordinates of the Shape on the Canvas as different CanvasElements
* are likely to position the shapes in different locations on the Canvas. Instead CanvasElement
* defines unique data such as the location of the Shape on the Canvas.
* <p>
* The Shape is integral to the flyweight pattern. Its data has been refactored out of CanvasElement
* into the Shape implementation as it is identical data required by many instances.
*
* @author John Dickerson - 23 Feb 2020
*/
public class CanvasElement {
private Shape shape;
private int xcoordinate;
private int ycoordinate;
/**
* Constructor
*
* @param shape
* The flyweight instance to reference
*
* @param xcoordinate
* The X location of the Shape on the canvas
*
* @param ycoordinate
* The Y location of the Shape on the canvas
*/
public CanvasElement( Shape shape, int xcoordinate, int ycoordinate ) {
this.shape = shape;
this.xcoordinate = xcoordinate;
this.ycoordinate = ycoordinate;
}
public Shape getShape() {
return shape;
}
public int getXcoordinate() {
return xcoordinate;
}
public int getYcoordinate() {
return ycoordinate;
}
}
package com.javaspeak.designpatterns.go4.structural.flyweight;
import java.util.ArrayList;
import java.util.List;
/**
* CanvasElements are added to this canvas and then rendered to System.out
*
* @author John Dickerson - 23 Feb 2020
*/
public class CanvasImpl implements Canvas {
private List<CanvasElement> canvasElements = new ArrayList<CanvasElement>();
/**
* Works out the dimension of the Canvas required to accomodate all the Canvas Elements
*
* @param canvasElements
* CanvasElements which will be added to the Canvas
*
* @return
* the Dimension of the Canvas
*/
private Dimension getCanvasDimension( List<CanvasElement> canvasElements ) {
int height = 0;
int width = 0;
for ( CanvasElement canvasElement : canvasElements ) {
int shapeLength =
canvasElement.getShape().points.length +
canvasElement.getYcoordinate();
if ( shapeLength > height ) {
height = shapeLength;
}
int shapeWidth =
canvasElement.getShape().points[0].length +
canvasElement.getXcoordinate();
if ( shapeWidth > width ) {
width = shapeWidth;
}
}
return new Dimension( width, height );
}
/**
* Renders a CanvasElement on the canvas. This involves copying the pixels of the shape onto
* the canvasPixels array.
*
* @param canvasElement
* @param canvasPixels
*/
private void render( CanvasElement canvasElement, int[][] canvasPixels ) {
int[][] points = canvasElement.getShape().points;
for ( int y = 0; y < points.length; y++ ) {
for ( int x = 0; x < points[y].length; x++ ) {
canvasPixels[y + canvasElement.getYcoordinate()][x + canvasElement
.getXcoordinate()] =
points[y][x];
}
}
}
/**
* Paints the canvas pixels to System.out
*
* @param canvasPixels
* The canvas pixels to paint to System.out
*/
private void paint( int[][] canvasPixels ) {
for ( int y = 0; y < canvasPixels.length; y++ ) {
StringBuilder sb = new StringBuilder();
for ( int x = 0; x < canvasPixels[y].length; x++ ) {
if ( canvasPixels[y][x] == 1 ) {
sb.append( canvasPixels[y][x] );
}
else {
sb.append( " " );
}
}
System.out.println( sb.toString() );
}
}
@Override
public void addCanvasElement( CanvasElement canvasElement ) {
canvasElements.add( canvasElement );
}
@Override
public void render() {
Dimension canvasDimension = getCanvasDimension( canvasElements );
int[][] canvasPixels =
new int[canvasDimension.getHeight()][canvasDimension.getWidth()];
for ( CanvasElement canvasElement : canvasElements ) {
render( canvasElement, canvasPixels );
}
paint( canvasPixels );
}
}
package com.javaspeak.designpatterns.go4.structural.flyweight;
/**
* Class used to capture largest dimensions necessary to accomodate all
* Shapes on the canvas
*
* @author John Dickerson - 23 Feb 2020
*/
public class Dimension {
private int width;
private int height;
/**
* Constructor
*
* @param width
* The max width of all the shapes
*
* @param height
* The max height of all the shapes
*/
public Dimension( int width, int height ) {
this.width = width;
this.height = height;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
}
package com.javaspeak.designpatterns.go4.structural.flyweight;
/**
* Abstract class that shapes should extend. Contains a points array where the pixels for the
* Shape can be specified.
*
* @author John Dickerson - 23 Feb 2020
*/
public abstract class Shape {
// Uses array of arrays. For example the following is a square
//
// 1111
// 1001
// 1001
// 1111
protected int[][] points;
}
package com.javaspeak.designpatterns.go4.structural.flyweight;
/**
* ShapeCache is a flyweight factory that returns flyweight shapes. There is only one instance of
* each Shape and each Shape may be referenced by many CanvasElements. This saves on memory.
*
* @author John Dickerson - 23 Feb 2020
*/
public class ShapeCache {
private static Shape square = new SquareImpl();
private static Shape triangle = new TriangleImpl();
/**
* @return
* SquareImpl
*/
public static Shape getSquare() {
return square;
}
/**
* @return
* TriangleImpl
*/
public static Shape getTriangle() {
return triangle;
}
}
package com.javaspeak.designpatterns.go4.structural.flyweight;
/**
* Defines pixels for a Square
*
* @author John Dickerson - 23 Feb 2020
*/
public class SquareImpl extends Shape {
public SquareImpl() {
// 1 1 1 1
// 1 1
// 1 1
// 1 1 1 1
points = new int[4][4];
points[0][0] = 1;
points[0][1] = 1;
points[0][2] = 1;
points[0][3] = 1;
points[1][0] = 1;
points[1][3] = 1;
points[2][0] = 1;
points[2][3] = 1;
points[3][0] = 1;
points[3][1] = 1;
points[3][2] = 1;
points[3][3] = 1;
}
}
package com.javaspeak.designpatterns.go4.structural.flyweight;
/**
* Defines pixels for a Triangle
*
* @author John Dickerson - 23 Feb 2020
*/
public class TriangleImpl extends Shape {
public TriangleImpl() {
// 1
// 1 1
// 1 1
// 1 1 1 1 1 1 1
points = new int[4][7];
points[0][3] = 1;
points[1][2] = 1;
points[1][4] = 1;
points[2][1] = 1;
points[2][5] = 1;
points[3][0] = 1;
points[3][1] = 1;
points[3][2] = 1;
points[3][3] = 1;
points[3][4] = 1;
points[3][5] = 1;
points[3][6] = 1;
}
}
Back: Gang of Four
Page Author: JD