← Назад к вопросам
Для чего нужен шаблон проектирования мост?
2.7 Senior🔥 21 комментариев
#SOLID и паттерны проектирования
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Шаблон проектирования мост (Bridge Pattern)
Bridge Pattern (Мост) — это структурный паттерн проектирования, который разделяет абстракцию от её реализации так, чтобы они могли изменяться независимо друг от друга. Мост используется для избежания декартова произведения при комбинировании двух ортогональных иерархий классов.
Проблема: Декартово произведение иерархий
// ПЛОХО: Взрывной рост классов
// Нам нужны разные формы (Shape) и разные рисовальщики (Renderer)
public abstract class Shape {}
public class CircleUsingCanvas extends Shape {} // Circle + Canvas
public class CircleUsingOpenGL extends Shape {} // Circle + OpenGL
public class SquareUsingCanvas extends Shape {} // Square + Canvas
public class SquareUsingOpenGL extends Shape {} // Square + OpenGL
public class TriangleUsingCanvas extends Shape {} // Triangle + Canvas
public class TriangleUsingOpenGL extends Shape {} // Triangle + OpenGL
// и так далее...
// Если добавить DirectX рисовальщик:
// CircleUsingDirectX, SquareUsingDirectX, TriangleUsingDirectX...
// Проблема: N форм × M рисовальщиков = N×M классов!
// Это экспоненциальный рост сложности
Решение: Bridge Pattern
// Шаг 1: Создаем абстракцию для рисовальщика (реализация)
public interface Renderer {
void renderCircle(double radius);
void renderSquare(double side);
void renderTriangle(double side);
}
// Конкретные рисовальщики
public class CanvasRenderer implements Renderer {
@Override
public void renderCircle(double radius) {
System.out.println("Рисуем окружность на Canvas с радиусом " + radius);
}
@Override
public void renderSquare(double side) {
System.out.println("Рисуем квадрат на Canvas со стороной " + side);
}
@Override
public void renderTriangle(double side) {
System.out.println("Рисуем треугольник на Canvas со стороной " + side);
}
}
public class OpenGLRenderer implements Renderer {
@Override
public void renderCircle(double radius) {
System.out.println("Рисуем окружность в OpenGL с радиусом " + radius);
}
@Override
public void renderSquare(double side) {
System.out.println("Рисуем квадрат в OpenGL со стороной " + side);
}
@Override
public void renderTriangle(double side) {
System.out.println("Рисуем треугольник в OpenGL со стороной " + side);
}
}
public class DirectXRenderer implements Renderer {
@Override
public void renderCircle(double radius) {
System.out.println("Рисуем окружность в DirectX с радиусом " + radius);
}
@Override
public void renderSquare(double side) {
System.out.println("Рисуем квадрат в DirectX со стороной " + side);
}
@Override
public void renderTriangle(double side) {
System.out.println("Рисуем треугольник в DirectX со стороной " + side);
}
}
// Шаг 2: Создаем абстрактные формы, которые используют рисовальщик
public abstract class Shape {
protected Renderer renderer; // МОСТ: ссылка на рисовальщик
public Shape(Renderer renderer) {
this.renderer = renderer;
}
public abstract void draw();
}
public class Circle extends Shape {
private double radius;
public Circle(double radius, Renderer renderer) {
super(renderer);
this.radius = radius;
}
@Override
public void draw() {
renderer.renderCircle(radius); // Делегируем рисовальщику
}
}
public class Square extends Shape {
private double side;
public Square(double side, Renderer renderer) {
super(renderer);
this.side = side;
}
@Override
public void draw() {
renderer.renderSquare(side);
}
}
public class Triangle extends Shape {
private double side;
public Triangle(double side, Renderer renderer) {
super(renderer);
this.side = side;
}
@Override
public void draw() {
renderer.renderTriangle(side);
}
}
// Шаг 3: Использование
public class BridgePatternExample {
public static void main(String[] args) {
// Создаем рисовальщики
Renderer canvasRenderer = new CanvasRenderer();
Renderer openGLRenderer = new OpenGLRenderer();
Renderer directXRenderer = new DirectXRenderer();
// Создаем формы с разными рисовальщиками
Shape circle1 = new Circle(5.0, canvasRenderer);
Shape circle2 = new Circle(5.0, openGLRenderer);
Shape circle3 = new Circle(5.0, directXRenderer);
Shape square1 = new Square(10.0, canvasRenderer);
Shape square2 = new Square(10.0, openGLRenderer);
Shape triangle1 = new Triangle(7.0, canvasRenderer);
// Рисуем
circle1.draw(); // Рисуем окружность на Canvas
circle2.draw(); // Рисуем окружность в OpenGL
circle3.draw(); // Рисуем окружность в DirectX
square1.draw(); // Рисуем квадрат на Canvas
square2.draw(); // Рисуем квадрат в OpenGL
triangle1.draw(); // Рисуем треугольник на Canvas
}
}
// Вывод:
// Рисуем окружность на Canvas с радиусом 5.0
// Рисуем окружность в OpenGL с радиусом 5.0
// Рисуем окружность в DirectX с радиусом 5.0
// Рисуем квадрат на Canvas со стороной 10.0
// Рисуем квадрат в OpenGL со стороной 10.0
// Рисуем треугольник на Canvas со стороной 7.0
Преимущества Bridge Pattern
1. Избегаем взрыва классов
// БЕЗ Bridge: 3 формы × 3 рисовальщика = 9 классов
// С Bridge: 3 класса Shape + 3 класса Renderer = 6 классов
// При добавлении новой формы (Polygon):
// БЕЗ Bridge: добавляем 3 класса (PolygonCanvas, PolygonOpenGL, PolygonDirectX)
// С Bridge: добавляем 1 класс (Polygon)
// При добавлении нового рисовальщика (Vulkan):
// БЕЗ Bridge: добавляем 3 класса (CircleVulkan, SquareVulkan, TriangleVulkan)
// С Bridge: добавляем 1 класс (VulkanRenderer)
2. Независимое развитие абстракций
// Команда 1 разрабатывает Shape иерархию
// Команда 2 разрабатывает Renderer интерфейсы
// Они могут работать независимо, т.к. связаны только через интерфейс
// Даже если Renderer еще не готов, можно использовать mock
Renderer mockRenderer = new Renderer() {
@Override
public void renderCircle(double radius) {
System.out.println("Mock: рисуем окружность");
}
@Override
public void renderSquare(double side) {
System.out.println("Mock: рисуем квадрат");
}
@Override
public void renderTriangle(double side) {
System.out.println("Mock: рисуем треугольник");
}
};
Shape circle = new Circle(5.0, mockRenderer);
circle.draw(); // Работает с mock
3. Динамическая подмена реализации
public class DrawingApp {
private Shape[] shapes;
private Renderer currentRenderer;
public void switchRenderer(Renderer newRenderer) {
this.currentRenderer = newRenderer;
// Обновляем рисовальщик для всех форм
for (Shape shape : shapes) {
shape.renderer = newRenderer;
}
}
public void redraw() {
for (Shape shape : shapes) {
shape.draw();
}
}
}
// Использование
DrawingApp app = new DrawingApp();
// Пользователь переключается на другой режим рисования
app.switchRenderer(new OpenGLRenderer()); // Перерисовка в OpenGL
app.redraw();
Пример из реальной жизни: Логирование
// Интерфейс вывода (реализация)
public interface LogOutput {
void write(String message);
}
// Конкретные выходы
public class FileLogOutput implements LogOutput {
private String filename;
public FileLogOutput(String filename) {
this.filename = filename;
}
@Override
public void write(String message) {
// Пишем в файл
System.out.println("Пишем в файл " + filename + ": " + message);
}
}
public class ConsoleLogOutput implements LogOutput {
@Override
public void write(String message) {
System.out.println("Консоль: " + message);
}
}
public class DatabaseLogOutput implements LogOutput {
@Override
public void write(String message) {
System.out.println("БД: " + message);
}
}
// Абстрактный логгер (абстракция)
public abstract class Logger {
protected LogOutput output; // МОСТ
public Logger(LogOutput output) {
this.output = output;
}
public abstract void log(String message);
}
// Конкретные логгеры
public class ErrorLogger extends Logger {
public ErrorLogger(LogOutput output) {
super(output);
}
@Override
public void log(String message) {
output.write("[ERROR] " + message);
}
}
public class InfoLogger extends Logger {
public InfoLogger(LogOutput output) {
super(output);
}
@Override
public void log(String message) {
output.write("[INFO] " + message);
}
}
public class DebugLogger extends Logger {
public DebugLogger(LogOutput output) {
super(output);
}
@Override
public void log(String message) {
output.write("[DEBUG] " + message);
}
}
// Использование
public class BridgeLoggingExample {
public static void main(String[] args) {
LogOutput fileOutput = new FileLogOutput("app.log");
LogOutput consoleOutput = new ConsoleLogOutput();
LogOutput dbOutput = new DatabaseLogOutput();
// Разные логгеры с разными выходами
Logger errorLogger = new ErrorLogger(fileOutput);
Logger infoLogger = new InfoLogger(consoleOutput);
Logger debugLogger = new DebugLogger(dbOutput);
errorLogger.log("Критическая ошибка"); // В файл
infoLogger.log("Приложение запущено"); // В консоль
debugLogger.log("Отладочная информация"); // В БД
}
}
Bridge vs Adapter vs Decorator
Bridge: Разделяет абстракцию от реализации
Цель: изменения в обеих независимы
Используется: на этапе проектирования
Adapter: Адаптирует несовместимые интерфейсы
Цель: заставить работать несовместимые классы
Используется: когда уже есть существующие классы
Decorator: Добавляет функциональность к объекту
Цель: расширить поведение динамически
Используется: для добавления ответственности
Когда использовать Bridge
✅ ИСПОЛЬЗУЙ Bridge когда:
- Есть две независимые иерархии классов (абстракция + реализация)
- Нужно избежать комбинаторного взрыва классов
- Реализация может меняться в runtime
- Разные команды разрабатывают абстракцию и реализацию
- Нужна открытость для расширения обеих иерархий
❌ НЕ ИСПОЛЬЗУЙ Bridge когда:
- Всего несколько комбинаций (наследование достаточно)
- Иерархия проста и не будет расширяться
- Нет второй независимой иерархии
Best Practices
- Определи две ортогональные иерархии перед использованием Bridge
- Используй композицию вместо наследования (contains-a vs is-a)
- Инъецируй реализацию через конструктор для гибкости
- Документируй разделение между абстракцией и реализацией
- Тестируй независимо абстракции и реализации
Bridge Pattern — это мощный инструмент для управления сложностью в больших системах, где есть две независимые иерархии классов, которые должны работать вместе гибко и масштабируемо.