设计模式-单一职责模式

  • Decorator
  • Bridge

Decorator

  • 动机

    • 在某些情况下我们可能会 “过度地使用继承来扩展对象的功能”,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。

    • 如何使得 “对象功能的扩展” 能够根据需求来 动态 的实现?同时避免“扩展功能的增多带来的子类膨胀问题?使功能的拓展变化导致的影响最低?

  • 定义

    动态(组合)地给一个对象增加一些额外的职责。就增加功
    能而言,Decorator 模式比生成子类(继承)更为灵活(消
    除重复代码 & 减少子类个数)。
    ——《设计模式》GoF

  • 举例
    比如设计一个流系统,开始分别有FileStreamNetworkStreamMemoryStream,因为这些类使用相同的函数,但是面向对象不同,因此可以有一个虚基类Stream作为他们的父类。

        class Stream { publicvirtual char Read(int number)=0;virtual void Seek(int position)=0;virtual void Write(char data)=0;virtual ~Stream(){}};class FileStream: public Stream{ ...};class NetworkStream: public Stream{ ...};class MemoryStream: public Stream{ ...};
    

    但是当后期想扩展功能时,比如在FileStream的基础上扩展出CryptoFileStream BufferedFileStream等,如果NetworkStreamMemeoryStream类也有这样的需求,如果直接使用子类继承的方式设计,就会形成如下的类图:

    设,二级子类(图中第二层的类)有n个,三级子类有m个,那么共需要 1+m+mn 个类别,并且包含了大量的重复代码。

    Cryptoxxxtream为例:

        class CryptoFileStream :public FileStream{public:virtual char Read(int number) override;virtual void Seek(int position) override;virtual void Write(char data) override;};class CryptoNetworkStream : :public NetworkStream{public:virtual char Read(int number) override;virtual void Seek(int position) override;virtual void Write(char data) override;};
    

    CryptoFileStreamCryptoNetworkStream接口一致,只是面向不同的平台应用不同,导致了代码冗余。当扩展功能增多,那么冗余的更多,可维护性就更差。

  • Decorator
    应用此模式时的类图:


                              图2

    在上图2中,新拓展的功能类没有直接继承相对应的父类,而是通过加入一个Decotator类,使得在扩展功能类中到底继承哪个父类延迟到子类中。

        class Decorator: public Stream{protected:Stream* _stream;Decorator(Stream * stm):_stream(stm){ }};
    

    Decoraotr最大特点是:继承 stream 的同时也包含了一个 stream 类型的字段_stream

    • 继承stream:是为了继承stream的接口函数

    • _stream字段:是为了使用 多态,在后续的功能扩展中,在运行时再决定扩展FileStreamNetworkStreamMemoryStream中的哪个对象。
      比如 :

          //运行时装配FileStream* s1=new FileStream();// 扩展FileStream --> CryFileStreamCryptoStream* s2=new CryptoStream(s1);// 扩展FileStream --> BufferedStreamFileStreamBufferedStream* s3=new BufferedStream(s1);// 扩展CryFileStream --> CryBufferedStreamFileStreamBufferedStream* s4=new BufferedStream(s2);
      

      此时相比原本的设计,需要的类数是:1+n+1+m,由此可见代码复杂度降低了,可维护性上升了。

      这个案例中,变化的是那些扩展的功能,不变的FileStreamNetworkStreamMemoryStream以及Stream

  • 总结

    • 通过采用 组合而非继承 的手法, Decorator 模式实现了在运行时 动态扩展对象功能 的能力,而且可以根据需要扩展多个功能。避免了使用继承带来的“灵活性差”和“多子类衍生问题”
    • Decoraotr 类在 接口上表现为 is-a Component 的继承关系 ,即Decorator类继承了Component类所具有的接口。但 在实现上又表现为 has-a Component 的组合关系,即 Decoraotr r类又使用了另外一个Component类。
    • Decoraotr 模式的目的并非解决“多子类衍生的多继承”问题,Decoraotr 模式应用的要点在于解决 “主体类在多个方向上的扩展功能”

Bridge

  • 动机
    • 由于某些类型的固有的实现逻辑,使得它们具有两个变化的维度,乃至多个纬度的变化
    • 如何应对这种“多维度的变化”?如何利用面向对象技术来使得类型可以轻松地沿着两个乃至多个方向变化,而不引入额外的复杂度
  • 模式定义

    将抽象部分(业务功能)与实现部分(平台实现)分离,使它们都可以独立地变化
    ——《设计模式》GoF

  • 举例
    比如要设计一个Messageer类来应对不同的平台需求,比如有PCMobile两个平台:
        class Messager{public:virtual void Login(string username, string password)=0;virtual void SendMessage(string message)=0;virtual void SendPicture(Image image)=0;virtual void PlaySound()=0;virtual void DrawShape()=0;virtual void WriteText()=0;virtual void Connect()=0;virtual ~Messager(){}};
    
    Meassger抽象成虚基类,让子类来继承实现。 比如:
        // pc平台class PCMessagerBase : public Messager{public:virtual void PlaySound(){//**********}virtual void DrawShape(){//**********}virtual void WriteText(){//**********}virtual void Connect(){//**********}};// mobile平台class MobileMessagerBase : public Messager{public:virtual void PlaySound(){//==========}virtual void DrawShape(){//==========}virtual void WriteText(){//==========}virtual void Connect(){//==========}};
    
    如此设计容易出现两个问题:
    • 假设针对 PCMessagerBase 扩展出两个需求,一个是精简版,一个完全版本。在登录loginsendMessgae以及sendPicture时效果不同,以其中一个需求为例:

      class PCMessagerPerfect : public PCMessagerBase {
      public:virtual void Login(string username, string password){PCMessagerBase::PlaySound();//********PCMessagerBase::Connect();//........}virtual void SendMessage(string message){PCMessagerBase::PlaySound();//********PCMessagerBase::WriteText();//........}virtual void SendPicture(Image image){PCMessagerBase::PlaySound();//********PCMessagerBase::DrawShape();//........}
      };
      

      可以看出,PCMessagerPerfect只重新实现loginsendMessgaesendPicture,而其余的函数使用父类。而父类PCMessagerBase只实现后四个函数,前三个却不想实现,因为假设在不同平台只是后面的四个函数操作不同,前面三个一致。

      而产生这个问题,在于类Messager里面有朝着两个方向的发展,那么就需要将其分开来,变成两个类,让子类选择自己变化方向的类来继承:

      class Messager{
      protected:MessagerImp* messagerImp;//...
      public:virtual void Login(string username, string password)=0;virtual void SendMessage(string message)=0;virtual void SendPicture(Image image)=0;virtual ~Messager(){}
      };class MessagerImp{
      public:virtual void PlaySound()=0;virtual void DrawShape()=0;virtual void WriteText()=0;virtual void Connect()=0;virtual MessagerImp(){}
      };
      

      将后四个函数的具体业务实现剥离开来,然后组合该类的一个指针对象。那么子类也可以很方便的选择自己需求方向来继承:

      // 平台实现
      class PCMessagerImp : public MessagerImp{
      public:virtual void PlaySound(){//**********}virtual void DrawShape(){//**********}virtual void WriteText(){//**********}virtual void Connect(){//**********}
      };// 业务抽象
      class MessagerLite :public Messager {
      public:virtual void Login(string username, string password){messagerImp->Connect();//........}virtual void SendMessage(string message){messagerImp->WriteText();//........}virtual void SendPicture(Image image){messagerImp->DrawShape();//........}
      };
      

      MessagerLite 实现业务,PCMessagerImp实现逻辑, 这是两个方向的变化 。通过前者的messagerImp指针实现运行时函数调用确定。通过这种组合可以实现相互组合:MessagerImp可以开发出针对更多的平台(n个),Messager开发出更多的设计需求(m个),两个可以任意结合。然而只是需要 2+n+m 个类,就能设计出 n*m 个结果。

  • 总结
    • Bridge模式使用 “对象间的组合关系” 解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以 沿着各自的维度来变化 。所谓抽象和实现沿着各自纬度的变化,即“子类化”它们
    • Bridge模式有时候类似于多继承方案,但是多继承方案往往违背单一职责原则(即一个类只有一个变化的原因),复用性比较差。Bridge模式是比多继承方案更好的解决方法
    • Bridge模式的应用一般在“两个非常强的变化维度”,有时一个类也有多于两个的变化维度,这时可以使用Bridge的扩展模式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/397179.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

yolov8断点续训

1. 前言 我们在使用yolov8进行训练的时候往往会因为各种各样的原因中断训练,如: 开了太多程序导致崩溃突然断电其他原因 这时候如果下次能继续上次训练的结果继续训练会节省很多时间 2.复现与解决办法 2.1 正常启动训练 yolov8环境搭建与简单配置请…

给 Python 的第三方模块安装工具 pip 换源

sudo mkdir ~/.pipcd .pipsudo nano pip.conf[global] timeout 10 index-url http://mirrors.aliyun.com/pypi/simple/ extra-index-url http://pypi.douban.com/simple/ [install] trusted-hostmirrors.aliyun.compypi.douban.com喜欢或对你有帮助,点个赞吧&…

PMBOK® 第六版 规划范围管理

目录 读后感—PMBOK第六版 目录 规划范围管理就像是为项目划定清晰的界限,通过书面形式明确项目团队必须完成的工作,这有助于有效控制和交付目标产品,防止范围蔓延,合理分配资源,从而提高项目成功率和客户满意度。 一…

Tomcat搭建JSPServlet

一、Tomcat环境搭建 1. 将项目变为Web项目 选中项目,点击Help中的Find Action 搜索Add Framework Support 勾选Web Application 出现这些文件就是成功了 2. 配置Tomcat 点击Edit Configurations 点击加号,选择Tomcat Server Local Deployment栏下点击…

WPF篇(9)-CheckBox复选框+RadioButton单选框+RepeatButton重复按钮

CheckBox复选框 CheckBox继承于ToggleButton&#xff0c;而ToggleButton继承于ButtonBase基类。 案例 前端代码 <StackPanel Orientation"Horizontal" HorizontalAlignment"Center" VerticalAlignment"Center"><TextBlock Text"…

前端学习笔记-JS篇-01

JS基础Day1-01-必看-基本软件以及准备工作_哔哩哔哩_bilibili JavaScript介绍 是什么 1.JavaScript (是什么?) 是一种运行在客户端(浏览器)的编程语言&#xff0c;实现人机交互效果2.作用(做什么?) 网页特效(监听用户的一些行为让网页作出对应的反馈)表单验证(针对表单…

8.9-python管理

一、回顾 [rootpython ~]# vim test.py ​ a3 b4 print(ab) print(a**2b**2) print(25**0.5) ​ [rootpython ~]# python3 test.py 7 25 5.0 ​ ​ #调试test.py脚本 [rootpython ~]# python3 -m pdb test.py > /root/test.py(1)<module>() -> a3 (Pdb) n > …

SpringCloud天机学堂:我的课表(三)

SpringCloud天机学堂&#xff1a;我的课表&#xff08;三&#xff09; 文章目录 SpringCloud天机学堂&#xff1a;我的课表&#xff08;三&#xff09;1、添加课程到课表2、分页查询课表3、查询正在学习的课程 1、添加课程到课表 首先&#xff0c;用户支付完成后&#xff0c;需…

JSON + AJAX + ThreadLocal + 文件上传下载

JSON数据交换 规则 JSON对象和字符串转换 <script type"text/javascript">var jsonPerson {"name": "jack","age": 20}console.log(jsonPerson);var strPerson JSON.stringify(jsonPerson);//对jsonPerson没有影响console.lo…

django如何更新数据库字段并与数据库保持同步?

关键步骤&#xff1a; 第一步&#xff1a; 执行&#xff1a;python manage.py makemigrations 你的项目名称第二步&#xff1a;它会提示你选1还是2&#xff0c;这里因为添加字段&#xff0c;所以选1第三步&#xff1a;出现>>>这个&#xff0c;直接输入这个第四步&am…

cnetos部署高可用以及七层负载均衡

文章目录 centos部署高可用以及七层负载均衡一、准备环境二、配置高可用第一台&#xff1a;第二台&#xff1a; 三、配置负载均衡第一台的七层负载均衡器为haproxy第二台的七层负载均衡器为nginx 四、配置后端服务器五、测试 centos部署高可用以及七层负载均衡 一、准备环境 …

密码学基础:搞懂Hash函数SHA1、SHA-2、SHA3(1)

目录 1.消息摘要(Hash) 2.SHA-1 3.SHA-2 4.小结 1.消息摘要(Hash) Hash函数是一种单向密码体制&#xff0c;把任意长度的输入经过变换得到一个固定长度的输出&#xff0c;同时它还具备单向性&#xff0c;只能从明文到密文&#xff0c;不能逆向&#xff0c;正是由于Hash函数…

数据结构----队列和栈

小编会一直更新数据结构相关方面的知识&#xff0c;使用的语言是Java&#xff0c;但是其中的逻辑和思路并不影响&#xff0c;如果感兴趣可以关注合集。 希望大家看完之后可以自己去手敲实现一遍&#xff0c;同时在最后我也列出一些基本和经典的题目&#xff0c;可以尝试做一下。…

C# winform 三层架构 增删改查 修改数据(修改篇)

ss一.留言 本专栏三层架构已经更新了 添加 登录 显示&#xff0c;还差修改以及删除&#xff0c;本篇更新修改&#xff0c;主要操作为点击修改某一条数据&#xff0c;然后跳转页面进行修改。 二.展示 我们先看DAL代码 /// <summary>/// 修改/// </summary>/// &l…

算法基础知识——11种距离度量

简介&#xff1a;个人学习分享&#xff0c;如有错误&#xff0c;欢迎批评指正。 前言&#xff1a;距离的作用 数据聚类&#xff1a;距离度量在聚类算法&#xff08;如K-means、层次聚类&#xff09;中用于衡量数据点之间的相似性或差异性&#xff0c;帮助确定聚类的中心和边界…

haproxy总结与实验

一、负载均衡 1.1 简述负载均衡 在高并发的业务场景下&#xff0c;解决单个节点压力过大&#xff0c;导致Web服务响应过慢&#xff0c;特别是严重的情况下导致服务瘫痪&#xff0c;无法正常提供服务的问题&#xff0c;而负载均衡的目的就是为了维护系统稳定可靠。负载均衡&…

Redis -LFU(Least Frequently Used,最少使用频率)缓存淘汰算法

在 Redis 的 LFU&#xff08;Least Frequently Used&#xff0c;最少使用频率&#xff09;缓存淘汰算法中&#xff0c;lru 字段被拆分成两部分&#xff1a;高 16 位存储 ldt&#xff08;Last Decrement Time&#xff09;&#xff0c;低 8 位存储 logc&#xff08;Logistic Coun…

Postgresql导入矢量数据

前期准备 工具&#xff1a;PgAdmin&#xff0c;postgis-bundle Postgres安装和postgis安装可以百度别的教程。 创建数据库添加扩展 如图&#xff0c;使用PgAdmin创建名为shp的数据库&#xff0c;并在扩展item中添加postgis扩展。 添加扩展方法可以用查询工具输入以下sql语句&…

LeetCode 热题 HOT 100 (024/100)【宇宙最简单版】

【哈希表】No. 0128 最长连续序列【中等】&#x1f449;力扣对应题目指路 希望对你有帮助呀&#xff01;&#xff01;&#x1f49c;&#x1f49c; 如有更好理解的思路&#xff0c;欢迎大家留言补充 ~ 一起加油叭 &#x1f4a6; 欢迎关注、订阅专栏 【力扣详解】谢谢你的支持&am…

ES6模块化简明笔记

1、什么是模块化 详见看上一篇笔记CommonJs模块化简明笔记 2、为什么需要模块化 详见看上一篇笔记CommonJs模块化简明笔记 3、导入和导出的概念 详见看上一篇笔记CommonJs模块化简明笔记 4、模块导出&#xff08;暴露&#xff09; 4.1 导出&#xff08;暴露&#xff09;方式1&…