[设计模式] 模板方法
1. 定义
模板方法是在一个方法中定义一个流程/算法的骨架,而其中某些步骤交由子类提供实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中某些操作的具体实现。
2. 应用场景
当多个对象的行为基本一致,每个对象都维护自己的一整套逻辑,对象之间又有完全相同的某些步骤代码,而逻辑又基本一致,这样让代码比较臃肿。我们在设计中遇到在几个类中遇到相同的流程逻辑,但是具体的计算方式不一致,可以考虑引入模板方法。
3. 类图
如下图所示,具体的方法步骤是由模板父类制定的,暴露给子类指定的方法,使得子类可以重写某些现有方法;某些在父模板中无法决定执行逻辑的方法,设定成为抽象类,要求子类一定需要实现该抽象方法;
4. 具体实现
在抽象模板类中,为了避免算法步骤遭到修改,特意将 doSomeThing() 设置成final,禁止子类的重写。然后提供某些通用的操作,例如doSomeThingPartA(), doSomeThingPartB(),这两个方法是针对所有的子类都是通用的,所以提取公共代码到父类中,这样也避免了代码的冗余和对修改的不便。而doSomeThingPartC(), doSomeThingPartD() 是抽象的方法,需要特定的子类才知道如何进行操作,父类就知道要做这个事情,但是具体怎么做,是由子类自行决定的。
在这里引入一个设计原则:“好莱坞原则”,即 不要调用我,我会去调用你;这里可以看出,子类只是负责将实现部分补全,但是具体何时调用,是由 客户 和 父类来决定的。这样统一了程序入口,各个子类的职责变成单纯和统一。
public abstract class AbstractTemplate { protected AbstractTemplate () { }; /** */ public final void doSomeThing() { doSomeThingPartA(); doSomeThingPartB(); doSomeThingPartC(); doSomeThingPartD(); } public abstract void doSomeThingPartC(); public abstract void doSomeThingPartD(); private void doSomeThingPartA() { System.out.println("doSomeThingPartA"); } private void doSomeThingPartB() { System.out.println("doSomeThingPartB"); } }
这是子类的具体实现,将父类中定义好的方法,重写,完成类的具体逻辑,等待服务的调用。然而不仅仅是抽象的方法,在父类中可以有 “空”逻辑的方法,对子类的重写开放,这叫做 “钩子”, 当子类需要重写该方法时候可以重新编写这一方法的逻辑,对于其他不需要的子类,只是当这个方法不可见看待即可,父类不强制重写,提供了空的实现。
public class ConcreteClassA extends AbstractTemplate { public ConcreteClassA () { }; public void doSomeThingPartC() { System.out.println("A doSomeThingPartC"); } public void doSomeThingPartD() { System.out.println("A doSomeThingPartD"); } }
ConcreteClassB 的 具体代码类似 ConcreteClassA , 此处就不做展示。
5. jdk中的模板方法
ThreadPoolExecutor.Worker
6. 总结
模板方法相对于一组流程,相对步骤固定且一致,但是某些具体的步骤有差别,通过 模板方法来固定流程,实现、重写的方法来实现某些节点的具体逻辑。
引自 “菜鸟教程”: http://www.runoob.com/design-pattern/template-pattern.html
优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。
缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
使用场景: 1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。