适配器模式
本课属于 Coddy C++ 学习旅程的“面向对象编程”单元。第 97 课,共 104 课。
适配器模式可以让不兼容的接口协同工作。它充当两个类之间的桥梁,将一个类的接口转换为客户端所需的接口。当你集成与系统接口不匹配的旧代码或第三方库时,这种模式尤其有用。
假设你有一个现有的类,它能满足你的需求,但它的接口与你的代码所期望的不匹配。你不必修改任何一方,只需创建一个适配器来包装现有的类,并转换调用:
#include <iostream>
#include <string>
// 接口不兼容的现有类
class LegacyPrinter {
public:
void printMessage(const std::string& msg) {
std::cout << "Legacy: " << msg << "\n";
}
};
// 客户端期望的目标接口
class Printer {
public:
virtual void print(const std::string& text) = 0;
virtual ~Printer() = default;
};
// 适配器封装 LegacyPrinter 并实现 Printer 接口
class PrinterAdapter : public Printer {
LegacyPrinter& legacy;
public:
PrinterAdapter(LegacyPrinter& lp) : legacy(lp) {}
void print(const std::string& text) override {
legacy.printMessage(text); // 转换这个调用
}
};
int main() {
LegacyPrinter oldPrinter;
PrinterAdapter adapter(oldPrinter);
// 客户端使用预期的接口
adapter.print("Hello World");
}适配器持有对 LegacyPrinter 的引用,并实现 Printer 接口。调用 print() 时,它会委托给 printMessage()。客户端代码通过 Printer 接口进行操作,无需了解底层的旧版实现。
当你想使用现有的类,但它的接口与你的需求不匹配时,可以使用适配器模式;如果你需要创建一个可复用的类,让它与接口不兼容的类协同工作,也可以使用适配器模式。
挑战
简单让我们构建一个媒体播放器适配器系统,连接不同音频库的接口。想象一下,你要将一个旧版音频库集成到现代媒体播放器应用中:旧库运行良好,但它的接口与播放器的预期不匹配。这正是适配器模式发挥作用的地方。
你要将代码分布在四个文件中:
MediaPlayer.h:定义现代媒体播放器所需的目标接口。创建一个抽象类
MediaPlayer,并包含以下纯虚方法:play(const std::string& filename):播放指定文件stop():停止播放setVolume(int level):设置音量(0-100)
包含一个虚析构函数。
LegacyAudioLib.h:创建现有的“旧版”音频库,它的接口与目标接口不兼容。LegacyAudioLib类表示一个工作方式与播放器预期不同的旧库。它应包含:loadAudio(const std::string& path):输出Legacy: Loading [path]startPlayback():输出Legacy: Playback startedstopPlayback():输出Legacy: Playback stoppedadjustVolume(double percentage):接收一个小数(0.0 到 1.0),并输出Legacy: Volume set to [percentage](显示小数值)
AudioAdapter.h:构建连接这两个接口的适配器。AudioAdapter类应继承MediaPlayer,并持有对LegacyAudioLib对象的引用。适配器会将现代接口的调用转换为对旧版接口的调用:play()应同时调用旧版库的loadAudio()和startPlayback()stop()应委托给stopPlayback()setVolume()接收一个整数(0-100),但旧版库需要小数(0.0-1.0),因此你需要进行转换
main.cpp:演示适配器的工作方式。读取两个输入:
- 要播放的文件名(字符串)
- 音量级别(整数,0-100)
创建一个
LegacyAudioLib实例,以及一个用于包装它的AudioAdapter。然后通过MediaPlayer接口使用适配器(将其存储为MediaPlayer指针),来完成以下操作:- 将音量设置为输入的级别
- 播放输入的文件名
- 停止播放
例如,输入 song.mp3 和 75 时:
Legacy: Volume set to 0.75
Legacy: Loading song.mp3
Legacy: Playback started
Legacy: Playback stopped输入 podcast.wav 和 50 时:
Legacy: Volume set to 0.5
Legacy: Loading podcast.wav
Legacy: Playback started
Legacy: Playback stopped请注意,客户端代码完全通过 MediaPlayer 接口进行操作。它并不知道底层实际工作的是旧版库。适配器负责所有转换,包括将整数音量转换为小数。这就是适配器模式的核心:无需修改任一端,就能让不兼容的接口协同工作。
亲手试试
#include <iostream>
#include <string>
#include "MediaPlayer.h"
#include "LegacyAudioLib.h"
#include "AudioAdapter.h"
using namespace std;
int main() {
// 读取输入
string filename;
int volumeLevel;
cin >> filename;
cin >> volumeLevel;
// TODO: 创建一个 LegacyAudioLib 实例
// TODO: 创建一个封装旧版库的 AudioAdapter
// TODO: 将适配器存储为 MediaPlayer 指针
// TODO: 使用 MediaPlayer 接口完成以下操作:
// 1. 将音量设置为输入的级别
// 2. 播放输入的文件名对应的文件
// 3. 停止播放
return 0;
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: C++ 在线编译器