Question

There is a class which has many fields and all of them are required. The java code is like:

public class MyCls {

    private String aaa;
    private String bbb;
    private String ccc;
    private String ddd;
    private String eee;
    private String fff;
    private String ggg;

    public MyCls(String aaa, String bbb, String ccc, String ddd, String eee, String fff, String ggg) {
        this.aaa = aaa;
        this.bbb = bbb;
        this.ccc = ccc;
        this.ddd = ddd;
        this.eee = eee;
        this.fff = fff;
        this.ggg = ggg;
    }

    // here are getters for all fields, no setters
}

The caller code will be:

new MyCls("111","222","333","444","555","666","777");

My friend said it's not easy to find the meaning of each parameter, so create a builder for it. This is the code:

package builder;

public class MyClsBuilder {
    private String aaa;
    private String bbb;
    private String ccc;
    private String ddd;
    private String eee;
    private String fff;
    private String ggg;

    public MyClsBuilder setAaa(String aaa) {
        this.aaa = aaa;
        return this;
    }

    // similar code for 'bbb','ccc',...,'ggg'

    public MyClsBuilder setGgg(String ggg) {
        this.ggg = ggg;
        return this;
    }

    public MyCls createMyCls() {
        return new MyCls(aaa, bbb, ccc, ddd, eee, fff, ggg);
    }
}

The caller code will be:

    MyCls cls = new MyClsBuilder()
            .setAaa("111").setBbb("222").setCcc("333")
            .setDdd("444").setEee("555").setFff("666").setGgg("777")
            .createMyCls();

Ok, we can see what setter we used for each parameter, it's clearer. But I have a question:

Do we really need such a builder? I can't see much benefit.

In fact, if we add setters for the original code, we can write similar code without creating a new class:

MyCls cls = new MyClsBuilder();
    cls.setAaa("111");
    cls.setBbb("222");
    cls.setCcc("333");
    cls.setDdd("444");
    cls.setEee("555");
    cls.setFff("666");
    cls.setGgg("777");

The only difference is is's not a chain.

And to use that MyClsBuilder, I have to worry about if I forget to call any setX by mistaken.

MyCls cls = new MyClsBuilder()
        .setAaa("111").setBbb("222").setCcc("333")
        .setDdd("444").setEee("555").setFff("666")
        .createMyCls();

// forget to invoke `.setGgg("777")`

But with the "constructor" one, there is no such problem since the compiler will check it for me.

So I don't think a builder a good solution, is it any better one?

Was it helpful?

Solution

In fact, if we add setters for the original code, we can write similar code without creating a new class

Yes, but only if MyCls is mutable. Sometimes you want an immutable object, which will have no setters.

And to use that MyClsBuilder, I have to worry about if I forget to call any setX by mistake.

Builders are less useful if all of the arguments are required. Builders are very useful if there are lots of arguments and most of the arguments can be made optional, because the builder can initialize them with defaults.

With a plain constructor, implementing optional arguments requires several overloaded constructors (confusing), or for users to specify defaults for the arguments they don't care about (distracting). A builder makes this easier because you only need to specify the couple of settings you care about.

OTHER TIPS

This is to address Freewind's observation that the simple counter method doesn't cater for accidentally setting a parameter twice. For clarity I've used a bit set but an int counter could be used in the same way, however the bit set is probably more elegant and less cryptic.

private BitSet parameterBits = new BitSet(7);  // there are 7 parameters

public MyClsBuilder setAaa(String aaa) 
{
    parameterBits.set(0);  // set bit position 0 to true
    this.aaa = aaa;
    return this;
}

public MyClsBuilder setBbb(String bbb) 
{
    parameterBits.set(1);
    this.bbb = bbb;
    return this;
}

public MyClsBuilder setGgg(String ggg) 
{
    parameterBits.set(6);
    this.ggg = ggg;
    return this;
}

public MyCls createMyCls() 
{
    if ( parameterBits.cardinality() < parameterBits.length() )
    {
        throw new Exception("Not all parameters supplied.");
    }
    return new MyCls(aaa, bbb, ccc, ddd, eee, fff, ggg);
}

A disadvantage / problem with the constructor version is that in your particular case you could accidentally swap two parameters around. With a builder this is less likely to happen.

A builder can still be used if all of the arguments are required, it just takes a bit more code to check that all the parameters have been supplied.

A simple way (but not necessarily the best way) for instance is to have counter that increments when a parameter is set:

private int counter = 0;

public MyClsBuilder setAaa(String aaa) 
{
    counter++;
    this.aaa = aaa;
    return this;
}

public MyCls createMyCls() 
{
    if ( counter < 7 ) throw new Exception("Not all parameters supplied.");
    return new MyCls(aaa, bbb, ccc, ddd, eee, fff, ggg);
}

So builders I would say are less error prone and more readable. And clear code is good code :-)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top