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 — это мост между абстракцией и реализацией!