C++ 设计模式——适配者模式

news2024/9/20 16:02:49

C++ 设计模式——适配者模式

    • C++ 设计模式——适配者模式
      • 1. 主要组成成分
      • 2. 逐步构建适配者模式
        • 2.1 目标抽象类定义
        • 2.2 源类实现
        • 2.3 适配器类实现
        • 2.4 客户端
      • 3. 适配者模式 UML 图
        • 适配者模式 UML 图解析
      • 5. 类适配者
      • 6. 适配者模式的优点
      • 7. 适配者模式的缺点
      • 8. 适配者模式适用场景
      • 总结
      • 完整代码

C++ 设计模式——适配者模式

适配者模式(Adapter Pattern)是一种结构型设计模式,主要用于解决接口不兼容的问题。其核心思想是通过创建一个适配器类,将一个类的接口转换成客户端所期待的另一种接口。

1. 主要组成成分

  • 目标抽象类(Target):这是客户端所期望的接口,定义了客户端使用的方法。
  • 源类(Adaptee):这是需要被适配的类,拥有某些功能,但其接口与目标接口不兼容。
  • 适配器类(Adapter):适配器类实现目标接口,并持有一个源类的实例。在适配器中,适配器会调用源类的方法,以满足目标接口的要求。
  • 客户端(Client):客户端通过目标接口与适配器进行交互,而不是直接与源类交互。

2. 逐步构建适配者模式

以日志系统为例,逐步构建适配者模式。

2.1 目标抽象类定义

目标抽象类LogToDatabase,包含与日志数据库操作相关的方法:初始化数据库、写入日志、读取日志和关闭数据库。

//目标接口
class LogToDatabase
{
public:
    virtual void initdb() = 0; //不一定非是纯虚函数
    virtual void writetodb(const char* pcontent) = 0;
    virtual void readfromdb() = 0;
    virtual void closedb() = 0;

    virtual ~LogToDatabase() {} //做父类时析构函数应该为虚函数
};
2.2 源类实现

定义源类 LogToFile,实现日志文件操作的方法,这些方法与目标接口不兼容,需要通过适配器转换。

//源类
class LogToFile
{
public:
    void initfile()
    {
        //做日志文件初始化工作,比如打开文件等等
        //......
    }
    void writetofile(const char* pcontent)
    {
        //将日志内容写入文件
        //......
    }
    void readfromfile()
    {
        //从日志中读取一些信息
        //......
    }
    void closefile()
    {
        //关闭日志文件
        //......
    }
    //......可能还有很多其他成员函数,略
};
2.3 适配器类实现

实现适配器类 LogAdapter,它继承自目标接口 LogToDatabase。构造函数接受 LogToFile 对象的指针,实现目标接口的方法时调用源类的方法。

//适配器类
class LogAdapter :public LogToDatabase
{
public:
    //构造函数
    LogAdapter(LogToFile* pfile) //形参是老接口所属类的指针
    {
        m_pfile = pfile;
    }
    virtual void initdb()
    {
        cout << "在LogAdapter::initdb()中适配LogToFile::initfile()" << endl;
        //这其中也可以加任何的其他代码......
        m_pfile->initfile();
    }
    virtual void writetodb(const char* pcontent)
    {
        cout << "在LogAdapter::writetodb()中适配LogToFile::writetofile()" << endl;
        m_pfile->writetofile(pcontent);
    }
    virtual void readfromdb()
    {
        cout << "在LogAdapter::readfromdb()中适配LogToFile::readfromdb()" << endl;
        m_pfile->readfromfile();
    }
    virtual void closedb()
    {
        cout << "在LogAdapter::closedb()中适配LogToFile::closedb()" << endl;
        m_pfile->closefile();
    }
private:
    LogToFile* m_pfile;
};
2.4 客户端

main 函数中,创建 LogToFileLogAdapter 实例,并通过适配器调用目标接口的方法。

//客户端使用
int main()
{
    LogToFile* plog = new LogToFile();
    LogToDatabase* plogdb = new LogAdapter(plog);
    plogdb->initdb();
    plogdb->writetodb("向数据库中写入一条日志,实际是向日志文件中写入一条日志");
    plogdb->readfromdb();
    plogdb->closedb();
    delete plogdb;
    delete plog;

    return 0;
}

3. 适配者模式 UML 图

适配者模式 UML 图

适配者模式 UML 图解析
  • Target (目标抽象类): 此类定义了客户端期望使用的接口,如 initdbwritetodbreadfromdbclosedb 等。这些接口代表了调用者希望利用的未来接口,并将由客户端直接调用。这里指LogToDatabase 类。
  • Adaptee (源类): 这个类扮演被适配的角色,拥有一套已经存在的接口,这些接口与目标接口不兼容需要被适配。适配涉及将调用目标接口的行为转换为对这些已存在接口的调用。在适配者模式中,适配者类可以不止一个,示例中的 LogToFile 类就是这样一个适配者。
  • Adapter (适配器类): 适配器类是适配者模式的核心,它连接 Target 和 Adaptee 角色。此类通过包装一个或多个 Adaptee 对象,使得 Adaptee 的接口看起来像是 Target 的接口。适配器类负责将 Target 接口的调用转换为 Adaptee 接口的调用,实现接口的兼容。在给出的例子中,这个角色由 LogAdapter 类实现。

5. 类适配者

除了上面介绍的对象适配器,还有一种实现方式叫做类适配器。类适配器使用多重继承来实现适配,这在C++中是可行的,但在Java等不支持多重继承的语言中就不能使用。以下是类适配器的实现示例:

类适配器与对象适配器的主要区别在于:类适配器通过继承来实现适配,而对象适配器通过组合来实现适配。类适配器可以重写Adaptee的行为,但也会增加耦合度。

//类适配器
class LogAdapter :public LogToDatabase, private LogToFile
{
public:
    virtual void initdb()
    {
        cout << "在LogAdapter::initdb()中适配LogToFile::initfile()" << endl;
        //这其中也可以加任何的其他代码......
        initfile();
    }
    virtual void writetodb(const char* pcontent)
    {
        cout << "在LogAdapter::writetodb()中适配LogToFile::writetofile()" << endl;
        writetofile(pcontent);
    }
    virtual void readfromdb()
    {
        cout << "在LogAdapter::readfromdb()中适配LogToFile::readfromdb()" << endl;
        readfromfile();
    }
    virtual void closedb()
    {
        cout << "在LogAdapter::closedb()中适配LogToFile::closedb()" << endl;
        closefile();
    }
};
// 客户端
int main()
{
    LogToDatabase* plogdb = new LogAdapter();
    plogdb->initdb();
    plogdb->writetodb("向数据库中写入一条日志,实际是向日志文件中写入一条日志");
    plogdb->readfromdb();
    plogdb->closedb();
    delete plogdb;

    return 0;
}

6. 适配者模式的优点

  • 灵活性:可以通过添加新的适配器来支持新接口,而无需修改现有代码。
  • 复用性:可复用现有的类,减少了重复代码。
  • 解耦:客户端与源类之间的关系通过适配器解耦,降低了系统的复杂性。

7. 适配者模式的缺点

  • 增加复杂性:引入适配器类可能使系统结构变得更加复杂。
  • 性能开销:适配器增加了一层间接调用,可能影响性能,但在大多数情况下影响微乎其微。

8. 适配者模式适用场景

  • 已存在的类的接口不符合系统的需求:当系统需要使用现有的类,但这些类的接口不符合系统的需求时,可以使用适配者模式来使现有的类与系统接口兼容。
  • 需要创建一个可复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作:适配器模式可以提供一个中间层,通过包装一个不兼容的对象,将其接口转换成目标接口,从而使其能与多种不同的对象协同工作。
  • 在需要使用几个现有的子类,但是子类的接口不一致时:可以使用适配者模式来适配这些接口。通过定义一个统一的接口,并在适配器中将调用分派给相应的子类接口,可以使得原本由于接口不兼容而不能一起工作的类可以一起工作。
  • 系统需要使用现有的类,而类的接口不符合系统的需求:例如,系统数据的输入需要特定的格式,而现有的库提供的数据格式与之不符。适配器可以在这两者之间进行转换。
  • 整合多个库或框架时:当使用多个库或框架构建应用程序时,经常会遇到因为接口不兼容而无法一起工作的情况。使用适配器模式可以解决这些库或框架间的接口不兼容问题,使它们可以一起工作。
  • 替换系统中的旧组件时:在软件维护或升级过程中,旧的组件可能需要被更现代或功能更强大的组件替换。如果新组件的接口与系统现有的接口不匹配,适配器模式可以用来适配这些接口,从而允许系统平滑过渡到新的组件,而不需要重写大量的代码。

总结

适配者模式是一种强大而灵活的设计模式,它允许不兼容的接口能够协同工作。通过创建适配器,我们可以复用现有的类,而无需修改其代码。这种模式特别适用于系统集成、旧系统改造或者与第三方库协作的场景。

适配者模式的核心在于:

  1. 识别目标接口和源类之间的差异。
  2. 设计适配器类来桥接这些差异。
  3. 在适配器中实现接口转换逻辑。

在使用适配者模式时,需要注意以下几点:

  1. 确保适配器只处理接口转换,不要在其中添加额外的业务逻辑。
  2. 考虑使用对象适配器还是类适配器,根据实际需求和语言特性来选择。
  3. 当需要适配的类较多时,可以考虑使用工厂模式来创建适配器。

虽然适配者模式可能会增加一些复杂性,但它提供的灵活性和可维护性通常会超过这些缺点。在使用时,应该权衡其利弊,选择最适合特定场景的解决方案。

完整代码

#include <iostream>
#include <vector>
#include <string>
#include <fstream>

using namespace std;

//日志文件操作相关类
class LogToFile
{
public:
    void initfile()
    {
        //做日志文件初始化工作,比如打开文件等等
        //......
    }
    void writetofile(const char* pcontent)
    {
        //将日志内容写入文件
        //......
    }
    void readfromfile()
    {
        //从日志中读取一些信息
        //......
    }
    void closefile()
    {
        //关闭日志文件
        //......
    }
    //......可能还有很多其他成员函数,略
};

class LogToDatabase
{
public:
    virtual void initdb() = 0; //不一定非是纯虚函数
    virtual void writetodb(const char* pcontent) = 0;
    virtual void readfromdb() = 0;
    virtual void closedb() = 0;

    virtual ~LogToDatabase() {} //做父类时析构函数应该为虚函数
};

/*//适配器类
class LogAdapter :public LogToDatabase
{
public:
    //构造函数
    LogAdapter(LogToFile* pfile) //形参是老接口所属类的指针
    {
        m_pfile = pfile;
    }
    virtual void initdb()
    {
        cout << "在LogAdapter::initdb()中适配LogToFile::initfile()" << endl;
        //这其中也可以加任何的其他代码......
        m_pfile->initfile();
    }
    virtual void writetodb(const char* pcontent)
    {
        cout << "在LogAdapter::writetodb()中适配LogToFile::writetofile()" << endl;
        m_pfile->writetofile(pcontent);
    }
    virtual void readfromdb()
    {
        cout << "在LogAdapter::readfromdb()中适配LogToFile::readfromdb()" << endl;
        m_pfile->readfromfile();
    }
    virtual void closedb()
    {
        cout << "在LogAdapter::closedb()中适配LogToFile::closedb()" << endl;
        m_pfile->closefile();
    }
private:
    LogToFile* m_pfile;
}*/;

//类适配器
class LogAdapter :public LogToDatabase, private LogToFile
{
public:
    virtual void initdb()
    {
        cout << "在LogAdapter::initdb()中适配LogToFile::initfile()" << endl;
        //这其中也可以加任何的其他代码......
        initfile();
    }
    virtual void writetodb(const char* pcontent)
    {
        cout << "在LogAdapter::writetodb()中适配LogToFile::writetofile()" << endl;
        writetofile(pcontent);
    }
    virtual void readfromdb()
    {
        cout << "在LogAdapter::readfromdb()中适配LogToFile::readfromdb()" << endl;
        readfromfile();
    }
    virtual void closedb()
    {
        cout << "在LogAdapter::closedb()中适配LogToFile::closedb()" << endl;
        closefile();
    }
};

int main()
{
    //    LogToFile* plog = new LogToFile();
    //    LogToDatabase* plogdb = new LogAdapter(plog);
    //    plogdb->initdb();
    //    plogdb->writetodb("向数据库中写入一条日志,实际是向日志文件中写入一条日志");
    //    plogdb->readfromdb();
    //    plogdb->closedb();
    //    delete plogdb;
    //    delete plog;

    LogToDatabase* plogdb = new LogAdapter();
    plogdb->initdb();
    plogdb->writetodb("向数据库中写入一条日志,实际是向日志文件中写入一条日志");
    plogdb->readfromdb();
    plogdb->closedb();
    delete plogdb;

    return 0;
}

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

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

相关文章

永久去除windows11推荐产品的软件

永久去除windows11推荐产品的软件 去除windows11 推荐的项目&#xff0c;并用来固定软件 要求 22621及以上版本 企业版&#xff0c;专业教育版&#xff0c;教育版&#xff08;可以自行找工具切换&#xff0c;无需重装系统,非常方便的。&#xff09; [软件原创作者]&#xff…

【Python】Python 函数综合指南——从基础到高阶

文章目录 Python 函数综合指南1. 函数介绍1.1 什么是函数&#xff1f;1.2 定义函数示例&#xff1a;1.3 调用函数1.4 函数参数1.4.1 必需参数1.4.2 默认参数1.4.3 关键字参数1.4.4 可变长度参数 2. Python 内置函数2.1 字符串处理函数示例&#xff1a; 2.2 数学函数示例&#x…

音视频相关

ffmpeg 安装 1. 源码安装 git clone https://git.ffmpeg.org/ffmpeg.git 2. 配置 编译 安装 ./configure --prefix/usr/local/ffmpeg --enable-debug3 --enable-shared --disable-static --disable-x86asm --enable-ffplaymake -jnproc && make install Q: 没有ff…

C++ 基础学习

提示并输入一个字符串&#xff0c;统计该字符串中字母个数、数字个数、空格个数、其他字符的个数 #include <iostream>using namespace std;int main() {cout<<"请输入字符串:";string str;getline(cin,str);int num0;int alp0;int spa0;int other0;int …

大语言模型-GPT3-Language Models are Few-Shot Learners

一、背景信息&#xff1a; GPT3是于2020 年由OpenAI 发布的预训练语言模型。 GPT3在自然语言处理&#xff08;NLP&#xff09;任务中表现出色&#xff0c;可以生成连贯的文本、回答问题、进行对话等。 GPT3的网络架构继续沿用GPT1、GPT2的是多层Transformer Decoder改的结构。…

论文笔记:GEO-BLEU: Similarity Measure for Geospatial Sequences

22 sigspatial 1 intro 提出了一种空间轨迹相似性度量的方法比较了两种传统相似度度量的不足 DTW 基本特征是它完全对齐序列以进行测量&#xff0c;而不考虑它们之间共享的局部特征这适用于完全对齐的序列&#xff0c;但不适用于逐步对齐没有太多意义的序列BLEU 适用于不完全…

MVSEP-MDX23容器构建详细教程

一、介绍 模型GitHub网址&#xff1a;MVSEP-MDX23-music-separation-model/README.md 在 main ZFTurbo/MVSEP-MDX23-音乐分离模型 GitHub 上 在音视频领域&#xff0c;把已经发布的混音歌曲或者音频文件逆向分离一直是世界性的课题。音波混合的物理特性导致在没有原始工程文件…

股指期货的交易规则有哪些?

股指期货作为一种金融衍生品&#xff0c;其合约条款和交易规则是投资者必须了解的重要内容。以下是关于股指期货合约条款及交易规则的详细解释&#xff1a; 一、合约乘数 沪深300指数期货合约的乘数为每点人民币300元。 中证500股指期货合约的乘数为每点200元。 上证50股指…

【iOS】Masonry学习

Masonry学习 前言NSLayoutConstraintMasonry学习mas_equalTo和equalToMasonry的优先级Masorny的其他写法 Masonry的使用练习 前言 Masonry是一个轻量级的布局框架。通过链式调用的方式来描述布局&#xff0c;是排版代码更加简洁易读。masonry支持iOS和Mac OS X。相比原生的NSL…

浅谈【数据结构】图-最短路径问题

目录 1、最短路径问题 2、迪杰斯特拉算法 3、算法的步骤 谢谢帅气美丽且优秀的你看完我的文章还要点赞、收藏加关注 没错&#xff0c;说的就是你&#xff0c;不用再怀疑&#xff01;&#xff01;&#xff01; 希望我的文章内容能对你有帮助&#xff0c;一起努力吧&#xff0…

足球数据分析管理系统(JSP+java+springmvc+mysql+MyBatis)

项目文件图 项目介绍 随着足球运动的专业化和商业化程度不断提高&#xff0c;对运动员的表现进行分析和管理变得越来越重要。一个高效的足球运动员数据管理系统可以帮助教练团队、球探和俱乐部管理层全面了解每位运动员的训练情况、比赛表现、身体状态和其他关键指标。这样的系…

Leetcode JAVA刷刷站(99)恢复二叉搜索树

一、题目概述 二、思路方向 要解决这个问题&#xff0c;我们可以采用中序遍历二叉搜索树&#xff08;BST&#xff09;的方法&#xff0c;因为中序遍历BST会返回一个有序的数组。由于只有两个节点被错误地交换了&#xff0c;所以中序遍历的结果中将有两个位置上的元素是逆序的。…

AD7606芯片驱动-FPGA实现

简介 AD7606是一款16位ADC芯片&#xff0c;可实现8通道并行采集&#xff0c;每通道最大速度可达1M&#xff0c;可实现多种模式数据采集。 介绍 本次FPGA使用的是8通道串行采样模式&#xff0c;设计中所用到的AD7606引脚说明如下&#xff1a; 名称定义CONVST同步采集转换开始信…

并发服务器开发基础

一、服务器模型 1. 单循环服务器&#xff1a; 单循环服务器在同一时刻只能处理一个客户端的请求。由于其结构简单&#xff0c;适合低负载的场景&#xff0c;但在并发请求增加时可能导致性能问题。 2. 并发服务器模型&#xff1a; 并发服务器可以同时响应多个客户端…

openzgy编译和测试应用

zgy是仅次于segy重要的地震数据格式,最早在petrel软件中使用,目前已基本成为行业标准,具有更快的数据存储效率。openzgy是其开源版本。 ZGY文件格式由Schlumberger公司开发,用于存储地震解释的三维数据。OpenZGY库提供了读写该格式的能力。存在C++和Python两种版本。对于P…

web应用程序之服务器部署

当一个web应用层序开发好后&#xff0c;无论你是用什么语言&#xff0c;最后都要考虑部署到服务器上测试使用&#xff0c;这里就常见的服务器上部署进行如下的摸索总结。WSGI&#xff08;Web Server Gateway Interface&#xff09;&#xff0c;翻译为Python web服务器网关接口&…

【与C++的邂逅】--- 模板初阶

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; 与C的邂逅 本篇博客我们将了解C中泛型编程体现的一大利器 --- 模板&#xff0c;有了模板可以帮我们用户省力。 &#x1f3e0; 泛型编程 如何实现一个通…

二叉树的三个简单题

1、二叉树的第k个结点 思路解析 由题可知这是一棵二叉搜索树 它或者是一棵空树&#xff0c;或者是具有下列性质的二叉树&#xff1a; 1. 若它的左子树不空&#xff0c;则左子树上所有结点的值均小于它的根结点的值&#xff1b; 2. 若它的右子树不空&#xff0c;则右子树…

LSTM唐诗生成

LSTM唐诗生成 1课程简介1-2递归神经网络RNN1-3RNN网络细节1-4LSTM网络架构2-1处理Minist数据集2-2RNN网络模型及训练3-1任务概述与环境配置3-2参数配置3-3数据预处理模块3-4batch数据制作3-5RNN模型定义3-8测试唐诗生成效果 1课程简介 使用深度网络模型 写首歌 写个剧本等 原…

openshift node NotReady kubelet http: TLS handshake error

文章目录 问题现象解决方法 问题现象 openshift 集群 node 节点 notready $ oc get node NAME STATUS ROLES AGE VERSION master1.ocp4.demo.com Ready control-plane,master 4d14h v1.29.76abe8a1 master2.ocp4…