设计模式第七讲-外观模式、适配器模式、模板方法模式详解
一. 外观模式
1. 背景
在现实生活中,常常存在办事较复杂的例子,如办房产证或注册一家公司,有时要同多个部门联系,这时要是有一个综合部门能解决一切手续问题就好了。
软件设计也是这样,当一个系统的功能越来越强,子系统会越来越多,客户对系统的访问也变得越来越复杂。这时如果系统内部发生改变,客户端也要跟着改变,这违背了“开闭原则”,也违背了“迪米特法则”,所以有必要为多个子系统提供一个统一的接口,从而降低系统的耦合度,这就是外观模式的目标。
2. 定义和特点
(1). 定义
是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。
(2). 优点
A. 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
B. 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
C. 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。
(3). 缺点
A. 不能很好地限制客户使用子系统类。
B. 增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
3. 具体实现
(1). 模式结构
A. 外观角色:为多个子系统对外提供一个共同的接口。
B. 子系统角色:实现系统的部分功能,客户可以通过外观角色访问它。
C. 客户端:通过一个外观角色访问各个子系统的功能。
结构图如下:
(2). 使用场景
见下面代码。
(3). 代码实操
子系统代码
/// <summary>/// 子业务类1/// </summary>public class ChildService1{public void MyHandler1(){Console.WriteLine("我正在处理业务1");}}/// <summary>/// 子业务类2/// </summary>public class ChildService2{public void MyHandler2(){Console.WriteLine("我正在处理业务2");}}/// <summary>/// 子业务类3/// </summary>public class ChildService3{public void MyHandler3(){Console.WriteLine("我正在处理业务3");}}
外观角色
/// <summary>/// 外观角色/// </summary>public class FacadeService{private ChildService1 s1 = new ChildService1();private ChildService2 s2 = new ChildService2();private ChildService3 s3 = new ChildService3();public void MyHandler(){s1.MyHandler1();s2.MyHandler2();s3.MyHandler3();}}
测试
{Console.WriteLine("---------------下面是普通调用---------------");ChildService1 s1 = new ChildService1();ChildService2 s2 = new ChildService2();ChildService3 s3 = new ChildService3();s1.MyHandler1();s2.MyHandler2();s3.MyHandler3();Console.WriteLine("---------------下面是外观模式调用---------------");FacadeService f = new FacadeService();f.MyHandler();}
运行结果

4. 适用场景分析
(1). 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
(2). 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
(3). 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。
更多C++后台开发技术点知识内容包括C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,MongoDB,ZK,流媒体,音视频开发,Linux内核,TCP/IP,协程,DPDK多个高级知识点。
【文章福利】另外还整理一些C++后台开发架构师 相关学习资料,面试题,教学视频,以及学习路线图,免费分享有需要的可以点击 C++后端学习资料 免费领取

二. 适配器模式
1. 背景
在现实生活中,经常出现两个对象因接口不兼容而不能在一起工作的实例,这时需要第三者进行适配。例如,讲中文的人同讲英文的人对话时需要一个翻译,用直流电的笔记本电脑接交流电源时需要一个电源适配器,用计算机访问照相机的 SD 内存卡时需要一个读卡器等。
在软件设计中也可能出现:需要开发的具有某种业务功能的组件在现有的组件库中已经存在(且不能修改),但它们与当前系统的接口规范不兼容,如果重新开发这些组件成本又很高,这时用适配器模式能很好地解决这些问题。
2. 定义和特点
(1). 定义:
将一个类的方法转换成客户希望的另外一种要求的实现规范,使得原本由于接口不兼容而不能一起工作的那些类能一起工作,这就是适配器模式。适配器模式分为【类适配器模式】和【对象适配器模式】两种,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
(2). 优点
A. 客户端通过适配器可以按照系统要求的编程规范透明地调用目标接口。
B. 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
C. 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
(3). 缺点
对类适配器来说,更换适配器的实现过程比较复杂。
3. 具体实现
(1). 模式结构
A. 目标接口:当前系统业务所要求遵守编程规范的接口,它可以是抽象类或接口。
B. 适配者类(被适配的类):第三方提供的新类或系统中已经存在的类,它与系统要求的编程规范的目标接口不兼容。
C. 适配器类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
类适配器图:
对象适配器图:
(2). 使用场景
系统要求所有的数据库帮助类必须实现ISqlHelp接口,面向该接口编程,如SQLServerHelp类。 此时第三方提供了一个新的MySql的帮助类(假设是dll,不能修改),它的编程规范和ISqlHelp不兼容,这个时候就需要引入适配器类,使二者能相互兼容。
(3). 代码实操
系统要求的标准开发规范代码
/// <summary>/// 数据库连接抽象接口/// (代码中统一要求, 对数据库操作都要面向该接口编程)/// </summary>public interface ISqlHelp{public void Add();public void Del();public void Modify();}/// <summary>/// SQLServer数据库操作类/// (这是一个样例)/// </summary>public class SQLServerHelp : ISqlHelp{public void Add(){Console.WriteLine($"sqlserver数据库Add成功");}public void Del(){Console.WriteLine($"sqlserver数据库Del成功");}public void Modify(){Console.WriteLine($"sqlserver数据库Modify成功");}}
//1. 项目要求的标准编程模式Console.WriteLine("----------------------------1. 项目要求的标准编程模式---------------------------------");ISqlHelp s1 = new SQLServerHelp();s1.Add();s1.Del();s1.Modify();
第三提供的新类, 不满足开发规范
/// <summary>/// MySQL帮助类(第三方提供,不能修改)/// 没有实现ISqlHelp接口,有自己的一套逻辑./// 但是项目有统一编程要求,要基于ISqlHelp接口编程,/// 我们不能修改MySqlHelp内部的逻辑,所以这个时候要通过适配器模式进行适配/// </summary>public class MySQLHelp{public void AddMySQL(){Console.WriteLine($"MySQL数据库Add成功");}public void DelMySQL(){Console.WriteLine($"MySQL数据库Del成功");}public void ModifyMySQL(){Console.WriteLine($"MySQL数据库Modify成功");}}
//2. 第三方给的MySql帮助类,不符合标准模式Console.WriteLine("------------------2. 第三方给的MySql帮助类,不符合标准模式----------------------");MySQLHelp m = new MySQLHelp();m.AddMySQL();m.DelMySQL();m.ModifyMySQL();
通过适配器模式兼容新的开发规范
/// <summary>/// MySQLHelp适配器类1/// (通过继承的方式实现)/// </summary>public class MySQLHelpAdapter1 : MySQLHelp, ISqlHelp {public void Add(){AddMySQL();}public void Del(){DelMySQL();}public void Modify(){ModifyMySQL();}}/// <summary>/// MySQLHelp适配器类2/// (通过组合的方式实现,可以属性注入、构造函数注入、方法注入)/// </summary>public class MySQLHelpAdapter2 : ISqlHelp{public MySQLHelpAdapter2(){}//属性注入,直接写死private MySQLHelp _mySqlHelp = new MySQLHelp();/// <summary>/// 构造函数注入/// (可以改成抽象模式)/// </summary>/// <param name="mySqlHelp"></param>public MySQLHelpAdapter2(MySQLHelp mySqlHelp){this._mySqlHelp = mySqlHelp;}/// <summary>/// 方法注入/// (可以改成抽象模式)/// </summary>/// <param name="mySqlHelp"></param>public void SetAdapter(MySQLHelp mySqlHelp){this._mySqlHelp = mySqlHelp;}public void Add(){_mySqlHelp.AddMySQL();}public void Del(){_mySqlHelp.DelMySQL();}public void Modify(){_mySqlHelp.ModifyMySQL();}}
//3.通过适配器模式使用MySQL帮助类,且满足标准编程模式Console.WriteLine("------------------3.1 通过继承模式实现适配器------------------");ISqlHelp s2 = new MySQLHelpAdapter1();s2.Add();s2.Del();s2.Modify();Console.WriteLine("------------------3.2 通过组合模式实现适配器------------------");ISqlHelp s3 = new MySQLHelpAdapter2();s3.Add();s3.Del();s3.Modify();
适配器运行结果:
4. 适用场景分析
(1). 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
(2). 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。
三. 模板方法模式
1. 背景
在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。
例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。
这样的例子在生活中还有很多,例如,一个人每天会起床、吃饭、做事、睡觉等,其中“做事”的内容每天可能不同。我们把这些规定了流程或格式的实例定义成模板,允许使用者根据自己的需求去更新它,例如,简历模板、论文模板、Word 中模板文件等。
2. 定义和特点
(1). 定义
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。
(2). 优点
A. 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
B. 它在父类中提取了公共的部分代码,便于代码复用。
C. 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
(3). 缺点
A. 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
B. 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
3. 具体实现
(1). 模式结构
A. 抽象类:负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。
① 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
② 基本方法:是整个算法中的一个步骤,包含以下几种类型。
抽象方法:在抽象类中申明,由具体子类实现。
具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
B. 具体子类:实现抽象类中所定义的抽象方法。
结构图如下:
(2). 使用场景
处理银行业务:取号→排队→办业务→评分, 除了办业务因人而异外(在子类中实现), 其它三步都是固定的,在抽象类中实现。
(3). 代码实操
抽象类:
/// <summary>/// 抽象类/// 处理银行业务:取号→排队→办业务→评分/// 除了办业务因人而异外, 其它三步都是固定的,在抽象类中实现/// </summary>public abstract class AbstractHandler{/// <summary>/// 模板方法/// 里面的步骤是按照固定顺序执行的/// </summary>public void TemplateMethod(){Handler1();Handler2();Handler3();Handler4();}/// <summary>/// 具体方法1/// </summary>public void Handler1(){Console.WriteLine("我是取号业务");}/// <summary>/// 具体方法2/// </summary>public void Handler2(){Console.WriteLine("我是排队业务");}/// <summary>/// 抽象方法3/// 个人业务,因人而异,需要去子类中实现/// </summary>public abstract void Handler3();/// <summary>/// 具体方法4/// </summary>public void Handler4(){Console.WriteLine("我是评分业务");}}
具体子类:
/// <summary>/// 具体子类1/// </summary>public class ChildHandler1 : AbstractHandler{/// <summary>/// 重写办个人业务的方法/// </summary>public override void Handler3(){Console.WriteLine("我来进行理财业务");}}/// <summary>/// 具体子类2/// </summary>public class ChildHandler2 : AbstractHandler{/// <summary>/// 重写办个人业务的方法/// </summary>public override void Handler3(){Console.WriteLine("我来进行存款业务");}}
测试:
{//流程1Console.WriteLine("---------------下面是流程1---------------------");AbstractHandler h1 = new ChildHandler1();h1.TemplateMethod();//流程2Console.WriteLine("---------------下面是流程2---------------------");AbstractHandler h2 = new ChildHandler2();h2.TemplateMethod();}
运行结果:
4. 适用场景分析
(1). 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
(2). 当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
原文https://www.cnblogs.com/yaopengfei/p/13496730.html
相关文章:
设计模式第七讲-外观模式、适配器模式、模板方法模式详解
一. 外观模式 1. 背景 在现实生活中,常常存在办事较复杂的例子,如办房产证或注册一家公司,有时要同多个部门联系,这时要是有一个综合部门能解决一切手续问题就好了。 软件设计也是这样,当一个系统的功能越来越强&…...
flutter-第1章-配置环境
flutter-第1章-配置环境 本文针对Windows系统。 一、安装Android Studio 从Android Studio官网下载最新版本,一直默认安装就行。 安装完成要下载SDK,可能会需要科学上网。 打开AS,随便创建一个新项目。 点击右上角的SDK Manager 找到SDK…...
“消息驱动、事件驱动、流 ”的消息模型
文章目录背景消息驱动 Message-Driven事件驱动 Event-Driven流 Streaming事件规范标准简介: 本文旨在帮助大家对近期消息领域的高频词“消息驱动(Message-Driven),事件驱动(Event-Driven)和流(S…...
量化股票配对交易可以用Python语言实现吗?
量化股票配对交易可以用Python语言实现吗?Python 是一种流行的编程语言,可用于所有类型的领域,包括数据科学。有大量软件包可以帮助您实现目标,许多公司使用 Python 来开发与金融界相关的以数据为中心的应用程序和科学计算。 最重…...
机器学习洞察 | 一文带你“讲透” JAX
在上篇文章中,我们详细分享了 JAX 这一新兴的机器学习模型的发展和优势,本文我们将通过 Amazon SageMaker 示例展示如何部署并使用 JAX。JAX 的工作机制JAX 的完整工作机制可以用下面这幅图详细解释:图片来源:“Intro to JAX” video on YouT…...
OpenFaaS介绍
FaaS 云计算时代出现了大量XaaS形式的概念,从IaaS(Infrastructure as a Service)、PaaS(Platform as a Service)、SaaS(Software as a Service)到容器云引领的CaaS(Containers as a Service),再到火热的微服务架构,它们都在试着将各种软、硬…...
【算法设计与分析】STL容器、递归算法、分治法、蛮力法、回溯法、分支限界法、贪心法、动态规划;各类算法代码汇总
文章目录前言一、STL容器二、递归算法三、分治法四、蛮力法五、回溯法六、分支限界法七、贪心法八、动态规划前言 本篇共为8类算法(STL容器、递归算法、分治法、蛮力法、回溯法、分支限界法、贪心法、动态规划),则各取每类算法中的几例经典示例进行展示。 一、STL容…...
vue初识
第一次接触vue,前端的html,css,jquery,js学习也有段时间了,就照着B站的视频简单看了一些,了解了一些简单的用法,这边做一个记录。 官网 工具:使用VSCode以及Live Server插件(能够实时预览) 第…...
火山引擎入选《2022 爱分析 · DataOps 厂商全景报告》,旗下 DataLeap 产品能力获认可
更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群 2 月 9 日,国内领先的数字化市场研究与咨询机构爱分析发布了《2022 爱分析DataOps 厂商全景报告》(以下简称报告),报…...
java-spring_bean的生命周期
生命周期:从创建到消亡的完整过程初始化容器 1. 创建对象(内存分配 ) 2. 执行构造方法 3. 执行属性注入(set操作) 4. 执行bean初始化方法 使用bean 执行业务操作 关闭/销毁容器 1.执行bean销毁方法 bean销毁时机 容…...
微服务相关概念
一、谈谈你对微服务的理解,微服务有哪些优缺点?微服务是由Martin Fowler大师提出的。微服务是一种架构风格,通过将大型的单体应用划分为比较小的服务单元,从而降低整个系统的复杂度。优点:1、服务部署更灵活࿱…...
论文解读:(TransA)TransA: An Adaptive Approach for Knowledge Graph Embedding
简介 先前的知识表示方法:TransE、TransH、TransR、TransD、TranSparse等。的损失函数仅单纯的考虑hrh rhr和ttt在某个语义空间的欧氏距离,认为只要欧式距离最小,就认为h和th和th和t的关系为r。显然这种度量指标过于简单,虽然先…...
js将数字转十进制+十六进制(联动el-ui下拉选择框)
十进制与十六进制的整数转化一、十进制转十六进制二、十六进制转十进制三、联动demo一、十进制转十六进制 正则表达式: /^([0-9]||([1-9][0-9]{0,}))$/解析:[0-9]代表个位数,([1-9][0-9]{0,})代表十位及以上 二、十六进制转十进制 正则表达…...
关于RedissonLock的一些所思
关于RedissonClient.getLock() 我们一般的使用Redisson的方式就是: RLock myLock redissonClient.getLock("my_order");//myLock.lock();//myLock.tryLock();就上面的例子里,如果某个线程已经拿到了my_order的锁,那别的线程调用m…...
C++:倒牛奶问题
文章目录题目一、输入二、输出三、思路代码题目 农业,尤其是生产牛奶,是一个竞争激烈的行业。Farmer John发现如果他不在牛奶生产工艺上有所创新,他的乳制品生意可能就会受到重创! 幸运的是,Farmer John想出了一个好主…...
MySQL8.x group_by报错的4种解决方法
在我们使用MySQL的时候总是会遇到各种各样的报错,让人头痛不已。其中有一种报错,sql_modeonly_full_group_by,十分常见,每次都是老长的一串出现,然后带走你所有的好心情,如:LIMIT 0, 1000 Error…...
具有非线性动态行为的多车辆列队行驶问题的基于强化学习的方法
论文地址: Reinforcement Learning Based Approach for Multi-Vehicle Platooning Problem with Nonlinear Dynamic Behavior 摘要 协同智能交通系统领域的最新研究方向之一是车辆编队。研究人员专注于通过传统控制策略以及最先进的深度强化学习 (RL) 方法解决自动…...
TrueNas篇-硬盘直通
硬盘直通 在做硬盘直通之前,在trueNas(或者其他虚拟机)内是检测不到安装的硬盘的。 在pve节点查看硬盘信息 打开pve的shell控制台 输入下面的命令查看硬盘信息: ls -l /dev/disk/by-id/该命令会显示出实际所有的硬盘设备信息,其中ata代…...
手机子品牌的“性能战事”:一场殊途同归的大混战
在智能手机行业进入存量市场后,竞争更加白热化。当各国产手机品牌集体冲高端,旗下子品牌们也正厮杀正酣,显现出刀光剑影。处理器、屏幕、内存、价格等各方面无不互相对标,激烈程度并不亚于高端之争。源于OPPO的中端手机品牌realme…...
dockerfile自定义镜像安装jdk8,nginx,后端jar包和前端静态文件,并启动容器访问
dockerfile自定义镜像安装jdk8,nginx,后端jar包和前端静态文件,并启动容器访问简介centos7系统里面我准备的服务如下:5gsignplay-web静态文件内容如下:nginx.conf配置文件内容如下:Dockerfile内容如下:run.sh启动脚本内容如下:制作镜像并启动访问简介 通过用docker…...
页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...
2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...
多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...
【Linux】Linux 系统默认的目录及作用说明
博主介绍:✌全网粉丝23W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...
wpf在image控件上快速显示内存图像
wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像(比如分辨率3000*3000的图像)的办法,尤其是想把内存中的裸数据(只有图像的数据,不包…...
Kafka主题运维全指南:从基础配置到故障处理
#作者:张桐瑞 文章目录 主题日常管理1. 修改主题分区。2. 修改主题级别参数。3. 变更副本数。4. 修改主题限速。5.主题分区迁移。6. 常见主题错误处理常见错误1:主题删除失败。常见错误2:__consumer_offsets占用太多的磁盘。 主题日常管理 …...
xmind转换为markdown
文章目录 解锁思维导图新姿势:将XMind转为结构化Markdown 一、认识Xmind结构二、核心转换流程详解1.解压XMind文件(ZIP处理)2.解析JSON数据结构3:递归转换树形结构4:Markdown层级生成逻辑 三、完整代码 解锁思维导图新…...
LangChain 中的文档加载器(Loader)与文本切分器(Splitter)详解《二》
🧠 LangChain 中 TextSplitter 的使用详解:从基础到进阶(附代码) 一、前言 在处理大规模文本数据时,特别是在构建知识库或进行大模型训练与推理时,文本切分(Text Splitting) 是一个…...
js 设置3秒后执行
如何在JavaScript中延迟3秒执行操作 在JavaScript中,要设置一个操作在指定延迟后(例如3秒)执行,可以使用 setTimeout 函数。setTimeout 是JavaScript的核心计时器方法,它接受两个参数: 要执行的函数&…...
