Proxy 代理模式

设计模式 2020-04-12 4574 字 926 浏览 点赞

起步

代理模式属于结构型,允许在不改变被代理类代码的基础上,为被代理类添加附加功能。

代理模式出没在各种场景,比如: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、缓存都可以用代理模式。

代理模式与门面模式看上去很像,实际上存在许多差别。前者为被代理的对象提供一个中间人,用于控制访问被代理对象,而后者是各种子系统调用集合;前者要求代理类与被代理类有相同的接口,后者要求最大限度地减少子系统之间的通信和依赖。

参考



本文由 Guan 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

还不快抢沙发

添加新评论