天天看点

设计模式之Builder建造者模式

在java开发中我们经常需要创建对象 ,我们常用的创建对象的方法有两种 

1  使用构造器来创建对象 如果有多个可变参数 ,就需要写多个构造方法,这种方法在遇到多个参数时不好控制

2. javabean的写法,私有化成员变量, 私有构造方法 ,通过setter和getter来设置和获取值 ,这种构造的缺点是传入的参数不好检测,例如有些非空的数据等

3.静态工厂

现在我们介绍的builder模式创建的对象 适用于有多个可变参数和一定数量的限制参数的时候

建造者模式(Builder):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

使用场景:

1、当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。

2、当构造过程必须允许被构造的对象有不同的表示时。

使用范例:

//抽象产品类,使用了模板方法模式,不同产品有不同的“组成部分part” 
abstract class AbstractProduct{ 
  protected abstract void part01(); 
  protected abstract void part02(); 
  protected abstract void part03(); 
    
  //模板方法给出了默认的组装方式,生成默认的产品 
  public final AbstractProduct defaultProduct() { 
    part01(); 
    part02(); 
    part03(); 
    return this;//返回当前对象,即默认组装方式的产品 
  } 
} 

//具体的产品A、B,不同产品实现了不同的“组成部分part” 
class ConcreteProductA extends AbstractProduct{ 
  protected void part01() { 
    System.out.println("产品A :part01() ..."); 
  } 
  protected void part02() { 
    System.out.println("产品A :part02() ..."); 
  } 
  protected void part03() { 
    System.out.println("产品A :part03() ..."); 
  } 
} 

class ConcreteProductB extends AbstractProduct{ 
  protected void part01() { 
    System.out.println("产品B :part01() ..."); 
  } 
  protected void part02() { 
    System.out.println("产品B :part02() ..."); 
  } 
  protected void part03() { 
    System.out.println("产品B :part03() ..."); 
  } 
} 

//抽象建造者,制定每一种产品应该实现的组合方式buildPart()和生产buildProduct()的标准 
abstract class AbstractBuilder{ 
  public abstract void buildPart(); 
  public abstract AbstractProduct buildProduct(); 
} 


/* 
* 具体建造者,如果对于默认产品(即当调用抽象产品中的defaultProduct()方法)不满意时, 
* 可以不调用它来获得产品,而是使用具体的建造者来改变产品的生产组装方式,以得到不同的产品 
*/ 
class ConcreteBuilderA extends AbstractBuilder{ 
  private AbstractProduct productA = new ConcreteProductA(); 
    
  public void buildPart() { 
    this.productA.part03(); 
    this.productA.part02(); 
    this.productA.part01(); 
  } 
    
  public AbstractProduct buildProduct() { 
    return this.productA; 
  } 
} 

class ConcreteBuilderB extends AbstractBuilder{ 
  private AbstractProduct productB = new ConcreteProductB(); 
    
  public void buildPart() { 
    this.productB.part02(); 
    this.productB.part01(); 
    //特地省略掉产品B中的一个组成部分,例如该部分的功能顾客不需要 
//    this.productB.part03(); 
  } 
    
  public AbstractProduct buildProduct() { 
    return this.productB; 
  } 
} 

//导演类,预先持有各个产品的建造者,为需要不同于默认产品的用户提供不同的组装方式 
class Director{ 
  private AbstractBuilder builderA = new ConcreteBuilderA(); 
  private AbstractBuilder builderB = new ConcreteBuilderB();    
    
  public AbstractProduct getProductA() { 
    this.builderA.buildPart(); 
    return this.builderA.buildProduct(); 
  } 
    
  public AbstractProduct getProductB() { 
    this.builderB.buildPart(); 
    return this.builderB.buildProduct(); 
  } 
} 

//测试类 
public class Client { 
  public static void main(String[] args) { 
    System.out.println("利用模板方法模式获得默认的产品A"); 
    AbstractProduct defualtProductA = new ConcreteProductA().defaultProduct();    
     
    System.out.println("\n利用Director类获得不同组装方式的产品A"); 
    Director director = new Director(); 
    director.getProductA(); 
     
    System.out.println("\n利用Director类获得不同组装方式的产品B"); 
    director.getProductB(); 
  } 
}      

测试结果:

利用模板方法模式获得默认的产品A
产品A :part01() ...
产品A :part02() ...
产品A :part03() ...
利用Director类获得不同组装方式的产品A
产品A :part03() ...
产品A :part02() ...
产品A :part01() ...
利用Director类获得不同组装方式的产品B
产品B :part02() ...
产品B :part01() ...      

  与抽象工厂的区别:在建造者模式里,有个指导者,由指导者来管理建造者,用户是与指导者联系的,指导者联系建造者最后得到产品。即建造模式可以强制实行一种分步骤进行的建造过程。

  建造模式是将复杂的内部创建封装在内部,对于外部调用的人来说,只需要传入建造者和建造工具,对于内部是如何建造成成品的,调用者无需关心。

  举个简单的例子,如汽车,有很多部件,车轮,方向盘,发动机还有各种小零件等等,部件很多,但远不止这些,如何将这些部件装配成一部汽车,这个装配过程也很复杂(需要很好的组装技术), builder模式就是为了将部件和组装分开。

Builder模式好处和优点

使用Builder模式必然会导致写两遍相关属性的代码和SETTER方法,看起来有点吃力不讨好。然而需要看到的是,客户端代码的可用性和可读性得到了大大提高。与此同时,构造函数的参数数量明显减少调用起来非常直观。

Builder方法另外一个优势在于,单个builder构建多个对象时Builder参数可在创建期间进行调整,还可以根据对象不同而进行改变。这就像我越来越推崇的以“不变”应“万变”。Builder模式特别适合那些属性个数很多的类,我认为没有必要给那些本不需要设置值传递参数(设置null)。

Builder模式在提高代码可读性的同时,使用IDE提供的代码补全功能也更加容易。Builder模式在与构造函数一起使用带来的更大优势在Josh Bloch的Effective Java第二版第2条中有详细阐述。

Builder模式的代价和缺点

使用Builder模式是肯定会增加代码量的。此外,尽管客户端的代码可读性明显改善,但随之而来的客户端代码变得更加冗长。我还是坚持这一观点:相比较参数数量的增加,相同类型的参数混在一起,可选参数的增加而言,改善代码可读性更有价值。

Builder会增加个类代码,这也意味着开发者在给类增加属性时有时会忘记给该属性添加支持的builder。为了克服这个问题,通常我会将builder嵌套到类中,这样可以很容易地发现哪个相关的builder需要更新。尽管忘记的风险依旧存在,但是这风险就像忘记给类的新属性增加toString()、 equals(Object)、 hashCode()或其他类基于是所有属性的方法一样。

在我的Builder实现中,我会用Builder的构造函数而不是set方法传递客户需要的属性。这样做的好处在于,对象总是能被一次完整的实例化,而不是靠开发人员调用时用set方法补充额外的属性完成实例化。这也体现了不可变性带来的好处。然而,相应地也会造成自己设定的属性方法可读性降低。

正如它名字表示的那样,Builder只是一个替代构造器的选择不能直接用于降低非构造函数方法的参数数量,但是结合参数对象的使用就能达到这一点。有关更多反对用Builder来进行对象构建的讨论可以参见 A dive into the Builder pattern上的相关评论。

继续阅读