起步
适配器模式属于结构型,常分为类适配器和对象适配器,负责将不兼容的接口转换为可兼容接口,让原本由于接口不兼容而不能一起工作的的类可以一起工作。
这实际上是一个很常见的设计模式,我们都在用,只是不知道有名字而已。
类适配器
类适配器用继承关系实现。
譬如老项目中有一个解析自定义配置文件的功能。
class ConfigParserInterface(metaclass=abc.ABCMeta):
"""
解析器接口
"""
@abc.abstractmethod
def read_config_file(self, file: str) -> str:
pass
@abc.abstractmethod
def extract_config_to_dict(self, content: str) -> dict:
pass
class CustomConfigParser(ConfigParserInterface):
def read_config_file(self, file: str) -> str:
with open(file, "r") as f:
return f.read()
def extract_config_to_dict(self, content: str) -> dict:
config = {}
for line in (line for line in content.split("\n") if line):
key, value = line.split(":", maxsplit=1)
config.setdefault(key.strip(), value.strip())
return config
class ConfigSource(object):
def load(self, file) -> dict:
paser = CustomConfigParser()
content = paser.read_config_file(file)
config = paser.extract_config_to_dict(content)
return config
虽然是自定义配置文件,仍要求一定的规范才行。以下是一个简单的配置文件示例:
WebName: YouGuan
Address: 192.168.2.171:80
Debug: False
但有一天我们发现,github 上有一个很牛逼的第三方包,支持解析各种配置文件。反正不管什么格式,你只要把文件扔进它开放出来的接口就行,它可以自己去智能分析。如果将其引入项目中来,json paser、xml parser、yaml parser 等等就都不需要自己实现了。岂不美哉!
阅读这个牛逼的解析器的使用文档,发现其用法为:
import NBConfigParser
nb_parser = NBConfigPaser()
# specific_content 经过智能处理之后的文本
specific_content = nb_parser.read_config_file(".../config")
# 解析配置
config = nb_parser.parse(specific_content)
虽然跟项目原有的 parser 用法不同,但摸上去把 ConfigSource.load 代码修改即可。另一种选择则是,使用适配器模式。让本不能一起工作的 ConfigSource 能与 NBConfigParser 一起工作。
# NBConfigParser 源码
class NBConfigParser(object):
def read_config_file(self, file: str) -> str:
...
def parse(self, content: str) -> dict:
...
# 自己的项目代码
class CustomConfigParser(NBConfigParser, ConfigParserInterface):
def extract_config_to_dict(self, content: str) -> dict:
return self.parse(content) # 使用父类 NBConfigParser 中的 parse 方法
基于类适配器方式,CustomConfigParser 需要继承 NBConfigParser。由于 NBConfigParser 本来就有 read_config_file
方法,且函数签名与原来的 CustomConfigParser 相同,我们就无需重写该方法。但 NBConfigParser 没有 extract_config_to_dict
方法,取而代之的是 parse
。为兼容 ConfigSource 的使用,需要在子类中实现 extract_config_to_dict
。
这毕竟只是一个示例,实际开发中 CustomConfigParser.extract_config_to_dict
方法很可能做不到直接调用父类的 parse
就完成适配任务。也有会有参数类型问题,参数个数问题等等。然而适配器的核心思想不应该有变化。
对象适配器
对象适配器用组合关系实现。
基于上述场景,我们也可以用对象适配器完成适配任务。
# NBConfigParser 源码
class NBConfigParser(object):
def read_config_file(self, file: str) -> str:
...
def parse(self, content: str) -> dict:
...
# 自己的项目代码
class CustomConfigParser(ConfigParserInterface):
def __init__(self):
self.nb_parser = NBConfigParser()
def read_config_file(self, file: str) -> str:
return self.nb_parser.read_config_file(file)
def extract_config_to_dict(self, content: str) -> dict:
return self.nb_parser.parse(content)
总结
在本文示例中,可能会觉得类适配器和对象适配器没啥差啦。但在实际开发中,如果引入的新包与被替换类的接口多数重合,使用类适配器可以大大减少代码量。当接口重合少或者无时,建议使用对象适配器。一则是:组合方式更灵活,容易面对以后不可知的需求变化;二则是:避免继承方式造成子类拥有过多无用接口,违反了接口隔离原则。
看上去适配器模式与代理模式好像蛮相似的。但代理模式主要是为了控制访问而存在,适配器模式是为了解决接口不兼容问题,常见于弥补旧有的接口设计缺陷;兼容系统新旧版本;适配不同的数据格式等。二者存在明显的职责差异。
参考
- 感谢 极客时间王争老师的《设计模式之美:适配器模式》
还不快抢沙发