目的

桥接是一种结构型设计模式,它允许你将一个大型类或一组紧密相关的类拆分为两个独立的层次结构——抽象层和实现层——这两者可以独立开发。


问题

抽象层?实现层?听起来有点吓人?别担心,我们来看一个简单的例子。

假设你有一个几何形状类`Shape`,它有两个子类:`Circle`(圆形)和`Square`(正方形)。你想扩展这个类层次结构以包含颜色,因此你计划创建`Red`(红色)和`Blue`(蓝色)形状的子类。然而,因为你已经有两个子类了,你需要创建四种类组合,比如`BlueCircle`(蓝色圆形)和`RedSquare`(红色正方形)。

类组合的数量呈几何级数增长。

在层次结构中添加新的形状类型和颜色会导致其呈指数级增长。例如,要添加一个三角形形状,你需要引入两个子类,每个颜色一个。之后,添加一种新颜色将需要创建三个子类,每个形状类型一个。越往后,情况越糟糕。


解决方案

这个问题之所以出现,是因为我们试图在两个独立的维度上扩展形状类:形状和颜色。这是类继承中一个非常常见的问题。

桥接模式试图通过从继承切换到对象组合来解决这个问题。这意味着你将其中一个维度提取到一个单独的类层次结构中,这样原始类将引用新层次结构中的一个对象,而不是将所有状态和行为都包含在一个类中。

你可以通过将一个类层次结构转化为几个相关的层次结构来防止类层次结构的爆炸式增长。

按照这种方法,我们可以将与颜色相关的代码提取到它自己的类中,并创建两个子类:`Red` 和 `Blue`。然后,`Shape` 类会有一个引用字段,指向其中一个颜色对象。现在,形状可以将任何与颜色相关的工作委托给链接的颜色对象。这个引用将作为`Shape`类和`Color`类之间的桥接。从现在开始,添加新的颜色将不再需要更改形状层次结构,反之亦然。


抽象与实现

在《设计模式》(GoF)一书中,引入了“抽象”(Abstraction)和“实现”(Implementation)这两个术语来定义桥接模式。在我看来,这些术语听起来过于学术化,使得该模式显得比实际更复杂。在阅读了关于形状和颜色的简单例子后,让我们来解读GoF书中这些“可怕”的词汇背后的含义。

抽象(也称为接口)是某个实体的高级控制层。这一层本身并不负责执行任何实际工作。它应该将工作委托给实现层(也称为平台)。

请注意,我们这里讨论的并不是你编程语言中的接口或抽象类。它们并不是同一件事。

在谈论实际应用程序时,抽象可以由图形用户界面(GUI)来表示,而实现可以是底层操作系统代码(API),GUI层会在用户交互时调用这些代码。

一般来说,你可以在这两个独立的方向上扩展这样的应用程序:

  • 提供几种不同的GUI(例如,为普通用户或管理员定制)。
  • 支持几种不同的API(例如,以便能够在Windows、Linux和macOS下运行该应用程序)。

在最糟糕的情况下,这个应用程序可能会看起来像一个巨大的意大利面碗,其中数百个条件语句将不同类型的GUI与各种API连接起来,遍布整个代码。

对单体代码库进行即使是简单的更改也相当困难,因为你必须非常了解整个代码库。相比之下,对较小且定义明确的模块进行更改要容易得多。

你可以通过将与特定接口-平台组合相关的代码提取到单独的类中来为这种混乱带来秩序。然而,很快你就会发现这些类的数量非常多。类层次结构会呈指数级增长,因为添加一个新的GUI或支持不同的API将需要创建越来越多的类。

让我们尝试用桥接模式来解决这个问题。它建议我们将类分为两个层次结构:

  • 抽象层:应用程序的GUI层。
  • 实现层:操作系统的API。
桥接模式是结构跨平台应用程序的一种方式。

抽象对象控制应用程序的外观,将实际工作委托给链接的实现对象。只要遵循共同的接口,不同的实现就是可互换的,从而允许相同的GUI在Windows和Linux下工作。

因此,你可以更改GUI类而不必触及与API相关的类。此外,添加对另一个操作系统的支持只需要在实现层次结构中创建一个子类。


结构

  1. Abstraction 
    抽象层提供高级别的控制逻辑。它依赖于实现对象来完成实际的低级工作。
  2. Implementation 

    实现层声明了所有具体实现类共同的接口。抽象层只能通过这里声明的方法与实现对象进行通信。

    抽象层可能会列出与实现层相同的方法,但通常抽象层会声明一些复杂的操作,这些操作依赖于实现层声明的各种基础操作。

  3. Concrete Implementations
    具体实现包含平台特定的代码。

  4. Refined Abstractions
    细化抽象提供了控制逻辑的不同变体。像它们的父类一样,它们通过通用的实现接口与不同的实现进行协作。

  5. Client 
    通常,客户端只对与抽象层交互感兴趣。然而,由客户端来将抽象对象与一个实现对象连接起来。


使用场景

当你想拆分并组织一个具有多种功能变体的单体类时(例如,该类可以与不同的数据库服务器一起工作),可以使用桥接模式。

类变得越大,就越难理解它是如何工作的,而且进行更改所需的时间也越长。对功能的一种变体所做的更改可能需要在整个类中进行更改,这通常会导致错误或未能解决一些关键的副作用。

桥接模式允许你将单体类拆分为几个类层次结构。之后,你可以独立于其他层次结构中的类来更改每个层次结构中的类。这种方法简化了代码维护,并最小化了破坏现有代码的风险。

当你需要在几个正交(独立)的维度上扩展一个类时,可以使用该模式。

桥接建议你为每个维度提取一个单独的类层次结构。原始类将相关工作委托给属于这些层次结构的对象,而不是自行完成所有工作。

如果你需要能够在运行时切换实现,可以使用桥接。

虽然这是可选的,但桥接模式允许你在抽象中替换实现对象。这就像给字段赋一个新值一样简单。

顺便说一下,这也是为什么很多人会把桥接模式和策略模式混淆的主要原因。记住,一个模式不仅仅是一种特定的方式来结构化你的类。它还可以传达意图和正在解决的问题。


如何实现

  1. 在你的类中识别出正交维度。这些独立的概念可以是:抽象层/平台、领域/基础设施、前端/后端或接口/实现。
  2. 确定客户端需要的操作,并在基础抽象类中定义它们。
  3. 确定在所有平台上可用的操作。在通用实现接口中声明抽象层需要的那些操作。
  4. 为你的领域中的所有平台创建具体的实现类,但要确保它们都遵循实现接口。
  5. 在抽象类中添加一个实现类型的引用字段。抽象层将大部分工作委托给该字段中引用的实现对象。
  6. 如果你有几种高级逻辑的变体,通过扩展基础抽象类为每种变体创建细化的抽象。
  7. 客户端代码应该将一个实现对象传递给抽象类的构造函数,以将它们关联起来。之后,客户端可以忘记实现,只与抽象对象一起工作。

优缺点

优点

  • 你可以创建与平台无关的类和应用程序。
  • 客户端代码与高级抽象进行交互。它不会接触到平台的细节。
  • 开闭原则(Open/Closed Principle)。你可以独立地引入新的抽象和实现。
  • 单一职责原则(Single Responsibility Principle)。你可以在抽象中专注于高级逻辑,在实现中专注于平台细节。

缺点

  • 如果将该模式应用于高度内聚的类,可能会使代码变得更加复杂。

相关设计模式

  1. 桥接模式通常是在设计初期就预先设计好的,它允许你独立开发应用程序的不同部分。另一方面,适配器模式通常用于现有的应用程序,以使一些原本不兼容的类能够很好地协同工作。
  2. 桥接模式、状态模式、策略模式(在某种程度上还有适配器模式)具有非常相似的结构。确实,所有这些模式都基于组合,即委托其他对象完成工作。然而,它们解决的问题各不相同。一个模式不仅仅是一种以特定方式结构化代码的配方。它还可以向其他开发人员传达该模式所解决的问题。
  3. 你可以将抽象工厂模式与桥接模式一起使用。当桥接模式定义的某些抽象只能与特定的实现一起工作时,这种组合非常有用。在这种情况下,抽象工厂可以封装这些关系,并隐藏复杂性,使客户端代码免受其影响。
  4. 你可以将建造者模式与桥接模式结合起来使用:指挥者类扮演抽象的角色,而不同的建造者则作为实现。

Python代码示例

桥接是一种结构型设计模式,它将业务逻辑或大型类拆分为可以独立开发的独立类层次结构。

其中一个层次结构(通常称为抽象层)将获得对第二个层次结构(实现层)对象的引用。抽象层将能够将部分(有时是大部分)调用委托给实现对象。由于所有实现都将有一个共同的接口,它们将在抽象层中可以互换使用。

from abc import ABC, abstractmethod
from sys import implementation


class Implementation(ABC):
    """
    实现层定义了所有实现类的接口。它不需要与抽象层的接口匹配。
    实际上,这两个接口可以完全不同。通常,实现层接口仅提供基础操作,而抽象层则基于这些基础操作定义更高层次的操作。

    """
    @abstractmethod
    def operation_implementation(self) -> str:
        pass
"""
每个具体实现对应于一个特定的平台,并使用该平台的API实现实现层接口。
"""

class ConcreteImplementationA(Implementation):
    def operation_implementation(self) -> str:
        return "ConcreteImplementationA Here's the result on the platform A."

class ConcreteImplementationB(Implementation):
    def operation_implementation(self) -> str:
        return "ConcreteImplementationB Here's the result on the platform B."

class Abstraction:
    """
    抽象层定义了两个类层次结构中“控制”部分的接口。
    它维护了一个实现层次结构对象的引用,并将所有实际工作委托给这个对象。
    """
    def __init__(self, implementation: Implementation):
        self.implementation = implementation

    def operation(self) -> str:
        return (f"Abstraction:Base operation with "
                f"{self.implementation.operation_implementation()}")

class ExtendsAbstraction(Abstraction):
    """
    你可以扩展抽象层,而无需更改实现类。
    """
    def operation(self) -> str:
        return (f"ExtendsAbstraction:Base operation with "
                f"{self.implementation.operation_implementation()}")

def client_code(abstract:Abstraction):
    print(abstract.operation(),end="")

if __name__ == '__main__':
    """
    客户端代码应该能够与任何预配置的抽象-实现组合一起工作。
    """
    abstract = ExtendsAbstraction(ConcreteImplementationA())
    client_code(abstract)
    print("\n")
    abstract = ExtendsAbstraction(ConcreteImplementationB())
    client_code(abstract)

更多推荐