简单记录 设计模式之禅-秦小波 & 软件秘笈-设计模式那些事-郑阿奇
文章目录
1、模板方法模式的定义
模板方法模式(Template Method Pattern)其定义如下:
Define the skeleton of an algorithm in anoperation,deferring some steps to subclasses.Template Method lets subclasses redefine certain steps of analgorithm without changing the algorithm’s structure.
(定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。)
模板方法模式是比较简单的一种设计模式,但是它却是代码复用的一项基本的技术,在类库中尤其重要,它遵循"抽象类应当拥有尽可能多的行为,应当拥有尽可能少的数据”的重构原则。作为模板的方法要定义在父类中,在方法的定义中使用抽象方法,而只看父类的抽象方法是根本不知道怎样处理的,实际具体处理的是子类,在子类中实现具体功能,因此不同的子类执行将会得出不同的实现结果,但是处理流程还是按照父类定制的方式。这就是模板方法的要义所在,制定算法骨架,让子类具体实现。
模板方法的要义所在,制定算法骨架,让子类具体实现。
2、模板方法的实现
模板方法的通用类图
模板方法模式的通用类图如图所示。
模板方法模式是非常简单的,仅仅使用了Java的继承机制,但它是一个应用非常广泛的模式。
其中,AbstractClass叫做抽象模板,它的方法分为两类:
●基本方法,基本方法也叫做基本操作,是由子类实现的方法,并且在模板方法被调用。
●模板方法可以有一个或几个,一般是一个具体方法,也就是一个框架,实现对基本方法的调度,完成固定的逻辑。
注意 为了防止恶意的操作,一般模板方法都加上final关键字,不允许被覆写。
在类图中还有一个角色:具体模板。
ConcreteClass1和ConcreteClass2属于具体模板,实现父类所定义的一个或多个抽象方法,也就是父类定义的基本方法在子类中得以实现。
模板方法中的角色
模板方法模式中的角色
(1)抽象参与者(AbstractClass):在抽象参与者中,声明了模板方法,在模板方法中实现业务处理逻辑。其中尚未实现的抽象方法由子类实现。
(2)具体参与者(ConcreteClass):具体参与者实现抽象参与者中定义的抽象方法,从而实现整个模板方法处理流程。
模板方法通用代码
模板方法通用代码,
AbstractClass如代码所示。
代码 抽象模板类
public abstract class AbstractClass {
//基本方法
protected abstract void doSomething();
//基本方法
protected abstract void doAnything();
//模版方法
public void templateMethod(){
/*
* 调用基本方法,完成相关的逻辑
*/
this.doAnything();
this.doSomething();
}
}
具体模板 如代码所示 ConcreteClass
public class ConcreteClass1 extends AbstractClass {
//实现基本方法
protected void doAnything() {
//业务逻辑处理
}
protected void doSomething() {
//业务逻辑处理
}
}
具体模板类
public class ConcreteClass2 extends AbstractClass {
//实现基本方法
protected void doAnything() {
//业务逻辑处理
}
protected void doSomething() {
//业务逻辑处理
}
}
场景类如代码所示。代码 场景类
public class Client {
public static void main(String[] args) {
AbstractClass class1 = new ConcreteClass1();
AbstractClass class2 = new ConcreteClass2();
//调用模版方法
class1.templateMethod();
class2.templateMethod();
}
}
private、protected、public
基本方法 思考一下,可用替换为public修饰么?
可以用public,public和private的主要目的是让子类可以访问要修改的方法,但protected比public有更好的封装性,用protected更好点。这些抽象方法通常只是让子类看到并实现,通常没必要暴露给外部。
注意 抽象模板中的基本方法尽量设计为protected类型,符合迪米特法则,不需要暴露的属性或方法尽量不要设置为protected类型。实现类若非必要,尽量不要扩大父类中的访问权限。
3、模板方法模式的应用
模板方法模式的优点
优点
●封装不变部分,扩展可变部分。
把认为是不变部分的算法封装到父类实现,而可变部分的则可以通过继承来继续扩展。
扩展,增加一个子类,实现父类的基本方法就行了。
●提取公共部分代码,便于维护。
如果不抽取公共部分代码到父类中,任由这种散乱的代码发生,想想后果是什么样子?维护人员为了修正一个缺陷,需要到处查找类似的代码!
●行为由父类控制.子类实现基本方法。
基本方法是由子类实现的,因此子类可以通过扩展的方式增加相应的功能,符合开闭原则。
模板方法模式的缺点
按照我们的设计习惯,抽象类负责声明最抽象、最一般的事物属性和方法,实现类完成具体的事物属性和方法。但是模板方法模式却颠倒了,抽象类定义了部分抽象方法,由子类实现,子类执行的结果影响了父类的结果,也就是子类对父类产生了影响,这在复杂的项目中,会带来代码阅读的难度,而且也会让新手产生不适感。
模版方法的使用场景
多个子类有公有的方法,并且逻辑基本相同时。重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现。重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数约束其行为。
4、设计原则
1.“开-闭”原则
“开-闭”原则是指一个软件实体应该对扩展开放,对修改关闭,也就是说软件实体应该是在不被修改的情况下被扩展。
模板方法模式的意图是由抽象父类控制顶级逻辑,并把某些基本操作的实现推迟到子类去实现,这是通过继承的手段来达到对象复用的目的,不同的子类实现不同的具体功能,遵守了“开-闭”原则。
在模板方法模式中,在父类中定义一个顶级的骨架逻辑,并提供一个具体的实现方法供外部调用,称为模板方法。
这里有两点内容需要注意,
一是模板方法不能被子类修改,必须使用父类提供的骨架算法,因此需要将模板方法定义为final的方法(在Java中为不可重写的方法),从而保证父类控制算法逻辑;
二是由子类实现的抽象方法需要定义为protected abstract,使数据不被外部对象恶意访问及错误使用而破坏封装性。
2.好莱坞原则
“不要打电话给我们,我们会打电话给你”——这是著名的好莱坞原则。
在好莱坞,把简历递交给演艺公司后就只有回家等待消息,整个过程是由演艺公司完全控制的,作为演员只能是被动式地接受。
模板方法模式充分地体现了好莱坞原则。由父类完全控制着子类的处理逻辑,子类可以实现父类的可变部分,但是却继承父类的逻辑,不能改变业务逻辑。其实这就是所谓的控制反转(IOC)。
好莱坞原则是一个有效防止“依赖腐败”的方法,所谓“依赖腐败”是指高层组件和低层组件之间相互依赖,高层组件又与其他边侧组件存在依赖关系,边侧组件与低层组件又有依赖关系,错综复杂的依赖关系让人理不清头绪。
好莱坞原则允许低层组件将自己挂钩到高层组件的算法过程中,什么时候调用,则是按照高层的处理逻辑决定,有效避免了系统环状依赖。
5、模板方法模式的使用场合
使用场合
(1)一次性实现一个算法不变的部分,并将可变的行为留给子类来实现;
(2)各子类中具有公共行为的时候,应被提取出来并集中到一个公共父类中以避免代码重复;
(3)当需要控制子类扩展的时候。模板方法在特定点调用钩子操作,这样就只允许在这些点进行扩展。
钩子???
所谓钩子函数是什么?
指向子类对象的引用,由子类复写差异化,说的好听点
钩子使子类更灵活
钩子方法
Hook,钩子函数,提供一个默认或空的实现
具体的子类可以自行决定是否挂钩以及如何挂钩
钩住 复写 protected
挂钩,更大的灵活性 选择性。
“钩子”的含义,所谓“钩子”就是声明在抽象基类中的方法,但是该方法是空的或者是含默认的实现。在子类中,可以根据需要重载“钩子”,从而让子类有能力对抽象基类中骨架算法的不同点进行挂钩。重载抽象基类中的方法,从而实现子类的特有行为。
在设计一个软件系统的时候,通常会遇到这样一个问题:我们知道一个算法所需要的关键步骤,并确定了这些步骤的执行顺序,但是某些步骤的具体实现是未知的,或者说某些步骤的实现与具体的环境相关。这个时候,模板方法模式就有用武之地了。模板方法模式把我们不知道具体实现的步骤封装成抽象方法,提供一个按正确顺序调用它们的具体方法,这些具体方法称为“模板方法”,提供给外部调用,这样构成一个抽象基类。子类通过继承这个抽象基类去实现各个步骤的抽象方法,而流程处理逻辑却是由父类来控制的。
在应用模板方法模式的时候,首先需要定义一个抽象的基类,在这个抽象类中,需要包含一个模板方法,即不能被子类修改的public final方法。在模板方法中,实现业务处理逻辑。模板方法中还包括一些本类中的钩子方法,用于让子类重载实现不同的处理逻辑。根据业务的需要,定义各种不同的子类,实现模板方法类的所有抽象方法,这样就形成了模板方法模式。
6、模板方法模式的扩展
在抽象类中,影响了模板方法的执行结果,该方法就叫做钩子方法(Hook Method)。有了钩子方法模板方法模式才算完美,可以想想,由子类的一个方法返回值决定公共部分的执行结果,是不是很有吸引力呀!模板方法模式就是在模板方法中按照一定的规则和顺序调用基本方法。
扩展1:Java SDK中的模板方法模式模板方法模式是比较简单的一种设计模式,但是它却是代码复用的一项基本的技术,在类库中尤其重要。在抽象父类中定义算法骨架,子类实现具体功能,处理流程还是按照父类定制的方式。例如,JDK中java.util.Arrays数组中的sort排序算法就是典型的模板方法模式。
扩展2:相关的设计模式工厂方法模式:工厂方法模式是模板方法模式的特殊情况,工厂方法模式中使用子类来产生对象实例。策略模式:策略模式中使用委托关系切换整个算法,不是部分修改算法内容,整个算法结构已经改变了。而模板方法模式则是使用继承的方式,让子类实现部分的算法内容,从而实现整个算法处理,但是算法骨架仍然是由抽象基类决定的。
7、最佳实践
父类建立框架,子类在重写了父类部分的方法后,再调用从父类继承的方法,产生不同的结果(而这正是模板方法模式)。这是不是也可以理解为父类调用了子类的方法呢?你修改了子类,影响了父类行为的结果,曲线救国的方式实现了父类依赖子类的场景,模板方法模式就是这种效果。
模板方法在一些开源框架中应用非常多,它提供了一个抽象类,然后开源框架写了一堆子类。如果你需要扩展功能,可以继承这个抽象类,然后覆写protected方法,再然后就是调用一个类似execute方法,就完成你的扩展开发,非常容易扩展的一种模式。
8、模板方法总结
模板方法模式(Template Method Pattern)
模板方法定义(Template Method Pattern),定义一个操作中的算法骨架,而将一些实现步骤延迟到子类当中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
模板方法模式的通用类图如下图:
模板方法模式确实非常简单,仅仅使用了Java的继承机制,但是它是一个应用非常广泛的模式。其中,AbstractClass叫做抽象模板,它的方法分为两类:
- 基本方法。基本方法也叫做基本操作,是由子类实现的方法,并且在模板方法被调用。
- 模板方法。可以有一个或几个,一般是一个具体方法,也就是一个骨架,实现对基本方法的调度,完成固定的逻辑。
在类图中还有一个角色:具体模板,ConcreteClass1和ConcreteClass2属于具体模板,实现父类所定义的一个或多个抽象方法,也就是父类定义的基本方法在子类中得以实现。
注意:为了防止恶意的操作,一般模板方法都加上final关键字,不允许被覆写。
模板方法模式的实现要素
抽象基类
具体子类
抽象基类
-
基本方法 相同的 直接定义在抽象基类
-
抽象方法 不知道具体实现原则
-
可选的钩子 提供一个默认或空的实现 具体的子类可以自行决定是否挂钩以及如何挂钩
-
Template方法(final)
模板方法 封装了所有子类共同遵循的算法框架
不能被子类复写 可以替换
具体子类
-
实现基类中的抽象方法
-
覆盖钩子方法
准备一个抽象类,将部分逻辑以具体方法的形式实现,然后声明一些抽象方法交由子类实现剩余逻辑,用钩子方法给予子类更大的灵活性。最后将方法汇总构成一个不可改变的模版方法。
设计原则
(1)“开-闭”原则:模板方法模式使用抽象父类控制顶级逻辑,把某些基本操作的实现推迟到子类去实现,这是通过继承的手段来达到对象复用的目的,不同的子类实现不同的具体功能,遵守了“开-闭”原则。
(2)好莱坞原则:允许低层组件将自己挂钩到高层组件的算法过程中,什么时候去调用,则按照高层的处理逻辑决定。有效避免了系统环状依赖。
模式中的角色
(1)抽象参与者(AbstractClass):在抽象参与者中,声明了模板方法,在模板方法中实现业务处理逻辑。其中尚未实现的抽象方法由子类实现。(2)具体参与者(ConcreteClass):具体参与者实现抽象参与者中定义的抽象方法,从而实现整个模板方法处理流程。
相关的设计模式
(1)工厂方法模式:工厂方法模式是模板方法模式的特殊情况,工厂方法模式中使用子类来产生对象实例。
(2)策略模式:策略模式中使用委托关系切换整个算法,不是部分修改算法内容,整个算法结构已经改变了。而模板方法模式则是使用继承的方式,让子类实现部分的算法内容,从而实现整个算法处理,但是算法骨架仍然是由抽象基类决定的。
使用场合
(1)一次性实现一个算法不变的部分,并将可变的行为留给子类来实现;
(2)各子类中具有公共行为的时候,应被提取出来并集中到一个公共父类中以避免代码重复;
(3)当需要控制子类扩展的时候。模板方法在特定点调用钩子操作,这样就只允许在这些点进行扩展。
模板方法模式的应用
1.模板方法模式的优点
-
封装不变部分,扩展可变部分。把认为是不变部分的算法封装到父类实现,而可变部分的则可以通过继承来继续扩展。
-
提取公共部分代码,便于维护。
-
行为控制交由子类来实现。基本方法是由子类实现的,因此子类可以通过扩展的方式增加相应的功能,符合开闭原则。
-
封装性好
-
复用性好
-
屏蔽细节
-
便于维护
2.模板方法模式的缺点
按照我们设计习惯,抽象类负责声明最抽象、最一般的事物属性和方法,实现类完成具体的事物属性和方法,但是模板方法模式却颠倒了,抽象类定义了部分抽象方法,由子类实现,子类执行的结果影响了父类的结果,也就是子类对父类产生了影响,这在复杂的项目中,会带来代码阅读的难度,而且也会让新手产生不适感。
- 继承 Java单继承
模板方法模式的使用场景
- 多个子类有公有的方法,并且逻辑基本相同时。
- 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现。
- 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数(见“模板方法模式的扩展”)约束其行为。
(1)算法或操作遵循相似的逻辑
(2)重构时(把相同的代码抽取到父类中)
共性 个性交给不同的子类去实现
(3) 重要、复杂的算法,核心算法设计为模板算法