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Контроль доступа
Поддержать проект