获课地址:666it.top/14228/
结构型模式关注如何将类或对象组合成更大、更复杂的结构,同时保持结构的灵活和高效。它们就像是软件世界的“建筑学”,决定了各个模块之间如何连接和协作。本文将深入讲解三种功能强大且常用的结构型模式:适配器模式、装饰器模式和代理模式。
一、适配器模式:让不兼容的接口协同工作
1.1 模式意图
将一个类的接口转换成客户期望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
1.2 代码实现:继承与组合两种方式
假设我们有一个遗留的OldPrinter类,但客户端代码期望使用一个Printer接口。
cpp
#include <iostream>
#include <memory>
// 目标接口(客户所期望的)
class Printer {
public:
virtual ~Printer() = default;
virtual void print(const std::string& content) = 0;
};
// 需要被适配的类(遗留代码,不兼容的接口)
class OldPrinter {
public:
void printInOldWay(const std::string& text) {
std::cout << "Old Printer: " << text << std::endl;
}
};
// 类适配器(通过继承OldPrinter)
class ClassAdapter : public Printer, private OldPrinter {
public:
void print(const std::string& content) override {
// 调用被适配者的方法,进行适配
printInOldWay("Adapted: " + content);
}
};
// 对象适配器(通过组合OldPrinter)- 更推荐的方式
class ObjectAdapter : public Printer {
private:
std::unique_ptr<OldPrinter> oldPrinter_;
public:
ObjectAdapter(std::unique_ptr<OldPrinter> oldPrinter) : oldPrinter_(std::move(oldPrinter)) {}
void print(const std::string& content) override {
// 调用被适配者的方法,进行适配
oldPrinter_->printInOldWay("Adapted: " + content);
}
};
// 实战应用
int main() {
// 使用类适配器
std::unique_ptr<Printer> classAdapter = std::make_unique<ClassAdapter>();
classAdapter->print("Hello with Class Adapter");
// 使用对象适配器
std::unique_ptr<OldPrinter> old = std::make_unique<OldPrinter>();
std::unique_ptr<Printer> objectAdapter = std::make_unique<ObjectAdapter>(std::move(old));
objectAdapter->print("Hello with Object Adapter");
return 0;
}
1.3 模式应用场景
- 集成遗留系统。
- 使用第三方库,但接口与现有系统不匹配。
二、装饰器模式:动态扩展对象功能
2.1 模式意图
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
2.2 UML结构与C++实现
它通过组合而非继承来扩展功能,保持了类的层次结构扁平。
cpp
#include <iostream>
#include <memory>
#include <string>
// 组件接口
class Beverage {
public:
virtual ~Beverage() = default;
virtual std::string getDescription() const = 0;
virtual double cost() const = 0;
};
// 具体组件
class Espresso : public Beverage {
public:
std::string getDescription() const override {
return "Espresso";
}
double cost() const override {
return 1.99;
}
};
// 装饰器基类
class CondimentDecorator : public Beverage {
protected:
std::unique_ptr<Beverage> beverage_;
public:
CondimentDecorator(std::unique_ptr<Beverage> beverage) : beverage_(std::move(beverage)) {}
// getDescription 和 cost 仍然是纯虚函数,由具体装饰器实现
};
// 具体装饰器A:摩卡
class Mocha : public CondimentDecorator {
public:
Mocha(std::unique_ptr<Beverage> beverage) : CondimentDecorator(std::move(beverage)) {}
std::string getDescription() const override {
return beverage_->getDescription() + ", Mocha";
}
double cost() const override {
return beverage_->cost() + 0.20;
}
};
// 具体装饰器B:奶泡
class Whip : public CondimentDecorator {
public:
Whip(std::unique_ptr<Beverage> beverage) : CondimentDecorator(std::move(beverage)) {}
std::string getDescription() const override {
return beverage_->getDescription() + ", Whip";
}
double cost() const override {
return beverage_->cost() + 0.15;
}
};
// 实战应用
int main() {
// 点一杯双倍摩卡奶泡Espresso
auto beverage = std::make_unique<Espresso>();
std::cout << beverage->getDescription() << " $" << beverage->cost() << std::endl;
// 动态添加装饰
beverage = std::make_unique<Mocha>(std::move(beverage)); // 第一份摩卡
beverage = std::make_unique<Mocha>(std::move(beverage)); // 第二份摩卡
beverage = std::make_unique<Whip>(std::move(beverage)); // 加奶泡
std::cout << beverage->getDescription() << " $" << beverage->cost() << std::endl;
return 0;
}
2.3 模式优点
- 比继承更灵活,可以动态地添加或撤销功能。
- 避免了在层次结构高层的类有太多的特征。
三、代理模式:控制对象访问
3.1 模式意图
为其他对象提供一种代理以控制对这个对象的访问。
3.2 代码实现:虚拟代理(延迟加载)
代理模式在需要控制访问权限、延迟加载、记录日志等场景非常有用。
cpp
#include <iostream>
#include <memory>
#include <thread>
#include <chrono>
// 主题接口
class Image {
public:
virtual ~Image() = default;
virtual void display() = 0;
};
// 真实主题:一个加载很慢的高清图片
class HighResolutionImage : public Image {
private:
std::string filename_;
void loadImageFromDisk() {
std::cout << "Loading very large image from disk: " << filename_ << " ..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(3)); // 模拟耗时加载
std::cout << "Image loaded!" << std::endl;
}
public:
HighResolutionImage(const std::string& filename) : filename_(filename) {
loadImageFromDisk();
}
void display() override {
std::cout << "Displaying image: " << filename_ << std::endl;
}
};
// 代理类:控制对HighResolutionImage的访问,实现延迟加载
class ImageProxy : public Image {
private:
std::string filename_;
mutable std::unique_ptr<HighResolutionImage> realImage_; // 延迟初始化
void loadRealImage() const {
if (!realImage_) {
realImage_ = std::make_unique<HighResolutionImage>(filename_);
}
}
public:
ImageProxy(const std::string& filename) : filename_(filename) {
std::cout << "Proxy created. Real image not loaded yet." << std::endl;
}
void display() override {
loadRealImage(); // 只有在真正需要显示时才加载图片
realImage_->display();
}
};
// 实战应用
int main() {
// 使用代理,创建很快,不会立即加载大图片
auto image = std::make_unique<ImageProxy>("sample_photo_10GB.jpg");
// ... 执行其他一些操作 ...
// 只有当调用display时,真实的图片才会被加载
std::cout << "\nAbout to display the image...\n";
image->display();
// 第二次调用,图片已经加载过,不会再次加载
std::cout << "\nDisplaying again...\n";
image->display();
return 0;
}
3.3 模式变体
- 远程代理:为一个对象在不同的地址空间提供局部代表。
- 虚拟代理:如上例,通过代理来延迟昂贵对象的创建。
- 保护代理:控制对原始对象的访问权限。
- 智能引用代理:在对象被访问时执行附加操作,如引用计数、内存管理等。
总结
适配器、装饰器和代理模式都涉及到一个对象包含另一个对象的引用,并将工作委派给这个对象。但它们的目的不同:适配器关注接口转换,装饰器关注动态添加职责,而代理关注控制访问。理解这些细微差别,是正确运用它们的关键。在下一篇文章中,我们将进入行为型模式的世界,探索对象之间的高效通信机制。