Коротка відповідь
middleBridge — це паттерн, що розділяє абстракцію і реалізацію на окремі й незалежні hierarchies, дозволяючи змінювати їх у будь‑який момент. У TS це реалізується через інтерфейс `Implementor` і клас `Abstraction`, який має поле типу `Implementor`. Приклад: `class RemoteControl { constructor(private impl: Display) {} setBrightness(b:number){this.impl.setLevel(b)} }`
Повне пояснення
1. Що це і навіщо
Bridge (Мост) – структурний паттерн, що розділяє абстракцію і реалізацію на окремі й незалежні hierarchies, щоб обидва могли розвиватися без взаємного впливу. У GoF це паттерн №12, корисний, коли потрібно комбінувати різні варіанти абстракції з різними реалізаціями, наприклад, UI‑компоненти для Web і Desktop.
2. Принцип роботи / алгоритм
Паттерн складається з двох частин: Abstraction (абстракція) і Implementor (реалізація). Абстракція містить посилання на Implementor і викликає його методи. Реалізація визначає інтерфейс, який абстракція використовує. Діаграма: Abstraction → Implementor – один‑на‑один, але обидва можуть мати власні підкласи.
3. Реалізація (JS / TS)
- Базова реалізація
interface Display {
setLevel(level: number): void;
}
class LEDDisplay implements Display { setLevel(l:number){ console.log(`LED: ${l}`); } }
class LCDDisplay implements Display { setLevel(l:number){ console.log(`LCD: ${l}`); } }
class RemoteControl {
constructor(private impl: Display){}
setBrightness(b:number){ this.impl.setLevel(b); }
}
const rc = new RemoteControl(new LEDDisplay());
rc.setBrightness(5);
- Реалістичний приклад – графічна бібліотека, де
Shape(абстракція) використовує різні рендерери: Canvas, SVG.
interface Renderer { renderCircle(radius:number): void; }
class CanvasRenderer implements Renderer{ /* ... */ }
class SVGRenderer implements Renderer{ /* ... */ }
abstract class Shape { constructor(protected renderer: Renderer){} abstract draw(): void; }
class Circle extends Shape{ constructor(renderer:Renderer, private radius:number){ super(renderer); } draw(){ this.renderer.renderCircle(this.radius); } }
- Спрощений для новачка – простий калькулятор, що підтримує різні мови виводу.
interface Output { print(msg:string): void; }
class ConsoleOutput implements Output{ print(m){ console.log(m); } }
class FileOutput implements Output{ /* ... */ }
class Calculator { constructor(private out: Output){} add(a:number,b:number){ this.out.print(`${a}+${b}=${a+b}`); } }
4. Тестування (Jest або Vitest)
import { RemoteControl, LEDDisplay } from './bridge';
test('sets brightness via implementor', () => {
const mock = jest.fn();
class MockDisplay implements Display{ setLevel(l:number){ mock(l); } }
const rc = new RemoteControl(new MockDisplay());
rc.setBrightness(7);
expect(mock).toHaveBeenCalledWith(7);
});
5. Проблеми та edge cases
- Неправильне використання ролей – коли абстракція викликає методи, яких не має Implementor.
- Зайва абстракція – створення мосту, коли варіантів реалізації мало.
- Cyclical dependencies – абстракція і реалізація залежать одна від одної через глобальні змінні.
- Плутанина між паттернами – замість Bridge використати Strategy або Adapter.
- Performance‑пастки – надмірна кількість об’єктів Bridge може збільшити накладні витрати на пам’ять.
6. Оптимізація та продуктивність
Bridge зменшує кількість класів, але збільшує кількість об’єктів. Якщо реалізації мають великий розмір, використовуйте фабрики для їх створення. Пам’ять: кожен об’єкт містить посилання на Implementor, що незначно збільшує footprint.
7. Без JS / архітектурні альтернативи
У чистій HTML/CSS можна розділити структуру (абстракція) і стиль (реалізація) через CSS‑класи. У React можна розділити компонент і його рендеринг за допомогою HOC або Render Props, що є практично Bridge‑подібним.
8. Cheatsheet + follow-up
- Bridge: абстракція ↔ реалізація, незалежні hierarchies.
- Питання: Як Bridge допомагає уникнути «прямого» наслідування? Які переваги має розділення абстракції і реалізації?