Adapter 适配器模式

设计模式 2020-04-18 3563 字 892 浏览 点赞

起步

适配器模式属于结构型,常分为类适配器对象适配器,负责将不兼容的接口转换为可兼容接口,让原本由于接口不兼容而不能一起工作的的类可以一起工作。

这实际上是一个很常见的设计模式,我们都在用,只是不知道有名字而已。

类适配器

类适配器用继承关系实现。

譬如老项目中有一个解析自定义配置文件的功能。

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)

总结

在本文示例中,可能会觉得类适配器和对象适配器没啥差啦。但在实际开发中,如果引入的新包与被替换类的接口多数重合,使用类适配器可以大大减少代码量。当接口重合少或者无时,建议使用对象适配器。一则是:组合方式更灵活,容易面对以后不可知的需求变化;二则是:避免继承方式造成子类拥有过多无用接口,违反了接口隔离原则。

看上去适配器模式与代理模式好像蛮相似的。但代理模式主要是为了控制访问而存在,适配器模式是为了解决接口不兼容问题,常见于弥补旧有的接口设计缺陷;兼容系统新旧版本;适配不同的数据格式等。二者存在明显的职责差异。

参考



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

还不快抢沙发

添加新评论