Lapa Knowledge Base
Структурные паттерны

Bridge

Паттерн Bridge

Bridge — это структурный паттерн проектирования, который разделяет абстракцию и реализацию так, чтобы они могли изменяться независимо.

Проблема

Представьте, что у вас есть класс Shape с подклассами Circle и Square. Вы хотите расширить эту иерархию цветами, поэтому планируете создать подклассы RedCircle и BlueSquare.

Но поскольку у вас уже есть два измерения (формы и цвета), вы получаете комбинаторный взрыв подклассов. Количество классов растет экспоненциально в каждом новом измерении.

Добавление нового типа формы требует создания подклассов для каждого цвета. И наоборот, добавление нового цвета требует создания подклассов для каждой формы.

Решение

Паттерн Bridge решает эту проблему, разделяя абстракцию и реализацию на отдельные иерархии классов. Абстракция содержит ссылку на объект реализации и делегирует ему выполнение.


Структура

Abstraction
├── implementation: Implementation
└── operation(): string

RefinedAbstraction
└── operation(): string

Implementation
└── operationImplementation(): string

ConcreteImplementationA
└── operationImplementation(): string

ConcreteImplementationB
└── operationImplementation(): string

Пример реализации

JavaScript

// Реализация (Implementor)
class Device {
    isEnabled() {
        throw new Error('Метод isEnabled должен быть переопределен');
    }
    
    enable() {
        throw new Error('Метод enable должен быть переопределен');
    }
    
    disable() {
        throw new Error('Метод disable должен быть переопределен');
    }
    
    getVolume() {
        throw new Error('Метод getVolume должен быть переопределен');
    }
    
    setVolume(percent) {
        throw new Error('Метод setVolume должен быть переопределен');
    }
    
    getChannel() {
        throw new Error('Метод getChannel должен быть переопределен');
    }
    
    setChannel(channel) {
        throw new Error('Метод setChannel должен быть переопределен');
    }
}

// Конкретные реализации
class TV extends Device {
    constructor() {
        super();
        this.enabled = false;
        this.volume = 30;
        this.channel = 1;
    }
    
    isEnabled() {
        return this.enabled;
    }
    
    enable() {
        this.enabled = true;
        console.log('Телевизор включен');
    }
    
    disable() {
        this.enabled = false;
        console.log('Телевизор выключен');
    }
    
    getVolume() {
        return this.volume;
    }
    
    setVolume(percent) {
        this.volume = percent;
        console.log(`Громкость телевизора установлена на ${percent}%`);
    }
    
    getChannel() {
        return this.channel;
    }
    
    setChannel(channel) {
        this.channel = channel;
        console.log(`Канал телевизора установлен на ${channel}`);
    }
}

class Radio extends Device {
    constructor() {
        super();
        this.enabled = false;
        this.volume = 20;
        this.channel = 1;
    }
    
    isEnabled() {
        return this.enabled;
    }
    
    enable() {
        this.enabled = true;
        console.log('Радио включено');
    }
    
    disable() {
        this.enabled = false;
        console.log('Радио выключено');
    }
    
    getVolume() {
        return this.volume;
    }
    
    setVolume(percent) {
        this.volume = percent;
        console.log(`Громкость радио установлена на ${percent}%`);
    }
    
    getChannel() {
        return this.channel;
    }
    
    setChannel(channel) {
        this.channel = channel;
        console.log(`Частота радио установлена на ${channel}`);
    }
}

// Абстракция (Abstraction)
class RemoteControl {
    constructor(device) {
        this.device = device;
    }
    
    togglePower() {
        if (this.device.isEnabled()) {
            this.device.disable();
        } else {
            this.device.enable();
        }
    }
    
    volumeDown() {
        this.device.setVolume(this.device.getVolume() - 10);
    }
    
    volumeUp() {
        this.device.setVolume(this.device.getVolume() + 10);
    }
    
    channelDown() {
        this.device.setChannel(this.device.getChannel() - 1);
    }
    
    channelUp() {
        this.device.setChannel(this.device.getChannel() + 1);
    }
}

// Расширенная абстракция (Refined Abstraction)
class AdvancedRemoteControl extends RemoteControl {
    constructor(device) {
        super(device);
    }
    
    mute() {
        this.device.setVolume(0);
        console.log('Устройство приглушено');
    }
    
    setVolume(volume) {
        this.device.setVolume(volume);
    }
    
    setChannel(channel) {
        this.device.setChannel(channel);
    }
}

// Использование
const tv = new TV();
const radio = new Radio();

const tvRemote = new RemoteControl(tv);
const radioRemote = new AdvancedRemoteControl(radio);

console.log('=== Управление телевизором ===');
tvRemote.togglePower();
tvRemote.volumeUp();
tvRemote.channelUp();

console.log('\n=== Управление радио ===');
radioRemote.togglePower();
radioRemote.setVolume(50);
radioRemote.setChannel(5);
radioRemote.mute();

TypeScript с интерфейсами

// Интерфейс реализации
interface DrawingAPI {
    drawCircle(x: number, y: number, radius: number): void;
    drawRectangle(x: number, y: number, width: number, height: number): void;
}

// Конкретные реализации
class VectorDrawingAPI implements DrawingAPI {
    drawCircle(x: number, y: number, radius: number): void {
        console.log(`Векторный круг: центр(${x}, ${y}), радиус ${radius}`);
    }
    
    drawRectangle(x: number, y: number, width: number, height: number): void {
        console.log(`Векторный прямоугольник: позиция(${x}, ${y}), размер ${width}x${height}`);
    }
}

class RasterDrawingAPI implements DrawingAPI {
    drawCircle(x: number, y: number, radius: number): void {
        console.log(`Растровый круг: центр(${x}, ${y}), радиус ${radius}`);
    }
    
    drawRectangle(x: number, y: number, width: number, height: number): void {
        console.log(`Растровый прямоугольник: позиция(${x}, ${y}), размер ${width}x${height}`);
    }
}

// Абстракция
abstract class Shape {
    protected drawingAPI: DrawingAPI;
    
    constructor(drawingAPI: DrawingAPI) {
        this.drawingAPI = drawingAPI;
    }
    
    abstract draw(): void;
    abstract resizeByPercentage(percentage: number): void;
}

// Конкретные абстракции
class Circle extends Shape {
    private x: number;
    private y: number;
    private radius: number;
    
    constructor(x: number, y: number, radius: number, drawingAPI: DrawingAPI) {
        super(drawingAPI);
        this.x = x;
        this.y = y;
        this.radius = radius;
    }
    
    draw(): void {
        this.drawingAPI.drawCircle(this.x, this.y, this.radius);
    }
    
    resizeByPercentage(percentage: number): void {
        this.radius *= percentage / 100;
    }
}

class Rectangle extends Shape {
    private x: number;
    private y: number;
    private width: number;
    private height: number;
    
    constructor(x: number, y: number, width: number, height: number, drawingAPI: DrawingAPI) {
        super(drawingAPI);
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }
    
    draw(): void {
        this.drawingAPI.drawRectangle(this.x, this.y, this.width, this.height);
    }
    
    resizeByPercentage(percentage: number): void {
        this.width *= percentage / 100;
        this.height *= percentage / 100;
    }
}

// Использование
const vectorAPI = new VectorDrawingAPI();
const rasterAPI = new RasterDrawingAPI();

const vectorCircle = new Circle(10, 10, 5, vectorAPI);
const rasterCircle = new Circle(20, 20, 5, rasterAPI);

const vectorRectangle = new Rectangle(0, 0, 10, 5, vectorAPI);
const rasterRectangle = new Rectangle(15, 15, 10, 5, rasterAPI);

console.log('=== Векторная графика ===');
vectorCircle.draw();
vectorRectangle.draw();

console.log('\n=== Растровая графика ===');
rasterCircle.draw();
rasterRectangle.draw();

console.log('\n=== Изменение размера ===');
vectorCircle.resizeByPercentage(150);
vectorCircle.draw();

Когда использовать

Используйте Bridge, когда:

  • Нужно избежать постоянной привязки абстракции к реализации
  • Абстракции и реализации должны расширяться независимо
  • Изменения в реализации не должны влиять на клиентский код
  • Нужно скрыть детали реализации от клиентов

Преимущества

  • Разделяет абстракцию и реализацию
  • Улучшает расширяемость
  • Скрывает детали реализации от клиентов
  • Принцип открытости/закрытости: можно добавлять новые абстракции и реализации

Недостатки

  • Усложняет код из-за введения дополнительных классов
  • Может быть избыточным для простых случаев

Отличия от других паттернов

  • Adapter — делает несовместимые интерфейсы совместимыми
  • Decorator — добавляет новую функциональность объекту
  • Facade — предоставляет упрощенный интерфейс к подсистеме

Реальный пример

// Bridge для работы с разными базами данных
class DatabaseConnection {
    connect() {
        throw new Error('Метод connect должен быть переопределен');
    }
    
    query(sql) {
        throw new Error('Метод query должен быть переопределен');
    }
    
    disconnect() {
        throw new Error('Метод disconnect должен быть переопределен');
    }
}

class MySQLConnection extends DatabaseConnection {
    connect() {
        console.log('Подключение к MySQL');
    }
    
    query(sql) {
        console.log(`MySQL запрос: ${sql}`);
        return { rows: [], affected: 0 };
    }
    
    disconnect() {
        console.log('Отключение от MySQL');
    }
}

class PostgreSQLConnection extends DatabaseConnection {
    connect() {
        console.log('Подключение к PostgreSQL');
    }
    
    query(sql) {
        console.log(`PostgreSQL запрос: ${sql}`);
        return { rows: [], affected: 0 };
    }
    
    disconnect() {
        console.log('Отключение от PostgreSQL');
    }
}

// Абстракция для работы с пользователями
class UserRepository {
    constructor(database) {
        this.database = database;
    }
    
    findById(id) {
        return this.database.query(`SELECT * FROM users WHERE id = ${id}`);
    }
    
    save(user) {
        return this.database.query(`INSERT INTO users (name, email) VALUES ('${user.name}', '${user.email}')`);
    }
}

// Использование
const mysqlRepo = new UserRepository(new MySQLConnection());
const postgresRepo = new UserRepository(new PostgreSQLConnection());

mysqlRepo.findById(1);
postgresRepo.save({ name: 'John', email: 'john@example.com' });

Bridge — это мост между абстракцией и реализацией!

Copyright © 2026