有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java构建器模式和大量必需参数

到目前为止,我使用构建器模式的following实现(与here描述的实现相反):

public class Widget {
    public static class Builder {
        public Builder(String name, double price) { ... }
        public Widget build() { ... }
        public Builder manufacturer(String value) { ... }
        public Builder serialNumber(String value) { ... }
        public Builder model(String value) { ... }
    }

    private Widget(Builder builder) { ... }
}

这在我遇到的大多数情况下都很有效,在这些情况下,我需要使用各种必需/必需和可选参数构建复杂对象。然而,我最近一直在努力理解,当所有参数都是强制性的(或者至少绝大多数是强制性的)时,这种模式是如何带来好处的

解决这一问题的一种方法是对传递到它们自己的类中的参数进行逻辑分组,以减少传递给构建器构造函数的参数数量

例如,而不是:

Widget example = new Widget.Builder(req1, req2, req3,req4,req5,req6,req7,req8)
                           .addOptional(opt9)
                           .build();

按如下方式分组:

Object1 group1 = new Object1(req1, req2, req3, req4);
Object2 group2 = new Object2(req5, req6);

Widget example2 = new Widget.Builder(group1, group2, req7, req8)
                            .addOptional(opt9)
                            .build();

虽然使用单独的对象简化了很多事情,但如果不熟悉代码,也会使事情变得有点难以理解。我考虑的一件事是将所有参数移动到它们自己的addParam(param)方法中,然后在build()方法中对所需的参数执行验证

什么是最佳实践?是否有更好的方法,我还没有考虑过


共 (6) 个答案

  1. # 1 楼答案

    However, I've been struggling lately to understand how the pattern is of any benefit when all your parameters are mandatory (or at least the vast majority are).

    fluent builder模式仍然是有益的:

    1. 更具可读性-它有效地允许命名参数,因此调用不仅仅是一长串未命名参数

    2. 它是无序的-这允许您将参数分组到逻辑组中,或者作为单个生成器setter调用的一部分,或者只允许您使用自然顺序来调用生成器setter方法,这些方法最能理解这个特定的实例化


    Widget example = new Widget.Builder(req1, req2, req3,req4,req5,req6,req7,req8)
                                   .addOptional(opt9)
                                   .build();
    

    becomes grouped as follows:

    Object1 group1  = new Object1(req1, req2, req3, req4);
    Object2 group2  = new Object2(req5, req6);
    Widget example2 = new Widget.Builder(group1, group2, req7, req8)
                                .addOptional(opt9)
                                .build();
    

    While having separate objects simplifies things quite a bit, it also makes things a little difficult to follow if one is not familiar with the code. One thing I considered was moving all parameters into their own addParam(param) methods and then performing validation on required parameters in the build() method.

    在合适或自然的情况下,我更喜欢杂交。它不必全部在构造函数中,每个参数都有自己的addParam方法。Builder为您提供了执行一个、另一个、中间或组合的灵活性:

    Widget.Builder builder = new Widget.Builder(Widget.BUTTON);
    
    builder.withWidgetBackingService(url, resource, id);
    builder.withWidgetStyle(bgColor, lineWidth, fontStyle);
    builder.withMouseover("Not required");
    
    Widget example = builder.build();
    
  2. # 2 楼答案

    构建器模式的一个优点是,我很少(如果曾经)看到它被提升,它也可以用于有条件地构造对象,例如,只有在所有必需参数都正确或者其他必需资源可用的情况下。在这方面,它们提供了与a static factory method类似的好处

  3. # 3 楼答案

    构建器/工厂仍然允许您将接口与实现类型分离(或者允许您插入适配器等),假设Widget成为一个接口,并且您有办法注入或隐藏new Widget.Builder

    如果您不关心解耦,并且您的实现是一次性的,那么您是对的:构建器模式并不比普通的构造函数有用多少(它仍然使用每个构建器方法的属性样式标记其参数)

    如果重复创建参数变化不大的对象,那么它可能仍然有用。您可以传入、缓存在插入多个属性后获得的中间生成器等:

    Widget.Builder base = new Widget.Builder(name, price).model("foo").manufacturer("baz");
    
    // ...
    
    Widget w1 = base.serialNumber("bar").build();
    Widget w2 = base.serialNumber("baz").build();
    Widget w3 = base.serialNumber("quux").build();
    

    这假设构建器是不可变的:构建器设置器不设置属性并返回this,而是返回自身的新副本和更改。正如您在上面指出的,参数对象是绕过重复参数样板的另一种方法。在这里,您甚至不需要构建器模式:只需将参数对象传递给实现构造函数

  4. # 4 楼答案

    如果有许多必需参数,则可以使用Step Builder。简而言之:为每个强制参数定义一个接口,生成器方法返回下一个强制生成器接口,或者为可选方法返回生成器本身。生成器仍然是实现所有接口的单个类

    interface StepB {
        StepBuilder b(String b);
    }
    
    interface StepA {
        StepB a(String a);
    }
    
    final class StepBuilder implements StepA, StepB {
        private String a;
        private String b;
        private String c = "";
    
        private StepBuilder() {
        }
    
        static StepA with() {
          return new StepBuilder();
        }
    
        // mandatory, from StepA
        @Override
        StepB a(String a) {
            this.a = a;
            return this;
        }
    
        // mandatory, from StepB
        @Override
        StepBuilder b(String b) {
            this.b = b;
            return this;
        }
    
        // optional
        StepBuilder c(String c) {
            this.c = c;
            return this;
        }
    
        Product build() {
            return new Product(a, b, c);
        }
    }
    

    用法:

    StepBuilder.with().a("hello").b("world").build();
    
    // or with the optional parameter c
    StepBuilder.with().a("hello").b("world").c("!").build();
    

    像Kotlin和Scala这样的语言在这里更方便,因为它们提供带有默认值的命名参数

  5. # 5 楼答案

    我认为这在您有较大的强制值的情况下是合适的,尽管接口的数量会增加,但代码会很干净

    public class PersonBuilder implements NamePersonBuilder, LastNamePersonBuilder, 
                                      BirthDatePersonBuilder, FinalPersonBuilder {
    
    private String name;
    private String lastName;
    private Date birthDate;
    private String phoneNumber;
    
    /**
     * Private constructor to force the use of the factroy method
     */
    private PersonBuilder() {
    }
    
    /**
     * Creates a new person builder
     */
    public static NamePersonBuilder aPerson() {
        return new PersonBuilder();
    }
    
    public LastNamePersonBuilder withName(String aName) {
        name = aName;
        return this;
    }
    
    public BirthDatePersonBuilder withLastName(String aLastName) {
        lastName = aLastName;
        return this;
    }
    
    public FinalPersonBuilder withBirthDate(Date aBirthDate) {
        birthDate = aBirthDate;
        return this;
    }
    
    public FinalPersonBuilder andPhoneNumber(String aPhoneNumber) {
        phoneNumber = aPhoneNumber;
        return this;
    }
    
    public Person build() {
        // The constructor and setters for Person has default scope
        // and is located in the same package as the builder
        Person p = new Person();
        p.setName(name);
        p.setLastName(lastName);
        p.setBirthDate(birthDate);
        p.setPhoneNumber(phoneNumber);
        return p;
    }
    
    interface NamePersonBuilder {
        LastNamePersonBuilder withName(String aName);
    }
    
    interface LastNamePersonBuilder {
        BirthDatePersonBuilder withLastName(String aLastName);
    }
    
    interface BirthDatePersonBuilder {
        FinalPersonBuilder withBirthDate(Date aBirthDate);
    }
    
    interface FinalPersonBuilder {
        FinalPersonBuilder andPhoneNumber(String aPhoneNumber);
        Person build();
    }}
    

    这将强制用户设置所有强制值,并强制设置值的顺序。因此,要构造一个人,这将是生成的代码:

    PersonBuilder.aPerson()
        .withName("Name")
        .withLastName("LastName")
        .withBirthDate(new Date())
        .build();
    

    签出此引用: Builder Pattern with Twist

  6. # 6 楼答案

    I've been struggling lately to understand how the pattern is of any benefit when all your parameters are mandatory

    该模式简化了不可变类的创建,提高了可读性代码。考虑下面的人类(用传统的构造函数和生成器)。

    public static class Person {
    
        private static final class Builder {
            private int height, weight, age, income, rank;
            public Builder setHeight(final int height) { this.height = height; return this; }
            public Builder setWeight(final int weight) { this.weight = weight; return this; }
            public Builder setAge(final int age) { this.age = age; return this; }
            public Builder setIncome(final int income) {    this.income = income; return this; }
            public Builder setRank(final int rank) { this.rank = rank; return this; }
            public Person build() { return new Person(this); }
        }
    
        private final int height;
        private final int weight;
        private final int age;
        private final int income;
        private final int rank;
    
        public Person(final int height, final int weight, final int age, final int income, final int rank) {
            this.height = height; this.weight = weight; this.age = age; this.income = income; this.rank = rank;
        }
    
        private Person(final Builder builder) {
            height = builder.height; weight = builder.weight; age = builder.age; income = builder.income; rank = builder.rank;
            // Perform validation
        }
    
        public int getHeight() { return height; }
        public int getWeight() { return weight; }
        public int getAge() { return age; }
        public int getIncome() { return income; }
        public int getRank() {  return rank; }
    
    }
    

    哪种施工方法更容易理解

    final Person p1 = new Person(163, 184, 48, 15000, 23);
    final Person p2 = new Person.Builder().setHeight(163).setWeight(184).setAge(48).
        setIncome(15000).setRank(23).build();
    

    One means of getting around this has been to logically group the parameters being passed in to their own classes

    当然,这是cohesion的原则,不管对象构造语义如何,都应该采用