Yves

模板方法模式

先看一下wiki中的定义
模板方法模式定义了一个算法的步骤,并允许子类别为一个或多个步骤提供其实践方式。让子类别在不改变算法架构的情况下,重新定义算法中的某些步骤。

举个栗子,Android中的Activity就用到了模板方法模式:当启动一个Activity的时候,会调用onCreate() onStart() onResume()等一系列生命周期方法,它的生命周期就可以看成是一个算法的骨架,我们根据不同的业务需求,继承实现Activity的声明周期方法,这些方法往往不需要由我们直接调用,父类Activity已经规范了算法或流程。

优缺点

优点:

  • 代码复用
    抽取不变部分,可变部分交给具体的子类实现,同时公共的逻辑抽取出来,便于维护
  • 规范化、结构化算法或流程
  • 可扩展性强,符合开闭原则
  • 通过钩子方法,增加了灵活性

缺点:

  • 可读性降低
    因为完整的逻辑被拆分成了两部分,就Android中的Activity来说,如果不知道其生命周期,就不知道生命周期方法会在什么时候调用,应该在其中实现什么代码。
  • 如果使用了钩子方法,虽然增加了灵活性,但同时也带来了一定的风险,子类影响到父类的逻辑,违反了里氏替换原则。

UML

抽象类(AbstractClass):实现了模板方法,定义了算法的骨架。抽象类的好坏直接决定了程序是否稳定性
具体类(ConcreteClass):实现抽象类中的抽象方法,已完成完整的算法。

抽象类中的主要方法分为三种:

  • 抽象方法:父类中只声明但不加以实现,而是定义好规范,然后由它的子类去实现。
  • 模板方法:由抽象类声明并加以实现。一般来说,模版方法调用抽象方法来完成主要的逻辑功能,并且,模版方法大多会定义为final类型,指明主要的逻辑功能在子类中不能被重写。
  • 钩子方法:由抽象类声明并加以实现。但是子类可以去扩展,子类可以通过扩展钩子方法来影响模版方法的逻辑。一个钩子方法常常由抽象类给出一个空实现作为此方法的默认实现。这种空的钩子方法叫做“Do Nothing Hook”。

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
36
37
38
/**
* 这是模板爸爸
*
* Created by yves on 17-2-4.
*/
public abstract class TemplateView {
/**
* 留给儿子们去实现吧
*/
protected abstract void shape();
protected abstract void size();
/**
* 基本方法,爸爸实现好了,儿子们不许动
*/
private void draw(){
System.out.println("draw:");
setText();
}
/**
* 这是一个钩子,可以是空实现,也可以是默认实现。
*/
protected void setText(){
System.out.println("default");
}
/**
* 这是爸爸的模板方法,规范了流程,儿子们不许动
*/
public final void show(){
shape();
size();
draw();
}
}

第一个子类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Created by yves on 17-2-4.
*/
public class RoundView extends TemplateView {
@Override
protected void shape() {
System.out.println("圆形");
}
@Override
protected void size() {
System.out.println("半径1cm");
}
}

第二个子类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Created by yves on 17-2-4.
*/
public class RectangleView extends TemplateView {
@Override
protected void shape() {
System.out.println("矩形");
}
@Override
protected void size() {
System.out.println("长2cm;宽1cm");
}
@Override
protected void setText() {
System.out.println("重写钩子");
}
}

客户端调用:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Created by yves on 17-2-4.
*/
public class Client {
public static void main(String[] args){
new RoundView().show();
System.out.println("================");
new RectangleView().show();
}
}

运行结果:

1
2
3
4
5
6
7
8
9
圆形
半径1cm
draw:
default
================
矩形
长2cm;宽1cm
draw:
重写钩子