34

构建器模式在创建不可变对象方面很流行,但是创建构建器需要一些编程开销。所以我想知道为什么不简单地使用配置对象。

构建器的用法如下所示:

Product p = Product.Builder.name("Vodka").alcohol(0.38).size(0.7).price(17.99).build();

很明显,这是非常易读和简洁的,但是你必须实现构建器:

public class Product {

    public final String name;
    public final float alcohol;
    public final float size;
    public final float price;

    private Product(Builder builder) {
        this.name = builder.name;
        this.alcohol = builder.alcohol;
        this.size = builder.size;
        this.price = builder.price;
    }

    public static class Builder {

        private String name;
        private float alcohol;
        private float size;
        private float price;

        // mandatory
        public static Builder name(String name) {
            Builder b = new Builder();
            b.name = name;
            return b;
        }

        public Builder alcohol(float alcohol) {
            this.alcohol = alcohol;
            return.this;
        }

        public Builder size(float size) {
            this.size = size;
            return.this;
        }

        public Builder price(float price) {
            this.price = price;
            return.this;
        }

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

    }

}

我的想法是,通过使用这样的简单配置对象来减少代码:

class ProductConfig {

        public String name;
        public float alcohol;
        public float size;
        public float price;

        // name is still mandatory
        public ProductConfig(String name) {
            this.name = name;
        }

}

public class Product {

    public final String name;
    public final float alcohol;
    public final float size;
    public final float price;

    public Product(ProductConfig config) {
        this.name = config.name;
        this.alcohol = config.alcohol;
        this.size = config.size;
        this.price = config.price;
    }

}

用法:

ProductConfig config = new ProductConfig("Vodka");
config.alcohol = 0.38;
config.size = 0.7;
config.price = 17.99;
Product p = new Product(config);

这种用法需要多几行,但也非常易读,但实现要简单得多,对于不熟悉构建器模式的人来说可能更容易理解。顺便说一句:这种模式有名字吗?

我忽略的配置方法有缺点吗?

4

9 回答 9

20

构建器模式改进了解耦——你的产品可以是一个接口,唯一知道实现(或在某些情况下是实现)的类是构建器。如果构建器还实现了一个接口,那么您可以将其注入您的代码中以进一步增加解耦。

这种解耦意味着您的代码更易于维护和测试。

于 2010-08-03T08:35:56.987 回答
9

正如已经指出的那样,您正在失去构建器模式的几个优点(与干净的构建器相比,的很难维护和泄漏细节)。

然而,我最想念的是构建器模式可用于提供所谓的“流畅接口”

而不是这个:

ProductConfig config = new ProductConfig("Vodka");
config.alcohol = 0.38;
config.size = 0.7;
config.price = 17.99;
Product p = new Product(config);

你可以做:

ProductFactory.create()
    .drink("Vodka")
    .whereAlcohoolLevelIs(0.38)
    .inABottleSized(0.7)
    .pricedAt(17.99)
    .build();

不是每个人都喜欢流式接口,但它们绝对是构建器模式的一个非常好的使用(所有流式接口都应该使用构建器模式,但并非所有构建器模式都是流式接口)。

一些很棒的 Java 集合,比如 Google 集合,既非常自由又非常好地使用了“流利的接口”。我会选择这些任何一天而不是你的“更容易输入/更少字符”的方法:)

于 2010-08-03T09:34:48.210 回答
5

配置模式和构建器模式在功能上是等效的。他们都解决了同样的问题——

  • 消除对多个构造函数签名的需要

  • 只允许在构建期间设置字段

  • 允许消费者只设置他们关心的值,并为其他值设置逻辑默认值

您想在其中一种模式中执行的任何操作都可以在另一种模式中执行,例如仅允许使用执行验证的方法设置状态以及使用封装逻辑设置状态。唯一真正的区别是您是否喜欢使用new关键字创建对象,或者您是否喜欢调用.build()方法。

于 2017-08-16T22:55:18.513 回答
4

你试图用你的模式解决什么问题?构建器模式用于具有许多(可选)参数的对象,以防止大量不同的构造函数或非常长的构造函数。它还使您的对象在构造过程中保持一致的状态(与 javabean 模式相比)。

builder 和“config object”(感觉是个好名字)之间的区别在于,您仍然必须通过构造函数或 getter/setter 创建具有相同参数的对象。这 a) 不能解决构造函数问题或 b) 使配置对象处于不一致状态。配置对象的不一致状态并没有真正伤害它,但您可以将未完成的配置对象作为参数传递。[Michids 链接到幻像类型似乎解决了这个问题,但这又降低了可读性(new Foo<TRUE,TRUE, TRUE, FALSE, TRUE>有点糟)。]这就是构建器模式的一大优势:您可以在创建对象之前验证参数,并且可以返回任何子类型(行为像工厂)。

配置对象对所有必需的参数集都是有效的。我以前在 .NET 或 java 中多次看到这种模式。

于 2010-08-03T09:03:48.070 回答
2

您是否考虑过使用builder-builder

我确实认为构建器(带有“With”之类的前缀)读起来更自然/流利。

于 2010-08-03T09:16:43.520 回答
2

我个人认为,第一眼看到构建器模式为您提供了更清晰的代码,这些对象实际上是使用这些对象的。另一方面,没有 getter/setter 将不会被许多期望驼峰式 getter/setter 的框架非常有用。我觉得这是一个严重的退步。

我还喜欢 getter/setter 的一点是,您可以清楚地看到自己在做什么:获取或设置。我觉得与建造者在一起,我在这里失去了一些直观的清晰度。

我知道很多人都读过一本特定的书,现在突然之间,建造者模式受到了炒作,就好像它是新的 iPhone。但是,我不是早期采用者。我只使用“新方法”,当它真的证明在任何领域都可以节省大量时间时,无论是性能、维护、编码......

我的实践经验是,我通常更擅长使用 getter/setter 和构造函数。它允许我出于任何目的重用这些 POJO。

尽管我看到了您的 Config Object 的目的,但我也认为它比构建器的开销更大,而且是为了什么?二传手有什么问题?

也许我们需要发明一个 WITH 子句:例如,假设你有

public Class FooBar() {
    private String foo;

    public void setFoo(String bar) { 
      this.foo = bar; 
    }

    public String getFoo() { 
        return this.foo; 
    }
}

public static void main(String []args) {

 FooBar fuBar = new FooBar();
 String myBar;

 with fuBar {
    setFoo("bar");
    myBar = getFoo(); 
 }
}

啊,我不知道...我认为这可能会导致更快的代码编写,而无需所有内部类的麻烦。有人与 Oracle Java 大师有联系吗?

它看起来不像使用带有构建器的对象那么干净,但是可以节省构建器的构建时间。而且您仍然可以将该类用作可以在框架中使用的常规 pojo/bean...

你们是真的喜欢这个条款还是你认为它更糟糕?干杯

于 2012-09-18T14:18:33.037 回答
1

IMO,如果您有验证等内容,构建器模式会更加强大。

您的案例中的构建器模式可以更改为执行以下操作:

Product p = new ProductBuilder("pName").alcohol(0.38).size(0.7).price(17.99).build();

build()方法可以完成构建器所需的所有验证工作。构建器模式还有几个设计优势(所有这些都可能不适用于您的情况)。有关详细信息,请检查此问题

于 2010-08-03T08:37:21.523 回答
0

主要的缺点是它不在 Joshua 的书中,所以无人机无法将头绕在它周围。

您正在使用一个简单的值对象来保存函数(/方法/构造函数)需要的多个参数,这没有什么问题,它已经做了很长时间了。只要我们没有命名可选参数,我们就必须设计出这样的解决方法——这太可惜了,这不是来自太阳神的一些该死的辉煌发明。

真正的区别在于您直接公开字段。Joshua 永远不会有一个公共的可变字段——但他编写的 API 将被数百万人使用,其中大多数是白痴,并且 API 必须在未来几十年内安全地发展,他们可以分配很多人月来设计一个简单的类

我们要效仿谁?

于 2010-08-03T17:24:37.887 回答
-1

您不应使用公共字段,而应使用受保护或私有字段。对于访问,您应该使用 getter 和 setter 来保持封装..

于 2010-08-03T08:38:53.857 回答