我们还是举上一节的例子:生产汽车。上一节我们通过模板方法模式控制汽车跑起来的动作,那么需求是无止境的,现在如果老板又增加了额外的需求:汽车启动、停止、鸣笛引擎声都由客户自己控制,他想要什么顺序就什么顺序,那该如何做呢?

1. 汽车无休止的改造

假如现在要生产两种车,奔驰和宝马,这两辆车都有共性,我们所需要关注的是单个车的运行过程,这才是老板所关心的点所在。我们先这样想,针对这个需求,我们要找到一个切入点,那就是产品类,每个车都是一个产品,那么在产品类中我们可以控制车的运行顺序,这样每个车都可以拥有自己想要的顺序了。基于此,我们设计如下类图:

我们看到CarModel中有个setSequence方法,通过传入一个ArrayList来控制运行顺序,run方法根据这个ArrayList中保存的顺序执行,然后奔驰车和宝马车分别继承这个CarModel即可,这貌似是很好的实现,它很像上一节的模板方法模式,只是多了个方法可以设置运行顺序。我们看一下CarModel具体代码的实现:

public abstract class CarModel {  

    private ArrayList<String> sequence = new ArrayList<String>(); //维护一个ArrayList保存执行命令关键字  

    protected abstract void start();
protected abstract void stop();
protected abstract void alarm();
protected abstract void engineBoom(); final public void run() {
for(int i = 0; i < this.sequence.size(); i ++) { //根据ArrayList中保存的顺序执行相应的动作
String actionName = this.sequence.get(i);
if(actionName.equalsIgnoreCase("start")) {
this.start(); //启动汽车
} else if(actionName.equalsIgnoreCase("stop")) {
this.stop(); //停止汽车
} else if(actionName.equalsIgnoreCase("alarm")) {
this.alarm(); //汽车鸣笛
} else if(actionName.equalsIgnoreCase("engine boom")) {
this.engineBoom(); //汽车轰鸣
}
}
} final public void setSequence(ArrayList<String> sequence) { //获得执行顺序的命令,即一个ArrayList
this.sequence = sequence;
}
}

CarModel中的setSequence方法允许客户自己设置一个顺序,我们看看子类的实现:

public class BenzModel extends CarModel {  

    @Override
protected void start() {
System.out.println("奔驰启动……");
} @Override
protected void stop() {
System.out.println("奔驰停止……");
} @Override
protected void alarm() {
System.out.println("奔驰鸣笛……");
} @Override
protected void engineBoom() {
System.out.println("奔驰轰鸣");
} }
//宝马就略了……一样的

下面我们增加一个测试类实现该需求:

public class Client {  

    public static void main(String[] args) {
BenzModel benz = new BenzModel();
//存放run顺序
ArrayList<String> sequence = new ArrayList<String>();
sequence.add("engine boom"); //老板说:跑之前先轰鸣比较帅!
sequence.add("start");
sequence.add("stop");
//我们把这个顺序赋予奔驰
benz.setSequence(sequence);
benz.run();
}
}

这样好像已经顺利完成了任务了,但是别忘了,我们这只是满足了一个需求,如果下一个需求是宝马车只轰鸣,再下一个需求是奔驰车只跑不停……等等……那岂不是要一个个写测试类来实现?显然这不是我们想要的。

我们可以这样做:为每种产品模型定义一个建造者,你要啥顺序直接告诉建造者,由建造者来建造即可,于是我们重新设计类图:

我们增加了一个CarBuilder类,由它来组装各个车模型,要什么类型的顺序就由相关的子类去完成即可,我们来看看CarBuilder的代码:

public abstract class CarBuilder {
//建造一个模型,你要给我一个顺序要求
public abstract void setSequence(ArrayList<String> sequence);
//设置完毕顺序后,就可以直接拿到这个车辆模型了
public abstract CarModel getCarModel();
}

很简单,每个车辆模型都要有确定的运行顺序,然后才能返回一个车辆模型,奔驰车和宝马车组装者的代码如下:

public class BenzBuilder extends CarBuilder {  

    private BenzModel benz = new BenzModel(); //奔驰车模型  

    @Override
public void setSequence(ArrayList<String> sequence) {
this.benz.setSequence(sequence); //设置奔驰车模型的运行顺序
} @Override
public CarModel getCarModel() {
return this.benz; //将这个模型返回
}
}
//宝马车一样,不写了……

现在两辆车的组装者都写好了,现在我们写一个测试类来测试一下:

public class Client {  

    public static void main(String[] args) {  

        //存放run顺序
ArrayList<String> sequence = new ArrayList<String>();
sequence.add("engine boom");
sequence.add("start");
sequence.add("stop");
//要用这个顺序造一辆奔驰
BenzBuilder benzBuilder = new BenzBuilder();
//把顺序给奔驰组装者
benzBuilder.setSequence(sequence);
//奔驰组装者拿到顺序后就给你生产一辆来
BenzModel benz = (BenzModel) benzBuilder.getCarModel();
benz.run();
} }

如果我要生产一辆宝马车,只需要换成宝马车的组装者即可,这样我们不用直接访问产品类了,全部访问组装者就行,是不是感觉到很方便,我管你怎么生产,我扔给你个顺序,你给我弄辆车出来,要的就是这种效果!

可是人的需求是个无底洞,特别是老板,他哪天不爽了,又要换顺序,这样还是挺麻烦的,四个过程(start、stop、alarm、engine boom)按排列组合也有很多中情况,我们不能保证老板想要哪种顺序,咋整?无奈,我们只能使出最后的杀手锏了,找个设计师过来指挥各个时间的先后顺序,然后为每种顺序指定一个代码,你说一种我们立刻就给你生产!我们再修改一下类图……

类图看着有点复杂,其实不然,只是在原来的基础上增加了一个Director类充当着设计师的角色,负责按照指定的顺序生产模型,比如我们要一个A顺序的奔驰车,B顺序的奔驰车,A顺序的宝马车,B顺序的宝马车……等等,我们来看下Director类的代码:

public class Director {
private ArrayList<String> sequence = new ArrayList<String>();
private BenzBuilder benzBuilder = new BenzBuilder();
private BWMBuilder bwmBuilder = new BWMBuilder(); //A顺序的奔驰车
public BenzModel getABenzModel() {
this.sequence.clear();
this.sequence.add("start");
this.sequence.add("stop");
//返回A顺序的奔驰车
this.benzBuilder.setSequence(sequence);
return (BenzModel) this.benzBuilder.getCarModel();
} //B顺序的奔驰车
public BenzModel getBBenzModel() {
this.sequence.clear();
this.sequence.add("engine boom");
this.sequence.add("start");
this.sequence.add("stop");
//返回B顺序的奔驰车
this.benzBuilder.setSequence(sequence);
return (BenzModel) this.benzBuilder.getCarModel();
} //C顺序的宝马车
public BenzModel getCBWMModel() {
this.sequence.clear();
this.sequence.add("start");
this.sequence.add("alarm");
this.sequence.add("stop");
//返回C顺序的宝马车
this.bwmBuilder.setSequence(sequence);
return (BenzModel) this.bwmBuilder.getCarModel();
} //D顺序的宝马车
public BenzModel getDBWMModel() {
this.sequence.clear();
this.sequence.add("engine boom");
this.sequence.add("start");
//返回D顺序的宝马车
this.bwmBuilder.setSequence(sequence);
return (BenzModel) this.bwmBuilder.getCarModel();
} //还有很多其他需求,设计师嘛,想啥需求就给你弄啥需求
}

有了这样一个设计师,我们的测试类就更容易处理了,比如现在老板要10000辆A类奔驰车,100000辆B类奔驰车,20000C类型宝马车,D类型不要:

public class Client {  

    public static void main(String[] args) {  

        Director director = new Director();  

        for(int i = 0; i < 10000; i ++) {
director.getABenzModel();
} for(int i = 0; i < 100000; i ++) {
director.getBBenzModel();
} for(int i = 0; i < 20000; i ++) {
director.getCBWMModel();
}
} }

是不是很清晰很简单,我们重构代码的最终第就是简单清晰。这就是建造者模式。

2. 建造者模式的定义

我们来看看建造者模式的一般定义:Separate the construction of a complex object from its representation so that the same construction process can create different representations. 即:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。比如上面的例子,我们可以用同样的顺序构造不同的车。建造者模式的通用类图如下:

Product是最终的产品类,Builder是建造者,Director是指挥者。Director负责安排已有模块的顺序,然后告诉Builder开始建造。

3. 建造者模式的优点

1)封装性:使用建造者模式可以是客户端不必知道产品内部组成的细节。

2)建造者独立,容易扩展:BenzBuilder和BMWBuilder是相互独立的,对系统扩展非常有利。

3)便于控制细节风险:由于具体的建造者是独立的,因此可以对建造者过程逐步细化,而不对其他的模块产生任何影响。

4. 建造者模式的使用场景

1)相同的方法,不同的执行顺序,产生不同的事件结果时,可以使用建造者模式。

2)多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不想同时,可以使用建造者模式。

3)产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这时候可以使用建造者模式。

4)在对象创建过程中会使用到系统的一些其他对象,这些对象在产品对象的创建过程中不易得到,也可以采用建造者模式封装该对象的创建过程。这种场景只能是一个补偿的方法,因为一个对象不容易获得,而在设计阶段竟然没有发现,而要通过设计这模式来柔化创建过程,本身设计已经出问题了。

到这里,我们会发现,建造者模式和工厂方法模式有点像。但是两者有区别:建造者模式关注的是零件类型和装配工艺(顺序),而工厂模式是创建一个对象,这是最大不同的地方。

创建者模式就介绍这么多吧,如有错误之处,欢迎留言指正~

【java设计模式】之 建造者(Builder)模式的更多相关文章

  1. Java设计模式-建造者(Builder)模式

    目录 由来 使用 1. 定义抽象 Builder 2. 定义具体 Builder类 3. 定义具体 Director类 4. 测试 定义 文字定义 结构图 优点 举例 @ 最近在看Mybatis的源码 ...

  2. 折腾Java设计模式之建造者模式

    博文原址:折腾Java设计模式之建造者模式 建造者模式 Separate the construction of a complex object from its representation, a ...

  3. java设计模式4--建造者模式(Builder)

    本文地址:http://www.cnblogs.com/archimedes/p/java-builder-pattern.html,转载请注明源地址. 建造者模式 将一个复杂对象的构建与它的表示分离 ...

  4. Java 设计模式之建造者模式(四)

    原文地址:Java 设计模式之建造者模式(四) 博客地址:http://www.extlight.com 一.前言 今天继续介绍 Java 设计模式中的创建型模式--建造者模式.上篇设计模式的主题为 ...

  5. java设计模式3——建造者模式

    java设计模式3--建造者模式 1.建造者模式介绍: 建造者模式属于创建型模式,他提供了一种创建对象得最佳方式 定义: 将一个复杂对象的构建和与它的表示分离,使得同样的构建过程可以创建不同的表示 主 ...

  6. 折腾Java设计模式之中介者模式

    博文原址:折腾Java设计模式之中介者模式 中介者模式 中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性.这种模式提供了一个中介类,该类通常处理不同类之间的通信,并 ...

  7. Java设计模式 - - 单例模式 装饰者模式

    Java设计模式 单例模式 装饰者模式 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 静态代理模式:https://www.cnblogs.com/StanleyBlogs/p/1 ...

  8. 重学 Java 设计模式:实战桥接模式(多支付渠道「微信、支付宝」与多支付模式「刷脸、指纹」场景)

    作者:小傅哥 博客:https://bugstack.cn - 编写系列原创专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 为什么你的代码那么多ifelse 同类的业务.同样的功能, ...

  9. 重学 Java 设计模式:实战外观模式「基于SpringBoot开发门面模式中间件,统一控制接口白名单场景」

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 你感受到的容易,一定有人为你承担不容易 这句话更像是描述生活的,许许多多的磕磕绊绊总 ...

  10. 重学 Java 设计模式:实战代理模式「模拟mybatis-spring中定义DAO接口,使用代理类方式操作数据库原理实现场景」

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 难以跨越的瓶颈期,把你拿捏滴死死的! 编程开发学习过程中遇到的瓶颈期,往往是由于看不 ...

随机推荐

  1. elasticSearch indices VS type

    elasticSearch 的中文文档 http://es.xiaoleilu.com/010_Intro/05_What_is_it.html https://www.elastic.co/blog ...

  2. Sping--IOC概念

    1. 新建项目, 引入spring包(sping, common-annotation, common-logging包), 还有junit包. user.java: package com.bjsx ...

  3. (中等) POJ 2528 Mayor's posters , 离散+线段树。

    Description The citizens of Bytetown, AB, could not stand that the candidates in the mayoral electio ...

  4. MVC 5学习总结笔记1

    01.使用MVC自带的DataAnnotations实现数据验证 public class ExternalLoginConfirmationViewModel { [Required] [Displ ...

  5. HDU 3264 Open-air shopping malls ——(二分+圆交)

    纯粹是为了改进牛吃草里的两圆交模板= =. 代码如下: #include <stdio.h> #include <algorithm> #include <string. ...

  6. IOS开发-OC学习-NSTimer的使用

    上一篇博客中在改变属性值的时候使用了timer进行自动改变.关于NSTimer的更详细的用法如下: 定义一个NSTimer类型的timer,和一个count,其中timer是定时器,count是计数的 ...

  7. 搭建NDK环境

    2014.07.14 搭建OK,但是目前只能手动编译c代码,具体不清楚.

  8. Sed替换 内容带反斜杠(/)

    sed "s#XXXX#${NAME}#" $MAIL_CONTENT > /tmp/MAIL_CONTENT1.tmp -----不论什么字符,紧跟着s命令的都被认为是新的 ...

  9. Mysql死锁问题解决方式 & 聚簇索引、隔离级别等知识

    参考了这篇文章:http://www.cnblogs.com/LBSer/p/5183300.html  <mysql死锁问题分析> 写的不错. 如果Mysql死锁,会报出: 1.1 死锁 ...

  10. iOS 设置控件圆角、文字、字体

    以按钮为例: UIButton btn = [UIButton new]; btn.layer.masksToBounds = YES; btn.layer.cornerRadius = 10.0; ...