模板方法模式【Template Method Pattern】

2024-06-15 14:32

本文主要是介绍模板方法模式【Template Method Pattern】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

模板方法模式【Template Method Pattern】

周三,9:00,我刚刚坐到位置,打开电脑准备开始干活。
“小三,小三,叫一下其它同事,到会议室,开会”老大跑过来吼,带着淫笑。还不等大家坐稳,老大就开讲了,
“告诉大家一个好消息,昨天终于把牛叉模型公司的口子打开了,要我们做悍马模型,虽然是第一个车辆模型,但是我们有能力,有信心做好,我们一定要…(中间省略20 分钟的讲话,如果你听过领导人的讲话,这个你应该能够续上)”
动员工作做完了,那就开始压任务了,“这次时间是非常紧张的,只有一个星期的时间,小三,你负责在一个星期的时间把这批10 万车模(注:车模是车辆模型的意思,不是香车美女那个车模)建设完成…”
“一个星期?这个…,是真做不完,要做分析,做模板,做测试,还要考虑扩展性、稳定性、健壮性等,时间实在是太少了”还没等老大说完,我就急了,再不急我的小命就折在上面了!
“那这样,你只做实现,不考虑使用设计模式,扩展性等都不用考虑”老大又把我压回去了。“不考虑设计模式?那…”
哎,领导已经布置任务了,那就开始死命的做吧,命苦不能怨政府,点背不能怪社会呀,然后就开始准备动手做,在做之前先介绍一下我们公司的背景,我们公司是做模型生产的,做过桥梁模型、建筑模型、机械模型,甚至是一些政府、军事的机密模型,这个不能说,就是把真实的实物按照一定的比例缩小或放大,用于试验、分析、量化或者是销售等等,上面提到的牛叉模型公司专门销售车辆模型的公司,自己不生产,我们公司是第一次从牛叉模型公司接单,那我怎么着也要把活干好,可时间很紧张呀,怎么办?既然领导都说了,不考虑扩展性,那好办,我先设计个类图:



非常简单的实现,你要悍马模型,我就给你悍马模型,先写个抽象类,然后两个不同型号的模型实现类,那我们把这个程序实现出来:
HummerModel 抽象类的程序清单如下:



/*** @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you*         all. Hummer Model是悍马车辆模型的意思,不是悍马美女车模*/
public abstract class HummerModel {/** 首先,这个模型要能够被发动起来,别管是手摇发动,还是电力发动,反正 是要能够发动起来,那这个实现要在实现类里了*/public abstract void start();// 能发动,那还要能停下来,那才是真本事public abstract void stop();// 喇叭会出声音,是滴滴叫,还是哔哔叫public abstract void alarm();// 引擎会轰隆隆的响,不响那是假的public abstract void engineBoom();// 那模型应该会跑吧,别管是人推的,还是电力驱动,总之要会跑public abstract void run();
}

H1 型号悍马的定义如下:


/*** @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you*         all. 悍马车是每个越野者的最爱,其中H1最接近军用系列*/
public class HummerH1Model extends HummerModel {@Overridepublic void alarm() {System.out.println("悍马H1鸣笛...");}@Overridepublic void engineBoom() {System.out.println("悍马H1引擎声音是这样在...");}@Overridepublic void start() {System.out.println("悍马H1发动...");}@Overridepublic void stop() {System.out.println("悍马H1停车...");}/** 这个方法是很有意思的,它要跑,那肯定要启动,停止了等,也就是要调其他方法 */@Overridepublic void run() {// 先发动汽车this.start();// 引擎开始轰鸣this.engineBoom();// 然后就开始跑了,跑的过程中遇到一条狗挡路,就按喇叭this.alarm();// 到达目的地就停车this.stop();}
}

然后看悍马H2 型号的实现:

/*** @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you*         all. H1和H2有什么差别,还真不知道,真没接触过悍马*/
public class HummerH2Model extends HummerModel {@Overridepublic void alarm() {System.out.println("悍马H2鸣笛...");}@Overridepublic void engineBoom() {System.out.println("悍马H2引擎声音是这样在...");}@Overridepublic void start() {System.out.println("悍马H2发动...");}@Overridepublic void stop() {System.out.println("悍马H1停车...");}/** H2要跑,那肯定要启动,停止了等,也就是要调其他方法*/@Overridepublic void run() {// 先发动汽车this.start();// 引擎开始轰鸣this.engineBoom();// 然后就开始跑了,跑的过程中遇到一条狗挡路,就按喇叭this.alarm();// 到达目的地就停车this.stop();}
}

然后程序写到这里,你就看到问题了,run 方法的实现应该在抽象类上,不应该在实现类上,好,我们修改一下类图和实现:





就把run 方法放到了抽象类中,那代码也相应的改变一下,先看HummerModel.java:


/*** @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you*         all. Hummer Model是悍马车辆模型的意思,不是悍马美女车模*/
public abstract class HummerModel {/** 首先,这个模型要能够被发动起来,别管是手摇发动,还是电力发动,反正 是要能够发动起来,那这个实现要在实现类里了*/public abstract void start();// 能发动,那还要能停下来,那才是真本事public abstract void stop();// 喇叭会出声音,是滴滴叫,还是哔哔叫public abstract void alarm();// 引擎会轰隆隆的响,不响那是假的public abstract void engineBoom();// 那模型应该会跑吧,别管是人退的,还是电力驱动,总之要会跑public void run() {// 先发动汽车this.start();// 引擎开始轰鸣this.engineBoom();// 然后就开始跑了,跑的过程中遇到一条狗挡路,就按喇叭this.alarm();// 到达目的地就停车this.stop();}
}

下面是HummerH1Model.java 程序清单:


/*** @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you*         all. 悍马车是每个越野者的最爱,其中H1最接近军用系列*/
public class HummerH1Model extends HummerModel {@Overridepublic void alarm() {System.out.println("悍马H1鸣笛...");}@Overridepublic void engineBoom() {System.out.println("悍马H1引擎声音是这样在...");}@Overridepublic void start() {System.out.println("悍马H1发动...");}@Overridepublic void stop() {System.out.println("悍马H1停车...");}
}

下面是HummerH2Model.java 的程序清单:


/*** @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you*         all. H1和H2有什么差别,还真不知道,真没接触过悍马*/
public class HummerH2Model extends HummerModel {@Overridepublic void alarm() {System.out.println("悍马H2鸣笛...");}@Overridepublic void engineBoom() {System.out.println("悍马H2引擎声音是这样在...");}@Overridepublic void start() {System.out.println("悍马H2发动...");}@Overridepublic void stop() {System.out.println("悍马H2停车...");}
}


类图修改完毕了,程序也该好了,提交给老大,老大一看,挺好,就开始生产了,并提交给客户使用了,那客户是如何使用的呢?类图上增加一个Client 类,就是客户,我们这个是用main 函数来代替他使用,类图如下:


然后看增加的Client.java 程序,非常的简单:


/*** @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you*         all. 客户开始使用这个模型*/
public class Client {public static void main(String[] args) {// 客户开着H1型号,出去遛弯了HummerModel h1 = new HummerH1Model();h1.run(); // 汽车跑起来了;// 客户开H2型号,出去玩耍了HummerModel h2 = new HummerH2Model();h2.run();}
}

运行的结果如下:




非常非常的简单,那如果我告诉这就是模板方法模式你会不会很不屑呢?就这模式,太简单了,我一直在使用呀,是的,你经常在使用,但你不知道这是模板方法模式,那些所谓的高手就可以很牛X 的说“用模板方法模式就可以实现…”,你还要很崇拜的看着,哇,牛人,模板方法模式是什么呀?然后我们继续回顾我们这个模型,回头一想,不对呀,需求分析的有点问题,客户要关心模型的启动,停止,鸣笛,引擎声音吗?他只要在run 的过程中,听到或看都成了呀,暴露那么多的方法干啥?好了,我们重新修改一下类图:



把抽象类上的四个方法设置为protected 访问权限,好了,既然客户不关心这几个方法,而且这四个方法都是由子类来实现的,那就设置成protected 模式。咦~,那还有个缺陷,run 方法既然子类都不修改,那是不是可以设置成final 类型呢?是滴是滴,类图如下:




好了,这才是模板方法模式,就是这个样子,我们只要修改抽象类代码就可以了,HummerModel.java程序清单如下:


/*** @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you*         all. Hummer Model是悍马车辆模型的意思,不是悍马美女车模*/
public abstract class HummerModel {/** 首先,这个模型要能够被发动起来,别管是手摇发动,还是电力发动,反正 是要能够发动起来,那这个实现要在实现类里了*/protected abstract void start();// 能发动,那还要能停下来,那才是真本事protected abstract void stop();// 喇叭会出声音,是滴滴叫,还是哔哔叫protected abstract void alarm();// 引擎会轰隆隆的响,不响那是假的protected abstract void engineBoom();// 那模型应该会跑吧,别管是人退的,还是电力驱动,总之要会跑final public void run() {// 先发动汽车this.start();// 引擎开始轰鸣this.engineBoom();// 然后就开始跑了,跑的过程中遇到一条狗挡路,就按喇叭this.alarm();// 到达目的地就停车this.stop();}
}

其他的子类都不用修改(如果要修改,就是把四个方法的访问权限由public 修改protected),大家请看这个run 方法,他定义了调用其他方法的顺序,并且子类是不能修改的,这个叫做模板方法;start、stop、alarm、engineBoom 这四个方法是子类必须实现的,而且这四个方法的修改对应了不同的类,这个叫做基本方法,基本方法又分为三种:在抽象类中实现了的基本方法叫做具体方法;在抽象类中没有实现,在子类中实现了叫做抽象方法,我们这四个基本方法都是抽象方法,由子类来实现的;还有一种叫做钩子方法,这个等会讲。到目前为止,这两个模型都稳定的运行,突然有一天,老大又找到了我,“客户提出新要求了,那个喇叭想让它响就响,你看你设计的模型,车子一启动,喇叭就狂响,赶快
修改一下”,确实是设计缺陷,呵呵,不过是我故意的,那我们怎么修改呢?看修改后的类图:



增加一个方法,isAlarm(),喇嘛要不要响,这就是钩子方法(Hook Method),那我们只要修改一下抽象类就可以了:

/** 首先,这个模型要能够被发动起来,别管是手摇发动,还是电力发动,反正* 是要能够发动起来,那这个实现要在实现类里了*/
public abstract class HummerModel {protected abstract void start();// 能发动,那还要能停下来,那才是真本事protected abstract void stop();// 喇叭会出声音,是滴滴叫,还是哔哔叫protected abstract void alarm();// 引擎会轰隆隆的响,不响那是假的protected abstract void engineBoom();// 那模型应该会跑吧,别管是人推的,还是电力驱动,总之要会跑final public void run() {// 先发动汽车this.start();// 引擎开始轰鸣this.engineBoom();// 喇嘛想让它响就响,不想让它响就不响if (this.isAlarm()) {this.alarm();}// 到达目的地就停车this.stop();}// 钩子方法,默认喇叭是会响的protected boolean isAlarm() {return true;}
}
钩子方法模式是由抽象类来实现的,子类可以重写的,H2 型号的悍马是不会叫的,喇叭是个摆设,看
HummerH2Model.java 代码:
/*** @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you*         all. H1和H2有什么差别,还真不知道,真没接触过悍马*/
public class HummerH2Model extends HummerModel {@Overrideprotected void alarm() {System.out.println("悍马H2鸣笛...");}@Overrideprotected void engineBoom() {System.out.println("悍马H2引擎声音是这样在...");}@Overrideprotected void start() {System.out.println("悍马H2发动...");}@Overrideprotected void stop() {System.out.println("悍马H1停车...");}// 默认没有喇叭的@Overrideprotected boolean isAlarm() {return false;}
}

那H2 型号的模型都没有喇叭,就是按了喇叭也没有声音,那客户端这边的调用没有任何修改,出来的结果就不同,我们先看Client.java 程序:

/*** @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you*         all. 客户开始使用这个模型*/
public class Client {public static void main(String[] args) {HummerH2Model h2 = new HummerH2Model();h2.run(); // H2型号的悍马跑起来}
}

运行的出来的结果是这样的:



那H1 又有所不同了,它的喇叭要不要响是由客户来决定,其实在类图上已经标明了setAlarm 这个方法,我们看HummerH1Model.java 的代码:

/*** @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you*         all. 悍马车是每个越野者的最爱,其中H1最接近军用系列*/
public class HummerH1Model extends HummerModel {private boolean alarmFlag = true; // 是否要响喇叭@Overrideprotected void alarm() {System.out.println("悍马H1鸣笛...");}@Overrideprotected void engineBoom() {System.out.println("悍马H1引擎声音是这样在...");}@Overrideprotected void start() {System.out.println("悍马H1发动...");}@Overrideprotected void stop() {System.out.println("悍马H1停车...");}@Overrideprotected boolean isAlarm() {return this.alarmFlag;}// 要不要响喇叭,是有客户的来决定的public void setAlarm(boolean isAlarm) {this.alarmFlag = isAlarm;}
}

这段代码呢修改了两个地方,一是重写了父类的isAlarm()方法,一是增加了一个setAlarm 方法,由调用者去决定是否要这个功能,也就是喇叭要不要滴滴答答的响,哈哈,那我们看看Client.java 的修改:

package com.cbf4life;
/**
* @author cbf4Life cbf4life@126.com
* I'm glad to share my knowledge with you all.
* 客户开始使用这个模型
*/
public class Client {
public static void main(String[] args) {//客户开着H1型号,出去遛弯了HummerH1Model h1 = new HummerH1Model();h1.setAlarm(true);h1.run(); //汽车跑起来了;}
}


看到没,这个模型run 起来就有声音了,那当然把h1.setAlarm(false)运行起来喇叭就没有声音了,
钩子方法的作用就是这样滴。那我们总结一下模板方法模式,模板方法模式就是在模板方法中按照一个的规则和顺序调用基本方法,具体到我们上面那个例子就是run 方法按照规定的顺序(先调用start,然后再调用engineBoom,再调用alarm,最后调用stop)调用本类的其他方法,并且由isAlarm 方法的返回值确定run 中的执行顺序变更,
通用类图如下:




其中TemplateMethod 就是模板方法,operation1 和operation2 就是基本方法,模板方法是通过汇总或排序基本方法而产生的结果集。模板方法在一些开源框架中应用很多,它提供了一个抽象类,然后开源框架写了一堆子类,在《XXX In Action》中就说明了,如果你需要扩展功能,可以继承了这个抽象类,然后修改protected 方法,再然后就是调用一个类似execute 方法,就完成你的扩展开发,确实是一种简单的模式。
初级程序员在写程序的时候经常会问高手“父类怎么调用子类的方法”,这个问题很有普遍性,反正我是被问过好几回,那么父类是否可以调用子类的方法呢?我的回答是能,但强烈的、极度的不建议,怎么做呢?

这篇关于模板方法模式【Template Method Pattern】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1063722

相关文章

poj3468(线段树成段更新模板题)

题意:包括两个操作:1、将[a.b]上的数字加上v;2、查询区间[a,b]上的和 下面的介绍是下解题思路: 首先介绍  lazy-tag思想:用一个变量记录每一个线段树节点的变化值,当这部分线段的一致性被破坏我们就将这个变化值传递给子区间,大大增加了线段树的效率。 比如现在需要对[a,b]区间值进行加c操作,那么就从根节点[1,n]开始调用update函数进行操作,如果刚好执行到一个子节点,

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

浅谈主机加固,六种有效的主机加固方法

在数字化时代,数据的价值不言而喻,但随之而来的安全威胁也日益严峻。从勒索病毒到内部泄露,企业的数据安全面临着前所未有的挑战。为了应对这些挑战,一种全新的主机加固解决方案应运而生。 MCK主机加固解决方案,采用先进的安全容器中间件技术,构建起一套内核级的纵深立体防护体系。这一体系突破了传统安全防护的局限,即使在管理员权限被恶意利用的情况下,也能确保服务器的安全稳定运行。 普适主机加固措施:

webm怎么转换成mp4?这几种方法超多人在用!

webm怎么转换成mp4?WebM作为一种新兴的视频编码格式,近年来逐渐进入大众视野,其背后承载着诸多优势,但同时也伴随着不容忽视的局限性,首要挑战在于其兼容性边界,尽管WebM已广泛适应于众多网站与软件平台,但在特定应用环境或老旧设备上,其兼容难题依旧凸显,为用户体验带来不便,再者,WebM格式的非普适性也体现在编辑流程上,由于它并非行业内的通用标准,编辑过程中可能会遭遇格式不兼容的障碍,导致操

poj 1258 Agri-Net(最小生成树模板代码)

感觉用这题来当模板更适合。 题意就是给你邻接矩阵求最小生成树啦。~ prim代码:效率很高。172k...0ms。 #include<stdio.h>#include<algorithm>using namespace std;const int MaxN = 101;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int n

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

uva 1342 欧拉定理(计算几何模板)

题意: 给几个点,把这几个点用直线连起来,求这些直线把平面分成了几个。 解析: 欧拉定理: 顶点数 + 面数 - 边数= 2。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#inc

uva 11178 计算集合模板题

题意: 求三角形行三个角三等分点射线交出的内三角形坐标。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#include <stack>#include <vector>#include <