1 介绍
适配器模式是从生活中汲取灵感的。生活中我们经常会遇到这种因为不匹配而增加适配来满足需求的例子,比如电脑的端口和需要插入的接头不匹配时,我们需要一个适配线来连接两端来完成端口与接头的匹配。
在程序设计中也是一样。我们经常会遇到接口变更或者更换,这种情况下会伴随着接口使用方式上的不匹配,如接口名称变化,接口参数变化或者完全替换为新的接口等等。这种情况下,如果其他模块大量使用了接口,接口变更的工作量可能会很大,带来的风险也增加。因此,如果有办法可以降低接口变化的工作量,将是一种更好地设计。适配器模式就是为应对这种场景而产生的。在这种模式中,适配器类作为中间层,起到承上启下的作用。为调用方提供格式不变的接口,在接口内部又嵌入新接口的使用。
2 使用场景
- 当需要使用一个现存的类,但它提供的接口与我们系统的接口不兼容,而我们还不能修改它时
- 当多个团队独立开发系统的各功能模块,然后组合在一起,但由于某些原因事先不能确定接口时。
3 范例
class ILoger {
public:
virtual void LogRecord(int tag, string message) = 0;
virtual ~ILoger() {}
};
class CLoger : public ILoger {
public:
CLoger() = default;
~CLoger() = default;
void LogRecord(int tag, string message) {
std::cout << "CLoger::LogRecord" << std::endl;
}
};
class CLogerManager {
public:
enum {
LOGTAG_PRINT = 0,
LOGTAG_DEBUG = 1
};
CLogerManager() {
pLoger = new (std::nothrow) CLoger();
if (unlikely(pLoger == nullptr)) {
std::cout << "CLogManager Construct failed!" << std::endl;
}
}
~CLogerManager() {
if (pLoger) {
delete pLoger;
}
}
void LogPrint() {
if (pLoger) {
pLoger->LogRecord(LOGTAG_PRINT, "LogPrint");
}
}
void LogDebug() {
if (pLoger) {
pLoger->LogRecord(LOGTAG_DEBUG, "LogDebug");
}
}
private:
ILoger *pLoger = nullptr;
};
int main(int argc, char** argv) {
CLogerManager logManager;
logManager.LogPrint();
logManager.LogDebug();
}
如上面实例。ILoger是一个日志系统的纯虚类,接口LogRecord用于记录日志信息。CLoger是继承于ILoger的日志系统实例类。CLogerManager类是日志系统管理类,包含指向CLoger实例的纯虚指针来完成日志记录。
class ILogFactory {
public:
virtual void LogTrace(int prio, string path, int tag, string message) = 0;
virtual ~ILogFactory() {}
};
假如此时出现了更好的日志系统类,我们需要将ILoger类更换为ILogFactory类,由于ILogFactory类中用于做日志记录的接口为LogTrace,与原接口LogRecord名称和参数均不一致。这种情况下,通常的切换需要修改CLogerManager::LogPrint和CLogerManager::LogDebug中LogRecord的调用方式。这种修改在LogRecord调用很多的情况下很不友好,如果能不修改这2个接口就能完成新接口的使用时更好地,怎么实现呢?
class ILogFactory {
public:
virtual void LogTrace(int prio, string path, int tag, string message) = 0;
virtual ~ILogFactory() {}
};
class CLogFactory : public ILogFactory {
public:
void LogTrace(int prio, string path, int tag, string message) {
std::cout << "CLogFactory::LogTrace,prio=" << prio
<< ",path=" << path
<< ",tag=" << tag
<< ",message=" << message
<< std::endl;
}
};
class CAdapterLoger : public CLoger {
public:
CAdapterLoger() {
pLogFactory = new (std::nothrow) CLogFactory();
if (unlikely(pLogFactory == nullptr)) {
std::cout << "AdapterCLoger Construct failed!" << std::endl;
}
}
~CAdapterLoger() {
if (pLogFactory) {
delete pLogFactory;
}
}
void LogRecord(int tag, string message) {
if (pLogFactory) {
pLogFactory->LogTrace(1, "/home/trunk/workspace", tag, message);
}
}
private:
ILogFactory *pLogFactory = nullptr;
};
class CLogerManager {
public:
enum {
LOGTAG_PRINT = 0,
LOGTAG_DEBUG = 1
};
CLogerManager () {
pLoger = new (std::nothrow) CAdapterLoger();
if (unlikely(pLoger == nullptr)) {
std::cout << "CLogerManager Construct failed!" << std::endl;
}
}
~CLogerManager () {
if (pLoger) {
delete pLoger;
}
}
void LogPrint() {
if (pLoger) {
pLoger->LogRecord(LOGTAG_PRINT, "LogPrint");
}
}
void LogDebug() {
if (pLoger) {
pLoger->LogRecord(LOGTAG_DEBUG, "LogDebug");
}
}
private:
ILoger *pLoger = nullptr;
};
int main(int argc, char** argv) {
CLogerManager logManager;
logManager.LogPrint();
logManager.LogDebug();
}
如上示例代码,ILogFactory是新的日志系统虚拟类,CLogFactory是继承自ILogFactory的实例类。我们可以增加适配器类CAdapterLoger来继承CLoger,重写LogRecord方法,重写的方法中我们嵌入CLogFactory日志记录方法的调用。这样,适配后新的日志系统类CAdapterLoger提供的用于记录日志的方法还是LogRecord并且参数完全一致。管理类CLogerManager不需要修改LogPrint方法和LogDebug方法就可以使用,只需要将持有的日志系统实例从CLoger变更为CAdapterLoger即可。
总结:以上代码中为解决更换日志系统类,我们创建一个继承自原日志系统类的适配器类,适配器类重写了原日志系统类的LogRecord方法,在方法中嵌入新类日志记录方法LogTrace的使用,保持了日志系统对外接口的一致性。日志管理类只需要将日志系统实例从CLoger更换为CAdapterLoger即可完成切换。
4 UML