热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

设计模式第七讲外观模式、适配器模式、模板方法模式详解

一.外观模式1.背景在现实生活中,常常存在办事较复杂的例子,如办房产证或注册一家公司,有时要同多个部门联系,这时要是有一个

一. 外观模式
1. 背景

  在现实生活中,常常存在办事较复杂的例子,如办房产证或注册一家公司,有时要同多个部门联系,这时要是有一个综合部门能解决一切手续问题就好了。

  软件设计也是这样,当一个系统的功能越来越强,子系统会越来越多,客户对系统的访问也变得越来越复杂。这时如果系统内部发生改变,客户端也要跟着改变,这违背了“开闭原则”,也违背了“迪米特法则”,所以有必要为多个子系统提供一个统一的接口,从而降低系统的耦合度,这就是外观模式的目标。

2. 定义和特点

(1). 定义

  是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。

(2). 优点

 A. 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。

 B. 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。

 C. 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。

(3). 缺点

 A. 不能很好地限制客户使用子系统类。

 B. 增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。

3. 具体实现

(1). 模式结构

 A. 外观角色:为多个子系统对外提供一个共同的接口。

 B. 子系统角色:实现系统的部分功能,客户可以通过外观角色访问它。

 C. 客户端:通过一个外观角色访问各个子系统的功能。

结构图如下:



(2). 使用场景

见下面代码。

(3). 代码实操

子系统代码

///


/// 子业务类1
///

public class ChildService1
{
public void MyHandler1()
{
Console.WriteLine("我正在处理业务1");
}
}
///
/// 子业务类2
///

public class ChildService2
{
public void MyHandler2()
{
Console.WriteLine("我正在处理业务2");
}
}
///
/// 子业务类3
///

public class ChildService3
{
public void MyHandler3()
{
Console.WriteLine("我正在处理业务3");
}
}

外观角色

///


/// 外观角色
///

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). 代码实操

系统要求的标准开发规范代码

///


/// 数据库连接抽象接口
/// (代码中统一要求, 对数据库操作都要面向该接口编程)
///

public interface ISqlHelp
{
public void Add();
public void Del();
public void Modify();
}
///
/// SQLServer数据库操作类
/// (这是一个样例)
///

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();

第三提供的新类, 不满足开发规范

///


/// MySQL帮助类(第三方提供,不能修改)
/// 没有实现ISqlHelp接口,有自己的一套逻辑.
/// 但是项目有统一编程要求,要基于ISqlHelp接口编程,
/// 我们不能修改MySqlHelp内部的逻辑,所以这个时候要通过适配器模式进行适配
///

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();

通过适配器模式兼容新的开发规范

///


/// MySQLHelp适配器类1
/// (通过继承的方式实现)
///

public class MySQLHelpAdapter1 : MySQLHelp, ISqlHelp
{
public void Add()
{
AddMySQL();
}
public void Del()
{
DelMySQL();
}
public void Modify()
{
ModifyMySQL();
}
}
///
/// MySQLHelp适配器类2
/// (通过组合的方式实现,可以属性注入、构造函数注入、方法注入)
///

public class MySQLHelpAdapter2 : ISqlHelp
{
public MySQLHelpAdapter2()
{
}
//属性注入,直接写死
private MySQLHelp _mySqlHelp = new MySQLHelp();
///
/// 构造函数注入
/// (可以改成抽象模式)
///

///
public MySQLHelpAdapter2(MySQLHelp mySqlHelp)
{
this._mySqlHelp = mySqlHelp;
}
///
/// 方法注入
/// (可以改成抽象模式)
///

///
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). 代码实操

抽象类:

///


/// 抽象类
/// 处理银行业务:取号→排队→办业务→评分
/// 除了办业务因人而异外, 其它三步都是固定的,在抽象类中实现
///

public abstract class AbstractHandler
{
///
/// 模板方法
/// 里面的步骤是按照固定顺序执行的
///

public void TemplateMethod()
{
Handler1();
Handler2();
Handler3();
Handler4();
}
///
/// 具体方法1
///

public void Handler1()
{
Console.WriteLine("我是取号业务");
}
///
/// 具体方法2
///

public void Handler2()
{
Console.WriteLine("我是排队业务");
}
///
/// 抽象方法3
/// 个人业务,因人而异,需要去子类中实现
///

public abstract void Handler3();
///
/// 具体方法4
///

public void Handler4()
{
Console.WriteLine("我是评分业务");
}
}

具体子类:

///


/// 具体子类1
///

public class ChildHandler1 : AbstractHandler
{
///
/// 重写办个人业务的方法
///

public override void Handler3()
{
Console.WriteLine("我来进行理财业务");
}
}
///
/// 具体子类2
///

public class ChildHandler2 : AbstractHandler
{
///
/// 重写办个人业务的方法
///

public override void Handler3()
{
Console.WriteLine("我来进行存款业务");
}
}

测试:

{
//流程1
Console.WriteLine("---------------下面是流程1---------------------");
AbstractHandler h1 = new ChildHandler1();
h1.TemplateMethod();
//流程2
Console.WriteLine("---------------下面是流程2---------------------");
AbstractHandler h2 = new ChildHandler2();
h2.TemplateMethod();
}

运行结果:




4. 适用场景分析

(1). 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。

(2). 当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。

原文https://www.cnblogs.com/yaopengfei/p/13496730.html




推荐阅读
  • 本文介绍了Windows操作系统的版本及其特点,包括Windows 7系统的6个版本:Starter、Home Basic、Home Premium、Professional、Enterprise、Ultimate。Windows操作系统是微软公司研发的一套操作系统,具有人机操作性优异、支持的应用软件较多、对硬件支持良好等优点。Windows 7 Starter是功能最少的版本,缺乏Aero特效功能,没有64位支持,最初设计不能同时运行三个以上应用程序。 ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 本文详细介绍了SQL日志收缩的方法,包括截断日志和删除不需要的旧日志记录。通过备份日志和使用DBCC SHRINKFILE命令可以实现日志的收缩。同时,还介绍了截断日志的原理和注意事项,包括不能截断事务日志的活动部分和MinLSN的确定方法。通过本文的方法,可以有效减小逻辑日志的大小,提高数据库的性能。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • Oracle分析函数first_value()和last_value()的用法及原理
    本文介绍了Oracle分析函数first_value()和last_value()的用法和原理,以及在查询销售记录日期和部门中的应用。通过示例和解释,详细说明了first_value()和last_value()的功能和不同之处。同时,对于last_value()的结果出现不一样的情况进行了解释,并提供了理解last_value()默认统计范围的方法。该文对于使用Oracle分析函数的开发人员和数据库管理员具有参考价值。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • 本文讨论了编写可保护的代码的重要性,包括提高代码的可读性、可调试性和直观性。同时介绍了优化代码的方法,如代码格式化、解释函数和提炼函数等。还提到了一些常见的坏代码味道,如不规范的命名、重复代码、过长的函数和参数列表等。最后,介绍了如何处理数据泥团和进行函数重构,以提高代码质量和可维护性。 ... [详细]
  • python中安装并使用redis相关的知识
    本文介绍了在python中安装并使用redis的相关知识,包括redis的数据缓存系统和支持的数据类型,以及在pycharm中安装redis模块和常用的字符串操作。 ... [详细]
author-avatar
戴乐季206
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有