设计模式-结构型-代理模式

代理模式原理与实现

在不改变原始类(或叫被代理类)的情况下,通过引入代理类来给原始类附加功能。一般情况下,我们让代理类和原始类实现同样的接口。但是,如果原始类并没有定义接口,并且原始类代码并不是我们开发维护的。在这种情况下,我们可以通过让代理类继承原始类的方法来实现代理模式。

动态代理的原理与实现

静态代理需要针对每个类都创建一个代理类,并且每个代理类中的代码都有点像模板式的“重复”代码,增加了维护成本和开发成本。对于静态代理存在的问题,我们可以通过动态代理来解决。我们不事先为每个原始类编写代理类,而是在运行的时候动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。

代理模式的应用场景

代理模式常用在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放到代理类统一处理,让程序员只需要关注业务方面的开发。除此之外,代理模式还可以用在 RPC、缓存等应用场景中。

场景示例

需求场景

现需求需要开发一个 MetricsCollector 类,用来收集接口请求的原始数据,比如访问时间、处理时长等。原始使用方法如下:

上面的写法有两个问题。第一,性能计数器框架代码侵入到业务代码中,跟业务代码高度耦合。如果未来需要替换这个框架,那替换的成本会比较大。第二,收集接口请求的代码跟业务代码无关,本就不应该放到一个类中。业务类最好职责更加单一,只聚焦业务处理。

使用静态代理模式优化

为了将框架代码和业务代码解耦,代理模式就派上用场了。代理类 UserControllerProxy和原始类 UserController 实现相同的接口 IUserController。UserController 类只负责业务功能。代理类 UserControllerProxy 负责在业务代码执行前后附加其他逻辑代码,并通
过委托的方式调用原始类来执行业务代码。具体的代码实现如下所示:

继承方式实现代理

在刚刚的代理模式的代码实现中,代理类和原始类需要实现相同的接口。但是,如果原始类并没有定义接口,并且原始类代码并不是我们开发维护的(比如它来自一个第三方的类库),我们也没办法直接修改原始类,给它重新定义一个接口。在这种情况下,我们该如何实现代理模式呢?对于这种外部类的扩展,我们一般都是采用继承的方式。

上述继承方式实现的代理存在一些问题。一方面,我们需要在代理类中,将原始类中的所有的方法,都重新实现一遍,并且为每个方法都附加相似的代码逻辑。另一方面,如果要添加的附加功能的类有不止一个,我们需要针对每个类都创建一个代理类。

动态代理

上面的实现过程中,引入了新的问题。我们可以使用动态代理来解决这个问题。所谓动态代理(Dynamic Proxy),就是我们不事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。

以下为 Python 动态代理的实现,为类中的每个方法调用前后都增加日志打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import types


class LogProxy:
def __init__(self, obj, method):
self.obj = obj
self.method = method

def __call__(self, *args, **kws):
print("Log: " + str(self.obj) + " call " + self.method.__name__)
# 调用原始对象的方法
ret = self.method(*args, **kws)
print("Log: " + str(self.obj) + " call " + self.method.__name__ + " finished")


class Proxy:
def __init__(self, clz, pclz):
self.clz = clz
self.pclz = pclz
self.proxies = {} # 查表提高效率

def __call__(self, *args, **kws):
self.obj = self.clz(*args, **kws) # 调用被代理类的构造函数创建实例,即创建 Sample 实例
return self

def __getattr__(self, attr): # 调用方法之前需要通过getattr查找方法
ret = None
if hasattr(self.obj, attr): # 查看被代理实例成员是否存在
ret = getattr(self.obj, attr)
if isinstance(ret, types.MethodType): # 如果该成员是方法
if ret not in self.proxies: # 如果该方法的代理没有被生成
self.proxies[ret] = self.pclz(self.obj, ret) # 创建该方法的代理,即创建 LogProxy 实例
return self.proxies[ret]
return ret


class ProxyFactory:
def __init__(self, pclz):
self.pclz = pclz # pclz 为LogProxy

def __call__(self, clz):
return Proxy(clz, self.pclz) # clz 为Sample


"""
ProxyFactory(LogProxy) 调用 ProxyFactory __init__ 方法
加上 @ 后调用 ProxyFactory __call__ 方法
"""


@ProxyFactory(LogProxy)
class Sample:
def __init__(self, name):
self.name = name

def print_name(self):
print(self.name)


if __name__ == "__main__":
s = Sample("xxx")
s.print_name()
print("s.name: %s" % s.name)

参考地址: https://gitbook.cn/books/5eccda08fb2d034ba7657f6a/index.html