Gang of Four Creational pattern: Builder
Separates object construction from its representation. Separate the construction of a complex object from its representation so that the same construction processes can create different representations."
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.creational.builder
You can run the code from the main method of:
BuilderApplication
This example uses a buildManager which calls constructShape(..) on different builders to construct different shapes. The class Shape is used to encapsulate shapes. SquareBuilder and TriangleBuilder are used to populate Shape with the correct values for a Square and Triangle respectively.
The constructShape(..) method calls several methods on the builders to build a shape. The constructShape(..) method of the buildManager calls the builder methods in the correct order:
shapeBuilder.setName();
shapeBuilder.setPaintingLevel();
shapeBuilder.buildPoints();
shapeBuilder.translateCoordinates();
The name is the name of the shape, e.g. triangle.
The paintinglevel is the order to paint the shape. For example any shape with level 0 will be painted before any shape with level 1.
shapeBuilder.buildPoints() builds the points for the shape.
translateCoordinates() translates the points. e.g. it can move a shape both vertically and horizontally.
Once the Shape has been built it can be retrieved from the builder using getShape()
package com.javaspeak.designpatterns.go4.creational.builder;
/**
* Text book description:
* <ul>
* "Builder: Separates object construction from its representation. Separate the construction
* of a complex object from its representation so that the same construction processes can
* create different representations."
* </ul>
* This application uses a buildManager which calls constructShape(..) on different builders to
* construct different shapes. The class Shape is used to encapsulate shapes. SquareBuilder and
* TriangleBuilder are used to populate Shape with the correct values for a Square and Triangle
* respectively.
* <p>
* The constructShape(..) method calls several methods on the builders to build a shape. The
* constructShape(..) method of the buildManager calls the builder methods in the correct order:
* <pre>
* shapeBuilder.setName();
* shapeBuilder.setPaintingLevel();
* shapeBuilder.buildPoints();
* shapeBuilder.translateCoordinates();
* </pre>
* The name is the name of the shape, e.g. triangle.
* <p>
* The paintinglevel is the order to paint the shape. For example any shape with level 0 will be
* painted before any shape with level 1.
* <p>
* shapeBuilder.buildPoints() builds the points for the shape.
* <p>
* translateCoordinates() translates the points. e.g. it can move a shape both vertically and
* horizontally.
* <p>
* Once the Shape has been built it can be retrieved from the builder using getShape()
*
* @author John Dickerson - 22 Feb 2020
*/
public class BuilderApplication {
/**
* Constructs the Shapes using the SquareBuilder and TriangleBuilder and then merges their
* coordinates together into a points array in BuildManager and then finally paints the merged
* points.
*/
public void render() {
BuildManager buildManager = new BuildManager();
buildManager.constructShape( new SquareBuilder() );
buildManager.constructShape( new TriangleBuilder() );
buildManager.mergeCoordinates();
buildManager.paint();
}
public static void main( String[] args ) {
BuilderApplication application = new BuilderApplication();
application.render();
}
}
package com.javaspeak.designpatterns.go4.creational.builder;
import java.util.Comparator;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* BuildManager has a constructShape(..) method which calls the appropriate methods on the
* SquareBuilder and TriangleBuilder to populate the Shape instance with values which model a
* Square and Triangle respectively.
* <p>
* When a shape is constructed it is added to the "shapes" SortedSet.
* <p>
* The SortedSet sorts the Shapes by their paintingLevel. Shapes which are on level 0 will be
* painted before Shapes which are on a higher level.
*
* @author John Dickerson - 22 Feb 2020
*/
public class BuildManager {
int[][] points;
/**
* When a shape is constructed it is added to the "shapes" SortedSet.
* <p>
* The SortedSet sorts the Shapes by their paintingLevel. Shapes which are on level 0 will be
* painted before Shapes which are on a higher level.
*/
private SortedSet<Shape> shapes = new TreeSet<Shape>(
new Comparator<Shape>() {
public int compare( Shape shape1, Shape shape2 ) {
return Integer.valueOf( shape1.paintLevel ).compareTo( shape2.paintLevel );
}
} );
/**
* Finds the maximum dimension for the different Shape instances in the shapes SortedSet
*
* @param shapes
* the shapes to find the maximum dimension for
*
* @return
* max dimension
*/
private Dimension getMaxDimension( SortedSet<Shape> shapes ) {
int height = 0;
int width = 0;
for ( Shape shape : shapes ) {
if ( shape.points.length > height ) {
height = shape.points.length;
}
if ( shape.points[0].length > width ) {
width = shape.points[0].length;
}
}
return new Dimension( width, height );
}
/**
* The constructShape(..) method is responsible for calling the builder methods in the correct
* order. For example a shape cannot have its coordinates translated before it has created its
* points.
*
* @param shapeBuilder
*/
public void constructShape( ShapeBuilder shapeBuilder ) {
shapeBuilder.setName();
shapeBuilder.setPaintingLevel();
shapeBuilder.buildPoints();
shapeBuilder.translateCoordinates();
shapes.add( shapeBuilder.getShape() );
}
/**
* This method iterates through the shapes and copies their coordinates in to a new points
* array. Note that shapes which are copied after others overwrite previous points.
*/
public void mergeCoordinates() {
Dimension dimension = getMaxDimension( shapes );
points = new int[dimension.getHeight()][dimension.getWidth()];
for ( Shape shape : shapes ) {
int[][] shapePoints = shape.points;
for ( int y = 0; y < shapePoints.length; y++ ) {
for ( int x = 0; x < shapePoints[y].length; x++ ) {
if ( shapePoints[y][x] == 1 ) {
points[y][x] = shapePoints[y][x];
}
}
}
}
}
/**
* Paints the merged coordinates
*/
public void paint() {
for ( int i = 0; i < points.length; i++ ) {
StringBuilder sb = new StringBuilder();
for ( int y = 0; y < points[i].length; y++ ) {
if ( points[i][y] == 1 ) {
sb.append( points[i][y] );
}
else {
sb.append( " " );
}
}
System.out.println( sb.toString() );
}
}
}
package com.javaspeak.designpatterns.go4.creational.builder;
/**
* Class used to capture largest dimensions necessary to accommodate all Shapes
*
* @author John Dickerson - 22 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.creational.builder;
/**
* The Shape class models different shapes
*
* @author John Dickerson - 22 Feb 2020
*/
public class Shape {
// Uses array of arrays. For example the following is a square
//
// 1111
// 1001
// 1001
// 1111
//
// If the above coordinates are translated (4,1) they become:
//
// 00000000
// 00001111
// 00001001
// 00001001
// 00001111
protected int[][] points;
// 0 means paint first, larger numbers will be painted next
protected int paintLevel;
// Name of shape
protected String name;
}
package com.javaspeak.designpatterns.go4.creational.builder;
/**
* This abstract class provides instantiates a Shape class and provides an implementation for the
* getShape() method. It has several abstract methods that need to be implemented by builders
* extending ShapeBuilder.
*
* @author John Dickerson - 22 Feb 2020
*/
public abstract class ShapeBuilder {
protected Shape shape = new Shape();
/**
* Implementing class need to populate the points array in the Shape class
*/
public abstract void buildPoints();
/**
* Implementing class need to specify the paintingLevel in the Shape class. The paintingLevel
* specifies what order the shapes need to be painted in. "0" means painted first while larger
* numbers will be printed next.
*/
public abstract void setPaintingLevel();
/**
* Implementing class need to specify the name of the shape. For example a TriangleBuilder
* should name the shape a Triangle
*/
public abstract void setName();
/**
* Implementing classes can provide an implementation that translates the points. For example a
* translation of (1,2) moves each point down by one and to the right by 2.
*/
public abstract void translateCoordinates();
/**
* Returns a reference to the shape
*
* @return
* the shape
*/
public Shape getShape() {
return this.shape;
}
}
package com.javaspeak.designpatterns.go4.creational.builder;
/**
* This builder configures the Shape class in the parent ShapeBuilder class
* to model a Triangle.
*
* @author John Dickerson - 22 Feb 2020
*/
public class TriangleBuilder extends ShapeBuilder {
@Override
public void setName() {
this.shape.name = "Triangle";
}
@Override
public void buildPoints() {
// 1
// 1 1
// 1 1
// 1 1 1 1 1 1 1
this.shape.points = new int[4][7];
this.shape.points[0][3] = 1;
this.shape.points[1][2] = 1;
this.shape.points[1][4] = 1;
this.shape.points[2][1] = 1;
this.shape.points[2][5] = 1;
this.shape.points[3][0] = 1;
this.shape.points[3][1] = 1;
this.shape.points[3][2] = 1;
this.shape.points[3][3] = 1;
this.shape.points[3][4] = 1;
this.shape.points[3][5] = 1;
this.shape.points[3][6] = 1;
}
@Override
public void setPaintingLevel() {
shape.paintLevel = 2;
}
@Override
public void translateCoordinates() {
// translate (4,4)
int xTranslate = 2;
int yTranslate = 1;
int[][] translatedPoints =
new int[this.shape.points.length + yTranslate][this.shape.points[0].length
+ xTranslate];
for ( int y = 0; y < this.shape.points.length; y++ ) {
for ( int x = 0; x < this.shape.points[y].length; x++ ) {
translatedPoints[y + yTranslate][x + xTranslate] =
this.shape.points[y][x];
}
}
this.shape.points = translatedPoints;
}
}
package com.javaspeak.designpatterns.go4.creational.builder;
/**
* This builder configures the Shape class in the parent ShapeBuilder class
* to model a Square.
*
* @author John Dickerson - 22 Feb 2020
*/
public class SquareBuilder extends ShapeBuilder {
@Override
public void setName() {
this.shape.name = "Square";
}
@Override
public void buildPoints() {
// 1 1 1 1
// 1 1
// 1 1
// 1 1 1 1
this.shape.points = new int[4][4];
this.shape.points[0][0] = 1;
this.shape.points[0][1] = 1;
this.shape.points[0][2] = 1;
this.shape.points[0][3] = 1;
this.shape.points[1][0] = 1;
this.shape.points[1][3] = 1;
this.shape.points[2][0] = 1;
this.shape.points[2][3] = 1;
this.shape.points[3][0] = 1;
this.shape.points[3][1] = 1;
this.shape.points[3][2] = 1;
this.shape.points[3][3] = 1;
}
@Override
public void setPaintingLevel() {
// paint first
this.shape.paintLevel = 0;
}
@Override
public void translateCoordinates() {
// no translation necessary
}
}
Back: Gang of Four
Page Author: JD