Bridge и Flyweight кратко

Два структурных паттерна на закуску: Bridge борется со взрывом классов по двум осям, Flyweight экономит память.

Bridge разделяет абстракцию и её реализацию, чтобы менять их независимо. Flyweight экономит память, разделяя общее (внутреннее) состояние между множеством объектов.

Bridge: две независимые оси

Допустим, у вас есть фигуры (круг, квадрат) и способы отрисовки (вектор, растр). Если наследовать всё подряд, получится VectorCircle, RasterCircle, VectorSquare… — произведение двух осей. Bridge выносит вторую ось («как рисовать») в отдельную иерархию и связывает её композицией.

from abc import ABC, abstractmethod


class Renderer(ABC):             # ось «реализация»
    @abstractmethod
    def draw(self, shape): ...


class VectorRenderer(Renderer):
    def draw(self, shape):
        return f"вектор: {shape}"


class RasterRenderer(Renderer):
    def draw(self, shape):
        return f"пиксели: {shape}"


class Shape:                     # ось «абстракция»
    def __init__(self, renderer: Renderer):
        self.renderer = renderer

    def render(self):
        return self.renderer.draw(self.name)


class Circle(Shape):
    name = "круг"


print(Circle(VectorRenderer()).render())
print(Circle(RasterRenderer()).render())

Вывод:

вектор: круг
пиксели: круг

Теперь фигуры и рендереры развиваются независимо: добавить Square или SvgRenderer — это плюс один класс в своей оси, а не произведение. Связь между осями — это «мост» (composition).

Flyweight: разделяем общее

Когда объектов очень много (миллионы деревьев в игре, символы в редакторе), хранить у каждого полную копию данных расточительно. Flyweight выносит общее, неизменяемое состояние (тип дерева, его текстуру) в разделяемый объект, а уникальное (координаты) держит снаружи.

class TreeType:                  # flyweight: общее состояние
    def __init__(self, name, texture):
        self.name = name
        self.texture = texture


class TreeFactory:
    _types = {}

    @classmethod
    def get(cls, name, texture):
        key = (name, texture)
        if key not in cls._types:
            cls._types[key] = TreeType(name, texture)
        return cls._types[key]     # один объект на уникальный тип


# 1000 деревьев, но типов всего 2 -> экономия памяти
forest = [TreeFactory.get("дуб", "oak.png") for _ in range(1000)]
forest += [TreeFactory.get("ель", "fir.png") for _ in range(1000)]

print("деревьев:", len(forest))
print("уникальных типов:", len(TreeFactory._types))
print("один объект на тип:", forest[0] is forest[5])

Вывод:

деревьев: 2000
уникальных типов: 2
один объект на тип: True

2000 деревьев делят всего 2 объекта-типа. Координаты каждого дерева (внешнее состояние) хранились бы отдельно. Так миллионы объектов умещаются в память.

Где встречаются

Bridge — кросс-платформенная отрисовка, драйверы устройств, GUI поверх разных бэкендов. Flyweight — глифы шрифтов, тайлы в играх, кеш строк (интернирование в Python: маленькие int и некоторые строки уже разделяются).

Итог

  • Bridge разделяет две независимые оси изменения, связывая их композицией.
  • Flyweight разделяет общее неизменяемое состояние, экономя память.
  • Оба — про управление сложностью при росте числа вариантов/объектов.
Проверьте себя
1. Какую проблему решает Bridge?
AЕдинственный экземпляр
BКомбинаторный взрыв классов по двум независимым осям
CМедленный доступ к объекту
DКопирование объектов
2. Что Flyweight выносит в разделяемый объект?
AУникальное состояние каждого объекта
BМетоды класса
CОбщее, неизменяемое (внутреннее) состояние
DКоординаты
3. Главная выгода Flyweight?
AСкорость сети
BПростота интерфейса
CКонтроль доступа
DЭкономия памяти при огромном числе похожих объектов