简体   繁体   中英

Builder pattern vs. config object

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

The usage of a builder would look like this:

Product p = Product.Builder.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?

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.

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 : )

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.

Have you considered using builder-builder ?

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

I personally feel that the builder pattern at first sight offers you cleaner code where these objects are in fact used. On the other hand, not having getters/setters will not be very usable by a lot of frameworks that expect camel case getters/setters. This is a serious draw back I feel.

What I also like with getters/setters is that you clearly see what you are doing: get or set. I feel with the builder I am losing a bit of intuitive clarity here.

I know that many people have read a specific book and that now all of a sudden, the builder pattern has enjoyed the hype, as if it were the new iPhone. However, I am not an early adopter. I only use "the new way" when it really really proves a big time saver on whatever territory, being it performance, maintenance, coding...

My hands-on experience is that I usually am better of with getters/setters and constructors. It allows me to reuse these POJO's for any purpose.

Although I see the purpose of your Config Object, I also think it is even more overhead than the builder, and for what? What is wrong with setters?

Maybe we need to invent a WITH clause: example, let's say you have

public Class FooBar() {
    private String foo;

    public void setFoo(String bar) { 
      this.foo = bar; 
    }

    public String getFoo() { 
        return this.foo; 
    }
}

public static void main(String []args) {

 FooBar fuBar = new FooBar();
 String myBar;

 with fuBar {
    setFoo("bar");
    myBar = getFoo(); 
 }
}

Ah I dunno... I think this may yet result in quicker code writing without all the inner class hassle. Does any one have connections with the Oracle Java guru's?

It doesn't look as clean as using an object with a builder, but you save builder-construction time. And you can still use the class as a regular pojo/bean which can be used in frameworks...

Would you guys actually like this clause or you think it would rather suck? Cheers

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

The configuration pattern and the builder pattern are functionally equivalent. They both solve the same problems -

  • Eliminate the need for multiple constructor signatures

  • Allow fields to only be set during construction

  • Allow consumers to only set values they care about and have logical defaults for the other values

Anything you want to do in one of these patterns you can do in the other, such as only allowing state to be set with methods that do validation and setting state with encapsulated logic. The only real difference is if you like creating objects with the new key term or if you like calling a .build() method.

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?

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..

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM