首页 文章

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

提问于
浏览
52

到目前为止,我使用构建器模式的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() 方法中对所需参数执行验证 .

什么是最佳实践,是否有更好的方法可以解决这个问题?

5 回答

  • 3

    然而,最近我一直在努力了解当所有参数都是强制性的(或者至少绝大多数参数)时,模式是如何有益的 .

    流畅的构建模式仍然有益:

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

    • 它是无序的 - 这允许您将参数组合成逻辑组,或者作为单个构建器设置器调用的一部分,或者只是让您使用自然顺序来调用最能够理解此特定实例的构建器setter方法 .


    Widget示例=新的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()方法中对所需参数执行验证 .

    在适当或自然的时候,我会赞成混合动力 . 它不必全部在构造函数中,或者每个param都有自己的addParam方法 . Builder可让您灵活地执行其中一个,中间或组合:

    Widget.Builder builder = new Widget.Builder(Widget.BUTTON);

    builder.withWidgetBackingService(url,resource,id);
    builder.withWidgetStyle(bgColor,lineWidth,fontStyle);
    builder.withMouseover(“不需要”);

    Widget example = builder.build();

  • 30

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

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

  • 26

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

  • 0

    当我的所有参数都是强制性的时候,我最近一直在努力理解这种模式是如何有益的

    该模式简化了不可变类的创建并促进了可读代码 . 考虑下面的Person类(使用传统的构造函数和构建器) .

    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();
    

    解决这个问题的一种方法是将传入的参数逻辑分组到它们自己的类中

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

  • 2

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

相关问题