I'm busy implementing Step Builders into a Java application and I've written some horrendous code. I'm quite certain I'm missing a necessary step.
For an example, I'll use the buildable class Machine.java
public class Machine {
private String type;;
private boolean mobile;
private final String mobileType;
public Machine(MachineBuilder builder) {
this.type = builder.type;
this.mobile = builder.mobile;
this.mobileType = builder.mobileType;
}
public String getType() { return this.type; }
public boolean getMobile() { return this.mobile; }
public String getMobileType() { return this.mobileType; }
}
And the step builder for it as MachineBuilder.java
public class MachineBuilder {
public String type;
public boolean mobile;
public String mobileType;
public MachineBuilder() { }
// initialize builder
public Builder start() {
return new Builder();
}
// interfaces
public interface iType {
iBuild withType(String type);
}
public interface iMobileType {
iBuild withMobileType(String mobileType);
}
public interface iBuild {
iMobileType withMobile();
iBuild withMobileType(String mobileType);
Machine build();
}
// subclass to return
public static class Builder extends MachineBuilder implements iType, iMobileType, iBuild {
public iBuild withType(String type) {
super.type = type; return this;
}
public iMobileType withMobile() {
super.mobile = true; return this;
}
public iBuild withMobileType(String mobileType) {
super.mobileType = mobileType; return this;
}
public Machine build() {
return new Machine(this);
}
}
}
The intention is to have type
as a required step, then mobile
as optional but if mobile
is used then mobileType
must be used as well.
It's only half working though
// fine
Machine car = new MachineBuilder()
.start().withType("car").withMobile().withMobileType("driving").build();
System.out.println(car.getType() + ":" + car.getMobile() + ":" + car.getMobileType());
// fine
Machine washingMachine = new MachineBuilder()
.start().withType("washingMachine").build();
System.out.println(washingMachine.getType() + ":" + washingMachine.getMobile() + ":" + washingMachine.getMobileType());
// corrupt (no type)
Machine boat = new MachineBuilder()
.start().withMobile().withMobileType("sailing").build();
System.out.println(boat.getType() + ":" + boat.getMobile() + ":" + boat.getMobileType());
// corrupt (no anything)
Machine bicycle = new MachineBuilder()
.start().build();
System.out.println(bicycle.getType() + ":" + bicycle.getMobile() + ":" + bicycle.getMobileType());
I had to initialize the builder object with the method start
but this is not implementing any of the interfaces so just calling start
then build
will corrupt the object. Similarly calling the optional method for mobile
allows it to bypass the type
.
Is it possible to force flow direction from the start without using a start
method at all? I feel like I am missing something very stupid.
PS. sorry for slapping so much code into the question I just wanted to illustrate the issue as best as I can
Thrill to answer this question. I try to rewrite your code. Just reorganize it following Step Builder Pattern
's strategy.
Add no description here, hope you can easily understand the code.
class Machine {
private String type;
private boolean isMobile;
private String mobileType;
public static TypeStep builder(){
return new MachineBuilder();
}
public interface TypeStep{
IsMobileStep withType(String type);
}
public interface IsMobileStep{
MobileTypeStep withMobile(boolean isMobile);
}
public interface MobileTypeStep{
Build withMobileType(String mobileType);
}
public interface Build{
Machine build();
}
public static class MachineBuilder implements TypeStep, IsMobileStep, MobileTypeStep, Build {
private String type;
private boolean isMobile;
private String mobileType;
@Override
public IsMobileStep withType(String type) {
this.type = type;
return this;
}
@Override
public MobileTypeStep withMobile(boolean isMobile) {
this.isMobile = isMobile;
return this;
}
@Override
public Build withMobileType(String mobileType) {
this.mobileType = mobileType;
return this;
}
@Override
public Machine build() {
return new Machine(this);
}
}
private Machine(MachineBuilder machineBuilder) {
this.type = machineBuilder.type;
this.isMobile = machineBuilder.isMobile;
this.mobileType = machineBuilder.mobileType;
}
public String getType() {
return type;
}
public boolean isMobile() {
return isMobile;
}
public String getMobileType() {
return mobileType;
}
}
Test run:
public class Main{
public static void main(String[] args) {
Machine car = Machine.builder().withType("car").withMobile(true).withMobileType("driving").build();
System.out.println("Model 1:"+ car.getType() +":"+ car.isMobile()+":"+car.getMobileType());
Machine boat = Machine.builder().withType("boat").withMobile(true).withMobileType("driving").build();
System.out.println("Model 2:"+ boat.getType() +":"+ boat.isMobile()+":"+boat.getMobileType());
}
}
Output:
Model 1:car:true:driving
Model 2:boat:true:driving
For better readability: Github Repo Step Builder