Djorgje Popovic just published an article on the Step builder pattern which includes a nice video explaining the how and the why of this design.
So thanks to Djorgje and enjoy the video.
New ideas pass through three periods:
1) It can’t be done.
2) It probably can be done, but it’s not worth doing.
3) I knew it was a good idea all along!
- Arthur C. Clarke
package com.stepbuilder.bar;
import java.util.List;
public class Panino {
private final String name;
private String breadType;
private String fish;
private String cheese;
private String meat;
private List vegetables;
public Panino(String name) {
this.name = name;
}
public String getBreadType() {
return breadType;
}
public void setBreadType(String breadType) {
this.breadType = breadType;
}
public String getFish() {
return fish;
}
public void setFish(String fish) {
this.fish = fish;
}
public String getCheese() {
return cheese;
}
public void setCheese(String cheese) {
this.cheese = cheese;
}
public String getMeat() {
return meat;
}
public void setMeat(String meat) {
this.meat = meat;
}
public List getVegetables() {
return vegetables;
}
public void setVegetables(List vegetables) {
this.vegetables = vegetables;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Panino [name=" + name + ", breadType=" + breadType + ", fish="
+ fish + ", cheese=" + cheese + ", meat=" + meat
+ ", vegetables=" + vegetables + "]";
}
}
Now, in order to create a Panino you can write your Builder class that, more or less, will look like the following.
package com.stepbuilder.bar;
import java.util.ArrayList;
import java.util.List;
public class PaninoBuilder {
private String name;
private String breadType;
private String fish;
private String cheese;
private String meat;
private List vegetables = new ArrayList();
public PaninoBuilder paninoCalled(String name){
this.name = name;
return this;
}
public PaninoBuilder breadType(String breadType){
this.breadType = breadType;
return this;
}
public PaninoBuilder withFish(String fish){
this.fish = fish;
return this;
}
public PaninoBuilder withCheese(String cheese){
this.cheese = cheese;
return this;
}
public PaninoBuilder withMeat(String meat){
this.meat = meat;
return this;
}
public PaninoBuilder withVegetable(String vegetable){
vegetables.add(vegetable);
return this;
}
public Panino build(){
Panino panino = new Panino(name);
panino.setBreadType(breadType);
panino.setCheese(cheese);
panino.setFish(fish);
panino.setMeat(meat);
panino.setVegetables(vegetables);
return panino;
}
}
A user will be then able to nicely build a Panino using this builder.
package com.stepbuilder.bar.client;
import com.stepbuilder.bar.Panino;
import com.stepbuilder.bar.PaninoBuilder;
public class Bar {
public static void main(String[] args) {
Panino marcoPanino = new PaninoBuilder().paninoCalled("marcoPanino")
.breadType("baguette").withCheese("gorgonzola").withMeat("ham")
.withVegetable("tomatos").build();
System.out.println(marcoPanino);
}
}
So far so good.
package com.stepbuilder.bar.client;
import com.stepbuilder.bar.Panino;
import com.stepbuilder.bar.PaninoBuilder;
public class Bar {
public static void main(String[] args) {
Panino marcoPanino = new PaninoBuilder().paninoCalled("marcoPanino")
.withCheese("gorgonzola").build();
// or
marcoPanino = new PaninoBuilder().withCheese("gorgonzola").build();
// or
marcoPanino = new PaninoBuilder().withMeat("ham").build();
// or
marcoPanino = new PaninoBuilder().build();
}
}
All the above panino instances are wrong, and the user will not know until runtime when he will use the Panino object.package com.stepbuilder.bar; import java.util.ArrayList; import java.util.List; public class PaninoStepBuilder { public static FirstNameStep newBuilder() { return new Steps(); } private PaninoStepBuilder() { } /** * First Builder Step in charge of the Panino name. * Next Step available : BreadTypeStep */ public static interface FirstNameStep { BreadTypeStep paninoCalled(String name); } /** * This step is in charge of the BreadType. * Next Step available : MainFillingStep */ public static interface BreadTypeStep { MainFillingStep breadType(String breadType); } /** * This step is in charge of setting the main filling (meat or fish). * Meat choice : Next Step available : CheeseStep * Fish choice : Next Step available : VegetableStep */ public static interface MainFillingStep { CheeseStep meat(String meat); VegetableStep fish(String fish); } /** * This step is in charge of the cheese. * Next Step available : VegetableStep */ public static interface CheeseStep { VegetableStep noCheesePlease(); VegetableStep withCheese(String cheese); } /** * This step is in charge of vegetables. * Next Step available : BuildStep */ public static interface VegetableStep { BuildStep noMoreVegetablesPlease(); BuildStep noVegetablesPlease(); VegetableStep addVegetable(String vegetable); } /** * This is the final step in charge of building the Panino Object. * Validation should be here. */ public static interface BuildStep { Panino build(); } private static class Steps implements FirstNameStep, BreadTypeStep, MainFillingStep, CheeseStep, VegetableStep, BuildStep { private String name; private String breadType; private String meat; private String fish; private String cheese; private final List<String> vegetables = new ArrayList<String>(); public BreadTypeStep paninoCalled(String name) { this.name = name; return this; } public MainFillingStep breadType(String breadType) { this.breadType = breadType; return this; } public CheeseStep meat(String meat) { this.meat = meat; return this; } public VegetableStep fish(String fish) { this.fish = fish; return this; } public BuildStep noMoreVegetablesPlease() { return this; } public BuildStep noVegetablesPlease() { return this; } public VegetableStep addVegetable(String vegetable) { this.vegetables.add(vegetable); return this; } public VegetableStep noCheesePlease() { return this; } public VegetableStep withCheese(String cheese) { this.cheese = cheese; return this; } public Panino build() { Panino panino = new Panino(name); panino.setBreadType(breadType); if (fish != null) { panino.setFish(fish); } else { panino.setMeat(meat); } if (cheese != null) { panino.setCheese(cheese); } if (!vegetables.isEmpty()) { panino.setVegetables(vegetables); } return panino; } } }
The concept is simple:
package com.stepbuilder.bar.client;
import com.stepbuilder.bar.Panino;
import com.stepbuilder.bar.PaninoBuilder;
import com.stepbuilder.bar.PaninoStepBuilder;
public class Bar {
public static void main(String[] args) {
Panino solePanino = PaninoStepBuilder.newBuilder()
.paninoCalled("sole panino")
.breadType("baguette")
.fish("sole")
.addVegetable("tomato")
.addVegetable("lettece")
.noMoreVegetablesPlease()
.build();
}
}
The user will not be able to call the method breadType(String breadType) before calling the paninoCalled(String name) method, and so on.