模板方法模式

在看HDA代码的时候发现其中对于归档任务和清理任务的设计用了模板方法的设计模式,详细学习记录了一下。

基本介绍

模板方法定义了一个算法的步骤,并允许子类为其中的一些步骤提供具体的实现方式。这种模式让子类在不需要改变整体结构的情况下,可以重新定义算法的具体步骤。
一般来说,我们在抽象类中定义一个算法的步骤和流程,子类按需重写抽象类中的抽象方法。

具体实现案例

  1. HDA示例
    下面为HDA归档和清理任务的UML类图:

从上图可以很清楚的看出这几个类之间的结构,ArchiveTaskCleanTask作为具体的实现类,继承了抽象类AbstractTask,抽象类又实现了一个ITask的接口,接口中定义了算法中必须要实现的方法,强制性的定义了算法的必须要实现的方法,具体的任务执行方法execute和错误处理方法HandleError就要在两个子类中分别进行实现了,抽象类中提供了三个模板方法分别是isInterruptgetSummaryDescriptiongetErrorTableLog,这是三个通用的方法,抽象出来放在父类中。

这里模板方法没有用到钩子方法,钩子方法是在模板方法的父类中定义一个方法,它默认可以不做任何事,子类可以根据实际情况来重写,这样的方法称为“钩子”。

HDA这里两个子类中还有很多相同的方法,可以再进一步抽象到父类中。

  1. 示例二

抽象类:

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
/**
 * 抽象类AbstractDisplay
 */
public abstract class AbstractDisplay {
    /**
     * 交给子类去实现的抽象方法(1) open
     */
    public abstract void open();

    /**
     * 交给子类去实现的抽象方法(2) print
     */
    public abstract void print();

    /**
     * 交给子类去实现的抽象方法(3) close
     */
    public abstract void close();

    /**
     * 本抽象类中实现的display方法,模板方法
     */
    public final void display() {
        // 首先打开…
        open();
        // 循环调用5次print
        for (int i = 0; i < 5; i++) {
            print();
        }
        // …最后关闭。这就是display方法所实现的功能
        close();
    }
}

子类:CharDisplay

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
  
/**
 * CharDisplay是AbstractDisplay的子类
 */
public class CharDisplay extends AbstractDisplay {
    /**
     * 需要显示的字符
     */
    private char ch;

    public CharDisplay(char ch) {
        // 保存在字段中
        this.ch = ch;
    }

    public void open() {
        // 显示开始字符"<<"
        System.out.print("<<");
    }

    public void print() {
        // 显示保存在字段ch中的字符
        System.out.print(ch);
    }
   
    public void close() {
        // 显示结束字符">>"
        System.out.println(">>");
    }
}



子类:StringDisplay

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
56
57
58
59

/**
 * StringDisplay也是AbstractDisplay的子类
 */
public class StringDisplay extends AbstractDisplay {

    /**
     * 需要显示的字符串
     */
    private String string;

    /**
     * 以字节为单位计算出的字符串长度
     */
    private int width;


    public StringDisplay(String string) {
        this.string = string;
        // 将字符串的字节长度也保存在字段中,以供后面使用
        this.width = string.getBytes().length;
    }

    public void open() {
        // 调用该类的printLine方法画线
        printLine();
    }


    public void print() {
        // 给保存在字段中的字符串前后分别加上"|"并显示出来
        System.out.println("|" + string + "|");
    }

    public void close() {
        // 与open方法一样,调用printLine方法画线
        printLine();
    }

    /**
     * 被open和close方法调用。由于可见性是private,因此只能在本类中被调用
     */

    private void printLine() {
        // 显示表示方框的角的"+"
        System.out.print("+");                
        for (int i = 0; i < width; i++) {
            // 显示width个"-",和"+"一起组成边框
            System.out.print("-");                      
        }
        // 显示表示方框的角的"+"
        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
23
24
25
26
27



public class Main {

    public static void main(String[] args) {

        // 生成一个持有'H'的CharDisplay类的实例
        AbstractDisplay d1 = new CharDisplay('H');

        // 生成一个持有"Hello, world."的StringDisplay类的实例
        AbstractDisplay d2 = new StringDisplay("Hello, world.");

        // 生成一个持有"你好,世界。"的StringDisplay类的实例
        AbstractDisplay d3 = new StringDisplay("你好,世界。");

        /*
         * 由于d1、d2和d3都是AbstractDisplay类的子类
         * 可以调用继承的display方法
         * 实际的程序行为取决于CharDisplay类和StringDisplay类的具体实现
         */
        d1.display();
        d2.display();
        d3.display();
    }
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<<HHHHH>>
+-------------+
|Hello, world.|
|Hello, world.|
|Hello, world.|
|Hello, world.|
|Hello, world.|
+-------------+

+------------------+
|你好,世界。|
|你好,世界。|
|你好,世界。|
|你好,世界。|
|你好,世界。|
+------------------+
Process finished with exit code 0

总结

  • 优点
    • 统一了算法,同时也提供了一定的灵活性。父类中的模板方法确保了算法的结构保持不变,同时由子类提供一些具体方法的实现。
    • 实现了最大化的代码复用。父类中的模板方法和已实现的某些步骤子类可以直接复用,而如果模板方法需要修改也只要修改一个类即可,对子类的调用来说的透明的,不需要进行修改。