模板方法模式: 定义一个操作中的算法的骨架, 而将一些步骤延迟到子类中. 模板方法使得子类可以在不改变一个算法的结构的前提下重定义该算法的某些特定步骤.
(图片来自网络)
处理某个流程的骨架代码已经具备, 但其中某节点的具体实现暂不确定, 此时可采用模板方法, 将该节点的代码实现转移给子类完成. 即: 处理步骤在父类中定义好, 具体实现延迟到子类中定义.
模式实例:
以人们平时到银行ATM机办理业务为例,到ATM机办理业务, 都会经过插卡、处理具体业务(如存钱、取钱等)、取卡等几个过程, 而且这几个过程一定是顺序执行的, 且除了处理业务会有所不同之外, 其他的过程完全相同. 因此我们就可以参考模板方法模式把插卡、取卡这些个过程放到父类中实现, 并定义一个流程骨架, 然后将处理业务的具体逻辑放到子类中:
父类:
package designPattern.test.templet; import java.util.Date; public abstract class ATMBusiness { public final void run() { atmOn(); doBusiness(); atmOff(); } // 具体业务,子类实现 public abstract void doBusiness(); private void atmOn() { System.out.println("插卡"); } private void atmOff() { System.out.println("取卡走人"); } }
在父类中定义抽象的原语操作,具体的子类将重定义它们以实现一个算法的各步骤。
实现一个模板方法,定义一个算法的骨架,该模板方法不仅调用原语操作,也调用定义在父类或其他对象中的操作。在具体代码中上面的父类定义了ATM操作的一个主要步骤并确定他们的先后顺序,但允许子类改变这些具体步骤以满足各自的需求。
具体实现类:
package designPattern.test.templet; public class MoneyATMBusiness extends ATMBusiness{ @Override public void doBusiness() { System.out.println("取钱业务"); } }
其他不同业务:
package designPattern.test.templet; public class SaveMoneyATMBusiness extends ATMBusiness{ @Override public void doBusiness() { System.out.println("存钱业务"); } }
在具体的子类中实现原语操作以完成算法中与特定子类相关的步骤;每个抽象父类都可有任意多个子类,而每个子类都可以给出这些抽象方法的不同实现,从而使得顶级逻辑的功能各不相同。
客户端调用:
package designPattern.test.templet; public class Client { public static void main(String[] args) { ATMBusiness business = new MoneyATMBusiness(); business.run(); // 取钱 } }
运行结果:
插卡
取钱业务
取卡走人
上面就实现了一个模板方法模式基本的过程。此外,模板方法模式里面也可以有可选的设置钩子,比如现在想记录办理完业务完取卡离开的时间,我们就可以在超类中添加一个钩子,此时父类如下:
package designPattern.test.templet; import java.util.Date; public abstract class ATMBusiness { public final void run() { atmOn(); doBusiness(); atmOff(); } // 具体业务,子类实现 public abstract void doBusiness(); private void atmOn() { System.out.println("插卡"); } // private void atmOff() { // System.out.println("取卡走人"); // } private void atmOff() { if (isPrintOffTime()) { System.out.println("取卡时间--"+new Date().toString()); } System.out.println("取卡走人"); } /** * 是否打印取卡离开时间 */ public boolean isPrintOffTime() { return false; } }
父类中添加了一个isPrintOffTime方法,且默认返回false,不打印时间。如果某子类需要调用打印时间,可以复写钩子方法,返回true,比如要打印离开时间就复写这个方法返回true:
package designPattern.test.templet; public class MoneyATMBusiness extends ATMBusiness{ @Override public void doBusiness() { System.out.println("取钱业务"); } @Override public boolean isPrintOffTime() { return true; } }
运行结果:
插卡
取钱业务
取卡时间--Mon Aug 07 09:12:59 CST 2017
取卡走人
关于钩子,超类中可提供默认实现或者空实现,子类可覆盖或者不覆盖,具体就根据实际需求来定了。