**
设计模式(四)之建造者模式
**
- 案例说明
本文以创建一个电脑实体类的例子来体现建造者模式的写法和优缺点,并对传统意义上的建造者模式进行一定程度的魔改,结合UML类图和代码一起说明。
- 不用建造者模式的传统写法
我们通过对同一个实体类中不同的属性设置具体的值来模拟现实业务中建造某种商品的过程,故可以通过构造器或者是set方法来模拟实现。以下是UML类图和代码实现:
电脑实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Computer {
private String host;
private String screen;
private String keyBoard;
}
客户端
public class Client {
public static void main(String[] args) {
// 通过set方法来模拟制造一台电脑的过程
Computer computer = new Computer();
computer.setHost("Dell主机");
computer.setScreen("Dell屏幕");
computer.setKeyBoard("Dell键盘");
System.out.println(computer);
}
}
D:\jdk8\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=49516:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\ideaworkspace\design_pattern\design\target\classes;D:\dev_tools\repository\org\projectlombok\lombok\1.16.10\lombok-1.16.10.jar com.wd.builder.none.Client
Computer(host=Dell主机, screen=Dell屏幕, keyBoard=Dell键盘)
Process finished with exit code 0
可以看出设计的程序结构过于简单,没有设计缓存层对象,程序的扩展和维护性不好.。也就是说,这种设计方案,把产品(即:电脑) 和制造产品的过程(即:制造电脑的流程) 封装在一起,耦合性太强。于是我们就思考,能不能把客户端制造电脑的过程交给一个专业的制造者来做呢?客户端只要去通知制造者而不需要去管制造的过程,那我们就创建一个抽象的制造者,具体制造什么由它的子类去实现,客户端直接跟抽象的制造者对话。
- 建造者模式的雏形
新增的construct方法是将制造的三个过程整合起来,客户端只需要调用construct就可以完成三个制造过程。下面来看看代码实现:
电脑实体类跟上面的一样
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Computer {
private String host;
private String screen;
private String keyBoard;
}
抽象的制造者
public abstract class Builder {
public abstract void buildHost();
public abstract void buildScreen();
public abstract void buildKeyBoard();
/**
* 制造的总方法,整合以上三个方法
*/
public abstract void construct();
public abstract Computer getResult();
}
抽象的制造者的子类实现
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DellBuilder extends Builder {
private Computer computer =new Computer();
@Override
public void buildHost() {
computer.setHost("制造Dell主机");
}
@Override
public void buildScreen() {
computer.setScreen("制造Dell屏幕");
}
@Override
public void buildKeyBoard() {
computer.setKeyBoard("制造Dell键盘");
}
/**
* 制造的总方法,整合以上三个方法
*/
@Override
public void construct() {
buildHost();
buildScreen();
buildKeyBoard();
}
@Override
public Computer getResult() {
return computer;
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LenovoBuilder extends Builder {
private Computer computer =new Computer();
@Override
public void buildHost() {
computer.setHost("制造Lenovo主机");
}
@Override
public void buildScreen() {
computer.setScreen("制造Lenovo屏幕");
}
@Override
public void buildKeyBoard() {
computer.setKeyBoard("制造Lenovo键盘");
}
/**
* 制造的总方法,整合以上三个方法
*/
@Override
public void construct() {
buildHost();
buildScreen();
buildKeyBoard();
}
@Override
public Computer getResult() {
return computer;
}
}
客户端
public class Client {
public static void main(String[] args) {
// 初始化一个抽象的制造者
Builder builder = null;
// 具体的戴尔电脑制造者
builder = new DellBuilder();
// 调用制造的总方法
builder.construct();
System.out.println(builder.getResult());
System.out.println("---------------------------------------------------------------------");
// 具体的联想电脑制造者
builder = new LenovoBuilder();
// 调用制造的总方法
builder.construct();
System.out.println(builder.getResult());
}
}
D:\jdk8\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=51641:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\ideaworkspace\design_pattern\design\target\classes;D:\dev_tools\repository\org\projectlombok\lombok\1.16.10\lombok-1.16.10.jar com.wd.builder.build0.Client
Computer(host=制造Dell主机, screen=制造Dell屏幕, keyBoard=制造Dell键盘)
---------------------------------------------------------------------
Computer(host=制造Lenovo主机, screen=制造Lenovo屏幕, keyBoard=制造Lenovo键盘)
Process finished with exit code 0
使用这种实现方法将制造过程从客户端里面抽离了出来,专业的事交给专业的人去做。但有人或许会问,如果戴尔电脑和联想电脑的生产流程要是不一致呢?于是我们就想到了建造者模式里面一个很重要的角色——指挥者,我们把制造的具体流程安排交给一个抽象的指挥者,具体的实现类指挥者知道具体的商品应该用什么样的生产制造流程,客户端只需要跟这个抽象的指挥者对话就可以了。下面是类图和代码实现:
- 真正的建造者模式
我们将原先在builder里面的construct制造的总方法移动到了director里面,由具体的指挥者来决定制造的总方法里面的步骤。假设戴尔电脑的制造包括制造主机、制造屏幕和制造键盘的流程,联想电脑的制造只包括制造主机和制造屏幕的流程。下面是代码实现:
电脑实体类没变
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Computer {
private String host;
private String screen;
private String keyBoard;
}
将Builder中的construct方法移到了指挥者里面
public abstract class Builder {
public abstract void buildHost();
public abstract void buildScreen();
public abstract void buildKeyBoard();
public abstract Computer getResult();
}
具体的子类实现DellBuilder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DellBuilder extends Builder{
private Computer computer =new Computer();
@Override
public void buildHost() {
computer.setHost("制造Dell主机");
}
@Override
public void buildScreen() {
computer.setScreen("制造Dell屏幕");
}
@Override
public void buildKeyBoard() {
computer.setKeyBoard("制造Dell键盘");
}
@Override
public Computer getResult() {
return computer;
}
}
具体的子类实现LenovoBuilder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LenovoBuilder extends Builder{
private Computer computer =new Computer();
@Override
public void buildHost() {
computer.setHost("制造Lenovo主机");
}
@Override
public void buildScreen() {
computer.setScreen("制造Lenovo屏幕");
}
@Override
public void buildKeyBoard() {
computer.setKeyBoard("制造Lenovo键盘");
}
@Override
public Computer getResult() {
return computer;
}
}
抽象的指挥者类
public abstract class Director {
private Builder builder = null;
public Director() {
}
public Director(Builder builder){
this.builder = builder;
}
/**
* 指挥者规划制造流程的方法
*/
public abstract void construct();
}
具体的子类实现指挥者DellDirector
@Data
public class DellDirector extends Director{
private Builder builder = null;
public DellDirector(Builder builder){
this.builder = builder;
}
/**
* 指挥者规划制造流程的方法
*/
@Override
public void construct(){
builder.buildHost();
builder.buildScreen();
builder.buildKeyBoard();
}
}
具体的子类实现指挥者LenovoDirector
@Data
public class LenovoDirector extends Director{
private Builder builder = null;
public LenovoDirector(Builder builder){
this.builder = builder;
}
/**
* 指挥者规划制造流程的方法
*/
@Override
public void construct(){
builder.buildHost();
builder.buildScreen();
}
}
客户端
public class Client {
public static void main(String[] args) {
// 初始化一个抽象的指挥者
Director director = null;
// 初始化一个抽象的制造者
Builder builder = null;
// 由戴尔的制造者来制造
builder = new DellBuilder();
// 由使用戴尔电脑制造流程的指挥者来制造
director = new DellDirector(builder);
director.construct();
System.out.println(builder.getResult());
System.out.println("---------------------------------------------------------------------");
// 由联想的制造者来制造
builder = new LenovoBuilder();
// 由使用联想电脑制造流程的指挥者来制造
director = new LenovoDirector(builder);
director.construct();
System.out.println(builder.getResult());
}
}
测试结果可看出因为联想电脑的流程里面没有制造键盘的步骤,所以键盘的值为空,这就体现了建造者模式的拓展性和灵活性。
D:\jdk8\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=58477:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\ideaworkspace\design_pattern\design\target\classes;D:\dev_tools\repository\org\projectlombok\lombok\1.16.10\lombok-1.16.10.jar com.wd.builder.build.Client
Computer(host=制造Dell主机, screen=制造Dell屏幕, keyBoard=制造Dell键盘)
---------------------------------------------------------------------
Computer(host=制造Lenovo主机, screen=制造Lenovo屏幕, keyBoard=null)
Process finished with exit code 0
- 总结
1) 客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象;
2) 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象;
3) 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程;
4) 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,
系统扩展方便,符合 “开闭原则”;
5) 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间
的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制;
6) 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,
导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式;
7) 抽象工厂模式与建造者模式:抽象工厂模式实现对产品家族的创建,一个产品家族是具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品,它更关心一个产品的制造流程而不是产品的差异性。