起步
装饰器模式属于结构型,常用于运行中动态地为对象扩展功能。
装饰器主张以组合的方式为一个对象附加功能,避免继承引发子类数量庞大,而不便于维护的问题。
使用继承
这里还是用那个老掉牙的解析配置示例。
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.
较为认同的是:代理是要约束被代理对象的行为;装饰器是要增强被装饰对象的能力。
参考
- 感谢 极客时间王争老师的《设计模式之美:装饰器模式》
- 感谢 李建忠老师的《C++模式设计》
- 感谢 https://stackoverflow.com/a/60478875
还不快抢沙发