1. 场景
有一个报表打印程序,用户规定必须要打印表头、正文、表尾三个部分。
一般思路:
1 | public class Report { |
客户端调用的时候,直接new一个Report对象,然后调用print方法即可。现在看起来没什么问题,可是需求是不断变化的。如果用户提出需要把表头改一下样式,怎么办?最简单的方法就是在Report类里修改打印表头的代码。
修改完毕,用户又觉得还是原来的样式好看,要求再改回原来的样式,这时我们要再修改Report。
修改完毕,用户又要求修改正文和表尾,我们接着修改Report。
修改完毕,用户要求打印两套样式的报表,第一套就使用最初的样式,第二套使用修改过表头的样式。这时,我们没法通过仅仅修改Report来完成需求了,我们要添加一个Report2。以后也许还要添加Report3、Report4等等。
我们发现,改来改去非常麻烦,而且存在大量重复代码,还违反了开闭原则。那么怎么解决这个问题呢?
2. 分析
分析需求可以看出,打印表头、正文、表尾这个流程是不变的,而打印的方式是变化的。
根据开闭原则,一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。也就是说,一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。
那么我们最好把不变的部分和变化的部分分开,不变的部分作为软件实体,变化的部分作为扩展。
3. 解决办法
Report类作为抽象类,规定流程。子类继承Report类,实现不同样式。
1 | public abstract class Report |
1 | public class ReportImpl extends Report |
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. 参考文献
《易学设计模式》
《大话设计模式》
接口设计六大原则
软件设计六大设计原则讲解