代理模式
简单回顾下创建型模式
- 单例模式用来创建全局唯一的对象
- 工厂模式用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类), 由给定的参数来决定创建哪种类型的对象
- 建造者模式是用来创建复杂对象, 可以通过设置不同的可选参数, “定制化”地创建不同的对象
- 原型模式针对创建成本比较大的对象, 利用对已有对象进行复制的方式进行创建, 以达到节省创建时间的目的
接下来就是结构型模型
结构型模式包括:代理模式、桥接模式、装饰器模式、适配器模式、门面模式、组合模式、享元模式
原理
在不改变原始类(或叫被代理类)代码的情况下, 通过引入代理类来给原始类附加功能.
下述有个业务接口.
// 接口虚类
class Subject {
public:
virtual void method() = 0;
virtual void print() = 0;
};
//业务实现类
class RealSubject : public Subject {
public:
void method() { print(); }
void print() { std::cout << "real method" << std::endl; }
};
若我想在这个业务上加个功能, 访问时间, 执行时长 或者其他的扩展业务
- 扩展代码侵入业务代码, 跟业务代码高度耦合, 而且以后需要替换成其他扩展成本比较大
- 扩展代码跟业务代码无关, 不应该放一起. 一个类最好职责单一, 只需要关心业务的处理
假设我这边需要简单加个日期打印
// Log类
class Log {
public:
Log() {}
std::string getTime() {
time_t t = time(NULL);
char ch[64] = {0};
// 年-月-日 时:分:秒
strftime(ch, sizeof(ch) - 1, "%Y-%m-%d %H:%M:%S", localtime(&t));
return ch;
}
};
下面通过代理模式添加时间日志输出
//代理类
class StaticProxy : public Subject {
public:
StaticProxy() {
subject = new RealSubject();
log = new Log();
}
~StaticProxy() {
delete subject;
delete log;
}
void method() {
std::cout << log->getTime() << ": "
<< "static proxy start" << std::endl;
subject->method();
std::cout << log->getTime() << ": "
<< "static proxy end" << std::endl;
}
void print() {}
private:
RealSubject* subject;
Log* log;
};
调用方式
std::shared_ptr<Subject> sub = nullptr;
std::cout << "-----Orgin print-----" << std::endl;
sub = std::make_shared<RealSubject>();
sub->method();
std::cout << "-----Proxy Pattern-----" << std::endl;
sub = std::make_shared<StaticProxy>();
sub->method();
效果
./bin/design/Proxy
-----Orgin print-----
real method
-----Proxy Pattern-----
2023-10-13 10:08:37: static proxy start
real method
2023-10-13 10:08:37: static proxy end
这样就可以在不入侵原有业务代码的基础上扩展功能
但是 代码实现还是有点问题
- 我们需要在代理类中,将原始类中的所有的接口,都重新实现一遍,并且为每个方法都附加相似的代码逻辑.
- 如果要添加的附加功能的类有不止一个,我们需要针对每个类都创建一个代理类。
我们可以使用动态代理来解决这个问题
就是我们不事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。
业务2接口
// 接口虚类2
class Subject2 {
public:
virtual void method2() = 0;
virtual void print2() = 0;
};
// 业务实现类2
class RealSubject2 : public Subject2 {
public:
void method2() { print2(); }
void print2() { std::cout << "real2 method" << std::endl; }
};
代理类
template <class T>
class DynamicProxy : public T {
public:
DynamicProxy(std::function<void()> func) : func(func), log(new Log) {}
~DynamicProxy() { delete log; }
void extend() {
std::cout << log->getTime() << ": "
<< "dynamic proxy start" << std::endl;
func();
std::cout << log->getTime() << ": "
<< "dynamic proxy end" << std::endl;
}
void method() { extend(); }
void method2() { extend(); }
private:
std::function<void()> func;
Log* log;
};
调用方式
std::cout << "-----Dynamic Proxy Pattern-----" << std::endl;
std::shared_ptr<Subject> sub1 = std::make_shared<DynamicProxy<RealSubject>>(
std::bind(&RealSubject::method, RealSubject()));
sub1->method();
std::shared_ptr<Subject2> sub2 = std::make_shared<DynamicProxy<RealSubject2>>(
std::bind(&RealSubject2::method2, RealSubject2()));
sub2->method2();
效果
./bin/design/Proxy
-----Dynamic Proxy Pattern-----
2023-10-13 10:34:48: dynamic proxy start
real method
2023-10-13 10:34:48: dynamic proxy end
2023-10-13 10:34:48: dynamic proxy start
real2 method
2023-10-13 10:34:48: dynamic proxy end
通过动态代理方式(模版增加通用性)
- 可以通过绑定函数的方式动态添加需要增加扩展的业务函数接口
- 只需要实现增加扩展的接口, 不需要全部实现基类所以接口
适用
代理模式常用在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。
我们将这些附加功能与业务功能解耦,放到代理类统一处理,让程序员只需要关注业务方面的开发。
代码