views:

211

answers:

7

The builder pattern is popular to create immutable objects, but there is some programming overhead to create a builder. So I wonder why not to use simply a config object.

The usage of a builder would look like this:

Product p = Product.name("Vodka").alcohol(0.38).size(0.7).price(17.99).build();

It is obvious that this is very readable and concise, but you have to implement the builder:

public class Product {

    public final String name;
    public final float alcohol;
    public final float size;
    public final float price;

    private Product(Builder builder) {
        this.name = builder.name;
        this.alcohol = builder.alcohol;
        this.size = builder.size;
        this.price = builder.price;
    }

    public static class Builder {

        private String name;
        private float alcohol;
        private float size;
        private float price;

        // mandatory
        public static Builder name(String name) {
            Builder b = new Builder();
            b.name = name;
            return b;
        }

        public Builder alcohol(float alcohol) {
            this.alcohol = alcohol;
            return.this;
        }

        public Builder size(float size) {
            this.size = size;
            return.this;
        }

        public Builder price(float price) {
            this.price = price;
            return.this;
        }

        public Product build() {
            return new Product(this);
        }

    }

}

My idea is, to reduce the code by using a simple config object like this:

class ProductConfig {

        public String name;
        public float alcohol;
        public float size;
        public float price;

        // name is still mandatory
        public ProductConfig(String name) {
            this.name = name;
        }

}

public class Product {

    public final String name;
    public final float alcohol;
    public final float size;
    public final float price;

    public Product(ProductConfig config) {
        this.name = config.name;
        this.alcohol = config.alcohol;
        this.size = config.size;
        this.price = config.price;
    }

}

Usage:

ProductConfig config = new ProductConfig("Vodka");
config.alcohol = 0.38;
config.size = 0.7;
config.price = 17.99;
Product p = new Product(config);

This usage needs a few more lines but is also very readable, but the implementation is much simpler and maybe it is easier to understand for someone who isn't familiar with the builder pattern. By the way: is there a name for this pattern?

Is there a drawback in the config approach that I've overlooked?

+6  A: 

The builder pattern improves decoupling - your Product can be an interface and the only class that knows about the implementation (or implementations, in some cases) is the builder. If the builder also implements an interface then you can inject this into your code to increase decoupling further.

This decoupling means your code is more maintainable and easier to test.

Martin Hutchinson
In the example as shown I think there's still coupling. The caller must know to invoke all those chained methods, if he misses one then the Product is not correctly initialised. Changes in product require changes in builder require changes in client - sounds coupled to me.
djna
@djna the builder pattern is for optional parameters where you otherwise had to use telescope constructors. So its no problem if you miss one method. The mandatory params go in builders constructor or as params to the build method so you can enforce them.
atamanroman
+1  A: 

IMO, the builder pattern is much more roboust if you have things like validation, etc.

The builder pattern in your case can be changed to do the following:

Product p = new ProductBuilder("pName").alcohol(0.38).size(0.7).price(17.99).build();

The build() method can do all the validation stuff that is needed for your builder. The builder pattern also has several design advangates (all of which may not be applicable in your case). For deatils check this question

naikus
But the validation could also be performed in the constructor of `Product`.
deamon
@deamon Agreed, Builder is much more natural IMO and makes the API easier. e.g. compare p = new ProductBuilder("name").price()... against creating a config object assigning properties, etc.
naikus
+1  A: 

You should not use the public field, but protected or private. For accesing then you should use the getters and setter to keep the encapsulation..

Vash
I've only used public fields to keep my example short, but thank you anyway.
deamon
@Vash: actually, not always. If 'ProductConfig' is not a part of public API then it can be as simple as it shown. Even J. Bloch in Effective Java 2nd Edition says that its normal to have such value objects without any logic, in which getters/setters will only add some boilerplate burden code (Item 14).
Roman
Its a pure value class. Since validation happens in the constructor of the real class and the config object should never be shared with other threads, it looks ok for me. Of course you lose the ability to introduce additional validation later. In addition to my namesake roman: Builder pattern is pg 11 in Effective Java SE (Item 2).
atamanroman
+2  A: 

What problem do you try to solve with your pattern? The builder pattern is used for objects with many (optional) parameters in order to prevent tons of different constructors or very long ones. It also keeps your object in a consistent state (vs. javabean pattern) during construction.

The difference between builder and "config object" (feels like a good name) is, that you still have to create the object with the same params by constructor or getter/setter. This a) doesnt solve the constructor problem or b) keeps the config object in inconsistent state. Inconsistent states of the config object dont really hurt it but you could pass an unfinished config object as a param. [Michids link to phantom types seem to solve this problem, but that again kills readability (new Foo<TRUE,TRUE, TRUE, FALSE, TRUE> kinda sucks).] Thats the big advantage of the builder pattern: you can validate your params before you create an object and you can return any subtype (acts like a factory).

Config objects are valid for sets of params which are all mandatory. Ive seen this pattern many times in .NET or java before.

atamanroman
I want to have immutable objects and I'd like to avoid a constructor with many params or many constructors. If I perform validation in the constructor of the actual object, the object won't be created with an invalid state. Where is the benefit of performing validation in the builder?
deamon
It feels more cohesive. Instead of checking the config object in the actual constructor you can stay in your builder class and do all the object creation related stuff there. Also dont forget that a builder is an advanced factory (which have proven over time). The ability to return subtypes or interfaces is worth the extra lines of code. But i agree that config objects may be suitable sometimes.
atamanroman
+2  A: 

Have you considered using builder-builder?

I do think the builder (with prefixes like "With") reads more natrually/fluently.

mlk
+2  A: 

You are losing several advantages of the builder pattern, as has already been pointed out (new is ugly and harder to maintain and leaking details compared to a clean builder).

The one I miss the most however is that the builder pattern can be used to provide what are called "fluent interfaces".

Instead of this:

ProductConfig config = new ProductConfig("Vodka");
config.alcohol = 0.38;
config.size = 0.7;
config.price = 17.99;
Product p = new Product(config);

You can do:

ProductFactory.create()
    .drink("Vodka")
    .whereAlcohoolLevelIs(0.38)
    .inABottleSized(0.7)
    .pricedAt(17.99)
    .build();

Not everyone like fluent interfaces, but they are definitely a very nice use of the builder pattern (all fluent interfaces should use the builder pattern, but not all builder pattern are fluent interfaces).

Some great Java collections, like the Google collections, makes both very liberal and very good use of "fluent interfaces". I'd pick these any day over your "easier-to-type/less characters" approach : )

NoozNooz42
A: 

The main drawback is that it is not in Joshua's book, so drones can't wrap their heads around it.

You are using a simple value object to hold multiple arguments a function(/method/constructor) needs, there is nothing wrong with that, it has been done for ages. As long as we don't have named optional parameters we'll have to devise workarounds like this - it's a shame, not some god damn brilliant inventions from the gods in the Sun.

The real difference is that you expose fields directly. Joshua would never have a public mutable field - but he writes APIs that will be used by millions of people most of which are morons, and the APIs must be safe to evolve for decades to come, and they can allocate many man months just to design a simple class

Who are we to emmulate that?

irreputable
Its perfectly fine to expose fields in value objects. If you read something (effective java, this thread) then read all of it or dont. It has been said more than once that value objects as params are fine sometimes. Is has been said why factories and buiders are superior in other cases also. This config object has a completely different application than a builder pattern.
atamanroman