建造者模式与模板模式

创建型模式

  • 对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。为了使软件的结构更加清晰,外界对于这些对象只需要知道它们共同的接口,而不清楚其具体的实现细节,使整个系统的设计更加符合单一职责原则。

建造者模式(Builder pattern)

  • 在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象。

在这里插入图片描述

  • Product: 最终要生成的对象,例如 Computer实例。
  • Builder: 构建者的抽象基类(有时会使用接口代替)。其定义了构建Product的抽象步骤,其实体类需要实现这些步骤。其会包含一个用来返回最终产品的方法Product getProduct()。
  • ConcreteBuilder: Builder的实现类。
  • Director: 指挥者,决定如何构建最终产品的算法. 其会包含一个负责组装的方法void Construct(Builder builder), 在这个方法中通过调用builder的方法,就可以设置builder,等设置完成后,就可以通过builder的 getProduct() 方法获得最终的产品。

示例代码

1
2
3
4
ComputerDirector director=new ComputerDirector();//1
ComputerBuilder builder=new MacComputerBuilder("I5处理器","三星125");//2
director.makeComputer(builder);//3
Computer macComputer=builder.getComputer();//4
  • 首先生成一个director ,
  • 然后生成一个目标builder ,
  • 接着使用director组装builder,
  • 组装完毕后使用builder创建产品实例

实例讲解

背景:小成希望去电脑城买一台组装的台式主机
过程:

  1. 电脑城老板(Diretor)和小成(Client)进行需求沟通(买来打游戏?学习?)
  2. 了解需求后,电脑城老板将小成需要的主机划分为各个部件(Builder)的建造请求(CPU、主板blabla)
  3. 指挥装机人员(ConcreteBuilder)去构建组件;
  4. 将组件组装起来成小成需要的电脑(Product)

步骤1: 定义组装的过程(Builder):组装电脑的过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public  abstract class Builder {  

//第一步:装CPU
//声明为抽象方法,具体由子类实现
public abstract void BuildCPU()

//第二步:装主板
//声明为抽象方法,具体由子类实现
public abstract void BuildMainboard();

//第三步:装硬盘
//声明为抽象方法,具体由子类实现
public abstract void BuildHD();

//返回产品的方法:获得组装好的电脑
public abstract Computer GetComputer();
}

步骤2: 电脑城老板委派任务给装机人员(Director)

1
2
3
4
5
6
7
8
9
public class Director{
//指挥装机人员组装电脑
public void Construct(Builder builder){

builder. BuildCPU();
builder.BuildMainboard();
builder. BuildHD();
}
}

步骤3:创建具体的建造者(ConcreteBuilder):装机人员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//装机人员1
public class ConcreteBuilder extend Builder{
//创建产品实例
Computer computer = new Computer();

//组装产品
@Override
public void BuildCPU(){
computer.Add("组装好CPU")
}

@Override
public void BuildMainboard(){
computer.Add("组装好主板")
}

@Override
public void BuildHD(){
computer.Add("组装好硬盘")
}

//返回组装成功的电脑
@Override
public Computer GetComputer(){
return computer
}
}

//装机人员2
public class ConcreteBuilder2 extend Builder{
//创建产品实例
Computer computer = new Computer();

//组装产品
@Override
public void BuildCPU(){
computer.Add("组装垃圾CPU")
}

@Override
public void BuildMainboard(){
computer.Add("组装垃圾主板")
}

@Override
public void BuildHD(){
computer.Add("组装垃圾硬盘")
}

//返回组装成功的电脑
@Override
public Computer GetComputer(){
return computer
}
}

步骤4:定义具体产品类(Product):电脑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Computer{

//电脑组件的集合
private List<String> parts = new ArrayList<String>();

//用于将组件组装到电脑里
public void Add(String part){
parts.add(part);
}

public void Show(){
for (int i = 0;i<parts.size();i++){
System.out.println(“组件”+parts.get(i)+“装好了”);
}
System.out.println(“电脑组装完成,请验收”);
}
}

步骤5:客户端调用-小成到电脑城找老板买电脑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Builder Pattern{
public static void main(String[] args){

//逛了很久终于发现一家合适的电脑店
//找到该店的老板和装机人员
Director director = new Director();
Builder builder = new ConcreteBuilder()// Builder builder = new ConcreteBuilder2() ;

//沟通需求后,老板叫装机人员去装电脑
director.Construct(builder);

//装完后,组装人员搬来组装好的电脑
Computer computer = builder.GetComputer();

//组装人员展示电脑给小成看
computer.Show();
}
}

使用场景

  • 需要生成的产品对象有复杂的内部结构,这些产品对象具备共性;

  • 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。

优点

  • 易于解耦
    将产品本身与产品创建过程进行解耦,可以使用相同的创建过程来得到不同的产品。也就说细节依赖抽象。
  • 易于精确控制对象的创建
    将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰
  • 易于拓展
    增加新的具体建造者无需修改原有类库的代码,易于拓展,符合“开闭原则“。

缺点

  • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。

  • 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。


行为型模式

  • 不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。

模板方法模式(Template Method Pattern)

  • 定义一个操作中的算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
  • 抽象模板中包含三种类型的方法: 基本方法模板方法钩子方法(Hook Method)
  • 基本方法——基本方法也叫做基本操作,是由子类实现的方法,并且在模板方法被调用。
  • 模板方法——核心方法,不允许子类重写,所以都会加上final修饰符,可以有一个或几个,一般是一个具体方法框架,按照固定的流程对基本方法的调度
  • 钩子方法——为了让模板方法的执行结果的更好地适应因外界条件改变。比如说银行办理业务为例,办理业务是个模板方法,普通人要经历排队、取号、等待、办理四个基本流程,而Vip则不需要排队、取号、等待,那么在设计的时候我们的抽象模板类需要考虑到,于是乎你得需要在模板方法各基本方法调用之前增加条件判断,那么用于设置这个条件的方法就是,钩子方法(Hook Method),钩子方法也可以是抽象的还可以由子类的一个方法返回值决定公共部分的执行结果

实现

  • 定义抽象模板类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public abstract class AbstractClass {
//基本方法1
protected abstract void doOneStep();
//基本方法2
protected abstract void doSecStep();
//基本方法3
protected abstract void doThirdStep();

//模板方法,为了避免被子类覆写改变模板方法的算法骨架一般使用final修饰,可以有N个模板方法
public final void work(){
/*
* 调用基本方法,完成相关的逻辑
*/
if(oneStepNeedRun){//钩子方法控制基本方法是否执行
this.doOneStep();
}
this.doSecStep();

this.doThirdStep();
}

//钩子方法它在抽象类中不做事或者是默认的事情,子类可以选择覆盖它,可以有N个
protected boolean oneStepNeedRun(){
return true;
}
}
  • 继承抽象模板类实现具体模板类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class ConcreteClass extends AbstractClass {
    //实现基本方法
    protected void doOneStep() {
    //业务逻辑处理
    }
    protected void doSecStep() {
    //业务逻辑处理
    }
    protected void doThirdStep() {
    //业务逻辑处理
    }

    }

调用

1
public class TemplateMethod {    public static void main(String[] args) {        AbstractClass obj = new ConcreteClass();//多态构建        //调用模板方法        obj.work();    }}

实例讲解

  • 背景:直播流程,需要集成多家直播SDK
  • 用户看直播是有一套固定的模板流程的:登录—进入房间—获取音视频流—观看—停止音视频流—退出房间 虽然两套SDK每一个步骤的实现方式不同,但是基本都是遵循同一套流程
  1. 定义直播模板类

    1
    public abstract class LivePlay {    //模板方法    public final void seeLivePlay() {        login();        openRoom();        startAudioAndVideoStream();        pushVideoStream();        stopAudioAndVideoStream();        closeRoom();    }    //实体方法,这个方法实现通用的业务逻辑    private void login() {        System.out.println("用户登录");    }        /*抽象方法*/    //打开房间    public abstract void openRoom();    //打开音视频流    public abstract void startAudioAndVideoStream();    //关闭音视频流    public abstract void stopAudioAndVideoStream();    //关闭房间    public abstract void closeRoom();    /*钩子方法,可以被需要的子类overwrite*/    //旁路推流,可以通过视频链接在浏览器中查看视频    public void pushVideoStream() {    }}
  1. 定义具体的实体类,根据情况overwrite相应的抽象方法和钩子方法。

    ```java//腾讯直播类public class TencentLivePlay extends LivePlay  {    @Override    public void openRoom() {        System.out.println("腾讯打开房间");    }    @Override    public void startAudioAndVideoStream() {        System.out.println("腾讯打开音视频流");    }    @Override    public void stopAudioAndVideoStream() {        System.out.println("腾讯关闭音视频流");    }    @Override    public void closeRoom() {        System.out.println("腾讯关闭房间");    }    //覆写钩子方法,提供旁路推流功能    @Override    public void pushVideoStream() {        super.pushVideoStream();        System.out.println("腾讯进行旁路推流");    }}```
    
    1
    //金山直播类public class JinShanLivePlay extends LivePlay  {    @Override    public void openRoom() {        System.out.println("金山打开房间");    }    @Override    public void startAudioAndVideoStream() {        System.out.println("金山打开音视频流");    }    @Override    public void stopAudioAndVideoStream() {        System.out.println("金山关闭音视频流");    }    @Override    public void closeRoom() {        System.out.println("金山关闭房间");    }}
  1. 客户端调用
    我们根据后端返回的结果来决定使用哪家的SDK

    1
    public static void main(String[] args) {                //此处省略若干代码         ...        LivePlay tencentLive=new TencentLivePlay();        tencentLive.seeLivePlay();                System.out.println("");                LivePlay jinShanLive=new JinShanLivePlay();        jinShanLive.seeLivePlay();    }

使用场景

  • 多个子类有公有的方法,并且逻辑基本相同时。
  • 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个 子类实现。
  • 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子方法约束其行为。

优点

  • 主要是提高了代码的复用度,而且很好的符合的“开闭原则”。

缺点

  • 设计模式的通病:类增多了
  • 调用控制反转:一般情况下,程序的执行流是子类调用父类的方法,模板方法模式使得程序流程变成了父类调用子类方法,这个使得程序比较难以理解和跟踪

设计模式原则

  • 单一职责 : 类要职责单一
  • 里氏替换 : 不要破坏继承体系
  • 依赖倒置 : 面向接口编程
  • 接口隔离 : 接口设计要精简单一
  • 迪米特 : 降低耦合
  • 开闭原则 : 对扩展开放,对修改关闭