Builder模式是在Java中最流行的模式之一。它很简单,有助于保持对象不可变,并且可以使用Project Lombok的@Builder或Immutables等工具生成,仅举几例。

模式的流畅变体示例:

public class User {private final String firstName;private final String lastName;User(String firstName, String lastName) {this.firstName = firstName;this.lastName = lastName;}public static Builder builder() {return new Builder();}public static class Builder {String firstName;String lastName;Builder firstName(String value) {this.firstName = value;return this;}Builder lastName(String value) {this.lastName = value;return this;}public User build() {return new User(firstName, lastName);}}
}

调用方式:

User.Builder builder = User.builder().firstName("Sergey").lastName("Egorov");if (newRules) {builder.firstName("Sergei");
}User user = builder.build();

解释:

  1. User class是不可变的,一旦我们实例化它就无法更改。
  2. 它的构造函数具有包私有可见性,必须使用构建器来实例化实例User。
  3. Builder的字段不是不可变的,可以在构建实例之前多次更改User。
  4. builder流利并且返回this(类型Builder)并且可以链接。

有什么问题?

继承问题

想象一下,我们想扩展User类:(备注:其实如果User类是DDD值对象,实际是 final class,不能再被继承了)。

public class RussianUser extends User {final String patronymic;RussianUser(String firstName, String lastName, String patronymic) {super(firstName, lastName);this.patronymic = patronymic;}public static RussianUser.Builder builder() {return new RussianUser.Builder();}public static class Builder extends User.Builder {String patronymic;public Builder patronymic(String patronymic) {this.patronymic = patronymic;return this;}public RussianUser build() {return new RussianUser(firstName, lastName, patronymic);}}
}

调用代码时会出错:

RussianUser me = RussianUser.builder().firstName("Sergei") // returns User.Builder :(.patronymic("Valeryevich") // // Cannot resolve method!出错.lastName("Egorov").build();

这里的问题是因为firstName有以下定义:

User.Builder firstName(String value) {this.firstName = value;return this;}

Java的编译器无法检测到this的意思是RussianUser.Builder而不是User.Builder!

我们甚至无法改变顺序:

RussianUser me = RussianUser.builder().patronymic("Valeryevich").firstName("Sergei").lastName("Egorov").build() // compilation error! User is not assignable to RussianUser;

**可能的解决方案:**Self typing

解决它的一种方法是添加一个泛型参数User.Builder,指示要返回的类型:

 public static class Builder<SELF extends Builder<SELF>> {SELF firstName(String value) {this.firstName = value;return (SELF) this;}

并将其设置为RussianUser.Builder:

public static class Builder extends User.Builder<RussianUser.Builder> {

它现在有效:

RussianUser.builder().firstName("Sergei") // returns RussianUser.Builder :).patronymic("Valeryevich") // RussianUser.Builder.lastName("Egorov") // RussianUser.Builder.build(); // RussianUser

它还适用于多级继承:

class A<SELF extends A<SELF>> {SELF self() {return (SELF) this;}
}class B<SELF extends B<SELF>> extends A<SELF> {}class C extends B<C> {}

那么,问题解决了吗?好吧,不是真的… 基本类型不能轻易实例化!

因为它使用递归泛型定义,所以我们有一个递归问题!

new A<A<A<A<A<A<A<...>>>>>>>()

但是,它可以解决(除非你使用Kotlin):

A a = new A<>();

在这里,我们依赖于Java的原始类型和钻石运算符<>。

但是,正如所提到的,它不适用于其他语言,如Kotlin或Scala,并且一般来说是这是一种黑客方式。

理想的解决方案:使用Java的Self typing

在继续阅读之前,我应该警告你:这个解决方案不存在,至少现在还没有。拥有它会很好,但目前我不知道任何JEP。PS谁知道如何提交JEP?;)

Self typing作为语言功能存在于Swift等语言中。

想象一下以下虚构的Java伪代码示例:

class A {@Selfvoid withSomething() {System.out.println("something");}
}class B extends A {@Selfvoid withSomethingElse() {System.out.println("something else");}
}

调用:

new B().withSomething() // replaced with the receiver instead of void.withSomethingElse();

如您所见,问题可以在编译器级别解决。事实上,有像Manifold的@Self这样的 javac编译器插件。

真正的解决方案:想一想

但是,如果不是试图解决返回类型问题,我们…删除类型?

public class User {// ...public static class Builder {String firstName;String lastName;void firstName(String value) {this.firstName = value;}void lastName(String value) {this.lastName = value;}public User build() {return new User(firstName, lastName);}}
}
public class RussianUser extends User {// ...public static class Builder extends User.Builder {String patronymic;public void patronymic(String patronymic) {this.patronymic = patronymic;}public RussianUser build() {return new RussianUser(firstName, lastName, patronymic);}}
}

调用方式:

RussianUser.Builder b = RussianUser.builder();
b.firstName("Sergei");
b.patronymic("Valeryevich");
b.lastName("Egorov");
RussianUser user = b.build(); // RussianUser

你可能会说,“这不是方便而且冗长,至少在Java中”。我同意,但…这是Builder的问题吗?

还记得我说过这个Builder是可变的吗?那么,为什么不利用它呢!

让我们将以下内容添加到我们的基础构建器中:

public class User {// ...public static class Builder {public Builder() {this.configure();}protected void configure() {}

并使用我们的构建器作为匿名对象:

RussianUser user = new RussianUser.Builder() {@Overrideprotected void configure() {firstName("Sergei"); // from User.Builderpatronymic("Valeryevich"); // From RussianUser.BuilderlastName("Egorov"); // from User.Builder}
}.build();

继承不再是一个问题,但它仍然有点冗长。

这里是Java的另一个“特性”派上用场: Double brace initialization/双大括号初始化。

这里我们使用初始化块来设置字段。Swing / Vaadin人可能认识到这种模式;)

有些人不喜欢它(随意评论为什么,顺便说一句)。我不会在应用程序的性能关键部分使用它,但如果是,比方说,测试,那么这种方法似乎标记了所有检查:

  1. 可以与从Mammoths Age开始的任何Java版本一起使用。
  2. 对其他JVM语言友好。
  3. 简洁。
  4. 语言的本机特性,而不是黑客。

结论

我们已经看到,虽然Java不提供自键型语法,但我们可以通过使用Java的另一个功能来解决问题,而不会破坏替代JVM语言的体验。

虽然一些开发人员似乎认为双大括号初始化是一种反模式,但它实际上似乎对某些用例有其价值。毕竟,这只是匿名类中构造函数定义的糖。

Java 常用设计模式 -- Builder模式相关推荐

  1. Java常用设计模式————原型模式(一)

    介绍 原型模式(Prototype Pattern):用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象. 原型模式用于创建重复的对象,同时又能保证性能.当直接创建对象的代价比较大时,则采用 ...

  2. java的设计模式 - Builder模式

    Builder 模式的目的? 构造对象的方式过于复杂,不如将之抽离出来.比如,构造器参数过多 这样说也有点抽象,举个例子吧. 举个例子 比如 非常热门的消息队列RabbitMQ 的 AMQP.Basi ...

  3. Java常用设计模式————工厂模式

    简介: 工厂模式(Factory Pattern)是Java中最常用的设计模式之一,又称多态工厂模式.虚拟构造器模式.属于创建型模式. 在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通 ...

  4. Java常用设计模式————组合模式

    引言 组合模式,是一种类似递归算法的结构性设计模式,通过以简单的 List ,组合本类对象,实现树状对象结构的"部分.整体"的层次. 它可以让调用程序不需要关心复杂对象与简单对象的 ...

  5. Java常用设计模式————建造者模式

    引言 建造者模式(Builder Pattern)使用多个简单对象一步一步构建成一个复杂的对象.这种类型的设计模式属于建造型模式,它提供了一种创建对象的最佳方式. 一个Builder会一步步构建最终的 ...

  6. Java常用设计模式-策略模式

    策略模式是一个非常实用的设计模式,指定义了一类算法并将其封装起来,并使得它们之间可以灵活地切换,并且不影响客户端. 1,从一个例子开始 我们常常会在网上买东西,很多购物平台都会有着各种各样的优惠策略供 ...

  7. Java常用设计模式————桥接模式

    引言 在实际的业务中,经常会遇到多维度的概念组合,公园的门票,颐和园有年票.月票.日票,故宫也有年票.月票.日票.那么不同的公园和票种类型就可以视为两种不同的纬度,它们之间会形成相互组合的关系. 在类 ...

  8. Java常用设计模式————外观模式

    引言 外观模式(Facade Pattern),又叫"过程模式".外观模式为子系统中的一组接口提供一个一致的入口,此模式定义了一个高层接口,这个接口使得这一组子系统更加易用. 一. ...

  9. Java常用设计模式————原型模式(二)之深拷贝与浅拷贝

    引言 clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象.那么在java语言 ...

最新文章

  1. PHP之SQL防注入代码(360提供)
  2. mysql单点故障_如何解决云服务商单点故障频发的问题?
  3. 更新和插入的并发问题_mysql经典面试题:如何读写分离?主从原理是啥?同步的延时问题...
  4. 初识shardingsphere
  5. webpack在内存生成html,Vue学习之Webpack基本使用小结(十三)
  6. Android热修复技术——QQ空间补丁方案解析(1)
  7. R语言学习| 马氏距离mahanobis函数
  8. 全国计算机互联网城市排名,中国互联网+城市排名:杭州第一 北京仅排第八
  9. Hazelcast IMDG参考中文版手册-第四章-配置
  10. pydub 音频停顿 断句 切分
  11. 为大家推荐几个不错的公众号!
  12. uni-app基础入门
  13. 异常之IllegalAccessException
  14. android4.4 电池管理
  15. Could not contact [localhost:8005] (base port [8005] and offset [0]). Tomcat may not be running.
  16. HDTV入门扫盲篇HDTV入门
  17. 深度学习cptn+crnn的OCR原理
  18. visifire  柱状图控件
  19. 关于FT和TTa引脚作为数据IO时配置问题
  20. freeswitch常用命令

热门文章

  1. 华西生物医学大数据中心俞鹏课题组博士后招聘启事
  2. DNA甲基化与表观遗传学数据挖掘与分析学习会 (10月26-27 上海)
  3. 等我搞研究发财了,我就......
  4. ggbiplot-最好看的PCA作图:样品PCA散点+分组椭圆+变量贡献与相关
  5. R语言使用ggplot2包使用geom_boxplot函数绘制基础分组箱图(配置数据点抖动显示jitter)实战
  6. R语言使用table1包绘制(生成)三线表、使用单变量分列构建三线表、编写自定义三线表结构(将因子变量细粒度化重新构建三线图)、为不同的变量显示不同的统计信息
  7. pandas使用read_csv读取数据使用skiprows参数跳过指定的数据行但保留表头、pandas使用to_csv函数将dataframe保存为gzip压缩文件
  8. R语言使用str_locate函数和str_locate_all函数来定位特定字符串或者字符串模式在字符串中的位置:str_locate函数第一个位置、str_locate_all函数定位所有位置
  9. R语言dataframe获取每个字段(特征)的数据类型实战:使用sapply函数获取每个字段(特征)的数据类型、通过柱状图可视化特征的种类以及个数
  10. R卡方检验(CHI-SQUARE TEST)