当前位置: 首页 > news >正文

设计模式-单一职责模式

  • 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的扩展模式

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • WPS excel 数据最小二乘法拟合
  • 4、命令式和声明式
  • Linux shell编程:监控进程CPU使用率并使用 perf 抓取高CPU进程信息
  • 学习前端面试知识
  • cnetos部署高可用以及七层负载均衡
  • git clone 大文件 eof 错误
  • uniapp——列表选择样式
  • 消息队列项目
  • 职业本科大数据实训室
  • 如何使用 AWS CLI 创建和运行 EMR 集群
  • Nginx + PHP 8.0支持视频上传
  • Golang | Leetcode Golang题解之第326题3的幂
  • SoildWorks练习清单
  • C++初学者指南-5.标准库(第二部分)--二叉堆操作
  • node 与 webhdfs 交互
  • [ 一起学React系列 -- 8 ] React中的文件上传
  • 5、React组件事件详解
  • Android 架构优化~MVP 架构改造
  • Apache Zeppelin在Apache Trafodion上的可视化
  • electron原来这么简单----打包你的react、VUE桌面应用程序
  • HTTP请求重发
  • javascript数组去重/查找/插入/删除
  • JS专题之继承
  • Linux Process Manage
  • miaov-React 最佳入门
  • mongodb--安装和初步使用教程
  • Redash本地开发环境搭建
  • REST架构的思考
  • Spring Boot MyBatis配置多种数据库
  • Spring核心 Bean的高级装配
  • Transformer-XL: Unleashing the Potential of Attention Models
  • Vultr 教程目录
  • 创建一种深思熟虑的文化
  • 高程读书笔记 第六章 面向对象程序设计
  • 基于Volley网络库实现加载多种网络图片(包括GIF动态图片、圆形图片、普通图片)...
  • 记一次和乔布斯合作最难忘的经历
  • 聊聊spring cloud的LoadBalancerAutoConfiguration
  • 前嗅ForeSpider采集配置界面介绍
  • 试着探索高并发下的系统架构面貌
  • 详解移动APP与web APP的区别
  • 小程序上传图片到七牛云(支持多张上传,预览,删除)
  • 学习HTTP相关知识笔记
  • 曜石科技宣布获得千万级天使轮投资,全方面布局电竞产业链 ...
  • # Swust 12th acm 邀请赛# [ A ] A+B problem [题解]
  • #在线报价接单​再坚持一下 明天是真的周六.出现货 实单来谈
  • (2/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (22)C#传智:复习,多态虚方法抽象类接口,静态类,String与StringBuilder,集合泛型List与Dictionary,文件类,结构与类的区别
  • (4)事件处理——(6)给.ready()回调函数传递一个参数(Passing an argument to the .ready() callback)...
  • (Demo分享)利用原生JavaScript-随机数-实现做一个烟花案例
  • (不用互三)AI绘画:科技赋能艺术的崭新时代
  • (二)pulsar安装在独立的docker中,python测试
  • (黑马C++)L06 重载与继承
  • (机器学习-深度学习快速入门)第一章第一节:Python环境和数据分析
  • (教学思路 C#之类三)方法参数类型(ref、out、parmas)
  • (十二)Flink Table API