0%

设计模式——模板方法模式

定义一个操作中的算法的骨架(算法所需的一些步骤以及步骤执行的顺序),而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。它的主要思想是,定义一个操作的一系列步骤,对于某些暂时确定不下来的步骤,就留给子类去实现好了,这样不同的子类就可以定义出不同的步骤。因此,模板方法的核心在于定义一个“骨架”。

模板方法的核心思想是:父类(声明为 abstract)定义骨架,子类实现某些细节。

为了防止子类重写父类的骨架方法,可以在父类中对骨架方法使用final。对于需要子类实现的抽象方法,一般声明为protected,使得这些方法对外部客户端不可见。

模板方法(Template Method)模式包含以下主要角色:

  • 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。

    • 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。

    • 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:

      • 抽象方法(Abstract Method) :一个抽象方法由抽象类声明、由其具体子类实现。

      • 具体方法(Concrete Method):一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。

      • 钩子方法(Hook Method) :在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。一般钩子方法是用于判断的逻辑方法,这类方法名一般为 isXxx,返回值类型为 boolean 类型。

  • 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。

优点:

  • 提高代码复用性:将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中。

  • 实现了反向控制:通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制 ,并符合“开闭原则”。

缺点:

  • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。

适用场景:

  • 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
  • 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。

Java 标准库也有很多模板方法的应用。在集合类中,AbstractListAbstractQueuedSynchronizer都定义了很多通用操作,子类只需要实现某些必要方法。

比如 AQS

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
// 骨架方法(模板方法)定义为 final,不被实现
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

public final void acquire(int arg) { // 执行的步骤和顺序以定义好,其中某些步骤延迟到子类中实现
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// ....

// 其他方法由子类去重写
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
// ...

举例:

去银行的营业厅办理业务需要以下步骤:1.取号、2.办业务、3.评价。三个步骤中取号和评价都是固定的流程,每个人要做的事儿都是一样的。但是办业务这个步骤根据每个人要办的事情不同所以需要有不同的实现。我们可以将整个办业务这件事儿封装成一个抽象类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public abstract class AbstractBusinessHandeler {
/**
* 模板方法
*/
public final void execute(){
getRowNumber();
handle();
judge();
}
// 取号
private void getRowNumber(){
System.out.println("rowNumber-00" + RandomUtils.nextInt());
}
// 办理业务
public abstract void handle(); //抽象的办理业务方法,由子类实现
// 评价
private void judge(){
System.out.println("give a praised");
}
}

现在,有了这个抽象类和方法,如果有人想要办理业务,那么只需要继承该AbstractBusinessHandeler并且重写handle方法,然后再使用该实现类的对象调用execute方法,即可完成整个办理业务的流程。

1
2
3
4
5
6
7
8
9
10
11
public class SaveMoneyHandler extends AbstractBusinessHandeler {
@Override
public void handle() {
System.out.println("save 1000");
}
public static void main(String []args){
SaveMoneyHandler saveMoneyHandler = new SaveMoneyHandler();
saveMoneyHandler.execute();
}
}
// output:编号:rowNumber-001 save 1000 give a praised
-------本 文 结 束 感 谢 您 的 阅 读-------