你是一名C++大牛,正在开发公司的核心业务系统,这个系统里有一套统一的日志接口:
class ILogger {
public:
virtual void log(const std::string& message) = 0;
virtual void error(const std::string& message) = 0;
};
所有模块都依赖这个接口,文件日志、网络日志、控制台日志,都实现了ILogger,可以无缝切换。
有一天,老板兴冲冲地找到你:"我买了一个第三方日志库,性能比咱们的快10倍,集成进去!"
你打开文档,看到第三方库的接口是这样的:
// 第三方库,只有头文件和编译好的.so文件
class FastLogger {
public:
void writeLog(const char* msg, int level);
};
你愣住了,这两套接口完全不兼容,你们用std::string,它用const char*;你们用log()和error()分开的方法,它用level参数区分;你们期望用ILogger指针,它是一个完全不相关的类。
你盯着屏幕,很快意识到:这个库没法直接塞进你的系统。
修改自己的代码,适应第三方
你的第一反应是:既然第三方改不了,那就改我们自己的代码。
你把所有用到ILogger的地方都改成直接调用FastLogger:
// 原来的代码
void processOrder(ILogger* logger) {
logger->log("Processing order...");
// 业务逻辑
logger->error("Order failed!");
}
// 改成这样
void processOrder(FastLogger* logger) {
logger->writeLog("Processing order...", 0);
// 业务逻辑
logger->writeLog("Order failed!", 1);
}
你改了第一个文件,然后是第二个,第三个……
改到第20个文件时,你疯了,系统里有上百个地方在用ILogger接口,全部改完要一周,而且每个地方都要把std::string转成const char*,把log()/error()改成writeLog()加level参数。
更可怕的是,改完之后,你的系统就和这个第三方库绑死了,下次老板说"换一个更好的日志库",你是不是还要再改一遍?
就为了一个第三方库,改动整个系统的代码,代价太大,而且丧失了灵活性。
你需要一种方法:不改系统代码,也不改第三方代码,却能让它们协同工作。
写一个包装函数
你想到一个办法:在外面包一层函数,做接口转换。
FastLogger* g_logger = new FastLogger();
void logMessage(const std::string& message) {
g_logger->writeLog(message.c_str(), 0);
}
void logError(const std::string& message) {
g_logger->writeLog(message.c_str(), 1);
}
这样,你的代码调用logMessage(),内部转发给FastLogger。你测试了一下,能用,但很快你发现了新问题。
你们的框架有一个Logger注册中心:
class LoggerRegistry {
public:
void registerLogger(ILogger* logger) {
loggers_.push_back(logger);
}
void broadcast(const std::string& msg) {
for (auto* logger : loggers_) {
logger->log(msg);
}
}
private:
std::vector<ILogger*> loggers_;
};
registerLogger()需要一个ILogger*指针,而你的包装函数不是对象,没法注册进去!
LoggerRegistry registry;
registry.registerLogger(???); // 包装函数塞不进去
你意识到包装函数只能做简单转换,无法参与到面向对象的体系,显然你需要一个对象来充当翻译官。
类适配器——用继承做翻译
你决定创建一个新类,它既继承ILogger接口,又继承FastLogger实现:
class LoggerAdapter : public ILogger, public FastLogger {
public:
void log(const std::string& message) override {
writeLog(message.c_str(), 0); // 调用继承来的方法
}
void error(const std::string& message) override {
writeLog(message.c_str(), 1);
}
};
这个LoggerAdapter类非常巧妙,你不禁暗自赞叹道自己真是太聪明了。对外,它是一个ILogger,可以注册到框架里;对内,它是一个FastLogger,可以直接调用日志功能。
开测:
LoggerRegistry registry;
LoggerAdapter* adapter = new LoggerAdapter();
registry.registerLogger(adapter); // 成功注册!
registry.broadcast("Hello World"); // 成功写入日志!
完美!之前困扰你的接口不兼容问题,现在通过一个"翻译类"解决了。
但一个月后,你发现了一个新问题。
继承的局限性
供应商最近为了业绩在狂卷,发布了新版本的日志库,除了原来的 FastLogger,还新增了两个变种:
// 原来的日志类
class FastLogger {
public:
void writeLog(const char* msg, int level);
};
// 新增:高性能版(多线程优化)
class HighPerfLogger :public FastLogger {
public:
void writeLog(const char* msg, int level) override;
};
// 新增:低功耗版(移动端优化)
class LowPowerLogger :public FastLogger {
public:
void writeLog(const char* msg, int level) override;
};
你需要在不同场景下使用不同的日志实现:服务器上用 HighPerfLogger,移动设备上用 LowPowerLogger。
问题来了,你之前写的类适配器是这样的:
class LoggerAdapter : public ILogger, public FastLogger { ... };
它继承了 FastLogger,而不是 HighPerfLogger 或 LowPowerLogger。
如果你想适配 HighPerfLogger,得再写一个:
class HighPerfLoggerAdapter : public ILogger, public HighPerfLogger { ... };
想适配 LowPowerLogger?害得再写一个:
class LowPowerLoggerAdapter : public ILogger, public LowPowerLogger { ... };
三个变种,三个适配器类。 每次供应商新增一个变种,你就得新增一个适配器类。
你开始思考:有没有办法让一个适配器类能适配整个继承体系?
类适配器做不到这一点,因为继承关系是写死在代码里的。
你需要一种更灵活的方式。
对象适配器——用组合替代继承
你一拍大腿:不用继承,用组合!
把被适配对象作为成员变量,而不是父类:
class LoggerAdapter : public ILogger {
private:
FastLogger* adaptee_; // 组合:持有基类指针
public:
LoggerAdapter(FastLogger* logger) : adaptee_(logger) {}
void log(const std::string& message) override {
adaptee_->writeLog(message.c_str(), 0);
}
void error(const std::string& message) override {
adaptee_->writeLog(message.c_str(), 1);
}
};
关键变化:
- 之前:
LoggerAdapter是一个FastLogger(继承,绑定具体类) - 现在:
LoggerAdapter有一个FastLogger*(组合,持有基类指针)
因为 adaptee_ 是基类指针,它可以指向任何子类对象!
// 同一个适配器类,适配不同的实现
LoggerAdapter* adapter1 = new LoggerAdapter(new FastLogger());
LoggerAdapter* adapter2 = new LoggerAdapter(new HighPerfLogger());
LoggerAdapter* adapter3 = new LoggerAdapter(new LowPowerLogger());
// 甚至可以运行时切换!
FastLogger* currentImpl = new HighPerfLogger();
LoggerAdapter* adapter = new LoggerAdapter(currentImpl);
// 后来发现太耗电,换成低功耗版
adapter->setAdaptee(new LowPowerLogger()); // 运行时切换实现!
一个适配器类,适配了整个继承体系!
你对比了一下类适配器和对象适配器:类适配器中一个适配器只能绑定一个具体类;对象适配器通过持有基类指针,解决了这个问题!
这里的关键洞察是:
- 继承是"我是什么",编译时决定,绑定到具体类
- 组合是"我有什么",运行时可以指向基类的任意子类
这种"在两个不兼容接口之间搭建桥梁"的类,就是所谓的适配器(Adapter)。

评论区
登录后即可参与讨论
立即登录