原文: https://howtodoinjava.com/design-patterns/creational/builder-pattern-in-java/

顾名思义, 构建器模式是构建复杂对象的替代方法。 仅当您要使用相同的对象构建过程构建不同的不可变对象时,才应使用此方法。

在开始讨论之前,我想弄清楚我将在本文中讨论的构建器模式与 GOF“设计模式”书中提到的稍有不同。 这本书说:

构建器模式是一种设计模式,它允许使用正确的操作顺序逐步创建复杂的对象。 构造由导向对象控制,该导向对象仅需要知道要创建的对象的类型。

本书给出如下示例:

Java 中的构建器设计模式 - 图1

我真的很难在现实生活中的编程和应用中利用以上示例。 上面的过程与抽象工厂模式非常相似(并非完全相似),在该模式中,您找到了特定类型对象的工厂(或构建器),然后工厂为您提供了具体的 该对象的实例。 这个构建器模式与抽象工厂模式之间唯一的不同之处在于,构建器为您提供了对对象创建过程的更多控制,仅此而已。 除此之外,没有重大差异。

一句话,抽象工厂模式是对“WHAT”的回答,而构建器模式是对“HOW”的回答。

对我来说,构建器模式更像流利的接口 。 通常使用方法级联(或方法链接)来实现流利的接口。

现在,从这里开始,我将以我认为在实际情况下特别有用的方式来讨论构建器模式。 我也希望说服你们。

  1. Sections in this post:
  2. Definition of Builder Pattern
  3. Where we need Builder Pattern
  4. A sample implementation using Builder Pattern
  5. Existing implementations in JDK
  6. Benefits and Advantages
  7. Costs and Disadvantages
  8. Conclusion

构建器模式的定义

首先,给构建器模式一个定义:

构建器模式的目标是“将复杂对象的构造与其表示分开,以便同一构造过程可以创建不同的表示。”

我们需要构建器模式的地方

我们已经知道不可变和不可变实例在应用中的好处。 如果对此有任何疑问,请让我想起 Java 中的String类。 正如我已经说过的那样,构建器模式可以帮助我们创建具有大量状态属性的不可变类。

让我们讨论一下我们应用中的一个常见问题。 假设在任何用户管理模块中,主要实体都是User。 理想情况下,实际上,一旦完全创建了用户对象,您就不想更改其状态。 根本没有道理,对吧? 现在,假设我们的User对象具有以下 5 个属性,即firstNamelastNameagephoneaddress

在通常的实践中,如果要创建不可变的User类,则必须将所有五个信息作为参数传递给构造器。 它看起来像这样:

  1. public User (String firstName, String lastName, int age, String phone, String address){
  2. this.firstName = firstName;
  3. this.lastName = lastName;
  4. this.age = age;
  5. this.phone = phone;
  6. this.address = address;
  7. }

很好。 现在,如果只有firstNamelastName是必不可少的,剩下的 3 个字段是可选的,该怎么办。 问题! 我们需要更多的构造器。

  1. public User (String firstName, String lastName, int age, String phone){ ... }
  2. public User (String firstName, String lastName, String phone, String address){ ... }
  3. public User (String firstName, String lastName, int age){ ... }
  4. public User (String firstName, String lastName){ ... }

我们将需要更多类似上面的内容。 还能管理吗? 现在,让我们介绍我们的第六个属性,即薪水。 现在是问题。

一种创建更多构造器的方法,另一种方法是放松不变性并引入设置器方法。 您选择这两个选项中的任何一个,就松了点,对吧?

在这里,构建器模式将帮助您使用其他属性,同时保留 Use 类的不变性。

使用构建器模式的示例实现

下面是我们上面讨论的问题的编码解决方案。 这使用附加的类UserBuilder,该类可帮助我们构建具有所有必需属性和可选属性组合的所需User对象,而不会失去不变性。

  1. public class User
  2. {
  3. //All final attributes
  4. private final String firstName; // required
  5. private final String lastName; // required
  6. private final int age; // optional
  7. private final String phone; // optional
  8. private final String address; // optional
  9. private User(UserBuilder builder) {
  10. this.firstName = builder.firstName;
  11. this.lastName = builder.lastName;
  12. this.age = builder.age;
  13. this.phone = builder.phone;
  14. this.address = builder.address;
  15. }
  16. //All getter, and NO setter to provde immutability
  17. public String getFirstName() {
  18. return firstName;
  19. }
  20. public String getLastName() {
  21. return lastName;
  22. }
  23. public int getAge() {
  24. return age;
  25. }
  26. public String getPhone() {
  27. return phone;
  28. }
  29. public String getAddress() {
  30. return address;
  31. }
  32. @Override
  33. public String toString() {
  34. return "User: "+this.firstName+", "+this.lastName+", "+this.age+", "+this.phone+", "+this.address;
  35. }
  36. public static class UserBuilder
  37. {
  38. private final String firstName;
  39. private final String lastName;
  40. private int age;
  41. private String phone;
  42. private String address;
  43. public UserBuilder(String firstName, String lastName) {
  44. this.firstName = firstName;
  45. this.lastName = lastName;
  46. }
  47. public UserBuilder age(int age) {
  48. this.age = age;
  49. return this;
  50. }
  51. public UserBuilder phone(String phone) {
  52. this.phone = phone;
  53. return this;
  54. }
  55. public UserBuilder address(String address) {
  56. this.address = address;
  57. return this;
  58. }
  59. //Return the finally consrcuted User object
  60. public User build() {
  61. User user = new User(this);
  62. validateUserObject(user);
  63. return user;
  64. }
  65. private void validateUserObject(User user) {
  66. //Do some basic validations to check
  67. //if user object does not break any assumption of system
  68. }
  69. }
  70. }

下面是方法,我们将在代码中使用UserBuilder

  1. public static void main(String[] args) {
  2. User user1 = new User.UserBuilder("Lokesh", "Gupta")
  3. .age(30)
  4. .phone("1234567")
  5. .address("Fake address 1234")
  6. .build();
  7. System.out.println(user1);
  8. User user2 = new User.UserBuilder("Jack", "Reacher")
  9. .age(40)
  10. .phone("5655")
  11. //no address
  12. .build();
  13. System.out.println(user2);
  14. User user3 = new User.UserBuilder("Super", "Man")
  15. //No age
  16. //No phone
  17. //no address
  18. .build();
  19. System.out.println(user3);
  20. }
  21. Output:
  22. User: Lokesh, Gupta, 30, 1234567, Fake address 1234
  23. User: Jack, Reacher, 40, 5655, null
  24. User: Super, Man, 0, null, null

请注意,上面创建的用户对象没有任何设置方法,因此一旦建立,便无法更改其状态。 这提供了所需的不变性。

有时,开发人员将属性添加到User类时,可能会忘记为构建器添加对新属性的支持。 为了最大程度地减少这种情况,我们应该将构建器封装在它们所构建的类中(如上例所示),以使开发人员更清楚地知道也需要更新一个相关的构建器。

有时,我认为应该有一个驱逐舰模式(与构建器相对),该模式应该以系统的方式从复杂对象中拆除某些属性。 你怎么看?

JDK 中的现有实现

java.lang.Appendable 的所有实现实际上都是在 Java 中使用 Builder 模式的良好示例。 例如:

java.lang.StringBuilder#append() [未同步的类]

java.lang.StringBuffer#append() [同步类]

java.nio.ByteBuffer#put()(也在 CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer 和 DoubleBuffer 上)

另一种用法可以在javax.swing.GroupLayout.Group#addComponent()中找到。

看看这些实现与我们上面讨论的相似。

  1. StringBuilder builder = new StringBuilder("Temp");
  2. String data = builder.append(1)
  3. .append(true)
  4. .append("friend")
  5. .toString();
  6. System.out.println(data);
  7. Output:
  8. Temp1truefriend

构建器模式的优点和好处

毫无疑问,在构建器模式中,代码行数增加了至少增加了一倍,但是在设计灵活性和更多可读代码方面,努力得到了回报 。 构造器的参数被简化,并在高可读性方法调用中提供。

构建器模式还有助于最大程度地减少构造器中的参数数量,因此不需要将可选参数null传递给构造器。 真的吸引了我

另一个好处是,对象总是以complete状态实例化,而不是处于不完整的状态,直到开发人员调用(如果曾经调用)适当的设置器方法来设置其他字段。

最后,我可以在对象构建过程中构建不可变对象,而无需太多复杂的逻辑。

构建器模式的成本与缺点

尽管构建器模式减少了一些代码行,从而消除了使用设置器方法的需要,但仍然通过引入构建器对象使总行数翻了一番。 此外,尽管客户代码更具可读性,但客户代码也更为冗长。 虽然对我而言,可读性比代码行更重要。

我能想到的唯一缺点。

仅此而已。 如果您也分享您的想法,我会很高兴。

祝您学习愉快!

参考文献

http://en.wikipedia.org/wiki/Builder_pattern

http://www.javaspecialists.eu/archive/Issue163.html

http://en.wikipedia.org/wiki/Fluent_interface

http://martinfowler.com/bliki/FluentInterface.html