[设计模式]-模板方法模式

前言

在上一篇文章责任链模式中提到了模板方法模式,因此这里简单介绍一下.

模板方法模式比较简单,或者说比较常用.在开发过程中,许多人在不知不觉的情况下就会使用,只要他具有良好的面对对象思维.

比如当你写了DogCat两个类,发现很多相同的代码,你自然就会将相同模块提取抽象成父类,然后将一些公共的方法放到父类中,这样子就基本实现了模板方式模式.

介绍(摘自《Head FIrst 设计模式》)

在一个方法中定义一个算法的骨架,而将一些详细的步骤延迟到子类中.

模板方法使得子类可以在不改变算法结果的基础上,重新定义算法中的某些步骤.

类图

2019-03-19-23-58-35

角色

抽象模板: 抽象模板一般有一个具体实现的方法,用来定义算法的基础骨架.还有一些抽象方法留给子类去具体实现.此外还有一些有默认实现的钩子方法.子类可选实现.

具体模板: 继承父类的具体方法,实现他们的抽象方法,对于钩子方法,可以根据自身情况决定是都重写.

举个栗子

书上的例子好多了,网络上也有很多,我自己临时瞎想一个吧,不保证一定合适.

假如我们现在要实现两个类,DogCat,并且实现他们的进攻方法.

我们大致实现以下,如下.

Cat:

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
package design_patterns.template_pattern;

/**
* created by huyanshi on 2019/3/20
*/
public class Cat1 {

private void attack() {

prepared();
jump();
bite();

}

private void prepared() {

}

private void jump() {

}

private void bite() {

}

}

Dog:

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
package design_patterns.template_pattern;

/**
* created by huyanshi on 2019/3/20
*/
public class Dog1 {

private void attack(){

prepared();
run();
shout();
bite();

}

private void prepared(){

}

private void run(){

}

private void shout(){

}

private void bite(){

}
}

仔细查看代码,发现其实他们的攻击过程很相似.

狗:准备,跑过去,发出声音,咬住
猫:准备,跳过去,咬住

但是这样子编码的话,我们将相同的preparedbite分别写了两次,这是不科学的.

很明显我们可以实现一个父类,将preparedbite在父类里实现他们分别继承就好了.

那么对于shoutjump/run方法呢?就分别实现了嘛?

不是的,jumprun都是移动,只是实现方法不同,也是可以抽象到父类的,那么shout呢?这就是钩子的作用了,可以动态控制当前动物是否会发出声音之后再进行攻击.

改进后的代码如下:

首先是动物模板类:

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
package design_patterns.template_pattern;

/**
* created by huyanshi on 2019/3/20
*/
public abstract class AnimalTemplate {

protected boolean isShout;

public final void attack() {
//
prepared();
move();
if (isShout) {
shout();
}
bite();

}

private void prepared() {
//具体实现准备方案
System.out.println("准备");
}

abstract void move();

private void bite() {
//具体实现咬的方式
System.out.println("咬");
}

abstract void shout();

public void setShout() {
isShout = true;
}
}

注意,代码中的setShout是钩子方法,这里简单的用一个变量来当做钩子,此外,preparedbite是具体方法,而moveshout为抽象方法.

下面是狗的具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package design_patterns.template_pattern;

/**
* created by huyanshi on 2019/3/20
*/
public class Dog extends AnimalTemplate {

@Override
void move() {
System.out.println("我是狗,我跑过去");
}

@Override
void shout() {
System.out.println("我是狗,我叫一下,吓唬吓唬他");
}
}

猫的具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package design_patterns.template_pattern;

/**
* created by huyanshi on 2019/3/20
*/
public class Cat extends AnimalTemplate {

@Override
void move() {
System.out.println("我是猫,我跳过去.");
}

@Override
void shout() {
System.out.println("我是猫,我不叫.");
}

@Override
public void setShout() {
this.isShout = false;
}
}

测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package design_patterns.template_pattern;

/**
* created by huyanshi on 2019/3/20
*/
public class Test {

public static void main(String[] args) {

AnimalTemplate dog = new Dog();
dog.setShout();
dog.attack();

AnimalTemplate cat = new Cat();
cat.setShout();
cat.attack();
}
}

输出结果:

1
2
3
4
5
6
7
准备
我是狗,我跑过去
我是狗,我叫一下,吓唬吓唬他

准备
我是猫,我跳过去.

对比上下的代码可以发现,在第二个版本中,没有一些重复的代码,且子类的逻辑更加清晰,仅仅实现了自己与其他类不相同的部分,或者自己想要实现的部分钩子方法.

而且当动物越来越多,代码的总量会越来越少且容易维护,新添加一个动物,只需要继承动物模板,然后实现moveshout即可.

总结

首先注意一下:在模板方法的attack上,添加了final关键字,可以防止该方法被重写,可以保证attack这一方法,在定义及流程上的正确性及安全性,而具体的实现可以交给子类.

模板方法的优点:
1、封装不变部分,扩展可变部分。
2、提取公共代码,便于维护。
3、行为由父类控制,子类实现。

缺点:

类的个数较多.

完。





ChangeLog

2019-03-20 完成

以上皆为个人所思所得,如有错误欢迎评论区指正。

欢迎转载,烦请署名并保留原文链接。

联系邮箱:huyanshi2580@gmail.com

更多学习笔记见个人博客——>呼延十