Search code examples
javabuilder

Java Step Builder with Conditional Steps


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


Solution

  • 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