模板方法模式(模板方法设计模式)详解

在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。

例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。

这样的例子在生活中还有很多,例如,一个人每天会起床、吃饭、做事、睡觉等,其中“做事”的内容每天可能不同。我们把这些规定了流程或格式的实例定义成模板,允许使用者根据自己的需求去更新它,例如,简历模板、论文模板、Word 中模板文件等。

以下介绍的模板方法模式将解决以上类似的问题。

模式的定义与特点

模板方法(Template Method)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。

该模式的主要优点如下。

  1. 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
  2. 它在父类中提取了公共的部分代码,便于代码复用。
  3. 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。

该模式的主要缺点如下。

  1. 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。
  2. 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
  3. 由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。

模式的结构与实现

模板方法模式需要注意抽象类与具体子类之间的协作。它用到了虚函数的多态性技术以及“不用调用我,让我来调用你”的反向控制技术。现在来介绍它们的基本结构。

1. 模式的结构

模板方法模式包含以下主要角色。

1)抽象类/抽象模板(Abstract Class)

抽象模板类,负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。

① 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。

② 基本方法:是整个算法中的一个步骤,包含以下几种类型。

  • 抽象方法:在抽象类中声明,由具体子类实现。
  • 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
  • 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。

2)具体子类/具体实现(Concrete Class)

具体实现类,实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。

模板方法模式的结构图如图 1 所示。


图1 模板方法模式的结构图

2. 模式的实现

模板方法模式的代码如下:

public class TemplateMethodPattern {  public static void main(String[] args) {     AbstractClass tm = new ConcreteClass();    tm.TemplateMethod();    }
}//抽象类
abstract class AbstractClass {  //模板方法   public void TemplateMethod() {   SpecificMethod();     abstractMethod1();   abstractMethod2();  }   //具体方法  public void SpecificMethod() {    System.out.println("抽象类中的具体方法被调用..."); }   //抽象方法1   public abstract void abstractMethod1(); //抽象方法2 public abstract void abstractMethod2();
}//具体子类
class ConcreteClass extends AbstractClass { public void abstractMethod1() {        System.out.println("抽象方法1的实现被调用..."); }    public void abstractMethod2() {   System.out.println("抽象方法2的实现被调用...");  }
}

程序的运行结果如下:

抽象类中的具体方法被调用...
抽象方法1的实现被调用...
抽象方法2的实现被调用...

模式的应用实例

【例1】用模板方法模式实现出国留学手续设计程序。

分析:出国留学手续一般经过以下流程:索取学校资料,提出入学申请,办理因私出国护照、出境卡和公证,申请签证,体检、订机票、准备行装,抵达目标学校等,其中有些业务对各个学校是一样的,但有些业务因学校不同而不同,所以比较适合用模板方法模式来实现。

在本实例中,我们先定义一个出国留学的抽象类 StudyAbroad,里面包含了一个模板方法 TemplateMethod(),该方法中包含了办理出国留学手续流程中的各个基本方法,其中有些方法的处理由于各国都一样,所以在抽象类中就可以实现,但有些方法的处理各国是不同的,必须在其具体子类(如美国留学类 StudyInAmerica)中实现。如果再增加一个国家,只要增加一个子类就可以了,图 2 所示是其结构图。


图2 出国留学手续设计程序的结构图

程序代码如下:

public class StudyAbroadProcess {  public static void main(String[] args) {     StudyAbroad tm = new StudyInAmerica();    tm.TemplateMethod();   }
}
//抽象类: 出国留学
abstract class StudyAbroad {   public void TemplateMethod() //模板方法    {     LookingForSchool(); //索取学校资料      ApplyForEnrol();    //入学申请       ApplyForPassport(); //办理因私出国护照、出境卡和公证      ApplyForVisa();     //申请签证      ReadyGoAbroad();    //体检、订机票、准备行装   Arriving();         //抵达   }    public void ApplyForPassport() {     System.out.println("三.办理因私出国护照、出境卡和公证:");     System.out.println("  1)持录取通知书、本人户口簿或身份证向户口所在地公安机关申请办理因私出国护照和出境卡。");     System.out.println("  2)办理出生公证书,学历、学位和成绩公证,经历证书,亲属关系公证,经济担保公证。");  }   public void ApplyForVisa() {     System.out.println("四.申请签证:"); System.out.println("  1)准备申请国外境签证所需的各种资料,包括个人学历、成绩单、工作经历的证明;个人及家庭收入、资金和财产证明;家庭成员的关系证明等;");    System.out.println("  2)向拟留学国家驻华使(领)馆申请入境签证。申请时需按要求填写有关表格,递交必需的证明材料,缴纳签证。有的国家(比如美国、英国、加拿大等)在申请签证时会要求申请人前往使(领)馆进行面试。");    }   public void ReadyGoAbroad() {      System.out.println("五.体检、订机票、准备行装:");     System.out.println("  1)进行身体检查、免疫检查和接种传染病疫苗;");  System.out.println("  2)确定机票时间、航班和转机地点。"); }   public abstract void LookingForSchool();//索取学校资料   public abstract void ApplyForEnrol();   //入学申请  public abstract void Arriving();        //抵达
}//具体子类: 美国留学
class StudyInAmerica extends StudyAbroad {  @Override  public void LookingForSchool() {  System.out.println("一.索取学校以下资料:");     System.out.println("  1)对留学意向国家的政治、经济、文化背景和教育体制、学术水平进行较为全面的了解;");        System.out.println("  2)全面了解和掌握国外学校的情况,包括历史、学费、学制、专业、师资配备、教学设施、学术地位、学生人数等;");       System.out.println("  3)了解该学校的住宿、交通、医疗保险情况如何;");     System.out.println("  4)该学校在中国是否有授权代理招生的留学中介公司?");       System.out.println("  5)掌握留学签证情况;");      System.out.println("  6)该国政府是否允许留学生合法打工?");      System.out.println("  8)毕业之后可否移民?");   System.out.println("  9)文凭是否受到我国认可?");   }   @Override    public void ApplyForEnrol() {     System.out.println("二.入学申请:");      System.out.println("  1)填写报名表;");      System.out.println("  2)将报名表、个人学历证明、最近的学习成绩单、推荐信、个人简历、托福或雅思语言考试成绩单等资料寄往所申请的学校;");       System.out.println("  3)为了给签证办理留有充裕的时间,建议越早申请越好,一般提前1年就比较从容。");    }    @Override   public void Arriving() {     System.out.println("六.抵达目标学校:");    System.out.println("  1)安排住宿;");      System.out.println("  2)了解校园及周边环境。");   }
}

程序的运行结果如下:

一.索取学校以下资料:1)对留学意向国家的政治、经济、文化背景和教育体制、学术水平进行较为全面的了解;2)全面了解和掌握国外学校的情况,包括历史、学费、学制、专业、师资配备、教学设施、学术地位、学生人数等;3)了解该学校的住宿、交通、医疗保险情况如何;4)该学校在中国是否有授权代理招生的留学中介公司?5)掌握留学签证情况;6)该国政府是否允许留学生合法打工?8)毕业之后可否移民?9)文凭是否受到我国认可?
二.入学申请:1)填写报名表;2)将报名表、个人学历证明、最近的学习成绩单、推荐信、个人简历、托福或雅思语言考试成绩单等资料寄往所申请的学校;3)为了给签证办理留有充裕的时间,建议越早申请越好,一般提前1年就比较从容。
三.办理因私出国护照、出境卡和公证:1)持录取通知书、本人户口簿或身份证向户口所在地公安机关申请办理因私出国护照和出境卡。2)办理出生公证书,学历、学位和成绩公证,经历证书,亲属关系公证,经济担保公证。
四.申请签证:1)准备申请国外境签证所需的各种资料,包括个人学历、成绩单、工作经历的证明;个人及家庭收入、资金和财产证明;家庭成员的关系证明等;2)向拟留学国家驻华使(领)馆申请入境签证。申请时需按要求填写有关表格,递交必需的证明材料,缴纳签证。有的国家(比如美国、英国、加拿大等)在申请签证时会要求申请人前往使(领)馆进行面试。
五.体检、订机票、准备行装:1)进行身体检查、免疫检查和接种传染病疫苗;2)确定机票时间、航班和转机地点。
六.抵达目标学校:1)安排住宿;2)了解校园及周边环境。

模式的应用场景

模板方法模式通常适用于以下场景。

  1. 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
  2. 当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
  3. 当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。

模式的扩展

在模板方法模式中,基本方法包含:抽象方法、具体方法和钩子方法,正确使用“钩子方法”可以使得子类控制父类的行为。如下面例子中,可以通过在具体子类中重写钩子方法 HookMethod1() 和 HookMethod2() 来改变抽象父类中的运行结果,其结构图如图 3 所示。


图3 含钩子方法的模板方法模式的结构图

程序代码如下:

public class HookTemplateMethod {   public static void main(String[] args) {     HookAbstractClass tm = new HookConcreteClass();    tm.TemplateMethod();   }
}//含钩子方法的抽象类
abstract class HookAbstractClass {   //模板方法   public void TemplateMethod() {      abstractMethod1();   HookMethod1();   if (HookMethod2()) {     SpecificMethod();   }        abstractMethod2();  }   //具体方法public void SpecificMethod() {    System.out.println("抽象类中的具体方法被调用...");   }   //钩子方法1  public void HookMethod1() {   }    //钩子方法2 public boolean HookMethod2() {    return true;   }  //抽象方法1  public abstract void abstractMethod1();//抽象方法2  public abstract void abstractMethod2();
}//含钩子方法的具体子类
class HookConcreteClass extends HookAbstractClass { public void abstractMethod1() {       System.out.println("抽象方法1的实现被调用...");  }    public void abstractMethod2() {    System.out.println("抽象方法2的实现被调用...");  }    public void HookMethod1() {  System.out.println("钩子方法1被重写...");   }    public boolean HookMethod2() {    return false;  }
}

程序的运行结果如下:

抽象方法1的实现被调用...
钩子方法1被重写...
抽象方法2的实现被调用...
    System.out.println("抽象方法2的实现被调用...");
}    public void HookMethod1() {  System.out.println("钩子方法1被重写...");
}    public boolean HookMethod2() {    return false;
}

}


程序的运行结果如下:

抽象方法1的实现被调用…
钩子方法1被重写…
抽象方法2的实现被调用…


如果钩子方法 HookMethod1() 和钩子方法 HookMethod2() 的代码改变,则程序的运行结果也会改变。

模板方法模式(模板方法设计模式)详解——小马同学@Tian相关推荐

  1. 装饰模式(装饰设计模式)详解——小马同学@Tian

    装饰模式(装饰设计模式)详解 上班族大多都有睡懒觉的习惯,每天早上上班时间都很紧张,于是很多人为了多睡一会,就会用方便的方式解决早餐问题.有些人早餐可能会吃煎饼,煎饼中可以加鸡蛋,也可以加香肠,但是不 ...

  2. 责任链模式(职责链模式)详解——小马同学@Tian

    责任链模式(职责链模式)详解 在现实生活中,一个事件需要经过多个对象处理是很常见的场景.例如,采购审批流程.请假流程等.公司员工请假,可批假的领导有部门负责人.副总经理.总经理等,但每个领导能批准的天 ...

  3. 原型模式(设计模式详解)

    原型模式 描述 实现 使用场景 描述 原型模式(Prototype Pattern)是一种创建型设计模式,它允许通过复制现有对象来创建新对象,而无需从头开始编写代码. 在原型模式中,一个原型对象作为模 ...

  4. 《设计模式详解》行为型模式 - 模板方法模式

    模板方法模式 6.1 模板方法模式 6.1.1 概述 6.1.2 结构 6.1.3 案例实现 6.1.3 优缺点 6.1.4 适用场景 6.1.5 JDK源码 - InputStream 完整的笔记目 ...

  5. 《设计模式详解》行为型模式 - 观察者模式

    观察者模式 6.6 观察者模式 6.6.1 概述 6.6.2 结构 6.6.3 案例实现 6.6.4 优缺点 6.6.5 使用场景 6.6.6 JDK 提供的实现 - Observable 示例 完整 ...

  6. 《设计模式详解》行为型模式 - 职责链模式

    职责链模式 6.4 职责链模式 6.4.1 概述 6.4.2 结构 6.4.3 案例实现 6.4.4 优缺点 6.4.5 JavaWeb 源码 - FilterChain 完整的笔记目录:<设计 ...

  7. 《设计模式详解》行为型模式 - 命令模式

    命令模式 6.3 命令模式 6.3.1 概述 6.3.2 结构 6.3.3 案例实现 6.3.4 优缺点 6.3.5 使用场景 6.3.6 JDK 源码 - Runnable 完整的笔记目录:< ...

  8. 《设计模式详解》行为型模式 - 解释器模式

    解释器模式 6.11 解释器模式 6.11.1 概述 6.11.2 结构 6.11.3 案例实现 6.11.4 优缺点 6.11.5 使用场景 完整的笔记目录:<设计模式详解>笔记目录,欢 ...

  9. 《设计模式详解》行为型模式 - 备忘录模式

    备忘录模式 6.10 备忘录模式 6.10.1 概述 6.10.2 结构 6.10.3 案例实现 "白箱" 备忘录模式 "黑箱" 备忘录模式 6.10.4 优缺 ...

  10. 《设计模式详解》行为型模式 - 状态模式

    状态模式 6.5 状态模式 6.5.1 反例 6.5.2 结构 6.5.3 案例实现 6.5.4 优缺点 6.5.5 使用场景 完整的笔记目录:<设计模式详解>笔记目录,欢迎指点! 行为型 ...

最新文章

  1. 关于学习TensorFlow推荐的一些资源及使用方式
  2. Microsoft Visual C++ 14.0 is required解决方法
  3. Spring Boot 2.x基础教程:JSR-303实现请求参数校验
  4. 全面支持开源,微软加速 Visual Studio 和 Azure DevOps 云升级
  5. D. Multiset(树状数组 + 二分)
  6. HTML+CSS+JS实现 ❤️酷炫HUD科幻数据屏幕动画界面❤️
  7. webstorm两个文件比对_webstorm怎么更改文件名称?或者是重命名
  8. IterableThread
  9. 统计学习方法第二版知识点合集 - 自用笔记
  10. 计算机软件研发的相关会计分录,研发支出资本化的相关会计分录
  11. html中日期怎么显示,浅谈在网页上显示日期的两种方法_html
  12. #459 津津有味:北方人对饺子的痴迷可能是刻进骨子里的
  13. 文件上传利器SWFUpload使用
  14. python用turtle画小猪佩奇_python 用turtle 画小猪佩奇
  15. Snort - manual 笔记(二)
  16. Android接入高德地图SDK,Android高德SDK 地图篇一:集成高德SDK
  17. codeforces 1238
  18. 罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。例如, 罗马数字 2 写做 II ,即为两个并列的 1
  19. 武林外传手游怎么在电脑上玩,武林外传安卓模拟器电脑版教程
  20. HDU 1051 Wooden Sticks 造木棍【贪心】

热门文章

  1. 算法设计与分析(第二版)上机实验题——Java实现
  2. Cocos2d-X开发中国象棋《八》走棋
  3. A8. 无人机编队飞行定位分析与讨论-大结局
  4. 《图书管理系统》毕业论文
  5. 【持续更新】高通蓝牙SoC系列文章索引
  6. vscode-代码文件对比
  7. vs2010断点调试详细教程
  8. China Pub 高清书籍6CD 包含的图书目录
  9. 记录windows游戏编程——1
  10. 渗透测试入门—— 常见术语概述