Hexo

点滴积累 豁达处之

0%

23 模板方法模式

​ 模板方法模式是所有模式中最为常见的几个模式之一,是基于继承的代码复用的基本技术 。。。

模板方法模式

1 概述

什么是模板方法模式?

​ 定义一个操作中算法的骨架,而将这些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。

2 示例

这里我们可以抽象化的想象下饮料在机器里的制作过程

大致我们可以分成4个步骤

①烧水  ②冲泡饮料  ③把饮料倒入杯中  ④加入调料

例如:

咖啡:烧开热水–>加入咖啡粉冲泡–>把饮料倒入杯中–>加入少许糖

奶茶:烧开热水–>加入奶茶粉冲泡–>把饮料加入杯中–>加入椰果/珍珠

不难发现,饮料制作过程中的步骤中的①烧水、③把饮料倒入杯中是重复工作,制泡哪种饮料都一样,那么也就是重复工作,我们可以把它设定为通用性操作。

我们只需要去关心步骤②和步骤④即可

由于制泡饮料的步骤就是这4步,所以我们可以把它抽象成一个”制作饮料模板”出来,下面就以上面这个例子,我用代码来说明

DrinkTemplate.java(模板类)

这是一个制作饮料的模板类,也就是制作所有饮料的基类

我们可以把这4个步骤封装到一个模板方法里,并实现里面的通用步骤

​ 由于避免继承它的子类去修改整体制作架构,所以这个方法用了final修饰符来修饰,好比著名的”好莱坞原则”:Don’t call us, we’ll call you 子类需要听从父类的安排

​ 由于步骤②和步骤④需要根据具体制泡的饮料来确定,所以需要延迟到子类去实现,这里采用了protected修饰符以便子类可以复写,其他方法就可以直接写”死”掉,用private修饰符修饰,这样的使得代码工作人员能够更加关注自身的工作,而不必去考虑一些其他因素。

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
public abstract class DrinkTemplate {

/**抽象基类
*
* 制作饮料方法模板
* 4个步骤 1、烧水 2、冲泡饮料 3、把饮料倒入杯中 4、加调料
* 由于步骤1、3是通用的步骤,适合于制作任何饮料,所以可以把它写死
* 2和4步骤,针对不同的饮料有不同的选择,所以可以把它延迟到子类去复写实现(注意访问修饰符)
*/
public final void drinkTempLate(){
boilWater();//烧水
brew();//冲泡饮料
pourInCup();//把饮料倒入杯中
addCondiments();//加调料
}

//加调料,由于饮料所加调料各不相同,所以可以延迟到子类实现
protected abstract void addCondiments();

private void pourInCup() {
System.out.println("把饮料倒入杯中...");
}

//冲泡饮料 ,由于饮料所用的材料各不相同,所以可以延迟到子类实现
protected abstract void brew();

private void boilWater() {
System.out.println("烧水步骤进行中...");
}
}

**MakeCoffee.java(冲泡咖啡类) **

这个没啥好说的,就是继承了抽象基类,并复写了它的抽象方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
*
* @author Balla_兔子
* 冲泡咖啡
*
*/
public class MakeCoffee extends DrinkTemplate {

@Override
protected void addCondiments() {
System.out.println("加糖...");
}

@Override
protected void brew() {
System.out.println("加入咖啡粉冲泡...");
}

}

**MakeMilkTea.java(冲泡奶茶类) **

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 冲泡奶茶
* @author Balla_兔子
*
*/
public class MakeMilkTea extends DrinkTemplate {

@Override
protected void addCondiments() {
System.out.println("加椰果...");
}

@Override
protected void brew() {
System.out.println("加入奶茶粉冲泡...");
}

}

**Test.java(测试类) **

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test {

/**
* @author Balla_兔子
*/
public static void main(String[] args) {
DrinkTemplate coffee=new MakeCoffee();
coffee.drinkTempLate();
System.out.println("*******************************");
DrinkTemplate milkTea=new MakeMilkTea();
milkTea.drinkTempLate();
}

}

运行效果:

1
2
3
4
5
6
7
8
9
烧水步骤进行中...
加入咖啡粉冲泡...
把饮料倒入杯中...
加糖...
*******************************
烧水步骤进行中...
加入奶茶粉冲泡...
把饮料倒入杯中...
加椰果...

这样的实现类写起来很清晰明了,只需要去复写我们需要关心的方法即可,大大提高了代码的复用性。

但这里有个问题就暴露出来了,冲泡咖啡的实现固然没错,但总有些人喝咖啡是不加糖的,这是该怎么办呢?

这里就引入了一个”钩子”hook概念

3 钩子

​ 我们可以在某个具体实现方法前后分别加入钩子,就好比是前置方法或者后置方法,就像日志技术一样,在每完成一个业务动作前都需要记录日志

而这个前置方法,我们可以利用一个布尔来做判断,并给它一个默认,来看看具体实现方法

**DrinkTemplate.java(模板类) **

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
public abstract class DrinkTemplate {

/**抽象基类
*
* 制作饮料方法模板
* 4个步骤 1、烧水 2、冲泡饮料 3、把饮料倒入杯中 4、加调料
* 由于步骤1、3是通用的步骤,适合于制作任何饮料,所以可以把它写死
* 2和4步骤,针对不同的饮料有不同的选择,所以可以把它延迟到子类去复写实现(注意访问修饰符)
*/
public final void drinkTempLate(){
boilWater();//烧水
brew();//冲泡饮料
pourInCup();//把饮料倒入杯中
if(condition()==true){//若条件允许,则加入调料,默认允许
addCondiments();//加调料
}
}

protected boolean condition() {
return true;
}

//加调料,由于饮料所加调料各不相同,所以可以延迟到子类实现
protected abstract void addCondiments();

private void pourInCup() {
System.out.println("把饮料倒入杯中...");
}

protected abstract void brew();//冲泡饮料 ,由于饮料所用的材料各不相同,所以可以延迟到子类实现

private void boilWater() {
System.out.println("烧水步骤进行中...");
}
}

**Test.java(测试类) **

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

/**
*/
public static void main(String[] args) {
DrinkTemplate coffee=new MakeCoffee();
coffee.drinkTempLate();
System.out.println("咖啡制作完毕!");
System.out.println("*******************************");
DrinkTemplate milkTea=new MakeMilkTea();
milkTea.drinkTempLate();
System.out.println("奶茶制作完毕!");
}

}

示例:

1
2
3
4
5
6
7
8
9
10
烧水步骤进行中...
加入咖啡粉冲泡...
把饮料倒入杯中...
咖啡制作完毕!
*******************************
烧水步骤进行中...
加入奶茶粉冲泡...
把饮料倒入杯中...
加椰果...
奶茶制作完毕!