java - 这是Monster Builder的一个很好的Builder/Factory模式,用于抽象混合有setter的长构造函数吗?

标签 java design-patterns dsl builder fluent

这是有关将step builder patternenhancedwizard构建器模式组合到creational DSL中的人机交互问题。尽管它使用方法链接而不是级联,但它使用的是类似Fluent的界面。即,这些方法返回不同的类型。

我面对的是一个怪物类,它具有两个构造函数,这些构造函数混合使用整数,字符串和字符串数组。每个构造函数的长度为10个参数。它还有大约40个可选的二传手。如果一起使用,其中一些会相互冲突。其构造代码如下所示:

Person person = Person("Homer","Jay", "Simpson","Homie", null, "black", "brown", 
  new Date(1), 3, "Homer Thompson", "Pie Man", "Max Power", "El Homo", 
  "Thad Supersperm", "Bald Mommy", "Rock Strongo", "Lance Uppercut", "Mr. Plow");

person.setClothing("Pants!!");     
person.setFavoriteBeer("Duff");
person.setJobTitle("Safety Inspector");

最终失败,因为事实证明同时设置了喜爱的啤酒和职务不兼容。叹。

重新设计怪物类不是一个选择。它被广泛使用。有用。我只是不想再看它直接构造了。我想写一些干净的东西来养活它。会遵循其规则而不会使开发人员记住它们的东西。

与我一直在研究的奇妙的构建器模式相反,它没有不同的口味或类别。它始终需要一些字段,并在需要时需要其他字段,而某些字段仅取决于之前设置的内容。构造函数不会伸缩。它们提供了两种替代方法来使类进入同一状态。他们又长又丑。他们想要喂给他们的东西各不相同。

熟练的构建者肯定会使长构建者更容易看待。但是,大量的可选 setter 使所需的 setter 困惑。并要求层叠的流利的构建器不满足:编译时间执行。

构造函数会强制开发人员显式添加必填字段,即使将其为空也是如此。使用级联流利的构建器时,这会丢失。与二传手一样迷失。我想要一种方法来阻止开发人员构建,直到添加了每个必填字段。

与许多构建器模式不同,我追求的不是不变性。找到类(class)后,我要离开类(class)。我想通过查看构建对象的代码来知道该对象处于良好状态。无需引用文档。这意味着需要采取程序员有条件地采取的必要步骤。
Person makeHomer(PersonBuilder personBuilder){ //Injection avoids hardcoding implementation
    return personBuilder

         // -- These have good default values, may be skipped, and don't conflict -- //
        .doOptional()
            .addClothing("Pants!!")   //Could also call addTattoo() and 36 others

         // -- All fields that always must be set.  @NotNull might be handy. -- //
        .doRequired()                 //Forced to call the following in order
            .addFirstName("Homer")
            .addMiddleName("Jay")
            .addLastName("Simpson")
            .addNickName("Homie")
            .addMaidenName(null)      //Forced to explicitly set null, a good thing
            .addEyeColor("black")
            .addHairColor("brown")
            .addDateOfBirth(new Date(1))
            .addAliases(
                "Homer Thompson",
                "Pie Man",
                "Max Power",
                "El Homo",
                "Thad Supersperm",
                "Bald Mommy",
                "Rock Strongo",
                "Lance Uppercut",
                "Mr. Plow")

         // -- Controls alternatives for setters and the choice of constructors -- //
        .doAlternatives()           //Either x or y. a, b, or c. etc.
            .addBeersToday(3)       //Now can't call addHowDrunk("Hammered"); 
            .addFavoriteBeer("Duff")//Now can’t call addJobTitle("Safety Inspector");  

        .doBuild()                  //Not available until now
    ;
}   

可以在addBeersToday()之后构建Person,因为那时所有构造函数信息都是已知的,但直到doBuild()才返回。
public Person(String firstName, String middleName, String lastName,
               String nickName, String maidenName, String eyeColor, 
               String hairColor, Date dateOfBirth, int beersToday, 
               String[] aliases);

public Person(String firstName, String middleName, String lastName,
               String nickName, String maidenName, String eyeColor, 
               String hairColor, Date dateOfBirth, String howDrunk,
               String[] aliases);

这些参数设置的字段绝不能保留默认值。 beersToday和drunk如何在同一字段中设置不同的方式。 favoriteBeer和jobTitle是不同的字段,但与类的使用方式产生冲突,因此仅应设置一个。它们由setter(而非构造函数)处理。
doBuild()方法返回Person对象。这是唯一的一种,Person是它将返回的唯一类型。这样做时,Person已完全初始化。

在接口(interface)的每一步,返回的类型并不总是相同的。更改类型是通过步骤指导开发人员的方式。它仅提供有效的方法。只有完成所有必要的步骤,才能使用doBuild()方法。

The do/add prefixes are a kludge to make writing easier because the changing return type mismatches with the assignment and makes intelisense recommendations become alphabetical in eclipse. I've confirmed intellij doesn't have this problem. Thanks NimChimpsky.



这个问题是关于接口(interface)的,所以我将接受不提供实现的答案。但是,如果您知道其中一个,请分享。

如果您建议使用其他模式,请显示其正在使用的界面。使用示例中的所有输入。

如果您建议使用此处提供的界面或进行一些细微的改动,请免受this之类的批评。

我真正想知道的是,大多数人是否愿意使用此界面进行构建或其他。这是人机界面的问题。这违反了PoLA吗?不必担心实现会有多困难。

但是,如果您对实现感到好奇:

A failed attempt(没有足够的状态或了解有效与未默认)

A step builder implementation(对于多个构造函数或替代方法不够灵活)

An enhanced builder(仍然是类轮,但状态灵活)

Wizard builder(涉及派生,但不记得选择构造函数的路径)

Requirement:

  • The monster (person) class is already closed to modification and extension; no touchy

Goals:

  • Hide the long constructors since the monster class has 10 required parameters
  • Determine which constructor to call based on alternatives used
  • Disallow conflicting setters
  • Enforce rules at compile time

Intent:

  • Clearly signal when default values are not acceptable

最佳答案

一个静态的内部生成器,由josh bloch在有效的Java中闻名。

必需的参数是构造函数args,可选的是方法。

一个例子。仅需要用户名的调用:

RegisterUserDto myDto = RegisterUserDto.Builder(myUsername).password(mypassword).email(myemail).Build();

和底层代码(省略明显的实例变量):
private RegisterUserDTO(final Builder builder) {
        super();
        this.username = builder.username;
        this.firstName = builder.firstName;
        this.surname = builder.surname;
        this.password = builder.password;
        this.confirmPassword = builder.confirmPassword;
    }


    public static class Builder {
        private final String username;

        private String firstName;

        private String surname;

        private String password;

        private String confirmPassword;

        public Builder(final String username) {
            super();
            this.username = username;
        }

        public Builder firstname(final String firstName) {
            this.firstName = firstName;
            return this;
        }

        public Builder surname(final String surname) {
            this.surname = surname;
            return this;
        }

        public Builder password(final String password) {
            this.password = password;
            return this;
        }

        public Builder confirmPassword(final String confirmPassword) {
            this.confirmPassword = confirmPassword;
            return this;
        }

        public RegisterUserDTO build() {
            return new RegisterUserDTO(this);
        }
    }

关于java - 这是Monster Builder的一个很好的Builder/Factory模式,用于抽象混合有setter的长构造函数吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22909717/

相关文章:

java - Spring @validated 没有级联到字段

Java查找并删除子节点

java - 如何将 ArrayList 转换为 JSON 对象

c# - 异常处理的良好实践设计模式

design-patterns - 具有多个上下文的多个模式?

javascript - 处理 mongoose 查询,但使用不同的 JavaScript Promise 实现

Scala 编译器无法推断类型参数

java - Java层捕获JNI异常

haskell - 使用免费 Monad 对概率编程语言进行建模观察

安卓工作室 2.0 : Gradle DSL method not found: 'classpath()' error(27, 0)