Bridge 桥接模式

设计模式 2020-04-22 3910 字 895 浏览 点赞

起步

桥接模式属于结构型,其目的是将抽象和实现解耦,让它们可以独立变化。

这里“抽象”与“实现”都不是平常说的抽象类、子类等概念。用维基百科的原话就是:

The class itself can be thought of as the abstraction and what the class can do as the implementation.

(抽象)与类要做的事(实现)都在频繁变化时,桥接模式就有意义。

如何理解抽象与实现

虽说桥接模式中的抽象与实现不是抽象类子类,但可以做这样一个知识迁移。也就是说,不能完全画上等号,但能类比着来理解!仍用解析配置的示例。

class ConfigParser(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def parse(self, content):
        pass

class JsonConfigParser(ConfigParser):
    def parse(self, content):
        ...

class XmlConfigParser(ConfigParser):
    def parse(self, content):
        ...

class YamlConfigParser(ConfigParser):
    def parse(self, content):
        ...

在以上代码中,ConfigParser 即为“抽象”,指定了需要实现的接口;JsonConfigParser、XmlConfigParser 等为“实现”,用来解析不同的配置文件。代码这样设计源于作为接口的 ConfigParser 不会改变。

现在项目要求增加一个解析日志的功能,一个解析协议的功能。日志与协议都被项目的某个进程写到本地文件里,写的时候可以根据参数选择 json 或者 xml 格式。

最直接了当的法子就是多写几个接口,现在已经有了 ConfigParser,我们还需要写 ProtocolParser、LogParser。然后是对应的功能实现:JsonProtocolParser,XmlProtocolParser,JsonLogParser,XmlLogParser。

class ProtocolParser(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def parse(self, content):
        pass

class JsonProtocolParser(ProtocolParser):
    def parse(self, content):
        ...

class XmlProtocolParser(ProtocolParser):
    def parse(self, content):
        ...

class LogParser(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def parse(self, content):
        pass

class JsonLogParser(ProtocolParser):
    def parse(self, content):
        ...

class XmlLogParser(ProtocolParser):
    def parse(self, content):
        ...

当前功能呈现两个维度的变化,一个是什么样的解析器(配置解析器,还是日志解析器,还是协议解析器),一个是解析器要解析什么格式的文件(json 解析器,xml 解析器)。

在最初示例中,ConfigParser 作为抽象接口,为统一风格我们就可以把“什么样的解析器”视作抽象把“要解析什么格式”看作实现。你也可以把后者看作抽象,前者看作实现。这无关紧要。

再从代码角度来看,以上独立的 ConfigParser、ProtocolParser、LogParser 三个接口设计实在不太好,它们的代码有共同点,可以提取出来;其次子类数量过多。如果项目需要 20 种解析器,且要解析的文件格式有 10 种,那么子类就得有 200 个。桥接模式为解决这类问题而存在。

桥接模式

之所以子类数量爆炸增长,在于我们使用继承关系关联各个功能。在“组合 > 继承”的指导思想下,就算不知道桥接模式,我们也晓得要用组合代替原有的继承。

先端上来“抽象族”的代码:

class Parser(metaclass=abc.ABCMeta):
    def __init__(self, formatter):
        self.formatter = formatter

    def parse(self, content):
        normalized_data = self.formatter.retrieve(content)
        return self.post_retrieve(normalized_data)

    @abc.abstractmethod
    def post_retrieve(self, normalized_data):
        pass

class ConfigParser(Parser):
    def post_retrieve(self, normalized_data):
        ...

class ProtocolParser(Parser):
    def post_retrieve(self, normalized_data):
        ...

class LogParser(Parser):
    def post_retrieve(self, normalized_data):
        ...

Parser 作为接口,要求子类都必须实现 post_retrieve 方法,该方法负责从标准数据其中提炼或者组合出对应的配置信息、协议信息、日志信息。

而标准数据从哪儿来呢?其实就是调用 formatter.retrieve 方法返回的结果。在上述代码设计中,我们并不强制要求重写 parse 方法,而在不重写前提下,代码可以正常使用,那么 formatter 对象就必须实现 retrieve 方法。

所以“实现族”的代码呈现如下:

class Formatter(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def retrieve(self, content):
        """ 从文件中提取数据,并统一整理为字典格式,返回 """
        pass

class JsonFormatter(Formatter):
    def retrieve(self, content):
        ...

class XmlFormatter(Formatter):
    def retrieve(self, content):
        ...

现在需要什么样的解析器随意组合就可以了:

formatter = JsonFormatter()  # 选择文件格式
parser = ProtocolParser(formatter)  # 选择解析器的种类
parser.parse(...)  # 执行解析

总结

在上述的桥接模式代码示例中,我们需要刻意关注 parse 方法。

事实上,这里的 parse 方法是流程控制器,使得毫无关系的 fomatter 必须实现 retrieve 方法。所以 Parser 是不是就很像 Formatter 的抽象类了。桥接模式中的抽象与实现在这里会不会更好理解。

还要说明的是,桥接模式中流程控制 parse 方法并非必须实现,上面之所以这样设计是为了让你更容易理解“抽象与实现”。而我以为,只要两种类在独立变化,其中一种类依赖或者关联另一种类,那么它们就符合桥接模式。

参考



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

还不快抢沙发

添加新评论