【设计模式】使用建造者模式替换多构造器          返回主页

我们在开发中往往需要对一个类定义多个构造方法来应对不同数量参数的对象实例化的需求。当然我们也可以通过使用setter方法逐个添加想要实现的对象参数,但是这会带来一些问题,诸如:

因为构造过程被分到了几个调用中,在构造过程中,JavaBean可能处于不一致的状态。
类无法仅仅通过检验构造器参数的有效性来保证一致性。试图使用处于不一致的对象,
将会导致失败,这种失败与包含错误的代码大相径庭,因此它调试起来十分困难,
与此相关的另一点不足在于,JavaBeans模式阻止了把类当做不可变的可能。
                                                --《Effective Java中文第二版》

在之前的开发中,为了能够方便的组装不同数量参数的对象,避免大量的setter造成的代码冗余,我选择了使用多构造器的方式进行对象的组装,这使得我能够通过一行代码就实现对象的封装。

随着项目的演进,某一个类不是一成不变的,参数会增加,因此构造器的数量也随之增加,造成了单个JavaBean实体的代码行数急剧增加,使得维护变得不便。因此本文介绍一种新的(其实也不新了)的方式,能够优雅而巧妙的规避上述的缺点使得对象的构建变得极为轻量级。它也是设计模式的一种--建造者模式。

首先我们假设一个场景,我们想要通过制定的一辆车的“品牌”、“价格”、“型号”及“版本”从而唯一的构造一辆车的实体。代码如下

public class Car {

    private String type;
    private String size;
    private int    price;
    private String brand;   //品牌
    ...

这里我们定义了Car的属性,我们没有像之前习惯性的定义getter、setter方法,往下看你就明白了。

    public static class Builder {
     private final String brand;
     private final int price;
     private String type = "";
     private String size = "";

     public Builder(String brand, int price) {
         this.brand = brand;
         this.price = price;
     }

     public Builder type(String val) {
         type = val;
         return this;
     }

     public Builder size(String val) {
         size = val;
         return this;
     }

     //建造核心方法
     public Car build() {
         return new Car(this);
     }

    }

这段代码是一个静态内部类,它定义了和car一样的属性,之所以定义final变量是因为有些属性是必须的。然后,我们分别通过参数注入的形式逐个的构造出Builder对象,它们之间是独立的,只与传入的参数有关。最后,我们通过build()方法将在Builder中实例化的各个参数送入Car对象中。

    private Car(Builder builder) {
        type = builder.type;
        size = builder.size;
        price = builder.price;
        brand = builder.brand;
    }

接着,我们在Car类内声明一个private的构造器,并将内部类获取到参数传到其中,声明私有是为了防止通过new的方式显式组装对象,这就违背了我们的初衷。

    ....省略getter方法,getter方法时为了便于测试添加,正式开发可以不写...
        @Override
        public String toString() {
            return getBrand() + " " + getPrice() + " " + getSize() + getType();
        }
        public static void main(String[] args) {
            Car car = new Builder("玛莎拉蒂", 1000000).type("超跑").size("豪华版").build();
            System.out.println(car.toString());
        }
    }

我们重写了类Car的toString方法,并在客户端代码汇总通过声明一个Builder构造器的方式,通过该构造器中的公共方法逐个动态添加参数,灵活的制定参数个数,并通过build方法将实例化的Car对象返回。这就达到了我们要的动态参数添加,避免大量构造器的声明建立对象的要求。

小结

  1. Builder模式模拟了具名的可选参数,相比提前声明构造器更为灵活,且易于扩展;
  2. 如果随着项目的发展,类的参数发生变更,只需要同步建造者和类的参数数目,而不需要增删构造器;
  3. 与构造器相比,builder的略微优势在于其有多个可变参数,构造器就像方法一样,只能有一个可变参数;
  4. 当然,builder模式也有它自身的不足,为了创建对象,必须先创建它的构造器。虽然创建构造器的开销在实践中可能不那么明显,但是在某些十分注重性能的情况下,可能就成问题了。Builder模式还比重叠构造器模式更加长,(一定程度上)因此它只有在类本身有很多参数的情况先才使用,比如4个或更多的参数。但是记住,将来可能要添加更多的参数,如果一开始就使用构造器或者静态工厂,等到类需要多个参数时才添加建造者,就可能会无法控制,那些过时的构造器或者静态工厂显得十分不协调,因此最好一开始就使用建造者。