博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java设计模式系列 — 构造器模式
阅读量:5021 次
发布时间:2019-06-12

本文共 6312 字,大约阅读时间需要 21 分钟。

想象下你有一个类,像下图所示有许多属性。假设你想让你的类不可变(顺便说一下,除非有一个好的理由不这样做,否则你应该坚持。但是我们会以另一种方式来达到要求。)

public class User {  private final String firstName;    //required    private final String lastName;    //required    private final int age;    //optional    private final String phone;    //optional    private final String address;    //optional    ...}

 

现在,想象下你的类中有些属性是必须的,有些则是可选的。你将要如何创建你的对象?所有的属性都声明为final,所以你必须在构造器中给它们全部赋值,但是你也想给这个类的客户端忽略可选属性的机会。

第一个可行的选择是拥有一个只接受必要属性作为参数的构造器,还要一个构造器接受所有的必要属性以及第一个可选属性,再有一个构造器接受两个可选属性等等。它是什么样子呢?像下面这个样子:

public User(String firstName, String lastName) {  this(firstName, lastName, 0);} public User(String firstName, String lastName, int age) {  this(firstName, lastName, age, "");} public User(String firstName, String lastName, int age, String phone) {  this(firstName, lastName, age, phone, "");} public User(String firstName, String lastName, int age, String phone, String address) {  this.firstName = firstName;  this.lastName = lastName;  this.age = age;  this.phone = phone;  this.address = address;}

 

这种方式来构建类的实例的好处是它能很好的工作。然而,这种方式的问题也很明显。当你只有几个属性时还好,但是当这个数字扩大时,代码就变的难以理解和维护了。

更重要的是,代码对客户端来说变的很难。客户端应该调用哪个构造器?有两个参数的?有三个参数的?那些不用传确切值的参数的默认值是多少?如果我想给地址赋值,但是不给age和phone赋值要怎么办?那种情况下,我就不得不调用接受所有参数的构造器,并且给那些不需要的传入不在乎的默认值。此外,几个类型相同的参数是很令人费解的。第一个String是电话还是地址? 那么在这些情况下,我们还有其他选择吗?我们可以依照JavaBeans的约定,一个无参构造并且每个参数提供一个get和set。类似下面这个:

public class User {  private String firstName; // required  private String lastName; // required  private int age; // optional  private String phone; // optional  private String address;  //optional   public String getFirstName() {    return firstName;  }  public void setFirstName(String firstName) {    this.firstName = firstName;  }  public String getLastName() {    return lastName;  }  public void setLastName(String lastName) {    this.lastName = lastName;  }  public int getAge() {    return age;  }  public void setAge(int age) {    this.age = age;  }  public String getPhone() {    return phone;  }  public void setPhone(String phone) {    this.phone = phone;  }  public String getAddress() {    return address;  }  public void setAddress(String address) {    this.address = address;  }}

 

这 种方式看起来容易理解和维护。作为客户端,我只需要创建一个空对象并且set我感兴趣的属性即可。那么这种方式有什么弊端呢?有两个主要弊端。第一个是类 实例的不一致状态。如果你要用User的五个属性来创建一个User对象,那么在所有的setX方法调用前,对象处于不完全状态。这就意味着客户端的其他 部分可能看到对象,并且假设它已经完成构造了,实际它并没有。方法的第二个缺点是对象可变。你丧失了不可变对象的所有好处。

幸运的是还有第三个选择,建造者模式,方案看起来是下面这样的。

public class User {  private final String firstName; // required  private final String lastName; // required  private final int age; // optional  private final String phone; // optional  private final String address; // optional   private User(UserBuilder builder) {    this.firstName = builder.firstName;    this.lastName = builder.lastName;    this.age = builder.age;    this.phone = builder.phone;    this.address = builder.address;  }   public String getFirstName() {    return firstName;  }   public String getLastName() {    return lastName;  }   public int getAge() {    return age;  }   public String getPhone() {    return phone;  }   public String getAddress() {    return address;  }   public static class UserBuilder {    private final String firstName;    private final String lastName;    private int age;    private String phone;    private String address;     public UserBuilder(String firstName, String lastName) {      this.firstName = firstName;      this.lastName = lastName;    }     public UserBuilder age(int age) {      this.age = age;      return this;    }     public UserBuilder phone(String phone) {      this.phone = phone;      return this;    }     public UserBuilder address(String address) {      this.address = address;      return this;    }     public User build() {      return new User(this);    }   }}

 

有几个重点需要注意一下:

  • User的构造器是私有的,这就意味着客户端不能直接创建实例。
  • 这个类是不可变的。所有属性都是final类型并且他们由构造器设置值。此外,我们只提供getter操作。
  • 建造者使用流式接口习语来让客户端代码更易读(下面会有示例)。
  • 建造者的构造器只接受两个必须的参数,并且这两个属性是仅有的被设置为final类型的,这样就能保证这些属性在构造器中是被赋值的。

建造者模式的使用拥有开始所提两种方案的所有优点,并且没有它们的缺点。客户代码更容易写,最重要的是更易读。关于这个模式,我听到的唯一缺点是必须要复制类的属性到建造者中。既然建造者类通常是它所建造类的一个静态成员类,它们能相当容易的一起演进。

那么,客户代码尝试创建一个新的User对象会是什么样的?让我们来看看:

public User getUser() {  return new    User.UserBuilder("Jhon", "Doe")    .age(30)    .phone("1234567")    .address("Fake address 1234")    .build();}

 

很工整,不是吗?你能在一行内创建一个User对象,最重要的是它很容易理解。而且,你能确保,无论什么时候你拿到这个类的一个对象,它的状态都是完整的。

这个模式非常灵活。一个建造者可以通过在多次调用“build”之间改变属性用来创建多个对象。构造者甚至可以在两次调用之间自动补全一些生成的字段。例如id或其他序列号。

重点是,类似于构造器,建造者可以强制其参数的不变性。建造方法可以检查这些不变性, 如果它们无效就抛出IllegalStateException异常。关键是可以在从建造者中拷贝参数到对象时检查,并且是在对象字段上检查而不是在构造 器字段。这样做的理由是,既然建造者不是线程安全的,如果我们在实际创建对象前检查参数,参数值可能会在检查和拷贝之间被另一个线程改变。这个阶段的时间 被认为是“易损窗口”。在我们的例子中看起来是如下这样的:

public User build() {    User user = new user(this);    if (user.getAge() > 120) {        throw new IllegalStateException(“Age out of range”); // thread-safe    }    return user;}

 

之前的版本是线程安全的,因为我们先创建user然后检查不可变对象的不变性。下面的代码看起来功能一样,但是它不是线程安全的,你应该避免这样使用:

public User build() {    if (age > 120) {        throw new IllegalStateException(“Age out of range”); // bad, not thread-safe    }    // This is the window of opportunity for a second thread to modify the value of age    return new User(this);}

最后一个优点是建造者可以被传入到一个方法中,来让这个方法为客户创建一个或多个对象,而不用知道任何对象创建的细节。你通常需要一个简单的接口来完成此功能:

public interface Builder {    T build();}

 

在上面的例子中,UserBuilder类可以实现Builder接口。我们就可以使用下面这种方式:
UserCollection buildUserCollection(Builder
userBuilder){...}

 

这真是个很长的首发文章。总结一下,建造者模式是处理超过一个参数的类的绝佳选择(这不是严格意义上的说法,但是我通常将接受四个属性的类当成使用这种模式的暗示),特别是如果大部分的参数是可选的。你的客户端代码会更易读,易写,易维护。此外,你的类可以保持不变,这点可以让你的代码更安全。

-----------------------割-----------------------饭小胖 原创

上文算是我要介绍的原理,下面是自己的干货,目前所在公司比较奇葩,用rest风格,但是也是瞎鸡巴用,那耦合性和单体没多大区别,所以我所做的报表系统需要调用一个叫告警模块的接口,该接口用的是高级查询,其实就是用json代表sql去查询数据,比喻select * from alarm where id in (1,2,3) ;那么在json中就是

{    'where':[        {
'terms':{ 'field':'id', 'operator':'in', 'value':[1,2,3] }} ] }

当然还可能有多个terms对象,然后是对应不同的查询条件。为了在编程更好的获取rest数据,我把这个接口封装了,放在一个叫restdata的包里,作为视图数据库的repository,然后这个封装的对象类叫alarmPipeline.java,也就是说,构造一个这样的管道,然后调用其get方法返回http返回的规范化数据,屏蔽所有细节,对外提供一致的接口。但是如何构造拥有所有查询条件的对象呢?我灵光一闪,终于应用到这个构造器模式,利用一个builder,内建所有的查询方法,这样一来,就能轻而易举的把查询逻辑封装起来,而且面对用户则是友好的方法调用,是在妙哉。

 

 

更新:如果你使用Eclipse作为你的IDE,有一些插件可以让你避免建造者中大部分的官样文章代码。下面这三个是比较推荐的:

我个人还没使用过其中任何一种插件,所以对于哪个更好,我没办法提供一个指导性的意见。我估计其他IDE应该也有类似的插件。

原文链接:  翻译: - 

译文链接: 
转载请保留原文出处、译者和译文链接。]

转载于:https://www.cnblogs.com/iCanhua/p/8636085.html

你可能感兴趣的文章
Notes: CRM Analytics–BI from a CRM perspective (2)
查看>>
graphite custom functions
查看>>
列出所有的属性键
查看>>
js获取请求地址后面带的参数
查看>>
[原创]使用java批量修改文件编码(ANSI-->UTF-8)
查看>>
设计模式のCompositePattern(组合模式)----结构模式
查看>>
二进制集合枚举子集
查看>>
磁盘管理
查看>>
SAS学习经验总结分享:篇二—input语句
查看>>
UIImage与UIColor互转
查看>>
RotateAnimation详解
查看>>
系统管理玩玩Windows Azure
查看>>
c#匿名方法
查看>>
如何判断链表是否有环
查看>>
【小程序】缓存
查看>>
ssh无密码登陆屌丝指南
查看>>
MySQL锁之三:MySQL的共享锁与排它锁编码演示
查看>>
docker常用命令详解
查看>>
jQuery技巧大放送
查看>>
字符串转换成JSON的三种方式
查看>>