适配器模式
当客户端提供的参数与服务端提供的服务的参数不兼容时,适配器可以作为中间件让他们兼容。
例如服务端提供的服务需要一个 json 文件,而客户端(服务的调用者)只能提供 xml 文件。
例如网站需要你上传一个 PDF,但你手上只有 DOCS。当你为了将文件传入某个接口而转换文件时,你使用的转换工具可以被称为适配器。
class A;
class B;
class Server {
void handle(A a);
};
class Client {
B produce();
};
class Adapter1 {
void handle(Server server, B b) {
server.handle(this->convert(b));
}
private:
A convert(B b);
};
class Adapter2 {
static A convert(B b);
};
class A {
A get();
};
class B {
B get();
};
// 可以从 A 或 B 构造,并转换为任意一者
class AB : public A, public B {
AB(A a);
AB(B b);
A get();
B get();
};
并不是所有上述场景都需要适配器,有时直接修改服务端或者客户端的接口来适配另一方的接口也是一个选择。
桥接模式
如果需要操作某一类对象,我可以操作它们的抽象。
在生活中表现为定义一个标准,大家都遵循这个标准就可以让与该接口相关的产品互相兼容。
class Device {
virtual int get_volume() = 0;
virtual void set_volume() = 0;
};
class RemoteControl {
void volume_up() {
device.set_volume(device.get_volume() + 10);
}
private:
Device* device;
};
class DeviceA : public Device{};
class DeviceB : public Device{};
组合模式
对于一棵多叉树,我们有各种方式知道它有多少节点。但如果父节点不能访问子节点的成员,又该如何计算节点数量呢?
假设在一个军队中,一个师包含多个旅,一个旅包含多个团。要统计人数,师长等自己的每个旅汇报人数加起来,旅长等自己的每个团汇报人数加起来,每个团报数得到自己团的人数。
class A {
int sum() {
return data + std::accumulate(vector_a.begin(), vector_a.end(),
[](auto x){ return x.sum(); });
}
private:
vector<A> vector_a;
int data;
};
特征是命令逐级传递,嵌套调用。
装饰模式
“封装器包含与目标对象相同的一系列方法, 它会将所有接收到的请求委派给目标对象。 但是, 封装器可以在将请求委派给目标前后对其进行处理, 所以可能会改变最终结果。”
通俗的讲,给原始接口加个包装,在调用原始接口前/后做一些额外的工作。例如中央批下来的公款,本来应该全给到底层,但是中间每层都做一些额外的操作。
如果本来中央可以直接给到底层,但是没给,就是要让每层过一遍,这叫装饰模式。
但是如果中央没办法直接给到底层,不得已必须层层传递,这叫责任链模式。
class Data;
class Work {
void work(Data data); // 写入文件
};
class Decorator {
virtual void work(Data data) { wrapper.work(data); }
private:
Work& wrapper;
};
class CompressDecorator : public Decorator {
CompressDecorator(Decorator);
void work(Data data) {
// 压缩 data
decorator.work(data);
}
private:
Decorator decorator;
};
class EncryptDecorator : public Decorator {
EncryptDecorator(Decorator);
void work(Data data) {
// 加密 data
decorator.work(data);
}
private:
Decorator decorator;
};
int main()
{
Work work;
Data data;
Decorator* decorator = new Decorator(work);
decorator.work(data); // 无额外工作,仅写入文件
decorator = new CompressDecorator(decorator);
decorator.work(data); // 压缩,写入文件
decorator = new EnryptDecorator(decorator);
decorator.work(data); // 压缩,加密,写入文件
}
外观模式
为一个复杂模块提供一个简单接口。
例如加密算法库,压缩算法库。
class Data;
enum class CompressAlgorithm;
// 第三方库提供的唯一接口
class Compressor{
Data compress(Data data, CompressAlgorithm algorithm) {
return CompressFactory::Make(algorithm)->compress(data);
}
};
// 以下内容在第三方库中
class ICompress {
virtual Data compress(Data data) = 0;
};
class CompressFactory {
static ICompress* Make(CompressAlgorithm algorithm) {
return ZipCompress() or RarCompress() or _7zCompress();
};
};
class ZipCompress {
Data compress(Data data) override;
};
class RarCompress {
Data compress(Data data) override;
};
class _7zCompress {
Data compress(Data data) override;
};
享元模式
当一个对象的多个实例拥有相同的数据成员时,这些数据成员可以共享以节省内存。
比如当在画布上画一片森林时,假设每个树相同,仅有位置不同。那么除了位置之外的属性都可以共享。
代理模式
提供与服务相同的接口,将业务委托给真正的服务,可以在真正的服务前后做一些额外的工作。
与装饰模式的不同:
“两者之间的不同之处在于代理通常自行管理其服务对象的生命周期, 而装饰的生成则总是由客户端进行控制。”
例如 Nginx。