欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

java的设计模式 - Builder模式

程序员文章站 2022-05-04 13:31:31
builder模式,目的在于:抽离复杂对象的构造函数,让我们可以通过多种方法的排列组合构建复杂的对象。如果构造器参数过多,可以考虑 builder 模式 ......

builder 模式的目的?

抽离复杂对象的构造函数,让我们可以通过多种方法的排列组合构建复杂的对象。如果构造器参数过多,可以考虑 builder 模式
这样说也有点抽象,举个例子吧。

举个例子

比如 非常热门的消息队列rabbitmqamqp.basicproperties

因为它的属性比较多,所以构造函数也是挺吓人的。

java的设计模式 - Builder模式

我看到也不太想调用。
如果现在要构造一条消息

  • 投递模式(delivery mode)为 2
  • 优先级(priority)是 2
  • content-type 为 text/plain

在没有 builder 模式之前,是这样构造的

new amqp.basicproperties("text/plain",null,null,2,1,null,null,null,null,null,null,null,null,null);

痛苦啊!!!不信,你自己也可以尝试构造一下。

  • 构造函数有很多你不想设置的参数
  • 你要看准,哪个参数要赋值,哪个参数不赋值,一不小心就可能出错了。而这里有 14 个参数。。。
  • 维护性差,写完代码再看一下,也看不出这个参数究竟是什么意思。还要点进去,一个一个参数地看才知道是什么意思

而用了 builder 模式后。

new amqp.basicproperties.builder()
    .contenttype("text/plain")
    .deliverymode(2)
    .priority(1)
    .build();

舒畅!!!

builder 是如何实现?

很简单。

  • basicproperties中添加一个叫builder的内部类
  • builder 中所有字段和basicproperties类是完全一致的
  • builder实例在调用build函数的时候,再调用basicproperties的构造函数构造对象。

代码如下

public static class basicproperties{
    private string contenttype;
    private string contentencoding;
    private map<string,object> headers;
    private integer deliverymode;
    private integer priority;
    //... 还有很多属性

    public basicproperties(
        string contenttype,
        string contentencoding,
        map<string,object> headers,
        integer deliverymode,
        //...
        string clusterid){
            this.contenttype = contenttypel;
            this.contentencoding = contentencoding;
            //...
    }

    public static final class builder {
        private string contentencoding;
        private map<string,object> headers;
        private integer deliverymode;
        private integer priority;
        //.. 和basicproperties的字段一致的。
        
        public builder contenttype(string contenttype){
            this.contenttype = contenttype; 
            return this; 
        }
    
        public builder contentencoding(string contentencoding){
            this.contentencoding = contentencoding; 
            return this; 
        }
        
        public basicproperties build() {
            return new basicproperties
                ( contenttype
                  contentencoding,
                  //还有很多属性
                );
        }
    }
}

分析

builder 模式的好处

  • 不用花太多心思去记构造器的顺序,在 ide 中输入一个点就有自动提示了
  • 好维护,很容易看到看明白这是什么属性

坏处

  • 构造对象就要先调用 buidler 构造器,多了构造器的开销
  • 类的关系变得复杂了

其他的做法

如果不用 builder 模式,有其他的做法吗?

重叠构造器?

比如,上面的例子,我构造的消息只需 投递模式(delivery mode)、优先级(priority)、 content-type ,专门为这几个参数弄个专门的构造函数,可以吗?
调用就变成这样了。

new amqp.basicproperties("text/plain",2,1)

可以,

  • 但依然不太好看。
  • 如果有不同的需求,各种属性都排列组合一下也麻烦。
  • 不实际,因为类字段的类型可能会是一样的,有些组合注定不行

javabean 模式呢?

basicproperties  p = new amqp.basicproperties();
p.setcontenttype("text/plain");
p.setdeliverymode(2);
p.setpriority(1);

在《effective java》中就探讨过这个可能,书中是这样说的

因为构造过程被分到几个调用中,在构造过程中 javabean 可能处于不一致的状态。类无法仅仅通过检验构造器参数的有效性来保证一致性。试图使用处于不一致状态的对象,将导致失败,这种失败与包含错误的代码大相径庭,因此它调试起来十分困难。与此相关的另一点不足在于,javabeans 模式阻止了把类做成不可变的可能,这需要程序员付出格外的努力来确保它的线程安全。

这话就有点摸不着头脑,什么意思。读了几次也有点懵。

其实意思是大概的,

  1. javabean 是构造与字段赋值分离的,有可能 线程 1 在给对象 obj 赋值,还没有赋完成的时候,线程 2 就拿了 obj 的值了,就不一致了
  2. 如果 obj 的字段全都是 final 的,不会出现上面那种情况,但字段只能会通过构造函数赋值(builder 模式也行),不能使用 javabeans 的 setxxx 函数赋值了。
  3. 所以有多线程要求的,比如是传给消息队列的对象,程序员要保证下线程安全。
  4. 这是一个开放开闭的问题,javabean 这样的写法确实和完全开放没啥区别,如果字段确定下来不用改了就最好设为 final 。

以上