设计模式 ——— 模板方法模式

TEMPLATE METHOD(模板方法) ———— 类行为型模式

意图

定义一个操作中的算法骨架,而将一些步骤延迟到子类中。TemplateMethod使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

适用性

  • 需要固定定义算法骨架,实现一个算法的不变的部分,并把可变的行为留给子类来实现的情况;
  • 各个子类中具有公共行为,应该抽取出来,集中在一个公共类中去实现,从而避免代码重复;
  • 需要控制子类扩展的情况。模板方法模式会在特定的点来调用“hook”操作,这样只允许在这些点进行扩展;

结构

模板方法模式结构图

  • AbstractClass(抽象类) 定义抽象的原语操作(primitive operation),具体的子类将重定义它们以实现一个算法的各步骤。 实现一个模板方法,定义一个算法的骨架。该模板方法不仅调用原语操作,也调用定义在AbstractClass或其他对象中的操作。
  • ConcreteClass(具体类) 实现原语操作以完成算法中与特定子类相关的步骤。

认识模板方法模式

变与不变

程序设计的一个很重要的思考点就是“变与不变”,也就是分析程序中哪些功能是可变的,哪些功能是不变的,然后把不变的部分抽象出来,进行公共的实现,把变化的部分分离出去,用接口来封装隔离,或者是用抽象类来约束子类行为。

模板方法模式很好的体现了这一点。模板类实现的就是不变的方法和算法的骨架,而需要变化的地方,都通过抽象方法,把具体实现延迟到子类去了,而且还通过父类的定义来约束了子类的行为,从而使系统能有更好的复用性和扩展性。

好莱坞法则

什么是好莱坞法则呢?简单点说,就是“不要找我们,我们会联系你”。

模板方法模式很好的体现了这一点,做为父类的模板会在需要的时候,调用子类相应的方法,也就是由父类来找子类,而不是让子类来找父类。

这其实也是一种反向的控制结构,按照通常的思路,是子类找父类才对,也就是应该是子类来调用父类的方法,因为父类根本就不知道子类,而子类是知道父类的,但是在模板方法模式里面,是父类来找子类,所以是一种反向的控制结构。

对设计原则的体现

模板方法很好的体现了开闭原则和里氏替换原则。

首先从设计上,先分离变与不变,然后把不变的部分抽取出来,定义到父类里面,比如算法骨架,比如一些公共的、固定的实现等等。这些不变的部分被封闭起来,尽量不去修改它了,要扩展新的功能,那就使用子类来扩展,通过子类来实现可变化的步骤,对于这种新增功能的做法是开放的。

其次,能够实现统一的算法骨架,通过切换不同的具体实现来切换不同的功能,一个根本原因就是里氏替换原则,遵循这个原则,保证所有的子类实现的是同一个算法模板(为了防止子类改变模板方法中的算法,可以将模板方法声明为final),并能在使用模板的地方,根据需要,切换不同的具体实现。

模板方法模式的实现

模板方法调用下列类型的操作
  • 模板方法:就是定义算法骨架的方法 。
  • 具体的操作:在模板中直接实现某些步骤的方法,通常这些步骤的实现算法是固定的,而且是不怎么变化的,因此就可以当作公共功能实现在模板里面。如果不需提供给子类访问这些方法的话,还可以是private的。这样一来,子类的实现就相对简单些。如果是子类需要访问,可以把这些方法定义为protected final的,因为通常情况下,这些实现不能够被子类覆盖和改变了。
  • 具体的AbstractClass操作:在模板中实现某些公共功能,可以提供给子类使用,一般不是具体的算法步骤的实现,只是一些辅助的公共功能。
  • 原语操作:就是在模板中定义的抽象操作,通常是模板方法需要调用的操作,是必需的操作,而且在父类中还没有办法确定下来如何实现,需要子类来真正实现的方法。
  • 钩子操作:在模板中定义,并提供默认实现的操作。这些方法通常被视为可扩展的点,但不是必须的,子类可以有选择的覆盖这些方法,以提供新的实现来扩展功能。比如:模板方法中定义了5步操作,但是根据需要,某一种具体的实现只需要其中的1、2、3这几个步骤,因此它就只需要覆盖实现1、2、3这几个步骤对应的方法。那么4和5步骤对应的方法怎么办呢,由于有默认实现,那就不用管了。也就是说钩子操作是可以被扩展的点,但不是必须的。
  • Factory Method:在模板方法中,如果需要得到某些对象实例的话,可以考虑通过工厂方法模式来获取,把具体的构建对象的实现延迟到子类中去。
模板方法模式实现中指的注意的问题:

① 使用访问控制:必须重定义的原语操作须定义为abstract函数。模板方法自身不需要被重定义,并且也不应该被重定义,为了防止子类改变模板方法中的算法,可以将模板方法声明为final。 ② 尽量减少原语操作:定义模板方法的一个重要目的是尽量减少一个子类具体实现算法时必须重定义的那些原语操作的数目。需要重定义的操作越多,客户程序就越冗长。 ③ 命名约定:可以给应被重定义的那些操作的名字加上一个前缀以识别它们。

优缺点

优点: ①实现代码复用 模板方法模式是一种实现代码复用的很好的手段。通过把子类的公共功能提炼和抽取,把公共部分放到模板里面去实现。

缺点: ①算法骨架不容易升级 模板方法模式最基本的功能就是通过模板的制定,把算法骨架完全固定下来。事实上模板和子类是非常耦合的,如果要对模板中的算法骨架进行变更,可能就会要求所有相关的子类进行相应的变化。所以抽取算法骨架的时候要特别小心,尽量确保是不会变化的部分才放到模板中。

相关模式

  • 模板方法模式 VS 工厂方法模式 这两个模式可以配合使用。 模板方法模式可以通过工厂方法来获取需要调用的对象。
  • 模板方法模式 VS 策略模式 模板方法会定义一个算法的大纲,然后由子类通过继承来实现其中某些步骤的内容;策略模式则是通过对象组合的方式,让客户可以选择算法实现。即,使用委托来改变整个算法。 但是,我们可以在模板方法中使用策略模式,就是把那些变化的算法步骤通过使用策略模式来实现,但是具体选取哪个策略还是要由外部来确定,而整体的算法步骤,也就是算法骨架就由模板方法来定义了。
参考

《Head First 设计模式》 《设计模式:可复用面向对象软件的基础》 《研磨设计模式》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏智能算法

Python学习(二)---- 字符串操作、列表字典及深浅拷贝等

https://blog.csdn.net/fgf00/article/details/52061971

1002
来自专栏C/C++基础

C++ new的三种面貌

C++中使用new运算符产生一个存在于Heap(堆)上对象时,实际上调用了operator new()函数和placement new()函数。在使用new创建...

921
来自专栏北京马哥教育

Python程序员最常犯的十个错误,看完你自己都笑了

本文由马哥教育Python自动化实战班4期学员推荐,转载自简书,作者为EarlGrey,内容略经小编改编和加工,观点跟作者无关,最后感谢作者的辛苦贡献与付出。 ...

2754
来自专栏Java爬坑系列

【Java入门提高篇】Day11 Java代理——JDK动态代理

  今天来看看Java的另一种代理方式——JDK动态代理   我们之前所介绍的代理方式叫静态代理,也就是静态的生成代理对象,而动态代理则是在运行时创建代理对象。...

1997
来自专栏大数据

在Python中什么时候用Yield什么时候用Return

许多Python开发人员在代码中使用yield,而不考虑他们是否真的需要。这篇文章解释了你什么时候应该使用它。

3330
来自专栏Java帮帮-微信公众号-技术文章全总结

Java基础-day01-基础题

1. 简述java语言,具有哪些特性? (1).java语言是简单的 java语言是和c++语言类似的,其次java中丢弃了c++中一些难理解的特性,比如运算符...

2704
来自专栏JavaQ

多参数方法进阶

很多高级工程师还在写包含N个参数的方法、使用setter方法构造实例,其实这些方式都是过时并且有很大缺陷的,本篇将深入讲解这些问题及解决方法。 多参数方法的问题...

33511
来自专栏ml

C与C++在const用法上的区别

       首先,C和C++在大体结构上不同,却在语法上相同。  所以在使用的时候,我们会时常遇到一些莫名其妙的问题,觉得语法上是正确的,但是编译的时候却出现...

3174
来自专栏玄魂工作室

Hacker基础之Linux篇:基础Linux命令六

以后这个系列的每次就浓缩一下只推送一个命令~ ? sort sort命令是帮我们依据不同的数据类型进行排序,在Linux里非常常用的一个命令 sort命令使用...

3486
来自专栏SHERlocked93的前端小站

JS 利用高阶函数实现函数缓存(备忘模式)

高阶函数就是那种输入参数里面有一个或者多个函数,输出也是函数的函数,这个在js里面主要是利用闭包实现的,最简单的就是经常看到的在一个函数内部输出另一个函数,比如

4103

扫码关注云+社区