起步
代理模式属于结构型,允许在不改变被代理类代码的基础上,为被代理类添加附加功能。
代理模式出没在各种场景,比如:1. 简化复杂系统的使用;2. 隐藏被代理对象;3. 为远程服务提供本地调用接口;4. 为服务提供预处理;等等。
组合实现代理模式
我们仍拿加载配置文件举例。现在需要一个能够解析 json 文件的 parser,代码如下:
class JsonConfigParser(object):
def __init__(self, path):
self.path = path
def parse(self) -> dict:
# ... 解析配置文件
return {}
到后期,产品经理要求记录解析配置文件耗费的时间。这个需求极为简单,可以说上手就来:
class JsonConfigParser(object):
def __init__(self, path):
self.path = path
def parse(self) -> dict:
start = time.time()
# ... 解析配置文件
end = time.time()
logging.info(f"spend {end - start}")
return {}
但不管是单一职责还是开闭原则,将计时功能耦合到 parse 方法都不大合适。如果要选择代理模式解决当前困境,代码改动后呈现如下:
class ConfigParserInterface(metaclass=abc.ABCMeta):
@abc.abstractmethod
def parse(self) -> dict:
pass
class JsonConfigParser(ConfigParserInterface):
def __init__(self, path):
self.path = path
def parse(self) -> dict:
# ... 解析配置文件
return {}
class JsonConfigParserProxy(ConfigParserInterface):
def __init__(self, path):
self.parser = JsonConfigParser(path) # 被代理对象
def parse(self) -> dict:
start = time.time()
res = self.parser.parse() # 执行被代理对象的方法
end = time.time()
logging.info(f"spend {end - start}")
return res
我们先定义了接口 ConfigParserInterface,确保代理对象与被代理对象都有 parse 方法。然后在 JsonConfigParserProxy.parse 中增加计时功能,解析配置时,则委托 JsonConfigParser.parse 去做。
以上就是一个简单的、基于组合的代理模式。
继承实现代理模式
实际开发过程中,被代理对象不总是由我们编写,可能是第三方库中的类。也许此时类库没有定义接口,而我们又无权改写源码,一般这种情况采用继承实现代理模式。
class JsonConfigParser(object): # 假设是第三方库中的类
def __init__(self, path):
self.path = path
def parse(self) -> dict:
# ... 解析配置文件
return {}
class JsonConfigParserProxy(JsonConfigParser): # 需要我们实现的代理类
def __init__(self, path):
super().__init__(path)
def parse(self) -> dict:
start = time.time()
res = super().parse() # 执行被代理对象的方法
end = time.time()
logging.info(f"spend {end - start}")
return res
动态代理
上述的两种代理方式存在一个缺点,就是不得不为一个被代理类实现一个代理类,即便额外添加的功能是相同的。假设这里有功能 A-Z 26个都需要增加时间记录,就得写 26 个代理类。为解决这一问题,于是引入了动态代理,也就是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。
在 Python 中,我们可以基于 __getattr__
魔法方法实现动态代理:
class TimeCounterInterface(metaclass=abc.ABCMeta):
@abc.abstractmethod
def record_time(self, method):
pass
class TimeCounter(TimeCounterInterface):
def record_time(self, method):
"""
耗时记录功能
"""
def wrapper(*args, **kwargs):
start = time.time()
res = method(*args, **kwargs)
end = time.time()
logging.info(f"spend {end - start}")
return res
return wrapper
class TimeCounterProxy(TimeCounterInterface):
def __init__(self, proxied_obj, proxied_methods=None):
# 需要被代理的对象
self.proxied_obj = proxied_obj
# 需要被代理的方法
self.proxied_methods: Union[List, str] = proxied_methods or "ALL"
self.time_counter = TimeCounter()
def __getattr__(self, attr):
# 如果被代理对象没有属性 attr
if not hasattr(self.proxied_obj, attr):
return super().__getattribute__(attr)
# 如果调用的属性不是被代理对象的方法
method_or_attr = getattr(self.proxied_obj, attr)
if not inspect.ismethod(method_or_attr):
attr = method_or_attr
return attr
method = method_or_attr
# == “ALL” 表示代理全部方法
if self.proxied_methods == "ALL":
return self.record_time(method)
# 只有 proxied_methods 中的方法需要被代理
else:
if attr in self.proxied_methods:
return self.record_time_proxy(method)
return method
def record_time(self, method):
return self.time_counter.record_time(method)
动态代理使用方式:
parser = JsonConfigParser("...")
parser_timecounter = TimeCounterProxy(parser)
总结
代理模式常用在业务系统中开发一些非功能性需求,包括但不仅限于:限流、事物、幂等、日志、鉴权。除此之外,RPC、缓存都可以用代理模式。
代理模式与门面模式看上去很像,实际上存在许多差别。前者为被代理的对象提供一个中间人,用于控制访问被代理对象,而后者是各种子系统调用集合;前者要求代理类与被代理类有相同的接口,后者要求最大限度地减少子系统之间的通信和依赖。
参考
- 感谢 极客时间王争老师的《设计模式之美:代理模式》
- 感谢《Learning Python Design Patterns, 2nd Edition.pdf》
还不快抢沙发