Setters AND ( not OR or VS ) builder patterns

后端 未结 6 1054
一整个雨季
一整个雨季 2021-02-05 23:07

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;
  pr         


        
相关标签:
6条回答
  • 2021-02-05 23:29

    you can use @Builder(toBuilder = true).

    @Builder(toBuilder = true)
    class Pizza {
    int val;
    }
    
    Pizza ob = Pizza.builder().val(10)build();
    
    Pizza newObj = ob.toBuilder().val(11).build();
    

    Ref : https://projectlombok.org/features/Builder

    0 讨论(0)
  • 2021-02-05 23:31

    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're calling a setter here is simply a service that transform 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.

    0 讨论(0)
  • 2021-02-05 23:36

    I think it could help someone in the future, but what about Copy constructor thou?

    It will not break a contract of the immutability of an object and you will have an option to get a Pizza with different property values.

    So you can basically use two approaches:

    1. Pizza.Builder(pizza).cheese(cheese).build()
    2. Pizza.from(pizza).cheese(cheese).build() as Akash Srivastava suggested

    Both approaches basically expecting a copy constructor of Pizza or even feeding newly created builder instance from pizza parameter properties.

    0 讨论(0)
  • 2021-02-05 23:45

    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.

    0 讨论(0)
  • 2021-02-05 23:45

    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.

    0 讨论(0)
  • 2021-02-05 23:46

    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.

    0 讨论(0)
提交回复
热议问题