一个计算机技术爱好者与学习者

0%

行为型模式之模板方法模式

1. 场景

有一个报表打印程序,用户规定必须要打印表头、正文、表尾三个部分。

一般思路:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Report {
public void print()
{
printTitle();
printBody();
printTail();
}

public void printTitle()
{
System.out.println("采用一种方式打印表头");
}

public void printBody()
{
System.out.println("采用一种方式打印正文");
}

public void printTail()
{
System.out.println("采用一种方式打印表尾");
}
}

客户端调用的时候,直接new一个Report对象,然后调用print方法即可。现在看起来没什么问题,可是需求是不断变化的。如果用户提出需要把表头改一下样式,怎么办?最简单的方法就是在Report类里修改打印表头的代码。

修改完毕,用户又觉得还是原来的样式好看,要求再改回原来的样式,这时我们要再修改Report。

修改完毕,用户又要求修改正文和表尾,我们接着修改Report。

修改完毕,用户要求打印两套样式的报表,第一套就使用最初的样式,第二套使用修改过表头的样式。这时,我们没法通过仅仅修改Report来完成需求了,我们要添加一个Report2。以后也许还要添加Report3、Report4等等。

我们发现,改来改去非常麻烦,而且存在大量重复代码,还违反了开闭原则。那么怎么解决这个问题呢?

2. 分析

分析需求可以看出,打印表头、正文、表尾这个流程是不变的,而打印的方式是变化的。

根据开闭原则,一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。也就是说,一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。

那么我们最好把不变的部分和变化的部分分开,不变的部分作为软件实体,变化的部分作为扩展。

3. 解决办法

Report类作为抽象类,规定流程。子类继承Report类,实现不同样式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public abstract class Report
{
public void print()
{
printTitle();
printBody();
printTail();
}

public abstract void printTitle();

public abstract void printBody();

public abstract void printTail();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ReportImpl extends Report
{
public void printTitle()
{
System.out.println("采用一种方式打印表头");
}

public void printBody()
{
System.out.println("采用一种方式打印正文");
}

public void printTail()
{
System.out.println("采用一种方式打印表尾");
}
}

4. 总结

模板方法可以总结为四个字:按部就班。适用于流程是固定的,而流程的具体实现是变化的情况。

5. 应用

Web开发中的HttpServlet类就是一个典型模板应用。
如果没有HttpServlet,那么我们的MyServlet需要继承GenericServlet。

接下来的处理流程是确定的:
1、转化ServletRequest和ServletResponse为HttpServletRequest和HttpServletResponse。
2、判断请求类型是get、post或者其他类型。
3、从HttpServletRequest中拿到请求参数,进行业务处理。

其中经常变化的是第3步。

而HttpServlet,规定了处理的流程。我们的MyServlet,如果继承HttpServlet,那么只要专注于第3步即可。

6. 源码分享

https://github.com/voidking/design-pattern-behavior.git

7. 参考文献

《易学设计模式》
《大话设计模式》
接口设计六大原则
软件设计六大设计原则讲解