Паттерн Bridge (Мост)
Паттерн Bridge (Мост) — это структурный паттерн проектирования, который разделяет один или несколько классов на две отдельные иерархии: абстракцию и реализацию, позволяя изменять их независимо друг от друга.
Подробное описание
Постановка задачи: Часто возникает ситуация, когда класс должен поддерживать несколько вариантов реализации одной и той же функциональности (например, отрисовка фигуры может выполняться через векторную графику или растровую). Если использовать наследование, количество классов растет экспоненциально (Комбинация N абстракций и M реализаций дает N*M классов).
Ключевая идея: Заменить наследование композицией. Реализация выносится в отдельную иерархию классов, а абстракция хранит ссылку на объект реализации. Это позволяет менять реализацию во время выполнения программы и расширять обе иерархии независимо.
Исторический контекст: Паттерн описан в книге «Банды четырех» (Gang of Four) как один из фундаментальных структурных паттернов. Он решает проблему жесткой связи между интерфейсом и его реализацией.
Принцип работы
Паттерн состоит из четырех основных участников:
- Abstraction (Абстракция): определяет интерфейс управления. Хранит ссылку на объект реализации.
- Refined Abstraction (Уточненная абстракция): расширяет интерфейс абстракции.
- Implementor (Реализация): определяет интерфейс реализации. Этот интерфейс не обязательно должен точно соответствовать интерфейсу Abstraction; часто он более низкоуровневый.
- Concrete Implementor (Конкретная реализация): содержит конкретную логику реализации.
Блок-схема структуры
classDiagram
class Abstraction {
+Implementor implementor
+operation()
}
class RefinedAbstraction {
+operation()
}
class Implementor {
<<interface>>
+operationImpl()
}
class ConcreteImplementorA {
+operationImpl()
}
class ConcreteImplementorB {
+operationImpl()
}
Abstraction o-- Implementor : композиция
RefinedAbstraction --|> Abstraction : наследует
ConcreteImplementorA ..|> Implementor : реализует
ConcreteImplementorB ..|> Implementor : реализует
Пример реализации на Python
В данном примере мы реализуем систему отрисовки фигур. Абстракция — это сама фигура (Круг), а реализация — способ отрисовки (Векторный или Растровый рендерер).
from abc import ABC, abstractmethod
# --- Иерархия Реализации (Implementation) ---
class Renderer(ABC):
"""Интерфейс реализации (Implementor)."""
@abstractmethod
def render_circle(self, radius: float) -> None:
pass
class VectorRenderer(Renderer):
"""Конкретная реализация: векторная графика."""
def render_circle(self, radius: float) -> None:
print(f"Drawing a circle of radius {radius} using vector graphics")
class RasterRenderer(Renderer):
"""Конкретная реализация: растровая графика (пиксели)."""
def render_circle(self, radius: float) -> None:
print(f"Drawing a circle of radius {radius} using pixels")
# --- Иерархия Абстракции (Abstraction) ---
class Shape:
"""Базовая абстракция. Хранит ссылку на реализацию."""
def __init__(self, renderer: Renderer):
self.renderer = renderer
def draw(self) -> None:
raise NotImplementedError
def resize(self, factor: float) -> None:
raise NotImplementedError
class Circle(Shape):
"""Уточненная абстракция: Круг."""
def __init__(self, renderer: Renderer, radius: float):
super().__init__(renderer)
self.radius = radius
def draw(self) -> None:
# Делегируем работу реализации
self.renderer.render_circle(self.radius)
def resize(self, factor: float) -> None:
self.radius *= factor
if __name__ == "__main__":
# Создаем реализации
raster_renderer = RasterRenderer()
vector_renderer = VectorRenderer()
# Создаем абстракции с разными реализациями
circle_raster = Circle(raster_renderer, 5)
circle_vector = Circle(vector_renderer, 10)
# Используем абстракции
print("Raster Circle:")
circle_raster.draw()
print("\nVector Circle:")
circle_vector.draw()
# Меняем состояние абстракции
circle_raster.resize(2)
print("\nResized Raster Circle:")
circle_raster.draw()
Вывод программы:
Raster Circle:
Drawing a circle of radius 5 using pixels
Vector Circle:
Drawing a circle of radius 10 using vector graphics
Resized Raster Circle:
Drawing a circle of radius 10 using pixels
Достоинства и недостатки
Достоинства:
- Разделение ответственности: Абстракция и реализация развиваются независимо. Можно добавлять новые фигуры, не трогая код рендереров, и наоборот.
- Снижение количества классов: Вместо создания комбинаторного взрыва подклассов (КругВекторный, КругРастровый, КвадратВекторный...), мы создаем две линейные иерархии.
- Гибкость во время выполнения: Реализацию можно подменять на лету (например, переключить рендеринг с высокого качества на низкое для экономии ресурсов).
Недостатки:
- Усложнение кода: Появляются дополнительные уровни косвенности. Код становится сложнее для понимания новичками.
- Требует правильного проектирования: Нужно заранее выявить оси независимых изменений, иначе применение паттерна может быть избыточным.