一、為什么用橋接模式
在企業開發中,日志系統幾乎是標配。常見需求:
日志有多種類型(Info、Warning、Error 等);
日志需要支持多種輸出方式(控制臺輸出、寫文件、遠程上傳、數據庫存儲等)。
如果把這些維度直接用繼承組合(例如?ConsoleInfoLogger
、FileErrorLogger
),會導致類爆炸,而且擴展性極差。
橋接模式的核心思想是:把抽象(日志類別)與實現(輸出方式)分離,讓它們各自獨立擴展。這樣新增日志類型或新增輸出方式時,只需新增單一維度的類即可。
二、場景說明
假設我們要開發一個跨平臺日志系統,要求:
支持多種日志類型(Info、Error)。
支持多種輸出方式(控制臺輸出、寫文件)。
能靈活擴展,比如未來增加遠程網絡輸出。
我們用橋接模式來實現:
Logger
?是抽象層,定義?log(message)
;LogOutput
?是實現層,定義具體的輸出方式;Logger
?持有?std::shared_ptr<LogOutput>
,通過它完成最終日志寫入。
這樣:
新增日志類型:繼承?
Logger
,實現不同的前綴或處理邏輯;新增輸出方式:繼承?
LogOutput
,實現不同的寫入策略。
三、類圖
這個類圖展示了?Logger
?與?LogOutput
?的橋接關系:Logger
?持有?LogOutput
,而具體日志類別(Info、Error)委托不同的輸出方式完成記錄。
細心的朋友,可能看到了LogOutput是帶虛線的空心箭頭,Logger是帶實線的空心箭頭。這是因為LogOutput設計成了接口,而Logger設計為抽象類。
不要問為什么,問就是:在橋接模式中,將實現部分設計為接口、將抽象部分設計為抽象類是一種常見且合理的設計選擇。--手動調皮--
四、C++ 代碼實現
下面給出一個簡潔的 C++ 實現。
// bridge_logger.cpp
// 具體實現者:控制臺輸出
class ConsoleOutput : public LogOutput {
public:
void write(const std::string& msg) override {
std::cout << msg << std::endl;
}
};// 具體實現者:文件輸出
class FileOutput : public LogOutput {
public:
explicit FileOutput(const std::string& filename) : ofs(filename, std::ios::app) {}
void write(const std::string& msg) override {
ofs << msg << std::endl;
}
private:
std::ofstream ofs;
};// 抽象(Abstraction)
class Logger {
public:
Logger(std::shared_ptr<LogOutput> out) : output(std::move(out)) {}
virtual ~Logger() = default;
virtual void log(const std::string& msg) = 0;
protected:
std::shared_ptr<LogOutput> output;
};// 具體抽象:Info 日志
class InfoLogger : public Logger {
public:
using Logger::Logger;
void log(const std::string& msg) override {
output->write("[INFO] " + msg);
}
};// 具體抽象:Error 日志
class ErrorLogger : public Logger {
public:
using Logger::Logger;
void log(const std::string& msg) override {
output->write("[ERROR] " + msg);
}
};// 演示
int main() {
// 控制臺輸出
auto consoleOut = std::make_shared<ConsoleOutput>();
InfoLogger infoLogger(consoleOut);
ErrorLogger errorLogger(consoleOut);
infoLogger.log("程序啟動成功");
errorLogger.log("文件未找到");std::cout << "--- 切換到文件輸出 ---" << std::endl;// 文件輸出
auto fileOut = std::make_shared<FileOutput>("log.txt");
InfoLogger fileInfo(fileOut);
ErrorLogger fileError(fileOut);
fileInfo.log("寫入到文件的普通信息");
fileError.log("寫入到文件的錯誤信息");return 0;
}
程序輸出展示
[INFO] 程序啟動成功
[ERROR] 文件未找到--- 切換到文件輸出 ---[INFO] 寫入到文件的普通信息
[ERROR] 寫入到文件的錯誤信息
五、總結
橋接模式非常適合處理雙維度擴展的系統。本文通過“日志類型 + 輸出方式”的實際開發場景,展示了橋接模式的價值:
新增日志類型(如 DebugLogger)無需修改輸出類;
新增輸出方式(如網絡輸出)無需修改日志類;
抽象與實現各自獨立擴展,避免了類爆炸問題。