起步
桥接模式属于结构型,其目的是将抽象和实现解耦,让它们可以独立变化。
这里“抽象”与“实现”都不是平常说的抽象类、子类等概念。用维基百科的原话就是:
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 方法并非必须实现,上面之所以这样设计是为了让你更容易理解“抽象与实现”。而我以为,只要两种类在独立变化,其中一种类依赖或者关联另一种类,那么它们就符合桥接模式。
参考
- 感谢 极客时间王争老师的《设计模式之美:桥接模式》
- 感谢 Bridge pattern
还不快抢沙发