设计模式7-装饰模式

设计模式7-装饰模式

  • 写在前面
  • 动机
  • 模式定义
  • 结构
  • 代码推导
    • 原始代码
    • 解决
      • 问题分析
    • 选择装饰模式的理由
      • 1. 职责分离(Single Responsibility Principle)
      • 2. 动态扩展功能
      • 3. 避免类爆炸
      • 4. 开闭原则(Open/Closed Principle)
      • 5. 更好的组合复用
      • 例子对比
      • 详细说明
        • 1. 基本流接口 `Stream`
        • 2. 文件流实现 `FileStream`
        • 3. 装饰器基类 `StreamDecorator`
        • 4. 加密装饰器 `CryptoStream`
        • 5. 缓冲装饰器 `BufferedStream`
        • 6. 使用示例 `Process`
      • 总结
  • 要点总结

写在前面

单一职责模式:

  • 在软件组件的设计中,如果责任划分的不清晰,使用记者得到的结果往往是跟随需求的变化,以及子类的增加而急剧膨胀。同时充值的重复代码。这个时候就应该责任划分清楚。使每个类负责自己的责任模块。这才是单一职责模式的关键。

  • 典型模式:装饰模式(decorator model),桥模式(Bridge model)

动机

  • 在某些情况下,我们可能会过多的使用技巧来扩展对象的功能。由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性。随着子类的增多,也就是扩展功能增多。各种子类的组合(扩展功能组合)。

  • 那么如何使对象功能的扩展能够根据需要来动态的实现?而且同时避免扩展功能增多带来的子类膨胀问题。从而使得任何功能扩展变化所导致的影响降为最低。这就是装饰模式的目的。

模式定义

动态组合的给一个对象增加一些额外的职责,就增加工人而言,装饰模式比生成子类更加灵活。也就是消除重复代码以及减少子类个数。

结构

在这里插入图片描述

代码推导

原始代码

//业务操作
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{
public:virtual char Read(int number){//读文件流}virtual void Seek(int position){//定位文件流}virtual void Write(char data){//写文件流}};class NetworkStream :public Stream{
public:virtual char Read(int number){//读网络流}virtual void Seek(int position){//定位网络流}virtual void Write(char data){//写网络流}};class MemoryStream :public Stream{
public:virtual char Read(int number){//读内存流}virtual void Seek(int position){//定位内存流}virtual void Write(char data){//写内存流}};//扩展操作
class CryptoFileStream :public FileStream{
public:virtual char Read(int number){//额外的加密操作...FileStream::Read(number);//读文件流}virtual void Seek(int position){//额外的加密操作...FileStream::Seek(position);//定位文件流//额外的加密操作...}virtual void Write(byte data){//额外的加密操作...FileStream::Write(data);//写文件流//额外的加密操作...}
};class CryptoNetworkStream : :public NetworkStream{
public:virtual char Read(int number){//额外的加密操作...NetworkStream::Read(number);//读网络流}virtual void Seek(int position){//额外的加密操作...NetworkStream::Seek(position);//定位网络流//额外的加密操作...}virtual void Write(byte data){//额外的加密操作...NetworkStream::Write(data);//写网络流//额外的加密操作...}
};class CryptoMemoryStream : public MemoryStream{
public:virtual char Read(int number){//额外的加密操作...MemoryStream::Read(number);//读内存流}virtual void Seek(int position){//额外的加密操作...MemoryStream::Seek(position);//定位内存流//额外的加密操作...}virtual void Write(byte data){//额外的加密操作...MemoryStream::Write(data);//写内存流//额外的加密操作...}
};class BufferedFileStream : public FileStream{//...
};class BufferedNetworkStream : public NetworkStream{//...
};class BufferedMemoryStream : public MemoryStream{//...
}class CryptoBufferedFileStream :public FileStream{
public:virtual char Read(int number){//额外的加密操作...//额外的缓冲操作...FileStream::Read(number);//读文件流}virtual void Seek(int position){//额外的加密操作...//额外的缓冲操作...FileStream::Seek(position);//定位文件流//额外的加密操作...//额外的缓冲操作...}virtual void Write(byte data){//额外的加密操作...//额外的缓冲操作...FileStream::Write(data);//写文件流//额外的加密操作...//额外的缓冲操作...}
};void Process(){//编译时装配CryptoFileStream *fs1 = new CryptoFileStream();BufferedFileStream *fs2 = new BufferedFileStream();CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream();}

这段代码存在几个缺陷。主要包括类的继承结构复杂,重复代码多,扩展性差等问题。

缺陷分析:

在这里插入图片描述
从上述图像可以看出,按照继承模式去写子类。那么子类的数量将会是1个抽象stream类 ,n(此时n=3)个基础子类,n*m个扩展子类,子类与子类间功能还会交叉。

1.类结构复杂。

  • 类继承关系较为复杂,多个类之间存在多重继承(如CryptoFileStream和BufferedFileStream),且同一类的不同变种都要各自继承主类(FileStream)。
  • 这种设计导致继承树较为复杂,难以维护和扩展。

2.重复代码多

  • CryptoFileStream、CryptoNetworkStream、CryptoMemoryStream中的加密操作代码几乎相同。
  • BufferedFileStream、BufferedNetworkStream、BufferedMemoryStream中的缓冲操作代码也基本一致。

3.扩展性差

  • 如果需要增加新的功能,如压缩操作,需要再新增大量类似的类(如CryptoBufferedCompressedFileStream等),导致类的数量急剧增加,扩展性极差。

解决

那么按照继承的逻辑将加密功能以虚函数的形式写入到基类中,有需要重写加密函数不就行了?

将加密功能作为虚函数写入基类的确是一种方式,但是这种方法会有一些缺点,特别是在职责分离和代码扩展性方面。

问题分析

1. 职责不单一:

  • 如果将加密功能直接写入基类,那么基类需要承担多种职责:基本流操作(读、写、定位)和加密操作。这违反了单一职责原则(SRP),即一个类应该只有一个引起其变化的原因。

2. 代码复杂度增加:

  • 基类中加入加密相关的虚函数后,所有子类都需要考虑加密操作,即使某些子类并不需要加密功能。这会增加代码的复杂度。

3. 扩展性差:

  • 如果未来需要添加新的功能(例如压缩、日志记录等),那么基类将会变得越来越臃肿,不同子类需要覆盖不同的虚函数,扩展性差。

选择装饰模式的理由

选择装饰器模式的理由主要有以下几点:

1. 职责分离(Single Responsibility Principle)

装饰器模式允许将不同的功能(如加密、缓冲)分离到不同的类中,从而使每个类只负责一种职责。这符合单一职责原则(Single Responsibility Principle),使代码更易于理解、维护和扩展。

2. 动态扩展功能

装饰器模式可以在运行时动态地组合对象,添加或移除功能,而不需要修改对象本身。这提供了比继承更灵活的功能扩展方式。例如,可以动态地对一个流对象添加加密功能、缓冲功能,甚至多个功能的组合。

3. 避免类爆炸

如果通过继承来实现功能扩展,每种功能组合都需要一个新的子类,类的数量会迅速增加,导致类爆炸问题。装饰器模式通过将功能封装在独立的装饰器类中,避免了大量的子类定义。

4. 开闭原则(Open/Closed Principle)

装饰器模式使类对扩展开放,对修改关闭。可以通过添加新的装饰器类来扩展功能,而无需修改已有的类。这符合开闭原则,提高了代码的可扩展性和灵活性。

5. 更好的组合复用

装饰器模式允许通过不同的装饰器类进行自由组合,复用功能模块。例如,可以同时添加加密和缓冲功能,而无需创建一个专门的“加密缓冲流”类。

例子对比

继承方式的缺点

class Stream {
public:virtual char Read(int number) = 0;virtual void Seek(int position) = 0;virtual void Write(char data) = 0;virtual ~Stream() {}
};class FileStream : public Stream {
public:virtual char Read(int number) {// 读文件流}virtual void Seek(int position) {// 定位文件流}virtual void Write(char data) {// 写文件流}
};// 如果需要加密和缓冲功能,需要创建多个类
class CryptoFileStream : public FileStream {// 实现加密功能
};class BufferedFileStream : public FileStream {// 实现缓冲功能
};class CryptoBufferedFileStream : public FileStream {// 实现加密和缓冲功能
};

装饰器模式的优点

class Stream {
public:virtual char Read(int number) = 0;virtual void Seek(int position) = 0;virtual void Write(char data) = 0;virtual ~Stream() {}
};class FileStream : public Stream {
public:virtual char Read(int number) {// 读文件流}virtual void Seek(int position) {// 定位文件流}virtual void Write(char data) {// 写文件流}
};// 装饰器基类
class StreamDecorator : public Stream {
protected:Stream* stream;
public:StreamDecorator(Stream* strm) : stream(strm) {}virtual char Read(int number) {return stream->Read(number);}virtual void Seek(int position) {stream->Seek(position);}virtual void Write(char data) {stream->Write(data);}
};// 加密装饰器
class CryptoStream : public StreamDecorator {
public:CryptoStream(Stream* strm) : StreamDecorator(strm) {}virtual char Read(int number) {// 额外的加密操作...return StreamDecorator::Read(number);}virtual void Seek(int position) {// 额外的加密操作...StreamDecorator::Seek(position);// 额外的加密操作...}virtual void Write(char data) {// 额外的加密操作...StreamDecorator::Write(data);// 额外的加密操作...}
};// 缓冲装饰器
class BufferedStream : public StreamDecorator {
public:BufferedStream(Stream* strm) : StreamDecorator(strm) {}virtual char Read(int number) {// 额外的缓冲操作...return StreamDecorator::Read(number);}virtual void Seek(int position) {// 额外的缓冲操作...StreamDecorator::Seek(position);// 额外的缓冲操作...}virtual void Write(char data) {// 额外的缓冲操作...StreamDecorator::Write(data);// 额外的缓冲操作...}
};// 使用示例
void Process() {Stream* fileStream = new FileStream();Stream* cryptoStream = new CryptoStream(fileStream);Stream* bufferedCryptoStream = new BufferedStream(cryptoStream);bufferedCryptoStream->Read(100);bufferedCryptoStream->Write('A');bufferedCryptoStream->Seek(10);delete bufferedCryptoStream; // 注意:需要确保正确删除链上的所有装饰器delete cryptoStream;delete fileStream;
}

详细说明

这段代码演示了如何使用装饰器模式来动态地为基本的文件流添加加密和缓冲功能。以下是代码的详细说明:

1. 基本流接口 Stream

Stream 是一个抽象基类,定义了三个纯虚函数:ReadSeekWrite。所有具体的流类都必须实现这些函数。

class Stream {
public:virtual char Read(int number) = 0;virtual void Seek(int position) = 0;virtual void Write(char data) = 0;virtual ~Stream() {}
};
2. 文件流实现 FileStream

FileStream 是一个具体的流类,继承自 Stream 并实现了 ReadSeekWrite 方法。具体的实现细节在代码中没有给出,但假设它们涉及对文件的读写操作。

class FileStream : public Stream {
public:virtual char Read(int number) {// 读文件流}virtual void Seek(int position) {// 定位文件流}virtual void Write(char data) {// 写文件流}
};
3. 装饰器基类 StreamDecorator

StreamDecorator 继承自 Stream,并持有一个指向 Stream 对象的指针 streamStreamDecorator 通过调用 stream 指针的相应方法实现 ReadSeekWrite,从而将所有操作委托给被装饰的流对象。

class StreamDecorator : public Stream {
protected:Stream* stream;
public:StreamDecorator(Stream* strm) : stream(strm) {}virtual char Read(int number) {return stream->Read(number);}virtual void Seek(int position) {stream->Seek(position);}virtual void Write(char data) {stream->Write(data);}
};
4. 加密装饰器 CryptoStream

CryptoStream 继承自 StreamDecorator,并在调用 StreamDecoratorReadSeekWrite 方法之前或之后添加额外的加密操作。

class CryptoStream : public StreamDecorator {
public:CryptoStream(Stream* strm) : StreamDecorator(strm) {}virtual char Read(int number) {// 额外的加密操作...return StreamDecorator::Read(number);}virtual void Seek(int position) {// 额外的加密操作...StreamDecorator::Seek(position);// 额外的加密操作...}virtual void Write(char data) {// 额外的加密操作...StreamDecorator::Write(data);// 额外的加密操作...}
};
5. 缓冲装饰器 BufferedStream

BufferedStream 继承自 StreamDecorator,并在调用 StreamDecoratorReadSeekWrite 方法之前或之后添加额外的缓冲操作。

class BufferedStream : public StreamDecorator {
public:BufferedStream(Stream* strm) : StreamDecorator(strm) {}virtual char Read(int number) {// 额外的缓冲操作...return StreamDecorator::Read(number);}virtual void Seek(int position) {// 额外的缓冲操作...StreamDecorator::Seek(position);// 额外的缓冲操作...}virtual void Write(char data) {// 额外的缓冲操作...StreamDecorator::Write(data);// 额外的缓冲操作...}
};
6. 使用示例 Process

Process 函数中,首先创建一个 FileStream 对象,然后将其装饰为 CryptoStreamBufferedStream,最终形成一个既具有加密功能又具有缓冲功能的流对象。

void Process() {Stream* fileStream = new FileStream(); // 基本的文件流Stream* cryptoStream = new CryptoStream(fileStream); // 加密装饰Stream* bufferedCryptoStream = new BufferedStream(cryptoStream); // 缓冲装饰bufferedCryptoStream->Read(100); // 读取操作,包含加密和缓冲bufferedCryptoStream->Write('A'); // 写入操作,包含加密和缓冲bufferedCryptoStream->Seek(10); // 定位操作,包含加密和缓冲delete bufferedCryptoStream; // 注意:需要确保正确删除链上的所有装饰器delete cryptoStream;delete fileStream;
}

总结

  • 职责分离:每个装饰器类(如 CryptoStreamBufferedStream)只负责增加一个特定的功能,使得代码更符合单一职责原则。
  • 动态组合:通过层层包装,可以在运行时动态地为基本流对象添加多种功能(如加密和缓冲),提高了代码的灵活性和可扩展性。
  • 代码复用:装饰器类可以复用,避免了为每个功能组合创建大量子类的情况。

在这里插入图片描述

从上图可以看出此时类的结构为1+n+1+m。一个抽象类加上n个基础类,加上一个装饰类。再加上m个功能类。对比于修改之前类的层次。冗余的代码量大大减少同时也减少了子类的数量。

要点总结

  • 通过采用组合而非计件的方法,装饰模式实现了在运行时动态扩展对象功能的能力。而且可以根据需要扩展多功能,避免了使用继承带来的灵活性差和子类衍生问题。
  • 装饰器类在接口上表现为一个组合的继承关系。装饰器类继承了组合类所具有的接口但是在实现上又表现为组合关系。
  • 装饰器模式的目的并非解决多子类衍生的多继承问题。装饰器模式应用的要点在于解决主体内在多个方向上的扩展功能。就是装饰的含义。

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

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

相关文章

vue3项目,表单增删改

效果图 ArticleChannel.vue页面代码 <script setup> import {artGetChannelsService ,artDelChannelService} from /api/article.js import { Edit, Delete } from element-plus/icons-vue //调用open方法&#xff0c;ChannelEdit去修改组件内部类容 import ChannelEdit…

Unity通过NDK实现C#与C++之间的相互调用

由于一些历史遗留问题&#xff0c;我们项目还在使用一套C实现的Box2D定点数的库&#xff0c;由于最近修改了视野算法所以需要重新打包安卓的【.so】文件&#xff0c;特此记录 1、关于NDK 在Android平台&#xff0c;C/C需通过NDK编译成动态链接库.so文件&#xff0c;然后C#中通过…

【渗透测试】利用hook技术破解前端JS加解密 - JS-Forward

前言 在做渗透测试项目时&#xff0c;尤其是金融方面&#xff0c;经常会遇到前端JS加解密技术&#xff0c;看着一堆堆密密麻麻的密文&#xff0c;会给人一种无力感。Hook技术则会帮助我们无需获取加解密密钥的前提下&#xff0c;获取明文进行渗透测试 环境准备 JS-Forward Burp…

城市地下综合管廊物联网远程监控

城市地下综合管廊物联网远程监控 城市地下综合管廊&#xff0c;作为现代都市基础设施的重要组成部分&#xff0c;其物联网远程监控系统的构建是实现智慧城市建设的关键环节。这一系统集成了先进的信息技术、传感器技术、通信技术和数据处理技术&#xff0c;旨在对埋设于地下的…

sql 清空表,并清空自增 id

执行 sql TRUNCATE 表名 表名替换为自己要清空的表 在 Navicat 中 新建查询输入 上述 sql点击运行即可表页 f5 刷新&#xff0c;数据已经清空&#xff0c;再次新增数据&#xff0c;自增 id 从 1 开始

Tomcat的负载均衡、动静分离

一、如何tomcat和nginx负载均衡及动静分离&#xff1a;2台tomcat&#xff0c;3台nginx来实现 1.首先设置tomcat1和tomcat2服务器 关闭两台tomcat的防火墙及安全机制&#xff1a;systemctl stop filwalld setenforce 0 进入tomcat目录的webapps中&#xff0c;创建test 2.配…

音频demo:使用opencore-amr将PCM数据与AMR-NB数据进行相互编解码

1、README a. 编译 编译demo 由于提供的.a静态库是在x86_64的机器上编译的&#xff0c;所以仅支持该架构的主机上编译运行。 $ make编译opencore-amr 如果想要在其他架构的CPU上编译运行&#xff0c;可以使用以下命令&#xff08;脚本&#xff09;编译opencore-amr[下载地…

移除元素合并两个有序数组-LeetCode

一、移除元素 . - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a; int removeElement(int* nums, int numsSize, int val) {int src0;int dst0;while(src<numsSize){if(nums[src]val){src;}else if (nums[src]!val){nums[dst]nums[src];src;dst;}}return dst…

渲染引擎之ECS架构介绍

1.什么是ECS&#xff1f; 我们先简单地介绍一下什么是ECS&#xff1a; E -- Entity 实体&#xff0c;本质上是存放组件的容器C -- Component 组件&#xff0c;引擎所需的所有数据结构S -- System 系统&#xff0c;根据组件数据处理逻辑状态的管理器 ECS全称Entity-Component-…

SAPUI5基础知识11 - 组件配置(Component)

1. 背景 组件&#xff08;Component&#xff09;是SAPUI5应用程序中独立且可重用的部件。 SAPUI5提供以下两类组件: faceless组件 (class: sap.ui.core.Component): 无界面组件即没有用户界面相关的元素&#xff0c;用于不需要UI元素编码的场景&#xff1b; UI组件 (class: …

C# 实现基于exe内嵌HTTPS监听服务、从HTTP升级到HTTPS 后端windows服务

由于客户需要把原有HTTP后端服务升级为支持https的服务&#xff0c;因为原有的HTTP服务是一个基于WINDOWS服务内嵌HTTP监听服务实现的&#xff0c;并不支持https, 也不像其他IIS中部署的WebAPI服务那样直接加载HTTPS证书&#xff0c;所以这里需要修改原服务支持https和服务器环…

JAVA进阶学习11

文章目录 一、方法引用1.1 引用静态方法1.2 引用成员方法1.3 引用构造方法1.4 方法引用的其他调用方式1.4.1 使用类名引用成员方法1.4.2 引用数组的构造方法 1.5 总结二、异常2.1 异常的处理2.1.1 JVM虚拟机处理异常2.1.2 try...catch异常处理 2.2 异常中的常见方法2.3 异常的抛…

Java集合升序降序、转Set的方法

Collections.sort(list,Comparator.comparing(OcApplySquareVo::getApplyName).reversed()); 集合转set /** 集合转set */Set<String> pkCodeSet rows.stream().map(RailwayWeighBookResult.RailwayWeighBook::getPkCode).collect(Collectors.toSet());

互联网接入技术的简单介绍

引言 要连接到互联网&#xff0c;用户必须先连接到某个ISP&#xff08;互联网服务提供商&#xff09;。接入技术解决的就是用户如何连接到本地ISP的问题&#xff0c;通常称之为“最后一公里”。本文将详细介绍几种主要的互联网接入技术&#xff0c;帮助初学者了解不同的接入方…

【SOM神经网络的数据分类】SOM神经网络的数据分类的一个小案例

【SOM神经网络的数据分类】SOM神经网络的数据分类的一个小案例 注&#xff1a;本文仅作为自己的学习记录以备以后复习查阅 一 概述 自组织特征映射网络&#xff08;Self-Organizing Feature Map, SOM&#xff09;也叫做Kohonen网络&#xff0c;它的特点是&#xff1a;全连接、…

【C++深度探索】继承机制详解(二)

hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#xff1a;大耳朵土土垚的博客 &#x1…

如何把已经上传到gitlab的代码或者文件夹从git上删掉

有小伙伴不小心把缓存文件上传到了git&#xff0c;跑来问我&#xff0c;要怎么把这些文件给删掉&#xff0c;这里一共有两种方式&#xff0c; 先说第一种&#xff0c;通过命令删除&#xff0c;终端进入存在这个缓存文件的目录&#xff0c;执行命令ls&#xff0c;可以看到确实有…

从零开始搭建vite开发环境

准备 nodejs 18 pnpm https://vitejs.cn/ 开始 pnpm init pnpm add -D vite新建index.html <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width…

飞睿智能无线高速uwb安全数据传输模块,低功耗、抗干扰超宽带uwb芯片传输速度技术新突破

在信息化的时代&#xff0c;数据传输的速度和安全性无疑是每个企业和个人都极为关注的话题。随着科技的飞速发展&#xff0c;超宽带&#xff08;Ultra-Wideband&#xff0c;简称UWB&#xff09;技术凭借其性能和广泛的应用前景&#xff0c;逐渐成为了数据传输领域的新星。今天&…

JavaScript学习笔记(七)

45.9 JavaScript 可迭代对象 可迭代对象&#xff08;Iterables&#xff09;是可以使用 for..of 进行迭代的对象。 从技术上讲&#xff0c;可迭代对象必须实现 Symbol.iterator 方法。 45.9.1 遍历字符串 <body><p id"demo"></p><script>c…