Перейти к содержанию

Bridge (Мост)

Категория: структурный паттерн.

Проблема

Класс нужно расширять в двух независимых измерениях, например по типу фигуры и способу её отрисовки. Если выразить обе оси наследованием, понадобятся классы для каждой комбинации (RasterCircle, VectorCircle, RasterSquare, ...), и их число будет перемножаться.

Решение

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

Термины «абстракция» и «реализация» здесь обозначают роли паттерна, а не обязательно abstract class и interface языка C#.

Структура

  • Abstraction - высокоуровневая логика, использующая объект Implementor.
  • RefinedAbstraction - дополнительные варианты высокоуровневого поведения.
  • Implementor - контракт низкоуровневых операций.
  • ConcreteImplementor - платформенная или технологическая реализация этих операций.

Когда применять

  • Класс изменяется по двум независимым причинам, и число комбинаций подклассов растёт.
  • Нужно отделить предметную логику от платформы, драйвера, хранилища или способа визуализации.
  • Реализацию требуется выбирать или заменять во время выполнения.

Плюсы

  • Две оси расширения развиваются независимо вместо комбинаторного роста подклассов.
  • Платформенные детали изолированы от клиентского кода.
  • Реализацию можно подменять через композицию и удобно тестировать отдельно.

Минусы

  • Для простой стабильной модели добавляет лишний уровень косвенности.
  • Границу между абстракцией и реализацией нужно выбрать осмысленно; неудачное разделение только размазывает ответственность.

Отличие от Adapter и Strategy

Adapter обычно подключают после появления несовместимых интерфейсов, чтобы заставить существующие классы работать вместе. Bridge закладывают заранее, чтобы две части системы развивались независимо. Strategy похож технически - тоже подменяет объект поведения, - но решает поведенческую задачу выбора алгоритма, тогда как Bridge прежде всего разделяет структуру на две иерархии.

Пример в .NET

  • Отделение провайдера хранения от репозитория предметной области.
  • Абстракции ADO.NET (DbConnection, DbCommand) и конкретные провайдеры баз данных близки к идее Bridge: высокоуровневый клиентский код работает с общим API, а провайдер выполняет платформенную работу.

Источник для сверки структуры: Refactoring.Guru - Bridge.

Пример реализации на C#

Bridge.cs
using System;

namespace DesignPatterns.Structural.Bridge
{
    // Реализация: низкоуровневые операции конкретной платформы вывода.
    public interface IRenderer
    {
        string RenderCircle(double radius);
    }

    public sealed class VectorRenderer : IRenderer
    {
        public string RenderCircle(double radius) =>
            $"Векторный круг радиуса {radius}";
    }

    public sealed class RasterRenderer : IRenderer
    {
        public string RenderCircle(double radius) =>
            $"Растровый круг радиуса {radius} из пикселей";
    }

    // Абстракция развивается независимо от иерархии реализаций.
    public abstract class Shape
    {
        protected Shape(IRenderer renderer) => Renderer = renderer;

        protected IRenderer Renderer { get; }
        public abstract string Draw();
    }

    public class Circle : Shape
    {
        public Circle(IRenderer renderer, double radius) : base(renderer) =>
            Radius = radius;

        public double Radius { get; }
        public override string Draw() => Renderer.RenderCircle(Radius);
    }

    // Расширенная абстракция не требует новых RasterCircle/VectorCircle.
    public sealed class LabeledCircle : Circle
    {
        public LabeledCircle(IRenderer renderer, double radius, string label)
            : base(renderer, radius) => Label = label;

        public string Label { get; }
        public override string Draw() => $"{Label}: {base.Draw()}";
    }

    public static class Demo
    {
        public static void Run()
        {
            Shape vector = new Circle(new VectorRenderer(), 10);
            Shape rasterWithLabel = new LabeledCircle(new RasterRenderer(), 5, "Аватар");

            Console.WriteLine(vector.Draw());
            Console.WriteLine(rasterWithLabel.Draw());
        }
    }
}

Открыть Bridge.cs отдельно Скачать Bridge.cs