临时加点东西
越写越迷糊,适配器模式、装饰器模式、代理模式、桥接模式乍一看没什么关系,实际理解实现原理后就会发现,好像都是使用新的类或接口通过继承或者组合的方式代替原来的类。这里分析一下这几个模式的异同。
参考文章:适配器模式,装饰模式,代理模式异同
适配器、装饰器、代理、桥接模式对比
首先,重温一下这几个设计模式的概念:
- 适配器模式:将一个类的接口转换成客户端所期望的另一种接口,使得原本由于接口不兼容而无法一起工作的类能够一起工作。
- 装饰器模式:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式相比生成子类更为灵活。
- 代理模式:为对象提供一个代理以控制对这个对象的访问,而不直接操作这个对象。
- 桥接模式:将抽象部分与其实现部分分离,使它们都可以独立变化。
适配器模式的重点在于将一个类A转换成另一个类B,使程序可以继续使用B的接口去访问A的方法。
装饰器模式的重点在于在一个类A的基础上扩展A的功能,而不修改A的代码。
代理模式的重点在于使用一个代理类封装类A的功能,也不修改A的代码。其主要目的是控制访问,而非加强功能,这也是代理模式与装饰模式最大的区别。
桥接模式的重点在于将一个对象的某些属性拆分成不同的类ABCD,在需要使用时任意组装成一个成品对象,方便各个属性单独进行调整。
举个例子,你开发了一个图像处理的软件,其中需要一个给图片添加滤镜的功能,你想使用一个开源的库A,其代码如下:
#include <iostream>
#include <string>
#include <memory>
#include <stack>
// 库A,不能修改
class A { // 接口
public:
virtual ~A() = default;
virtual void SetFilterId(int filterId) = 0;
virtual void SetFilterStrength(float strength) = 0;
virtual void AddFilter(std::string picAddress) = 0;
};
class AImpl : public A { // 实现
public:
void SetFilterId(int filterId) override {
m_filterId = filterId;
}
void SetFilterStrength(float strength) override {
m_strength = strength;
}
void AddFilter(std::string picAddress) override {
std::cout << "A: Add filter \"" << m_filterId << "\" for picture: " << picAddress << "." << std::endl;
std::cout << "A: Filter strength is " << m_strength << "." << std::endl;
}
private:
int m_filterId = 0;
float m_strength = 0.0f;
};
适配器模式
在你的软件设计之初,没有考虑使用开源库来实现这个功能,所以你预留的接口并不适合使用这个开源库,而且现在修改软件代码的话会比较麻烦。为了使用这个开源库,你考虑使用适配器对其进行封装,以适配你的接口:
// 适配器模式
// 目标接口
class Filter {
public:
virtual void AddFilter(std::string picAddress, int filterId, float strength) = 0;
};
// 类适配器
class ClassAdapter : public Filter, private AImpl {
public:
void AddFilter(std::string picAddress, int filterId, float strength) override {
AImpl::SetFilterId(filterId);
AImpl::SetFilterStrength(strength);
AImpl::AddFilter(picAddress);
}
};
// 对象适配器
class ObjAdapter : public Filter {
private:
AImpl* m_a;
public:
ObjAdapter(AImpl* a) : m_a(a) {}
void AddFilter(std::string picAddress, int filterId, float strength) override {
m_a->SetFilterId(filterId);
m_a->SetFilterStrength(strength);
m_a->AddFilter(picAddress);
}
};
装饰器模式
假设你现在需要为该功能添加一些新功能,因为使用的是开源库,理论上你可以直接添加在库代码中,但是这样一方面影响后续开源库的更新或更换,另一方面需要深入去了解库中的代码逻辑,是违反开发原则的。最终你考虑使用装饰器模式实现,将你需要添加的代码写在自己的装饰器类中,其中添加了一些新的功能,以及一个历史树和回滚机制:
// 装饰器模式
// 装饰器接口
class Decorator : public Filter {
public:
Decorator(std::unique_ptr<Filter> filter)
: m_filter(std::move(filter)) {}
void AddFilter(std::string picAddress, int filterId, float strength) override {
if (m_filter)
m_filter->AddFilter(picAddress, filterId, strength);
}
protected:
std::unique_ptr<Filter> m_filter;
};
// 装饰器类
class DecoratorImpl_1 :public Decorator {
public:
DecoratorImpl_1(std::unique_ptr<Filter> filter)
: Decorator(std::move(filter)) {}
void AddFilter(std::string picAddress, int filterId, float strength) override {
std::cout << "Do something new." << std::endl;
Decorator::AddFilter(picAddress, filterId, strength);
if (m_address.size() == 0)
m_address = picAddress;
if(m_address == picAddress)
m_stack.push(m_info);
else {
while (!m_stack.empty())
m_stack.pop();
}
}
void rollback() {
if (m_address.size() == 0)
return;
if (m_stack.empty()) {
std::cout << "It is already the initial version, but the return failed."
<< std::endl;
return;
}
m_stack.pop();
if (m_stack.empty()) {
AddFilter(m_address, 0, 0.0f);
} else {
FilterInfo info = m_stack.top();
AddFilter(m_address, info.id, info.strength);
}
m_stack.pop();
std::cout << "Rollback success!" << std::endl;
}
private:
struct FilterInfo {
int id;
float strength;
} m_info = {};
std::string m_address;
std::stack<FilterInfo> m_stack;
};
代理模式
考虑到未来可能会考虑该软件的变现能力,你想为其添加一个vip版本,免费用户可以使用滤镜,但是照片上会被加上软件的水印,只有vip用户才可以去掉这个水印(可恶),顺便,你还给该功能加上了一个错误检查功能:
// 代理模式
class Proxy : public Filter {
public:
Proxy(bool vip) :m_vip(vip) {
m_filter = std::make_unique<ClassAdapter>();
}
void AddFilter(std::string picAddress, int filterId, float strength) override {
if (picAddress.size() == 0) {
std::cout << "Address is empty, add failed" << std::endl;
return;
}
if (filterId < 0) {
std::cout << "ID cannot be less than 0, automatically set to 0"
<< std::endl;
filterId = 0;
}
if (strength > 1.0f) {
std::cout << "Strength cannot exceed 1, automatically set to 1"
<< std::endl;
strength = 1.0f;
}
else if (strength < 0.0f) {
std::cout << "Strength cannot be less than 0, automatically set to 0"
<< std::endl;
strength = 0.0f;
}
if(!m_vip)
std::cout << "***&&&---watermark---&&&***" << std::endl;
m_filter->AddFilter(picAddress, filterId, strength);
}
private:
std::unique_ptr<Filter> m_filter;
bool m_vip = false;
};
桥接模式
最后,你希望软件中有一些设置好的预设格式,用户可以一键将照片转换为特定的风格,而不需要反复的调解滤镜,尺寸,明暗等参数,这恰好可以使用桥接模式实现:
// 桥接模式
// 另一个实现类
class Size {
public:
virtual ~Size() {}
void SetSize(int x, int y) {
m_x = x;
m_y = y;
}
virtual void Cropping(std::string addr) {
std::cout << "Crop photo \"" << addr << "\"to a length of " << m_x
<< "mm and a width of " << m_y << "mm." << std::endl;
}
private:
int m_x, m_y;
};
class Inch2 : public Size {
public:
void Cropping(std::string addr) override {
std::cout << "Crop photo \"" << addr << "\"to a length of 53mm and a width of 35mm."
<< std::endl;
}
};
// 抽象类
class Presets {
protected:
Size* m_size;
Filter* m_filter;
public:
Presets(Filter* filter, Size* size) : m_filter(filter), m_size(size) {}
virtual ~Presets() {
delete m_filter;
delete m_size;
}
void SetSize(float x, float y) {
delete m_size;
m_size = new Size();
m_size->SetSize(x, y);
}
virtual void OutputPicture(std::string addr) = 0;
};
// 具体类
class Inch2IDPhoto : public Presets {
public:
Inch2IDPhoto(Filter* filter, Size* size) : Presets(filter, size) {}
void OutputPicture(std::string addr) {
m_filter->AddFilter(addr, 1, 0.2f); // 证件照滤镜
m_size->Cropping(addr);
std::cout << "Bingo! Get a 2-inch ID photo!" << std::endl;
}
};
最后可以测试一下各部分代码的运行情况:
int main() {
// 直接使用A
std::cout << "----------直接使用A----------" << std::endl;
AImpl* a1 = new AImpl();
a1->SetFilterId(3); // 滤镜3:蓝调
a1->SetFilterStrength(0.8f);
a1->AddFilter("pic1.jpeg");
std::cout << std::endl;
delete a1;
// 使用适配器
std::cout << "----------类适配器----------" << std::endl;
ClassAdapter* adap_c = new ClassAdapter();
adap_c->AddFilter("pic2.jpeg", 3, 0.5f);
std::cout << "----------对象适配器----------" << std::endl;
AImpl* a2 = new AImpl();
ObjAdapter* adap_o = new ObjAdapter(a2);
adap_o->AddFilter("", -100, 1000.0f);
std::cout << std::endl;
delete adap_o;
delete adap_c;
delete a2;
// 装饰器模式
std::cout << "----------装饰器(新功能)----------" << std::endl;
std::unique_ptr<ClassAdapter> a3 = std::make_unique<ClassAdapter>();
DecoratorImpl_1* decorator = new DecoratorImpl_1(std::move(a3));
std::cout << " ------滤镜1------ " << std::endl;
decorator->AddFilter("pic3.jpeg", 10, -0.5f);
std::cout << " ------滤镜2------ " << std::endl;
decorator->AddFilter("pic3.jpeg", 15, 0.5f);
std::cout << " ------退回滤镜1------ " << std::endl;
decorator->rollback();
std::cout << " ------退回初始状态------ " << std::endl;
decorator->rollback();
std::cout << " ------退回(退多了)------ " << std::endl;
decorator->rollback();
std::cout << std::endl;
delete decorator;
// 代理模式
std::cout << "----------代理(错误检查和权限判断)----------" << std::endl;
Proxy* proxy = new Proxy(false); // 不是会员
proxy->AddFilter("", -100, 15.53f);
std::cout << " ------不是会员会有水印------ " << std::endl;
proxy->AddFilter("pic4.jpeg", -100, 15.53f);
std::cout << std::endl;
// 桥接模式
std::cout << "----------桥接----------" << std::endl;
std::unique_ptr<ClassAdapter> a4 = std::make_unique<ClassAdapter>();
Inch2IDPhoto* idCard = new Inch2IDPhoto(a4.get(), new Inch2());
idCard->OutputPicture("pic5.jpeg");
std::cout << std::endl;
return 0;
}
总结
综上所述,在设计模式中,适配器、装饰、代理和桥接都是结构型设计模式,它们都用于处理类与类之间的结构关系,但是它们各自有不同的用途和适用场景。
共同点:
- 都是结构型设计模式,用于处理对象之间的结构关系。
- 都可能通过接口或抽象类来定义一组共同的行为或属性。
不同点:
- 适配器(Adapter):将一个类的接口转换成客户端所期待的另一种接口形式,使得原本接口不兼容的类可以一起工作。
- 装饰(Decorator):动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式比生成子类更为灵活。
- 代理(Proxy):主要用于为其他对象提供一种代理以控制对这个对象的访问。常用于远程对象、创建开销大的对象或安全控制等场景。
- 桥接(Bridge):将抽象部分与它的实现部分分离,使它们都可以独立地变化。主要用于抽象和具体实现之间的解耦。
写在最后
实际上,设计模式更多的是作为一种设计思想,我们之前可能都写过很多类似的代码,只是不知道或者不明确这些代码是一种设计模式。就拿适配器模式来说,GOF对于适配器模式的解释就是将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
所以说,我认为,具体的代码是怎么样的不重要,只要你能实现 “将一个类的接口转换成客户希望的另一个接口” 这个功能,那这就是适配器模式,网上的各种代码只是大家公认的比较方便实现这个模式的一个范例而已,不需要拘泥于形式,重点在于思想。
…
To be continued.