Java 生成器模式和大量必需参数

Java 生成器模式和大量必需参数,java,parameters,design-patterns,builder,Java,Parameters,Design Patterns,Builder,到目前为止,我使用构建器模式的实现(与所描述的实现相反): 这在我遇到的大多数情况下都很有效,在这些情况下,我需要使用各种必需/必需和可选参数构建复杂对象。然而,我最近一直在努力理解,当所有参数都是强制性的(或者至少绝大多数是强制性的)时,这种模式是如何带来好处的 解决这一问题的一种方法是对传递到它们自己的类中的参数进行逻辑分组,以减少传递给构建器构造函数的参数数量 例如,而不是: Widget example = new Widget.Builder(req1, req2, req3,req4

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

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

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

例如,而不是:

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();
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()
方法中对所需参数执行验证


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

构建器/工厂仍然允许您将接口与实现类型分离(或允许您插入适配器等),假设
小部件
成为一个接口,并且您有办法插入或隐藏
新的小部件.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
,而是返回自身的一个新副本以及更改。正如您在上面指出的,参数对象是绕过重复参数样板的另一种方法。在这里,您甚至不需要构建器模式:只需将参数对象传递给实现构造函数

我最近一直在努力理解这种模式是如何产生的 当您的所有参数都是必需的时,您将受益

该模式简化了不可变类的创建,提高了可读性代码。考虑下面的人类(用传统的构造函数和生成器)。 哪种施工方法更容易理解

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();
解决这个问题的一种方法是对数据进行逻辑分组 参数被传递到它们自己的类中

当然,无论对象构造语义如何,都应该采用这一原则

然而,我最近一直在努力理解,当所有参数都是强制性的(或者至少绝大多数是强制性的)时,这种模式是如何带来好处的

fluent builder模式仍然是有益的:

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

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


  • 按如下方式分组:

    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();
    
    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()
    方法中对所需参数执行验证

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


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

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


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

    我认为这对于具有较大强制值的情况是合适的,尽管接口的数量会增加,但代码会更干净

    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();
    
    签出此引用:
    我的匿名类解决方案。以下是
    familyName
    是必需参数,而
    givenName
    是可选参数。如果此解决方案的主要目标是强制创建
    人员
    以设置所需参数的程序员(如果他不这样做,Java将不会编译)

    新人(
    Person.parametersObject(新Person.RequiredParameters(){
    @凌驾
    public void setFamilyName(){
    this.familyName=“Jonson”;
    }
    })
    .Setgivename(“约翰”)
    );
    
    实际上,目标还没有完全达到:因为我无法强迫程序员编写
    this.familyName=familyName,但他必须实现
    setFamilyNamepublic 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();