Setters AND ( not OR or VS ) builder patterns

10,209

Solution 1

You've never seen that used because most of the time, the builder pattern is used to build an immutable object.

But I don't see why they couldn't coexist. The builder builds an object, and you want the built object to be mutable, then it can have setters. But then, if it is mutable and has setters, why not build the object using a simple constructor, and call setters to change the state? The builder isn't really useful anymore, unless only one or two fields among many are mutable.

Solution 2

Now lets assume a usecase where I need to update the cheese. That needs a setter.

Rather than thinking of setters or builders, try to think of responsibilities of a class and services provided to users of the class.

What you call a setter here is simply a service that transforms an object. A builder is a service that creates a complex object.

If you're providing setters to access attributes (or the details of a complex object that should remain secret to the client), you're breaking encapsulation. That's an anti-pattern. Your cheese example is not sufficient to reveal why that might be bad. Does a user need to know a pizza has cheese and be able to modify it?

As JB Nizet said, there's no reason both services can't exist, but I'd ask the question whether revealing details is good or not.

Solution 3

It's possible that you haven't seen the builder pattern with setters because your sources might be strongly tied to the example published by the GoF. There can be many variations of the builder pattern, and this true for any design pattern. The builder pattern is obviously a creational pattern, but the main intent of the builder pattern is to solve the problem of telescoping constuctors. It's also a good pattern when you need construction in a very controlled and incremental fashion. None of these things which I have just mentioned are invalidated by having setters. One useful variation of the pattern is to have setters (via builder) which allow preparing the object state and then have a build method which builds/ instantiates the target object. The build method incrementally creates and possibly validates the final object. Take the following as an example:

Pizza pizza = pizzaBuilder.newBuilder().addCheese().addPepperoni().addBacon().Build();

The example above makes good use of the builder pattern using Java language constructs. It's quite common actually.

Solution 4

Let me suggest another couple of options, going further in the direction of the first answers. As has been mentioned, the virtues of the builder pattern include the ability to accumulate knowledge over multiple steps, support for immutable instances, and to ensure that a "fresh" object is created in a coherent state. I'll stay within the concepts of your question's design.

  1. You could extend the Pizza class by an instance method (e.g. withCheese(boolean value) that returns a new Pizza instance whose other attributes match those of the receiving instance but with the specified cheese attribute value. This preserves immutability of the original, but gives you a new instance with the intended difference.

  2. You could extend the Pizza class by an instance method (e.g. builder() that returns a Pizza.Builder initialized with the attributes of the receiving instance. Then all the methods already on Pizza.Builder are available, without needing to add instance methods per option 1. The cost of that benefit is the need to make a final build() call on the Pizza.Builder.

So after executing

Pizza pizza0 = new Pizza.Builder(10)
    .cheese(true)
    .build();

to get a ten-inch cheese pizza, you could execute

// option 1
Pizza pizza1 = pizza0.withPepperoni(true);

to get a ten-inch cheese-and-pepperoni pizza via option 1, or

// option 2
Pizza pizza2 = pizza0.builder().pepperoni(true).build();

to get the same thing via option 2.

Option 1 is shorter to get a new pizza with a single difference, but takes more effort to implement all the needed instance methods and builds more intermediate pizzas to make multiple differences.

Option 2 always gets a Pizza.Builder but then reuses all of its capabilities to get a single resulting pizza in the desired configuration. This option also allows more pizza attributes to be added more easily (by adding the instance attribute to Pizza and the single corresponding method to Pizza.Builder.

Share:
10,209
JavaDeveloper
Author by

JavaDeveloper

Updated on June 05, 2022

Comments

  • JavaDeveloper
    JavaDeveloper about 2 years

    I have a situation where I use a builder pattern for constructing an object. Best example to give is the pizza code

    public class Pizza {
      private int size;
      private boolean cheese;
      private boolean pepperoni;
      private boolean bacon;
    
      public static class Builder {
        //required
        private final int size;
    
        //optional
        private boolean cheese = false;
        private boolean pepperoni = false;
        private boolean bacon = false;
    
        public Builder(int size) {
          this.size = size;
        }
    
        public Builder cheese(boolean value) {
          cheese = value;
          return this;
        }
    
        public Builder pepperoni(boolean value) {
          pepperoni = value;
          return this;
        }
    
        public Builder bacon(boolean value) {
          bacon = value;
          return this;
        }
    
        public Pizza build() {
          return new Pizza(this);
        }
      }
    
      private Pizza(Builder builder) {
        size = builder.size;
        cheese = builder.cheese;
        pepperoni = builder.pepperoni;
        bacon = builder.bacon;
      }
    }
    

    So far so good.

    Now lets assume a usecase where I need to update the cheese. That needs a setter. I have never seen a single example where builder patterns coexist with setters, making me suspicious that what I was upto was an anti-pattern.

    Can setters AND builders coexist together ?

  • Balaji Boggaram Ramanarayan
    Balaji Boggaram Ramanarayan over 9 years
    But builder pattern makes code more readable when you have more class members. Isnt that true ? Correct me if I'm wrong..