模板方法模式(模板方法设计模式)详解
在面向对象编程的过程中,程序员经常会遇到这样的情况:在设计一个系统的时候,他们知道算法需要的关键步骤,并且确定了这些步骤的执行顺序,但是一些步骤的具体实现呢?尚不清楚,或者某些步骤的实现与具体环境有关。
例如去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行人员进行分级方法,其中取号、排队、分级银行staff 业务对每个客户都是一样的,可以在父类中实现,但具体处理的业务因人而异。可能是存款、取款或转账等方法,可以延迟到子类。
生活中这样的例子很多,比如一个人每天都会起床、吃饭、做事、睡觉等,而“做事”的内容可能每天都不一样。我们将这些指定流程或格式的实例定义为模板,允许用户根据自己的需要进行更新,例如简历模板、论文模板、Word中的模板文件等。
下面描述的模板方法模式将解决上面类似的问题。
模式的定义和特点模板方法()模式的定义如下:在一个操作中定义算法骨架,将算法的一些步骤延迟到子类,使子类不能改变算法结构重新定义算法的一些具体步骤。这是一种类型的行为模式。
这种模式的主要优点如下。它封装了不可变部分并扩展了可变部分。它将被认为是不变部分的算法封装到父类中,通过继承子类的方式实现可变部分算法,使子类可以继续扩展。提取父类中代码的公共部分,方便代码复用。有些方法是由子类实现的,子类可以通过扩展添加相应的功能,符合开闭原则。
这种模式的主要缺点如下。每个不同的实现都需要定义一个子类,这会导致类的数量增加,系统更大,设计更抽象,间接增加了系统实现的复杂度。父类中的抽象方法由子类实现,子类的执行结果会影响父类的结果,从而导致反向控制结构,使代码更难阅读。由于继承关系本身的缺点,如果父类增加了新的抽象方法,所有的子类都必须重新更改。模式的结构和模板方法模式的实现需要注意抽象类和具体子类之间的协作。它使用了虚函数的多态技术和“别叫我,让我叫你”的逆向控制技术。现在让我们介绍一下它们的基本结构。
1.该模式的结构模板方法模式包含以下主要作用。
1)抽象类/抽象模板(Class) 抽象模板类负责给出一个算法的大纲和骨架。它由一个模板方法和几个基本方法组成。这些方法的定义如下。
①模板方法:定义算法的骨架,并按一定的顺序调用其中包含的基本方法。
②基本方法:是整个算法中的一个步骤,包括以下几种。 2)具体子类/具体实现(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】使用模板方法模式实现留学设计流程。
分析:出国留学的程序一般经过以下流程:索取学校信息、提交入学申请、申请私人护照、出境卡和公证、申请签证、体检、预订机票、准备行李,到达目标学校等,有些业务对于每个学校都是一样的,但是有些业务对于不同的学校是不同的,所以比较适合使用模板方法模式来实现。
在这个例子中,我们首先定义了一个留学抽象类,其中包含一个模板方法(),其中包含了留学程序中的基本方法,其中一些由于方法的处理是一样的在所有国家都可以在抽象类中实现,但是有些方法的处理在每个国家是不同的,必须在其具体的子类中实现(比如美国留学类)。如果你添加另一个国家,你只需要添加一个子类。图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)了解校园及周边环境。
模式的应用场景 模板方法模式通常适用于以下场景。算法的整体步骤是非常固定的,但是当个别部分是可变的时,此时可以使用模板方法模式将易变的部分抽象出来,供子类实现。当多个子类有共同的行为时,可以将它们提取并集中到一个共同的父类中,避免代码重复。首先,识别现有代码中的差异并将它们分离到新的操作中。最后,用调用这些新操作的模板方法替换不同的代码。当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,从而只允许在这些点进行扩展。模式的扩展 在模板方法模式中,基本方法包括:抽象方法、具体方法和钩子方法。正确使用“钩子方法”可以使子类控制超类的行为。如下例所示,可以通过重写具体子类中的钩子方法()和()来改变抽象父类中的操作结果。结构图如图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的实现被调用...
如果钩子方法()和钩子方法()的代码改变了,那么程序运行的结果也会改变。进阶阅读如果想进一步了解模板方法模式,可以阅读下面的文章。