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
函数中,创建 LogToFile
和 LogAdapter
实例,并通过适配器调用目标接口的方法。
//客户端使用
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 图解析
- Target (目标抽象类): 此类定义了客户端期望使用的接口,如
initdb
、writetodb
、readfromdb
、closedb
等。这些接口代表了调用者希望利用的未来接口,并将由客户端直接调用。这里指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. 适配者模式适用场景
- 已存在的类的接口不符合系统的需求:当系统需要使用现有的类,但这些类的接口不符合系统的需求时,可以使用适配者模式来使现有的类与系统接口兼容。
- 需要创建一个可复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作:适配器模式可以提供一个中间层,通过包装一个不兼容的对象,将其接口转换成目标接口,从而使其能与多种不同的对象协同工作。
- 在需要使用几个现有的子类,但是子类的接口不一致时:可以使用适配者模式来适配这些接口。通过定义一个统一的接口,并在适配器中将调用分派给相应的子类接口,可以使得原本由于接口不兼容而不能一起工作的类可以一起工作。
- 系统需要使用现有的类,而类的接口不符合系统的需求:例如,系统数据的输入需要特定的格式,而现有的库提供的数据格式与之不符。适配器可以在这两者之间进行转换。
- 整合多个库或框架时:当使用多个库或框架构建应用程序时,经常会遇到因为接口不兼容而无法一起工作的情况。使用适配器模式可以解决这些库或框架间的接口不兼容问题,使它们可以一起工作。
- 替换系统中的旧组件时:在软件维护或升级过程中,旧的组件可能需要被更现代或功能更强大的组件替换。如果新组件的接口与系统现有的接口不匹配,适配器模式可以用来适配这些接口,从而允许系统平滑过渡到新的组件,而不需要重写大量的代码。
总结
适配者模式是一种强大而灵活的设计模式,它允许不兼容的接口能够协同工作。通过创建适配器,我们可以复用现有的类,而无需修改其代码。这种模式特别适用于系统集成、旧系统改造或者与第三方库协作的场景。
适配者模式的核心在于:
- 识别目标接口和源类之间的差异。
- 设计适配器类来桥接这些差异。
- 在适配器中实现接口转换逻辑。
在使用适配者模式时,需要注意以下几点:
- 确保适配器只处理接口转换,不要在其中添加额外的业务逻辑。
- 考虑使用对象适配器还是类适配器,根据实际需求和语言特性来选择。
- 当需要适配的类较多时,可以考虑使用工厂模式来创建适配器。
虽然适配者模式可能会增加一些复杂性,但它提供的灵活性和可维护性通常会超过这些缺点。在使用时,应该权衡其利弊,选择最适合特定场景的解决方案。
完整代码
#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;
}