Мост (Bridge) — структурный шаблон проектирования, который позволяет отделить абстракцию от реализации таким образом, чтобы и абстракцию, и реализацию можно было изменять независимо друг от друга.
Даже если мы отделим абстракцию от конкретных реализаций, то у нас все равно все наследуемые классы будут жестко привязаны к интерфейсу, определяемому в базовом абстрактном классе. Для преодоления жестких связей и служит паттерн Мост.
Когда использовать данный паттерн?
- Когда надо избежать постоянной привязки абстракции к реализации
- Когда наряду с реализацией надо изменять и абстракцию независимо друг от друга. То есть изменения в абстракции не должно привести к изменениям в реализации
Общая реализация паттерна состоит в объявлении классов абстракций и классов реализаций в отдельных параллельных иерархиях классов.
Представление паттерна с помощью UML:

Связь агрегации между классами Abstraction и Implementor фактически и представляет некоторый мост между двумя параллельными иерархиями классов. Собственно поэтому паттерн получил название Мост.
Формальное описание паттерна на языке C#:
class Client
{
static void Main()
{
Abstraction abstraction;
abstraction = new RefinedAbstraction(new ConcreteImplementorA());
abstraction.Operation();
abstraction.Implementor=new ConcreteImplementorB();
abstraction.Operation();
}
}
abstract class Abstraction
{
protected Implementor implementor;
public Implementor Implementor
{
set { implementor = value; }
}
public Abstraction(Implementor imp)
{
implementor = imp;
}
public virtual void Operation()
{
implementor.OperationImp();
}
}
abstract class Implementor
{
public abstract void OperationImp();
}
class RefinedAbstraction : Abstraction
{
public RefinedAbstraction(Implementor imp)
: base(imp)
{}
public override void Operation()
{
}
}
class ConcreteImplementorA : Implementor
{
public override void OperationImp()
{
}
}
class ConcreteImplementorB : Implementor
{
public override void OperationImp()
{
}
}
Участники
- Abstraction: определяет базовый интерфейс и хранит ссылку на объект Implementor. Выполнение операций в Abstraction делегируется методам объекта Implementor
- RefinedAbstraction: уточненная абстракция, наследуется от Abstraction и расширяет унаследованный интерфейс
- Implementor: определяет базовый интерфейс для конкретных реализаций. Как правило, Implementor определяет только примитивные операции. Более сложные операции, которые базируются на примитивных, определяются в Abstraction
- ConcreteImplementorA и ConcreteImplementorB: конкретные реализации, которые унаследованы от Implementor
- Client: использует объекты Abstraction
Теперь рассмотрим реальное применение. Существует множество программистов, но одни являются фрилансерами, кто-то работает в компании инженером, кто-то совмещает работу в компании и фриланс. Таким образом, вырисовывается иерархия различных классов программистов. Но эти программисты могут работать с различными языками и технологиями. И в зависимости от выбранного языка деятельность программиста будет отличаться. Для решения описания данной задачи в программе на C# используем паттерн Мост:
class Program
{
static void Main(string[] args)
{
// создаем нового программиста, он работает с с++
Programmer freelancer = new FreelanceProgrammer(new CPPLanguage());
freelancer.DoWork();
freelancer.EarnMoney();
// пришел новый заказ, но теперь нужен c#
freelancer.Language = new CSharpLanguage();
freelancer.DoWork();
freelancer.EarnMoney();
Console.Read();
}
}
interface ILanguage
{
void Build();
void Execute();
}
class CPPLanguage : ILanguage
{
public void Build()
{
Console.WriteLine("С помощью компилятора C++ компилируем программу в бинарный код");
}
public void Execute()
{
Console.WriteLine("Запускаем исполняемый файл программы");
}
}
class CSharpLanguage : ILanguage
{
public void Build()
{
Console.WriteLine("С помощью компилятора Roslyn компилируем исходный код в файл exe");
}
public void Execute()
{
Console.WriteLine("JIT компилирует программу бинарный код");
Console.WriteLine("CLR выполняет скомпилированный бинарный код");
}
}
abstract class Programmer
{
protected ILanguage language;
public ILanguage Language
{
set { language = value; }
}
public Programmer (ILanguage lang)
{
language = lang;
}
public virtual void DoWork()
{
language.Build();
language.Execute();
}
public abstract void EarnMoney();
}
class FreelanceProgrammer : Programmer
{
public FreelanceProgrammer(ILanguage lang) : base(lang)
{
}
public override void EarnMoney()
{
Console.WriteLine("Получаем оплату за выполненный заказ");
}
}
class CorporateProgrammer : Programmer
{
public CorporateProgrammer(ILanguage lang)
: base(lang)
{
}
public override void EarnMoney()
{
Console.WriteLine("Получаем в конце месяца зарплату");
}
}
В роли Abstraction выступает класс Programmer, а в роли Implementor — интерфейс ILanguage, который представляет язык программирования. В методе DoWork()
класса Programmer вызываются методы объекта ILanguage.
Языки CPPLanguage и CSharpLanguage определяют конкретные реализации, а классы FreelanceProgrammer и CorporateProgrammer представляют уточненные абстракции.
Таким образом, благодаря применению паттерна реализация отделяется от абстракции. Мы можем развивать независимо две параллельные иерархии. Устраняются зависимости между реализацией и абстракцией во время компиляции, и мы можем менять конкретную реализацию во время выполнения.