模板方法模式
定义一个算法的框架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法结构即可重定义该算法的某些特定步骤,是一种行为对象行为型模式。
上述代码存在如下问题
(1)系统的扩展性差,如果需要增加一种新型类型的用户,那就要修改Account类的源代码,在calculateInsterest()方法中增加新的判断逻辑和实现代码,违背了开闭原则
(2)客户端需要逐个调用Account类中定义的方法,而且需要了解这些方法的执行次序,比如validate ->calculateInsterest ->display,如果不按照次序调用,可能会导致结果的出错,这无疑增加了客户端使用难度,给用户带来较多不便。
解决方案
- 针对上述问题(1)可以增加Account类的子类来解决,子类覆盖父类的calculateInsterest ,实现对系统扩展
- 使用模板方法模式
结构图
(1)AbstractClass(抽象类)
在抽象类中定义一系列基本操作(private operation),这些基本操作可以是具体也可以是抽象,每一个基本操作对应算法的一个步骤,可以在其子类中实现或者重定义这些步骤,同时在抽象类中实现了一个模板方法,用于定义一个算法的框架**(模板方法)**,模板方法不仅可以调用抽象类中实现的方法,也可以调用抽象类子类实现的基本的方法,还可以调用其他对象中的方法
(2)ConcreteClass(具体子类)抽象类的子类,用于实现父类中的抽象方法,也可以覆盖已经实现的具体操作。
模板方法
定义在抽象类中,把基本操作组合在一起,形成一个总算法或者总行为的方法,这个模板定义在抽象类中,并在子类中不加以修饰地完全继承了下来,模板方法也是一个具体的方法,给出了一个顶层逻辑,而逻辑的组成步骤在抽象类中是具体的方法,模板方法模式的抽象层只能是抽象类而不是接口。
基本方法
基本方法是实现各个步骤的算法,模板方法的组成部分,基本方法有可以分成3种,抽象方法,具体方法,钩子方法
抽象方法
一个抽象方法由抽象类声明,由其子类实现,java中以abstract关键字命名抽象方法
具体方法
一个具体方法由一个抽象类或具体类声明实现,其子类可以直接进行覆盖也可以直接继承
钩子方法
一个钩子方法由一个抽象类或者具体类声明并实现,而子类可能会增加扩展。通常在父类中给出的实现就是一个空实现,并以该空实现作为方法的默认实现,当然钩子方法也可以提供一个非空的默认实现。
一般情况下有两类
(1)可以与具体步骤挂钩,以实现在不同条件下执行模板方法中的不同步骤,这类钩子方法返回类型通常是boolean类型的,这类方法名一般为isxxx,用于对某个条件进行判断,如果条件满足则执行某一步骤,代码片段如下
isPrint就是钩子方法,它可以决定是否打印,一般情况下钩子方法返回true,如果不希望某方法的执行,可以在子类重写,将其的返回值改成false,可以对一个算法进行控制。
(2)实现体为空的具体方法,子类可以根据需要覆盖或者继承钩子方法,与抽象方法相比,这类钩子方法的好处在于子类如果没有覆盖父类中定义的钩子方法,编译可以正常通过,但是如果覆盖父类中生命的抽象方法,编译将报错
根据依赖倒转原则,父类的钩子方法被子类覆盖,从而实现对父类的行为的控制
完整的解决方案
package com.learn.designmode.mode.template.demo;
/**
* 账户类
*/
public abstract class Account {
/** 基本方法-具体方法
* @param account
* @param password
* @return
*/
public boolean validate(String account,String password){
System.out.println("账号" + account);
System.out.println("密码" + password);
if ("张无忌".equals(account) && "123456".equals(password)){
return true;
}else {
return false;
}
}
/**
* 基本方法-抽象方法
*/
public abstract void calculateInterest();
public void display(){
System.out.println("显示利息");
}
/** 模板方法
* @param account
* @param password
*/
public void handle(String account,String password){
if (!validate(account,password)){
System.out.println("账号密码错误");
return;
}
calculateInterest();
display();
}
}
/**
* 活期账户类:具体子类
*/
class CurrentAccount extends Account{
@Override
public void calculateInterest() {
System.out.println("按活期利率计算利息");
}
}
/**
* 定期账户类:具体子类
*/
class SavingAccount extends Account{
@Override
public void calculateInterest() {
System.out.println("按定期利率计算利息");
}
}
package com.learn.designmode.mode.template.demo;
import com.learn.designmode.utils.XMLUtil;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
public class Client {
public static void main(String[] args) throws SAXException, IllegalAccessException, IOException, InstantiationException, ParserConfigurationException, ClassNotFoundException {
Account account = (Account) XMLUtil.getBean("template");
account.handle("张无忌","123456");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<config>
<chartType>
A
</chartType>
<ImageImplclassName>com.learn.designmode.mode.birdge.imageImpl.LinuxImageImp</ImageImplclassName>
<ImageclassName>com.learn.designmode.mode.birdge.baseimage.GIFImage</ImageclassName>
<className>com.learn.designmode.mode.adapter.OperationAdpater</className>
<facade>com.learn.designmode.mode.facade.abstractFacade.NewEncryptFacade</facade>
<proxy>com.learn.designmode.mode.proxy.demo.ProxySearcher</proxy>
<command1>com.learn.designmode.mode.command.demo.HelpCommand</command1>
<command2>com.learn.designmode.mode.command.demo.WindowsCommand</command2>
<strategy>com.learn.designmode.mode.strategy.demo.StudentDiscount</strategy>
<template>com.learn.designmode.mode.template.demo.SavingAccount</template>
</config>
钩子方法的使用
钩子方法的使得子类可以控制父类的行为,最简单的实现就是空方法
package com.learn.designmode.mode.template.demo2;
public abstract class DataViewer {
/**
* 抽象方法:获取数据
*/
public abstract void getData();
/**
* 具体方法:转换器
*/
public void converData(){
System.out.println("将数据转换为XML格式");
}
/**
* 抽象方法:显示数据
*/
public abstract void displayData();
/** 钩子方法,判断是否为XML格式的数据
*
* @return
*/
public boolean isNotXMLData(){
return true;
}
/**
* 模板方法
*/
public void process(){
getData();
if (isNotXMLData()){
converData();
}
displayData();
}
}
package com.learn.designmode.mode.template.demo2;
public class SubDataViewer extends DataViewer {
@Override
public void getData() {
System.out.println("从XML文件获取数据");
}
@Override
public void displayData() {
System.out.println("以柱状图显示");
}
@Override
public boolean isNotXMLData(){
return false;
}
}
package com.learn.designmode.mode.template.demo2;
public class Client {
public static void main(String[] args) {
DataViewer dataViewer = new SubDataViewer();
dataViewer.process();
}
}
总结
优点
(1)模板方法在父类中形式化定义一个算法,而由他的子类来实现细节的处理,子类中实现详细的算法时不会改变算法中的步骤。
(2)模板方法模式是一种代码复用技术,它在类库设计尤为重要,提取了类中公共行为,将公共行为方法在父类中,而通过其子类实现不同的行为,它鼓励恰当的使用继承来实现代码复用
(3)模板方法模式可实现一种反向控制结构,通过子类覆盖父类的钩子方法来决定某一特定步骤是否需要执行
(4)通过子类覆盖父类的基本方法,不同的子类可以提供基本方法不同的实现,更换和增加新的子类很方便,符合单一职责和开闭原则。
缺点
需要为每一个基本方法的不同实现提供一个子类,如果父类中可变的基本方法太多,将导致类的个数增加,系统设计庞大,设计更加抽象,此时可以结合桥接模式。
适用场景
(1)对一些复杂的算法进行分割,将其算法中固定不变的部分设计为模板方法和父类具体方法,将一些可以改变的细节由于子类实现。
(2)各个子类公共的行为应被提取出来并集中到一个公共父类中避免代码重复
(3)需要通过子类来决定父类的模板方法某个步骤是否执行,实现对子类对父类的反向控制。