How to implement conditional visibility of methods in a Builder design pattern?

Viewed 176

How to change the visibility of methods in a Builder design pattern ?

For example I have this Builder :

public class Builder {

    public Builder a() {
        //
        return this;
    }

    public Builder b() {
        //
        return this;
    }

}

User can use the API and do this :

new Builder().a().b();
new Builder().a();
new Builder().b();
new Builder().b().a();

I want to allow him to access method b() only if a() has been called :

new Builder().a().b();
new Builder().a();

A simple example could be a SQL request Builder. You shouldn't be allowed to call when() before select().

How to do so?

2 Answers

Two approaches : StepBuilder pattern, mentioned here (thanks to Eritrean) and a custom one, described after.


StepBuilder

Inside your builder class, define as many interfaces as "steps" your building should have. The returning type of a method in an interface returns to the type where the next methods should be available.

Here b() can only be called after a().

public class Builder {
    /*
     * next available methods are defined by 
     * the returned interface type
     */
    private static interface AStep {
        BStep a();
    }

    private static interface BStep {
       void b();
    }

    private static class Steps implements AStep, BStep {
        BStep a() {
            //
            return this;
        }

        void b() {
           //
           return this;
        }
    }
}

Custom approach with Abstract class (I call it : the AbstractBuilder)

As I couldn't define same methods names with the StepBuilder approach I tried something else.

  • I defined a main Builder with the first callable methods (visible), common methods and the build() method (protected, because it will only be availble through other classes if needed).

  • I defined an abstract class, called AbstractBuilder, with an attribute of Builder type and a constructor setting this attribute.

  • I defined as many builders as steps I have. All those builders extends AbstractBuilder and thus have common methods and ending method available if needed, by calling it on the Builder instance.

It looks like this :

public class AbstractBuilder {

    protected Builder builder;

    protected AbstractBuilder(Builder builder) {
        this.builder = builder;
    }
}

 

public class Builder {

    public BStep a() {
        //
        return new BStep(this);
    }

    protected Object build() {
        // 
        return null;
    }
}

 

public class BStep extends AbstractBuilder {

    protected BStep(Builder builder) {
        super(builder);
    }

    public CStep b() {
        //
        return new CStep(builder);
    }

    public Object build() {
        return builder.build();
    }
}

 

public class CStep extends AbstractBuilder {

    protected CStep(Builder builder) {
        super(builder);
    }
    
    public BStep and() {
        //
        return new BStep(builder);
    }

    public DStep c() {
        //
        return new DStep(builder);
    }

    public Object build() {
        return builder.build();
    }
}

 

public class DStep extends AbstractBuilder {

    protected DStep(Builder builder) {
        super(builder);
    }
    
    public CStep and() {
        //
        return new CStep(builder);
    }

    public EStep d() {
        // etc. ... return new EStep(builder);
    }

    public Object build() {
        return builder.build();
    }
}

with this pattern you can manage the availability of methods after each calls and use "backwards" loops :

Builder builder = new Builder();
builder.a().b().and().a().b().c().and().b().build();

If I'm understanding your question correctly, you can do this:

public class Builder {
    
    private boolean aProcessed;
    
    public Builder() {
        this.aProcessed = false;
    }

    public Builder a() {
        //
        aProcessed = true;
        return this;
    }

    public Builder b() throws IllegalAccessException {
        if (!aProcessed) {
            throw new IllegalAccessException();
        }
        //
        return this;
    }

}
Related