这个Monster Builder是一个很好的构建器/工厂模式,用于抽象混合了setter的长构造函数吗?

can*_*nge 18 java dsl design-patterns fluent builder

这是一个关于将步骤构建器模式增强向导构建器模式组合到创建 DSL中的人机界面问题.它使用流畅的界面,虽然它使用方法链接,而不是级联.也就是说,这些方法返回不同的类型.

我面对的是一个怪物类,它有两个构造函数,它们混合了整数,字符串和一系列字符串.每个构造函数都是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");
Run Code Online (Sandbox Code Playgroud)

这最终失败了,因为事实证明设置了两个最喜欢的啤酒职位是不相容的.叹.

重新设计怪物类不是一种选择.它被广泛使用.有用.我只是不想再看它直接构建了.我想写一些干净的东西来喂它.在不让开发人员记住它们的情况下遵循其规则的东西.

与我一直在研究的美妙的建造者模式相反,这个东西没有风味或类别.它在需要时始终需要一些字段和其他字段,有些仅取决于之前设置的字段.施工人员不是伸缩式的.它们提供了两种使类进入相同状态的替代方法.它们漫长而丑陋.他们想要给他们的东西各不相同.

一个流畅的建设者肯定会让长建筑师更容易看到.然而,大量可选的设置器使所需的设置器变得混乱.并且要求级联的流利构建器不满足:编译时执行.

构造函数强制开发人员显式添加必需的字段,即使将其归零也是如此.使用级联流畅的构建器时,这会丢失.与安装者失去的方式相同.我想要一种方法来阻止开发人员构建,直到添加了每个必填字段.

与许多建筑师模式不同,我所追求的不是不变性.我发现它,我正在离开课堂.我想通过查看构建它的代码来了解构造的对象是否处于良好状态.无需参考文档.这意味着它需要通过有条件的必要步骤来接受程序员.

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
    ;
}   
Run Code Online (Sandbox Code Playgroud)

可以在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);
Run Code Online (Sandbox Code Playgroud)

这些参数设置的字段绝不能保留默认值.啤酒今天和howDrunk以不同的方式设置相同的字段.favoriteBeer和jobTitle是不同的字段,但会导致与类的使用方式发生冲突,因此只应设置一个.它们由setter而不是构造函数处理.

doBuild()方法返回一个Person对象.它是唯一一个Person可以返回的唯一类型.当它Person完全初始化.

在接口的每个步骤中,返回的类型并不总是相同的.更改类型是指通过步骤引导开发人员的方式.它只提供有效的方法.doBuild()在完成所有必需步骤之前,该方法不可用.

do/add前缀是一个使写入更容易的因素,因为更改的返回类型与赋值不匹配,并使得知识推荐在eclipse中变为字母.我已经确认intellij没有这个问题.谢谢NimChimpsky.

这个问题是关于界面的,所以我会接受不提供实现的答案.但如果你知道一个,请分享.

如果你建议一个替代模式,请显示它正在使用的界面.使用示例中的所有输入.

如果建议使用这里介绍的接口,或者一些微小的变化,请从喜欢批评保卫它这个.

我真正想知道的是,如果大多数人更喜欢使用这个界面来构建或其他.这是人机界面问题.这会违反PoLA吗?不要担心实施起来有多难.

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

尝试失败(没有足够的状态或理解有效而非违约)

步骤构建器实现(对于多个构造函数或替代方案而言不够灵活)

增强型构建器(Still衬里但具有灵活状态)

向导构建器(处理分叉但不记住选择构造函数的路径)

需求:

  • 怪物(人)类已经关闭修改和扩展; 没有敏感

目标:

  • 隐藏长构造函数,因为怪物类有10个必需参数
  • 根据使用的替代方法确定要调用的构造函数
  • 禁止有冲突的二传手
  • 在编译时强制执行规则

意图:

  • 当默认值不可接受时,清楚地发出信号

Nim*_*sky 3

一个静态内部构建器,因 josh bloch 在 effective java 中而闻名。

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

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

RegisterUserDto myDto = RegisterUserDto.Builder(myUsername).password(mypassword).email(myemail).Build();
Run Code Online (Sandbox Code Playgroud)

和底层代码(省略明显的实例变量):

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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

  • “如果他们需要分叉给不同的构造函数怎么办?” 只是有两个不同的构造函数调用吗?为了澄清,最好的解决方案恕我直言,是重构对象并使用包含这些字段/逻辑的组成对象,从而产生更简单的 api,换句话说,重构您的域。按一定顺序调用这些方法肯定不好。 (4认同)