设计模式-单一职责模式

news2024/11/25 1:05:45
  • 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 --> CryFileStream
          CryptoStream* s2=new CryptoStream(s1);
          // 扩展FileStream --> BufferedStreamFileStream
          BufferedStream* s3=new BufferedStream(s1);
          // 扩展CryFileStream --> CryBufferedStreamFileStream
          BufferedStream* 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的扩展模式

推荐课程:https://xxetb.xetslk.com/s/3oyV5o

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于RK3568+FPGA医用心电监护仪解决方案

医用心电监护仪解决方案 随着我国老龄化速度加快、规模扩大,越来越多民生领域的热点引起民众的关注。庞大的老龄化群体将是一个严峻的问题,各种社会保障政策的实施和各级医疗资源的扩展与升级正在有效化解这一难题。 在这种背景下,医用心电监…

如何构建一个帮助你高效学习编程的完美笔记系统?

在编程学习的过程中,笔记记录是一项至关重要的技能。尤其是在学习Python这样一门功能强大、广泛应用的编程语言时,建立一个高效的笔记系统不仅能帮助你更好地掌握知识,还能提高你的编程效率。那么,如何构建一个帮助你高效学习Pyth…

Java面试八股之消息队列有哪些协议?各种协议有哪些具体实现

消息队列有哪些协议?各种协议有哪些具体实现 消息队列协议是指在消息队列系统中,用于消息的发送、接收和管理的一套通信规则。不同的协议有着不同的特性和应用场景,以下是一些常见的消息队列协议及其具体实现: AMQP (Advanced M…

【leetcode】杨辉三角 、移除元素(Java语言描述)

杨辉三角 给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中,每个数是它左上方和右上方的数的和。 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]示例 2: 输入: numRows 1 输出: [[1]] …

SecureCoding in C and C++(二)

经过上期的环境搭建过后,我们将正式的学习C系列,首先要学习的是C的一些常用的变量 从编译和连接学起似乎也是不错的选择。 个人总结的一句话:编译其实就是对预处理语句进行处理后,然后对语句进行处理。对预处理语句,例…

C++——list列表容器经典案例——手机按销量降序排列,若销量相同则按价格降序排列

需求:使用list列表对商品进行排序,先通过销量降序排,若销量相同则根据价格升序排列输出 涉及到的知识点:list列表容器、自定义数据类型、自定义排序规则 实现步骤: 1,自定义数据类型Product,…

Android 实现多进程通讯(如何实现多进程开发,Binder、AIDL)

目录 1)为什么App需要多进程 2)什么是多进程开发? 3)如何实现多进程开发? 4)跨进程间通讯(案例) 5)多进程需要注意什么问题? 6)多进程的底层原理是什么?【待写】 …

【Python机器学习】树回归——使用Python的tkinter库创建GUI

机器学习给我们提供了一些强大的工具,能从未知数据中抽取出有用的信息。因此,能否这些信息以易于人们理解的方式呈现十分重要。如果人们可以直接与算法和数据交互,将可以比较轻松的进行解释。其中一个能够同时支持数据呈现和用户交互的方式就…

手机IP地址:是根据网络还是设备决定的?

在日益数字化的今天,手机已经成为我们日常生活中不可或缺的一部分。它不仅是我们沟通的桥梁,更是我们获取信息、享受娱乐和完成工作的得力助手。然而,在使用手机上网的过程中,你是否曾经好奇过手机的IP地址是如何被分配的&#xf…

Java中class文件结构分析二

第17个常量池:01 00 15 28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56 01:tag位表示的是utf8类型的字面量常量 00 15 二个字节表示的是字面量常量的长度为21 接下来21个字节: 28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56…

经典大语言模型解读(1):BERT——基于双向Transformer的预训练语言模型

论文:BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding 前言 BERT(Bidirectional Encoder Representation from Transformer)是Google于2019年提出的预训练语言模型。与寻常的Transformer架构不同&#…

eval和长度限制

目录 源码 解决方案 方法一 方法二 方法三 源码 <?php $param $_REQUEST[param]; if(strlen($param)<17 && stripos($param,eval) false && stripos($param,assert) false) {eval($param); } ?> 限制条件&#xff1a; 传入的参数长度不能…

Go语言+Vue3开发前后端后台管理系统实战 用户管理的前端界面和表结构分析

首页&#xff1a; 用户管理界面&#xff1a; 到这一步以后来看一下后端代码的表结构是如何设计的&#xff1a; 后端代码中&#xff0c;使用的操作MySQL的技术是gorm&#xff1a; gorm.io/gorm v1.25.5其中&#xff0c;用户表的定义位置如下&#xff1a; 此时的完整代码如…

C++虚函数习题

#include <iostream>using namespace std;class Animal { public:Animal() {}virtual void perform()0; };class Lion:public Animal { public:Lion() {}void perform(){cout << "狮子会吃小朋友&#xff01;&#xff01;&#xff01;快跑&#xff01;&#x…

设计模式(1)创建型模式和结构型模式

1、目标 本文的主要目标是学习创建型模式和结构型模式&#xff0c;并分别代码实现每种设计模式 2、创建型模式 2.1 单例模式&#xff08;singleton&#xff09; 单例模式是创建一个对象保证只有这个类的唯一实例&#xff0c;单例模式分为饿汉式和懒汉式&#xff0c;饿汉式是…

IP问题总结

IP基础知识 IP 在 TCP/IP 参考模型中处于第三层&#xff0c;也就是⽹络层。 ⽹络层的主要作⽤是&#xff1a;实现主机与主机之间的通信&#xff0c;也叫点对点&#xff08;end to end&#xff09;通信。 1.⽹络层与数据链路层有什么关系呢&#xff1f; 其实很容易区分&#…

eNSP 华为浮动路由

R1&#xff1a; <Huawei>system-view [Huawei]sysname R1 [R1]int g0/0/0 [R1-GigabitEthernet0/0/0]ip add 172.16.1.1 24 [R1-GigabitEthernet0/0/0]int g0/0/1 [R1-GigabitEthernet0/0/1]ip add 10.10.1.1 24 [R1-GigabitEthernet0/0/1]quit [R1]vlan 10 //e口是…

使用docker搭建aria2-pro+ariang并在alist中配置

一、安装aria2-pro 1.创建映射目录 # 配置目录 mkdir -p /usr/local/docker/aria2/config # 下载目录 mkdir -p /share_root/download-aria22.创建容器 docker run -d \--name aria2-pro \--restart unless-stopped \--log-opt max-size1m \--network host \-e PUID$UID \-e …

【秋招笔试】8.12-4399秋招(第一套)-三语言题解

🍭 大家好这里是 春秋招笔试突围,一起备战大厂笔试 💻 ACM金牌团队🏅️ | 多次AK大厂笔试 | 编程一对一辅导 ✨ 本系列打算持续跟新 春秋招笔试题 👏 感谢大家的订阅➕ 和 喜欢💗 和 手里的小花花🌸 ✨ 笔试合集传送们 -> 🧷春秋招笔试合集 🍒 本专栏已收…

全网最详细HAProxy入门小知识

目录 一. 负载均衡 负载均衡的意义&#xff1a; 负载均衡的类型&#xff1a; 二. HAProxy 简介 HAProxy 的特点&#xff1a; 社区版和企业版&#xff1a; 三. HAProxy 的安装和服务信息 1、实验环境 1&#xff09;安装并配置 Nginx 2&#xff09;在客户端测试 2、安装…