引言
激情火热的世界杯已经进行到了四分之一决赛,相信各位球迷都已熬夜熬的心肝脾肺肾俱虚。一场足球比赛包括以下几个部分:上半场比赛,中场休息,下半场,加时赛,点球大战。这五部分构成了一场完整的杯赛比赛,这是现行规则下的固定套路,不会因为其他因素而改变。将足球比赛转换成Java代码如下:
public abstract class WorldCupMatch {
/**
* 比赛流程,不可更改,是程序的顶级执行逻辑
* 注意是final修饰的
*/
public final void match() {
//上半场比赛
firstHalf();
//中场休息
halfTime();
//下半场
secondHalf();
//加时赛
overtime();
//点球大战
penalty();
}
//不同比赛的上半场比赛是不同的,留给具体的比赛去实现
public abstract void firstHalf();
//不同的比赛下半场比赛是不同的,留给具体的比赛区实现
public abstract void secondHalf();
//所有的比赛都要中场休息,中场休息的行为是相同的
public void halfTime() {
System.out.println("中场休息15分钟,双方球员返回更衣室休息!");
}
//不是所有的比赛都有加时赛,如果没有加时赛的比赛就不需要实现overtime(),由父类给出一个默认实现
public void overtime() {
System.out.println("90分钟结束战斗,无需加时,洗洗睡!");
}
//不是所有的比赛都有点球大战,如果没有点球大战的比赛就不需要实现penalty(),由父类给出一个默认实现
public void penalty() {
System.out.println("已经分出胜负,无需残酷的点球大战,洗洗睡!");
}
}
一场比赛要遵循FIFA的规定,具体的比赛必须要完成上半场和下半场,中场休息也是固定的套路,但是一场比赛可能没有加时赛和点球大战,至于加时和点球则要根据实际情况,下面以阿根廷VS瑞士为例:
class ArgVSSu extends WorldCupMatch {
/**
* 比赛的组成部分,必须由子类实现
*/
public void firstHalf() {
System.out.println("上半场双方比较平稳,毫无亮点");
}
/**
* 比赛的组成部分,必须由子类实现
*/
public void secondHalf() {
System.out.println("下半场阿根廷攻势狂暴,可惜中锋不给力");
}
/**
* 有父类实现的hook方法,子类可以改变其行为
*/
public void overtime() {
System.out.println("118分钟时梅西助攻迪玛利亚破门,阿根廷取胜");
}
}
模板方法模式
在上面的两段代码中有如下鲜明的特点:
(1)父类WorldCupMatch规定了程序执行的逻辑,也就是match()方法,它定义了一个算法的步骤,每一个步骤都被一个方法所代表,允许子类为一个或多个步骤提供实现。
(2)父类将由子类实现的方法设置为abstract,将这些具体行为推迟到子类中实现,子类也必须实现这些步骤;
(3)父类对某些步骤提供了空实现或者默认实现,这就给子类选择的余地,是否覆盖父类的行为完全由子类决定。这些由父类默认实现的方法叫做钩子方法(hook)。
将上面的三个特点再进行抽象,就得到了一个应用极广的设计模式,模板方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。在上面的代码中,match()就是一个模板方法,这也是这个设计模式的名字由来。这个模式涉及两个角色:
(1)抽象模板角色:定义并实现一个模板方法,它给出一个顶级逻辑的骨架,顶级逻辑的组成步骤放在相应的抽象操作中;定义组成逻辑步骤的方法,需要推迟到子类实现的定义为抽象方法,还可以提供默认实现的钩子方法,当然了也可提供具体方法;
(2)具体模板角色:实现父类定义的抽象方法,根据实际情况,还可以覆盖父类的钩子方法,这样在不改变算法逻辑的情况下给出不同的实现。
上面的例子已经很能代表模板方法模式了,还是再给出一个示意性代码:
abstract class AbstractTemplate {
/**
*模板方法
*/
public void templateMethod() {
//调用基本方法,由子类实现
doOperation1();
doOperation2();
//自己实现的基本方法
doOperation3();
}
//抽象方法,推迟到子类实现
public abstract void doOperation1();
public abstract void doOperation2();
//钩子方法
public void doOperation3() {
//实现代码
}
}
class ConcreteTemplate extends AbstractTemplate {
//必须实现父类中的抽象方法,逻辑的组成部分
public void doOperation1() {
//实现代码
}
public void doOperation2() {
//实现代码
}
//可以有选择的覆盖父类钩子方法
//public void doOperation3(){ 实现代码}
}
好莱坞原则
艺人在将简历提交给好莱坞的娱乐公司后,他们所能做的就是等待,因为娱乐告诉他们:不要给我们打电话,我们会打给你。这就是好莱坞原则,这个原则的关键之处是:高层拥有对低层的完全控制,低层只是整个流程的一部分实现。好莱坞原则体现了模板模式的关键:子类可以置换掉父类的可变部分,但是不可以改变模板方法所代表的顶级逻辑。
设计理念
模板方法模式使用是极广的,但是很多人在使用的时候没有意识到自己已经使用了这个模式。模板方法模式是基于继承的代码复用技术,它的设计理念是尽量减少必须由子类覆盖的基本方法的数目。我们来看看模板方法模式中的方法
模板方法
定在在抽象模板类中,把基本操作方法组合在一起形成一个总算法或总行为。
基本方法
在模板方法中有三类基本方法:
抽象方法,总行为的组成步骤,定义在抽象模板类中,由子类实现,且子类必须实现。
具体方法,由抽象模板类实现,子类并不对覆盖,也就是最普通的方法。
钩子方法,由抽象模板类提供空实现或者默认实现,子类可以选择是否覆盖此方法。钩子方法还能对某些将要发生或者已经发生的步骤做出反应,有能力为其父类做一些决定。
在写模板类时,怎么知道什么时候该使用抽象方法,什么时候使用钩子方法呢?如果这个方法是算法的必选部分,同时又是可变部分,那么就必须是抽象方法,子类必须提供该方法的实现;如果这个方法是算法的可选部分,那么就可以设置为钩子方法,同时提供默认实现。
在写模板类时要时刻记住:尽量减少必须由子类覆盖的方法数目,如果某些步骤是可选的,那么就实现成钩子方法,而不是抽象方法。
重构
在对一个继承的等级结构做重构时,一个应当遵从的原则是将行为尽量移动到结构的高端,而将状态移动到结构的低端。Auer指出:
(1)应当根据行为而不是状态定义一个类。也就是说,一个类的实现首先建立在行为的基础上,而不是建立在状态的基础上。
(2)在实现行为时,是用抽象状态而不是用具体状态。如果一个行为涉及到对象的状态时,用间接引用而不是直接引用。换言之,应当是用取值方法而不是直接引用属性。
(3)给操作划分层次。一个类的行为应当放到方法里面(对应抽象模板中的基本方法),这些方法可以很方便地在子类中加以置换。
(4)将状态属性的确认推迟到子类中。不要在抽象类中过早地声明属性变量,应当将它们尽量地推迟到子类中去声明。如果在抽象类中需要使用状态属性的话,可以调用抽象取值方法,将抽象的取值方法推迟到子类中实现。
Auer指出的这些原则,其实就将设计引导到了模板方法模式里,我们使用模板方法模式来对一段代码进行重构。
将大方法打破
关于一个方法多大才算合适,有一个较为直观的指导:一个方法的长度不应超过一页。对于一个很长的方法,应该将它拆分成若干个小的方法,将这些小的方法作为原来方法的组成部分。可以看出,拆分后的原方法就是一个模板方法。下面的方法就是一个很大的方法:
public void bigMethod() {
...
代码块1
...
代码块2
...
代码块3
...
代码块4
...
代码块5
}
应用模板方法模式拆分后:
public void bigMethod() {
step1();
step2();
if(...)
step3();
else if(...)
step4()
else
step5();
}
public void step1(); {
代码块1
}
public void step2(); {
代码块2
}
...
public void step5(); {
代码块5
}
建立取值方法
拆分之后,原本各个代码块共享的局部变量无法再被共享,有两种解决办法,一是将原本共享的局部变量改为父类的私有变量;二是使用抽象取值方法,在原本需要这个变量的地方调用抽象取值方法,这样可以将状态的声明推迟到子类中,使得等级结构的高端与状态分离,符合Auer提出的第(4)条规则。常量也应该建立取值方法,这样将常量的声明推迟到子类中。
反复进行
如果有一些特征相同,功能相同而细节不同的方法,那么就可以利用继承和多态继续重构下去。重构完成后,所有的基本方法都是合适的细粒度,所有的常数都放到了常数方法里面。这时候就得到一个类,里面有一个模板方法和一系列基本方法。
多态取代条件转移
在上面拆分后的代码中,有一个条件转移块,这样的条件转移块在大而不当的方法中常常见到,,可以使用多态取代条件转移,这是另一个重要的重构原则。将所考虑的类当做抽象模板类,设计一些具体子类,再将基本方法中特征相同、功能相同而细节不同的方法划分到不同子类中。在上面的例子中,step3(),step4(),step5()就符合这些条件,因此,我们可以将这三个类当做抽象方法newMethod()在不同子类的具体实现,这样根据多态性,便可以调用相应的方法了。
public abstract class AbstractClass {
/**
* 模板方法
*/
public void bigMethod() {
step1();
step2();
newMethod();
}
//抽象方法
public abstract void step1();
public abstract void step2();
public abstract void newMethod();
}
class ConcreteClass1 extends AbstractClass {
public void newMethod() {
代码块3
}
}
...
重构完成后,这其实是一个标准的模板方法模式。在需要的情况下,建立独立的类负责独立的行为,从而可以将独立的行为委派到独立的对象里面。
转载请注明:喻红叶《Java与模式-模板方法模式》
分享到:
相关推荐
JAVA-设计模式-行为型模式-模板方法模式
1. **抽象类(Abstract Class)**:这是模板模式的核心,它定义了模板方法以及一些具体方法。模板方法是一个具体方法,通常为final类型,以防止被子类重写。它定义了算法的主要结构,而将一些步骤留给了子类来实现。...
模板方法模式是设计模式中行为型模式的一种,它在软件工程中扮演着非常重要的角色,尤其是在Java编程中。模板方法模式定义了一个操作中的算法骨架,而将一些步骤延迟到子类中。它允许子类不改变一个算法的结构即可重...
在Java中,模板模式通常使用抽象类来定义模板方法,这个方法由一系列基本操作组成,这些操作在模板类中可能是抽象的或已经实现了的。子类通过重写这些抽象方法,为算法的具体步骤提供实现。这种设计模式遵循“开闭...
4. 行为型模式:包括职责链模式、命令模式、解释器模式、迭代器模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式和访问者模式。行为型模式关注于对象之间的交互和行为,帮助我们更好地管理复杂的...
在给定的PPT内容中,首先通过一个简单的生活实例——泡咖啡与泡茶的过程——来引入模板方法模式的应用场景。无论是泡咖啡还是泡茶,都有几个共同的步骤: 1. **烧开水**:这是泡咖啡和泡茶都必需的。 2. **泡制过程*...
例如,代理模式(Proxy Pattern)、单例模式(Singleton Pattern)、工厂方法模式(Factory Method Pattern)、抽象工厂模式(Abstract Factory Pattern)、适配器模式(Adapter Pattern)、模板方法模式(Template ...
《Java与模式-清晰书签版》是一份包含多种Java设计模式详解的资源包,旨在帮助开发者深入理解和应用设计模式。这份资源集成了多种格式的文档,包括详细的文本描述、图表解析以及实际代码示例,使得学习过程更加直观...
模板方法模式是一种行为设计模式,它允许在定义行为框架的同时,延迟部分具体步骤到子类中实现。这种模式主要用于在父类中定义算法的骨架,而将一些步骤的实现细节留给子类去完成,从而使得不同的子类可以重用相同的...
这个资源"Java设计模式----通俗易懂版"显然是一个专门针对初学者或需要深入理解设计模式的开发者编写的指南。作者以形象生动的例子解释了23种经典的Java设计模式,使得复杂的概念变得更加易于理解。 首先,我们要...
这个“JAVA设计模式-chm版”资源显然包含了关于Java设计模式的详细信息,便于理解和应用。设计模式是对常见问题的解决方案的标准化描述,它们在软件工程中起到了重要的作用,帮助开发者创建可维护、可扩展且易于理解...
Java设计模式之模板方法模式Java认证考试 Java设计模式之模板方法模式是Java认证考试中的一种重要的设计模式,它通过使用继承关系来定义一个操作中的算法骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个...
模板方法模式是面向对象设计模式中的行为模式之一,它在Java等面向对象编程语言中有着广泛的应用。模板方法模式的主要思想是在一个抽象类中定义一个算法的骨架,而将一些步骤延迟到子类中实现。这样,子类可以重写...
例如,责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法和访问者模式。这些模式帮助我们设计出更加灵活、可扩展和可维护的系统,比如观察者模式...
模板模式是一种行为设计模式,它在Java编程中扮演着重要的角色,主要用来定义算法的骨架,而将一些步骤延迟到子类中。这种模式让子类可以在不改变算法整体结构的情况下,重定义某些特定步骤。在Java设计模式之模板...
本文主要关注的是Java编程语言中的设计模式,结合“Java与模式-笔记二”的博客内容,我们将深入探讨其中的一些核心设计模式及其在实际开发中的应用。 首先,我们来谈谈单例模式(Singleton)。单例模式确保一个类...
模板方法模式是面向对象设计模式中的行为模式之一,它在Java编程中有着广泛的应用。这种模式定义了一个操作中的算法骨架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定...
这篇名为"JAVA设计模式---100%推荐"的资源,可能是对Java设计模式的深入解析,旨在帮助开发者更好地理解和应用这些模式。 设计模式通常分为三类:创建型、结构型和行为型。创建型模式涉及对象的实例化过程,如单例...