模板方法模式是准备一个抽象类,将部分逻辑以具体方法以及构造子的形式出现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑部分有不同的实现。这也是模板方法模式的用意。
模板方法模式是基于继承的代码复用的基本技术。
1. 结构
模板方法模式的静态结构如下:
涉及到的角色如下:
抽象模板角色(AbstractClass):其责任主要如下
(1)定义了一个或多个抽象操作,以便让子类实现。这些操作叫做基本操作,它们是一个顶级逻辑的组成步骤。
(2)定义并实现了一个模板方法。这个模板方法一般是一个具体方法,它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级逻辑也有可能调用一些具体方法。
具体模板(ConcretrClass)角色:其责任如下
(1)实现父类所定义的一个或多个抽象方法,它们是一个顶级逻辑的组成步骤。
(2)每一个抽象模板角色都可以有任意多个具体模板角色与之对应,而每一个具体模板角色都可以给出这些抽象方法的不同实现,从而使得顶级逻辑的实现各不相同。
代码如下:
package cn.qlq.teplate; public abstract class AbstractClass { public void templateMethod() { // 处理前 doOperation1(); doOperation2(); doOperation3(); // 处理后 } void doOperation1() { System.out.println("doOperation1默认实现"); } abstract void doOperation2(); abstract void doOperation3(); }
doOperation1(),doOperation2(),doOperation3()等基本方法是顶级逻辑的组成步骤。这个顶级逻辑由templateMethod方法代表。抽象类提供了doOperation1的默认实现,doOperation2和doOperation3交给子类去实现。
package cn.qlq.teplate; public class ConcreteClass1 extends AbstractClass { @Override void doOperation2() { System.out.println("ConcreteClass1 doOperation2"); } @Override void doOperation3() { System.out.println("ConcreteClass1 doOperation3"); } }
package cn.qlq.teplate; public class ConcreteClass2 extends AbstractClass { @Override void doOperation1() { System.out.println("ConcreteClass2 doOperation1"); } @Override void doOperation2() { System.out.println("ConcreteClass2 doOperation2"); } @Override void doOperation3() { System.out.println("ConcreteClass2 doOperation3"); } }
客户端代码:
package cn.qlq.teplate; public class Client { public static void main(String[] args) { AbstractClass abstractClass = new ConcreteClass2(); abstractClass.templateMethod(); } }
结果:
ConcreteClass2 doOperation1
ConcreteClass2 doOperation2
ConcreteClass2 doOperation3
2. 例子
一个银行计息的例子。假设银行需要两种存款账号,货币市场账号(MoneyMarket)和定期(Certificate of Deposite)存款账号。这个系统的总行为是计算出利息,这也就决定了作为模板方法的顶级逻辑应当是利息计算。利息计算涉及两步:一是确定账号类型、二是确定利息的百分比。
结构图如下:
模板方法模式的实现方法是从上向下的,也就是说需要先给出顶级的逻辑,然后给出具体步骤的逻辑。
代码如下:
package cn.qlq.template; public abstract class Account { protected String accountNumber; public Account(String accountNumber) { super(); this.accountNumber = accountNumber; } public final double calculateInterest() { double interestRate = doCalculateInterestRate(); String accountType = doCalculateAccountType(); double amount = doCalculateAmount(accountType, accountNumber); return amount * interestRate; } abstract double doCalculateAmount(String accountType, String accountNumber2); abstract String doCalculateAccountType(); abstract double doCalculateInterestRate(); }
package cn.qlq.template; public class CDAccount extends Account { public CDAccount(String accountNumber) { super(accountNumber); } @Override double doCalculateAmount(String accountType, String accountNumber2) { // 模拟从数据库查出数据 return 50D; } @Override String doCalculateAccountType() { return "CDAccount"; } @Override double doCalculateInterestRate() { return 0.048D; } }
package cn.qlq.template; public class MoneyMarket extends Account { public MoneyMarket(String accountNumber) { super(accountNumber); } @Override double doCalculateAmount(String accountType, String accountNumber2) { // 模拟从数据库查出数据 return 50D; } @Override String doCalculateAccountType() { return "Money Market"; } @Override double doCalculateInterestRate() { return 0.045D; } }
客户端代码:
package cn.qlq.template; public class Client { public static void main(String[] args) { Account account = new CDAccount("123"); double calculateInterest = account.calculateInterest(); System.out.println(calculateInterest); Account moneyMarket = new MoneyMarket("123"); double moneyMarketInterest = moneyMarket.calculateInterest(); System.out.println(moneyMarketInterest); } }
3.小结
1.继承作为复用的工具
(1)首先,初学Java的不知道什么是继承,或者认为继承是高深的工具。这样设计大部分的功能是通过委派进行的。
(2)慢慢的发现继承并不难,并且初步认识到继承可以使子类一下子得到父类的行为。于是试图将继承作为功能复用的主要工具,并把原来应当使用委派的地方改为使用继承,这时候就造成继承的滥用。
(3)设计中也提倡使用委派关系代替继承。比如状态模式、策略模式、装饰模式、桥梁模式等。
(4)事实上封装、继承、多态和抽象化并称为面向对象的特性。所以应当合理的使用继承。
2. Java语言的模板方法模式
HttpServlet技术就使用了模板方法模式。HttpServlet提供了一个service()方法,这个方法调用7个do方法中的一个或几个,完成对客户端调用的处理。
3.模板方法模式中的方法
模板方法中的方法可以分为两类:模板方法和基本方法。
模板方法:
一般是定义在抽象类中,把基本方法组合在一起形成一个总的方法或者行为的方法。这个方法一般定义在抽象类中并且由子类不加以修改地完全继承下来。
一个抽象类可以有任意多个模板方法,而不限于一个。每一个模板方法都可以调用任意多个具体方法。
基本方法:
基本方法又分为三种:抽象方法、具体方法和钩子方法
(1)抽象方法:由抽象类声明并由具体子类实现
(2)具体方法:由抽象类声明并实现,子类不实现或置换。有些具体方法可以起到工厂方法的作用,这样的具体方法又叫做工厂方法。
(3)钩子方法:一个钩子方法由抽象类声明并实现,而子类会加以扩展。通常抽象类给出的实现是一个空实现,作为方法的默认实现(在缺省适配器模式也见过)。
注意命名规则:
钩子方法的名字应当以do开始,这是熟悉设计模式的Java程序设计师的标准做法。在HttpServlet中也遵循这一规则。
总结:
意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
主要解决:一些方法通用,却在每一个子类都重新写了这一方法。
何时使用:有一些通用的方法。
如何解决:将这些通用算法抽象出来。
关键代码:在抽象类实现,其他步骤在子类实现。
应用实例: 1、在造房子的时候,地基、走线、水管都一样,只有在建筑的后期才有加壁橱加栅栏等差异。 2、西游记里面菩萨定好的 81 难,这就是一个顶层的逻辑骨架。 3、spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存。
优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。
缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
使用场景: 1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。
注意事项:为防止恶意操作,一般模板方法都加上 final 关键词