Decorator 装饰器模式

设计模式 2020-04-19 4504 字 1104 浏览 点赞

起步

装饰器模式属于结构型,常用于运行中动态地为对象扩展功能。

装饰器主张以组合的方式为一个对象附加功能,避免继承引发子类数量庞大,而不便于维护的问题。

使用继承

这里还是用那个老掉牙的解析配置示例。

class ConfigParser(metaclass=abc.ABCMeta):
    """
        interface
    """
    @abc.abstractmethod
    def parse(self, file: str) -> dict:
        pass

class JsonConfigParser(ConfigParser):
    def _to_dict(self, content: str) -> dict:
        ...  # 处理逻辑
        return {"time": time.time()}  # 返回假数据

    def parse(self, file: str) -> dict:
        with open(file, "r") as f:
            content = f.read()

        return self._to_dict(content)

现在每次调用 JsonConfigParser.parse 都会从磁盘读取文件,发生一次 IO 事件。实际上没有这个必要,我们只需要第一次加载配置文件时从磁盘读取,然后把处理后的内容存进内存中,以后都从内存里获取配置信息。

缓存是一个附加功能,可以用继承的方式为 JsonConfigParser 扩展该功能:

class CacheJsonConfigParser(JsonConfigParser):
    _cache: dict = {}

    def parse(self, file: str) -> dict:
        # 返回缓存内容
        if self.__class__._cache:
            return self.__class__._cache

        # 写入缓存
        self.__class__._cache = super().parse(file)
        return self.__class__._cache

虽说功能写好了,但这只是针对 JsonConfigParser 的缓存。倘若项目中还有 XmlParser,YamlParser 等等,就得对应实现 CacheXmlParser,CacheYamlParser …… 如果还要求解析加密过后的配置文件,代码设计会进一步复杂:

class DencryCacheJsonConfigParser(CacheJsonConfigParser):
    def _dencry(self, config: dict) -> dict:
        ...  # 反加密
        return {}  # 返回假数据

    def parse(self, file: str) -> dict:
        config = super().parse(file)
        return self._dencry(config)

# 针对 xml
class XmlConfigParser(ConfigParser):
    ...

class CacheXmlConfigParser(XmlConfigParser):
    ...

class DencryCacheXmlConfigParser(CacheXmlConfigParser):
    ...

# 针对 yaml
class YamlConfigParser(ConfigParser):
    ...

class CacheYamlConfigParser(YamlConfigParser):
    ...

class DencryCacheYamlConfigParser(CacheYamlConfigParser):
    ...

装饰器模式

为了避免上述窘境,于是有了装饰器模式。其实就是两板斧:组合+依赖注入。组合的好处不重复说了,依赖注入则是为了提高代码灵活性,在这里也能有效减少不必要的重复代码。代码大概如下模样:

class CacheConfigParser(ConfigParser):
    _cache: dict = {}

    def __init__(self, parser):
        self.parser = parser

    def parse(self, file: str) -> dict:
        # 返回缓存内容
        if self.__class__._cache:
            return self.__class__._cache
        # 写入缓存
        self.__class__._cache = self.parser.parse(file)
        return self.__class__._cache

class DencryConfigParser(ConfigParser):
    def __init__(self, parser):
        self.parser = parser

    def _dencry(self, config: dict) -> dict:
        ...  # 解密
        return config

    def parse(self, file: str) -> dict:
        config = self.parser.parse(file)
        return self._dencry(config)

这样设计之后,我们就可以想要啥功能就要啥功能了。如果既不需要缓存也不需要解密,那么 parser 可以这样用:

parser = JsonConfigParser()
parser.parse(...)

如果现在需要缓存功能,或者缓存+解密:

# 只缓存
parser = JsonConfigParser()
parser = CacheConfigParser(parser)
parser.parse(...)

# 缓存 + 解密
parser = JsonConfigParser()
parser = CacheConfigParser(parser)
parser = DencryConfigParser(parser)
parser.parse(...)

当 ConfigParser 接口需要实现的方法比较少时还可以让装饰器类 CacheConfigParser,DencryConfigParser 直接继承 ConfigParser。而如果方法比较多时,则不建议装饰器类直接继承抽象类 ConfigParser,而是去继承抽象装饰器类

class ConfigParser(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def parse(self, file: str) -> dict:
        pass
    
    @abc.abstractmethod
    def method_1(...):
        ...

    @abc.abstractmethod
    def method_2(...):
        ...
    ...

class DecoratorConfigParser(ConfigParser):
    """ 抽象装饰器类 """
    def __init__(self, parser):
        self.parser = parser

    @abc.abstractmethod
    def parse(self, file: str) -> dict:
        pass
    
    def method_1(...):  # 实现该方法
        ...

    def method_2(...):  # 实现该方法
        ...
    ...

DecoratorConfigParser 中的方法可以尽量写得通用些。具体装饰器类 CacheConfigParser,DencryConfigParser 继承 DecoratorConfigParser 之后只需要重写需要重写的方法。

总结

单从代码实现上看,装饰器模式与代理模式基本一样。尤其是动态代理与装饰器模式,感觉就没差。我从网上查询所得的资料来看,装饰器与代理的区别在于目的不同。

Although decorators can have similar implementations as proxies, decorators have a different purpose. A decorator adds one or more responsibilities to an object, whereas a proxy controls access to an object.

较为认同的是:代理是要约束被代理对象的行为;装饰器是要增强被装饰对象的能力

参考



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

还不快抢沙发

添加新评论