设计模式(五)创建者模式(Builder)
最后更新于:2022-04-01 11:05:13
### 一、模式定义
造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。建造者模式属于对象创建型模式。根据中文翻译的不同,建造者模式又可以称为生成器模式。
### 二、模式动机
无论是在现实世界中还是在软件系统中,都存在一些复杂的对象,它们拥有多个组成部分,如汽车,它包括车轮、方向盘、发送机等各种部件。而对于大多数用户而言,无须知道这些部件的装配细节,也几乎不会使用单独某个部件,而是使用一辆完整的汽车,可以通过建造者模式对其进行设计与描述,建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。
在软件开发中,也存在大量类似汽车一样的复杂对象,它们拥有一系列成员属性,这些成员属性中有些是引用类型的成员对象。而且在这些复杂对象中,还可能存在一些限制条件,如某些属性没有赋值则复杂对象不能作为一个完整的产品使用;有些属性的赋值必须按照某个顺序,一个属性没有赋值之前,另一个属性可能无法赋值等。
复杂对象相当于一辆有待建造的汽车,而对象的属性相当于汽车的部件,建造产品的过程就相当于组合部件的过程。由于组合部件的过程很复杂,因此,这些部件的组合过程往往被“外部化”到一个称作建造者的对象里,建造者返还给客户端的是一个已经建造完毕的完整产品对象,而用户无须关心该对象所包含的属性以及它们的组装方式,这就是建造者模式的模式动机。
### 三、模式结构
比较常见的是下面这种只有一个产品的模式结构,大多数的书中也是这样讲的。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_57175852f3660.jpg)
从图中我们可以看出,创建者模式由四部分组成。
- 抽象创建者角色:给出一个抽象接口,以规范产品对象的各个组成成分的建造。一般而言,此接口独立于应用程序的商业逻辑。模式中直接创建产品对象的是具体创建者角色。具体创建者必须实现这个接口的两种方法:一是建造方法,比如图中的buildPart1和buildPart2方法;另一种是结果返回方法,即图中的getProduct方法。一般来说,产品所包含的零件数目与建造方法的数目相符。换言之,有多少零件,就有多少相应的建造方法。
- 具体创建者角色:他们在应用程序中负责创建产品的实例。这个角色要完成的任务包括:
1、实现抽象创建者所声明的抽象方法,给出一步一步的完成产品创建实例的操作。 2、在创建完成后,提供产品的实例。
- 导演者角色:这个类调用具体创建者角色以创建产品对象。但是导演者并没有产品类的具体知识,真正拥有产品类的具体知识的是具体创建者角色。
- 产品角色:产品便是建造中的复杂对象。一般说来,一个系统中会有多于一个的产品类,而且这些产品类并不一定有共同的接口,而完全可以使不相关联的。
创建者模式的流程是这样的,客户端需要创建什么对象实例就创建一个导演类和这个对象的创建者,将创建者传给导演类,导演类会使用创建者来创建具体的产品。但是导演类并不清楚产品究竟是怎么创建出来的,产品的实际创建过程是由具体工厂来负责的,具体工厂在创建时也是分为若干步骤,比如图中表示出了两个部分part1和part2,分别对应产品的两个零件,具体工厂就是通过一点一点的创建产品的每个部分最后组成产品。
在实际的应用中不可能只用创建者模式创建一种产品,下边就以两种产品为例讨论一下怎么样通过创建者模式创建多种产品。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_57175853174cb.jpg)
要想使用创建者模式创建多种产品的话,有一个隐含的前提,那就是这些产品有共同的特性,也就是说可以用共同的接口实现。比如我们的上图中Product1和Product2都继承自AbstractProduct,并且这两种产品都有part1和part2两部分,但是每种产品的part1和part2实现方式不同,这时就可以使用创建者模式。除了这一点外,另外需要主要的是在AbsractBuilder中返回的是产品的共同父类,这样才能满足多个产品的返回都正常。
可能有人会产生疑问,如果一个产品有3部分组成而另一个产品由2部分组成,是不是就不能使用创建者模式了呢。不是的,同样可以使用创建者模式,在产品类中定义三部分,在只有2不部分的产品中不属于自己的哪一部分设为空就好了。
### 四、实例分析
前边我们讲了土豪坐车的例子,但是还没讲土豪的汽车是怎么生产出来的,我们知道汽车的生产过程是很复杂的,同时汽车又是由很多组建组成的。但是土豪不需要知道汽车是怎样制造出来的,其中的零件怎么安装,按照什么顺序安装等等,这些土豪都不需要知道。所以我们应该将汽车生产这一部分移到一个特定的地方。
可能有人会想我们可以在Audi类的构造方法中进行零件的生产和拼装,但是这样做的话Benz车和Bmw车都需要在构造方法中加入制造零件和拼装组建的功能。一是显得的很麻烦,因为每个类都增加了一块很复杂的代码,同时我们应当认识到,虽然Audi和Bnez的制造细节不同但是大体的流程是差不多的,大家都由引擎,轮胎,方向盘等组成,这些相似的代码在不同的类中重复是很不优雅的。
像这种产品的组成部分相同但是具体生产细节不同的情况特别适合使用创建者模式。
~~~
package com.designpattern.builder;
/**
* 抽象建造类,提供创建产品的共同接口,不同的产品可以有自己的具体实现
* @author
*
*/
public abstract class AbstractBuilder {
/**
* 创建引擎
*/
public abstract void buildEngine();
/**
* 创建车玻璃
*/
public abstract void buildGlass();
/**
* 创建方向盘
*/
public abstract void buildSteeringWheel();
/**
* 返回创建好的产品,为了兼容所有的产品,返回的类型定为共同的父类
* @return
*/
public abstract Car getCar();
}
~~~
~~~
package com.designpattern.builder;
/**
* Audi的具体创建者
* @author xingjiarong
*
*/
public class AudiBuilder extends AbstractBuilder{
/**
* 创建一个各部分都为空的对象
*/
Audi audi = new Audi();
/**
* 创建各个部分
*/
public void buildEngine() {
audi.engine = "AudiEngine";
}
public void buildGlass() {
audi.glass = 3.5;
}
public void buildSteeringWheel() {
audi.steeringWheel = "AudiSteeringWheel";
}
/**
* 返回创建好的对象
*/
public Car getCar() {
return audi;
}
}
~~~
~~~
package com.designpattern.builder;
/**
* 导演类,根据按照产品的建造和组装顺序组装产品
* @author xingjiarong
*
*/
public class Director {
private AbstractBuilder build;
public Director(AbstractBuilder build){
this.build = build;
}
/***
* 组成产品的方法,组成的过程可能是有顺序的
* @return
*/
public Car construct(){
build.buildSteeringWheel();
build.buildGlass();
build.buildEngine();
return build.getCar();
}
}
~~~
~~~
package com.designpattern.builder;
public abstract class Car {
/**
* 汽车引擎,实际应用中应该是一个对象,这里用字符串来表示
*/
public String engine;
/**
* 汽车玻璃,不同的汽车大小不一样,需要根据汽车的型号计算
*/
public double glass;
/**
* 汽车方向盘
*/
public String steeringWheel;
public abstract void drive();
}
~~~
~~~
package com.designpattern.builder;
public class Main {
public static void main(String[] args) throws Exception {
/**
* 创建导演类和Audi的建造者
*/
AudiBuilder build = new AudiBuilder();
Director director = new Director(build);
/**
* 利用导演类获得汽车而不是自己获得汽车
*/
Car car = director.construct();
//开车
car.drive();
}
}
~~~
Audi没有变化,Benz车的代码和Audi车的代码是相似的,不再贴具体的代码,最后会附上源码。
### 五、模式优点
- 在建造者模式中, 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
- 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。
- 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
- 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”。
### 六、模式缺点
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
- 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。
### 七、适用场景
- 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性。
- 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。
- 对象的创建过程独立于创建该对象的类。在建造者模式中引入了指挥者类,将创建过程封装在指挥者类中,而不在建造者类中。
- 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。
### 八、与抽象工厂模式的区别
在抽象工厂模式中,每一次工厂对象被调用时都会返回一个完整的产品对象,而客户端有可能会决定把这些产品组装成一个更大更复杂的产品,也有可能不会。建造类则不同,它一点一点的建造出一个复杂的产品,而这个产品的组装过程就发生在创建者角色内部。建造者的客户端拿到的是一个完整的最后产品。
换言之,抽象工厂模式处在更加具体的尺度上,而建造者模式处在更加宏观的尺度上。一个系统可以由一个建造模式和一个抽象工厂模式组成,客户端通过调用这个创建角色,间接地调用另一个抽象工厂模式的工厂角色。工厂模式返回不同产品族的零件,而建造者模式则把他们组装起来。
源码下载:[http://download.csdn.net/detail/xingjiarong/9299923](http://download.csdn.net/detail/xingjiarong/9299923)