Когда вы используете шаблон моста? Чем он отличается от шаблона адаптера?

154

Кто-нибудь когда-либо использовал паттерн моста в реальном приложении? Если да, то как ты это использовал? Это я, или это просто паттерн адаптера с небольшим внедрением зависимостей в микс? Это действительно заслуживает своего собственного образца?

Чарльз Грэм
источник
Пожалуйста, рассмотрите возможность принятия другого ответа на этот вопрос. В настоящее время принятый ответ является неправильным и бесполезным. Новые ответы намного лучше.
jaco0646
Книга GoF отвечает на этот вопрос напрямую.
jaco0646

Ответы:

76

Классический пример шаблона Bridge используется в определении фигур в среде пользовательского интерфейса (см. Статью Wikipedia по шаблону Bridge ). Схема моста представляет собой композит из шаблона и стратегии шаблонов.

Это общий вид некоторых аспектов шаблона адаптера в шаблоне моста. Однако, чтобы процитировать из этой статьи :

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

ши
источник
1
Мост не имеет ничего общего с шаблоном или стратегией. Мост является структурным рисунком. Шаблон и стратегия - это поведенческие паттерны.
jaco0646
249

Есть комбинация ответов Федерико и Джона .

Когда:

                   ----Shape---
                  /            \
         Rectangle              Circle
        /         \            /      \
BlueRectangle  RedRectangle BlueCircle RedCircle

Рефакторинг для:

          ----Shape---                        Color
         /            \                       /   \
Rectangle(Color)   Circle(Color)           Blue   Red
Антон Щастный
источник
6
Зачем тебе наследование цветов?
Vainolo
10
@vainolo, потому что цвет - это интерфейс, а синий, красный - конкретные цвета
Weltschmerz
3
Это просто рефакторинг. Назначение шаблона моста: «Отделите абстракцию от ее реализации, чтобы они могли различаться независимо». Где абстракция и где реализация здесь?
Clapas
1
Разве Rectangle (Color) не является более абстрактным, чем BlueRectangle?
Антон Щастный
2
@clapas, Abstraction - это свойство Shape.color, поэтому класс Red и класс Blue являются реализацией, а интерфейс Color - мостом.
Реко
230

Паттерн «Мост» - это применение старого совета «предпочитайте композицию наследованию». Это становится удобным, когда вы должны создавать подклассы разного времени так, чтобы они были ортогональны друг другу. Скажем, вы должны реализовать иерархию цветных фигур. Вы бы не создали подкласс Shape с Rectangle и Circle, а затем создали бы подкласс Rectangle с RedRectangle, BlueRectangle и GreenRectangle и то же самое для Circle, не так ли? Вы бы предпочли сказать, что у каждой фигуры есть Цвет и реализовать иерархию цветов, и это - Образец Моста. Ну, я бы не реализовал "иерархию цветов", но вы поняли ...

Федерико А. Рампони
источник
1
См. Также диаграмму Антона Щастного ниже для графической иллюстрации этого объяснения.
NomadeNumerique
2
Я не думаю, что цвет является хорошим примером для иерархии реализации, это довольно запутанно. Есть хороший пример шаблона Bridge в «
Шаблонах
215

Когда:

        A
     /     \
    Aa      Ab
   / \     /  \
 Aa1 Aa2  Ab1 Ab2

Рефакторинг для:

     A         N
  /     \     / \
Aa(N) Ab(N)  1   2
Джон Сонмез
источник
3
Я думаю, что это очень прагматичный подход к шаблонам: 1) описать неоптимальный простой дизайн 2) дизайн / код рефакторинга для лучшего факторинга
Алексей
1
Используйте математическую концепцию, чтобы объяснить шаблон проектирования моста. Очень заинтересован.
Цзян Хуан,
1
Это просто рефакторинг. Назначение шаблона моста: «Отделите абстракцию от ее реализации, чтобы они могли различаться независимо». Где абстракция и где реализация здесь?
Clapas
Джон прекрасно пишет об этом в своем блоге . Нашел, что это было хорошим чтением для обзора высокого уровня.
Вайбхав Бхалла
29

Адаптер и Мост, безусловно, связаны, и различие тонкое. Вполне вероятно, что некоторые люди, которые думают, что используют один из этих шаблонов, фактически используют другой шаблон.

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

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

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

Таким образом, с точки зрения кода, эти две модели очень похожи. В бизнесе они разные.

Смотрите также http://c2.com/cgi/wiki?BridgePattern

Билл Карвин
источник
Привет, Билл. Я не понимаю, почему мы обязательно используем паттерн Bridge в драйверах устройств. Я имею в виду, что мы можем легко делегировать реализацию (чтения, записи, поиска и т. Д.) Правильному классу с помощью полиморфизма, верно? Или с посетителем возможно? Почему это должен быть мост? Заранее спасибо.
выход
1
@zgulser, да, вы используете полиморфизм. Шаблон Bridge описывает один вид использования подклассов для отделения реализации от абстракции.
Билл Карвин
Вы имели в виду разделение реализации Shape (т.е. Rectangle) от Let's day Цветная абстракция, верно? И я полагаю, вы говорите, что есть множество способов сделать это, и Бридж - только один из них.
стандартный вывод
Да, у подклассов есть другое использование. Именно этот особый способ использования подклассов делает его паттерном Bridge.
Билл Карвин
Я имею в виду развязку от абстрактного интерфейса Shape до конкретной реализации Rectangle. Таким образом, вы можете написать код, который нуждается в объекте типа «Shape», даже если конкретный объект действительно является подклассом Shape.
Билл Карвин
27

По моему опыту, Bridge является довольно часто повторяющимся шаблоном, потому что это решение, когда в области есть два ортогональных измерения . Например, формы и методы рисования, поведение и платформы, форматы файлов и сериализаторы и так далее.

И совет: всегда думайте о шаблонах проектирования с концептуальной точки зрения , а не с точки зрения реализации. С правильной точки зрения Bridge нельзя спутать с Adapter, потому что они решают другую проблему, а композиция превосходит наследование не потому, что она сама по себе, а потому, что она позволяет обрабатывать ортогональные задачи отдельно.

thSoft
источник
22

Назначение моста и адаптера различно, и нам нужны оба шаблона по отдельности.

Образец моста:

  1. Это структурный паттерн
  2. Абстракция и реализация не связаны во время компиляции
  3. Абстракция и реализация - оба могут меняться без влияния на клиента
  4. Использует композицию по наследству.

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

  1. Вы хотите привязку реализации во время выполнения,
  2. У вас есть множество классов, вытекающих из связанного интерфейса и многочисленных реализаций,
  3. Вы хотите поделиться реализацией среди нескольких объектов,
  4. Вам необходимо отобразить ортогональные иерархии классов.

@ Джон Сонмез ответ ясно показывает эффективность схемы моста в сокращении иерархии классов.

Вы можете обратиться к ссылке ниже документации, чтобы лучше понять шаблон моста с примером кода

Адаптер шаблон :

  1. Это позволяет двум несвязанным интерфейсам работать вместе через разные объекты, возможно, играя одну и ту же роль.
  2. Это изменяет оригинальный интерфейс.

Ключевые отличия:

  1. Адаптер заставляет вещи работать после того, как они разработаны; Бридж заставляет их работать раньше, чем они.
  2. Мост спроектирован заранее, чтобы позволить абстракции и реализации варьироваться независимо . Адаптер модифицирован для совместной работы не связанных между собой классов.
  3. Цель: Адаптер позволяет двум не связанным интерфейсам работать вместе. Bridge позволяет абстракции и реализации варьироваться независимо.

Связанный вопрос SE с диаграммой UML и рабочим кодом:

Разница между образцом моста и образцом адаптера

Полезные статьи:

статья об образцовых мостах

sourcemaking адаптер шаблон статьи

journaldev мост шаблон статьи

РЕДАКТИРОВАТЬ:

Пример реального использования Bridge Pattern (в соответствии с предложением meta.stackoverflow.com, включенный в этот пост пример сайта с документацией, поскольку документация собирается заходить)

Шаблон моста отделяет абстракцию от реализации, так что оба могут варьироваться независимо. Это было достигнуто с помощью композиции, а не наследования.

Шаблон моста UML из Википедии:

Мост UML из Википедии

У вас есть четыре компонента в этом шаблоне.

Abstraction: Он определяет интерфейс

RefinedAbstraction: Реализует абстракцию:

Implementor: Он определяет интерфейс для реализации

ConcreteImplementor: Он реализует интерфейс реализатора.

The crux of Bridge pattern :Две ортогональные иерархии классов с использованием композиции (и без наследования). Иерархия абстракций и иерархия реализации могут варьироваться независимо. Реализация никогда не относится к абстракции. Абстракция содержит интерфейс реализации в качестве члена (посредством композиции). Эта композиция уменьшает еще один уровень иерархии наследования.

Реальный вариант использования слова:

Разрешить разные транспортные средства, чтобы иметь обе версии ручной и автоматической системы передач.

Пример кода:

/* Implementor interface*/
interface Gear{
    void handleGear();
}

/* Concrete Implementor - 1 */
class ManualGear implements Gear{
    public void handleGear(){
        System.out.println("Manual gear");
    }
}
/* Concrete Implementor - 2 */
class AutoGear implements Gear{
    public void handleGear(){
        System.out.println("Auto gear");
    }
}
/* Abstraction (abstract class) */
abstract class Vehicle {
    Gear gear;
    public Vehicle(Gear gear){
        this.gear = gear;
    }
    abstract void addGear();
}
/* RefinedAbstraction - 1*/
class Car extends Vehicle{
    public Car(Gear gear){
        super(gear);
        // initialize various other Car components to make the car
    }
    public void addGear(){
        System.out.print("Car handles ");
        gear.handleGear();
    }
}
/* RefinedAbstraction - 2 */
class Truck extends Vehicle{
    public Truck(Gear gear){
        super(gear);
        // initialize various other Truck components to make the car
    }
    public void addGear(){
        System.out.print("Truck handles " );
        gear.handleGear();
    }
}
/* Client program */
public class BridgeDemo {    
    public static void main(String args[]){
        Gear gear = new ManualGear();
        Vehicle vehicle = new Car(gear);
        vehicle.addGear();

        gear = new AutoGear();
        vehicle = new Car(gear);
        vehicle.addGear();

        gear = new ManualGear();
        vehicle = new Truck(gear);
        vehicle.addGear();

        gear = new AutoGear();
        vehicle = new Truck(gear);
        vehicle.addGear();
    }
}

вывод:

Car handles Manual gear
Car handles Auto gear
Truck handles Manual gear
Truck handles Auto gear

Объяснение:

  1. Vehicle это абстракция.
  2. Carи Truckдве конкретные реализации Vehicle.
  3. Vehicleопределяет абстрактный метод: addGear().
  4. Gear интерфейс разработчика
  5. ManualGearи AutoGearдве реализации Gear
  6. Vehicleсодержит implementorинтерфейс, а не реализацию интерфейса. CompositonИнтерфейс разработчика - суть этого паттерна: он позволяет абстракции и реализации варьироваться независимо.
  7. Carи Truckопределяют реализацию (Redefined абстракции) для абстракции: addGear(): Он содержит Gear- либо ManualилиAuto

Варианты использования для шаблона Bridge :

  1. Абстракция и реализация могут меняться независимо друг от друга, и они не связаны во время компиляции
  2. Карта ортогональных иерархий - одна для абстракции и одна для реализации .
Равиндра Бабу
источник
«Адаптер заставляет вещи работать после того, как они спроектированы; Bridge заставляет их работать раньше, чем они». Возможно, вы захотите взглянуть на сменный адаптер. Это разновидность Адаптера, описанная GoF в разделе «Адаптер» их книги «Шаблоны проектирования». Цель состоит в том, чтобы создать интерфейс для классов, которые еще не существуют. Подключаемый адаптер не является мостом, поэтому я не думаю, что первый пункт верен.
c1moore
Хотя ручное и автоматическое снаряжение может потребовать разных реализаций для грузовика и автомобиля
andigor
9

Я использовал шаблон моста на работе. Я программирую на C ++, где его часто называют идиомой PIMPL (указатель на реализацию). Это выглядит так:

class A
{
public: 
  void foo()
  {
    pImpl->foo();
  }
private:
  Aimpl *pImpl;
};

class Aimpl
{
public:
  void foo();
  void bar();
};  

В этом примере class Aсодержится интерфейс и class Aimplсодержит реализацию.

Одним из применений этого шаблона является предоставление только некоторых открытых членов класса реализации, но не других. Только в примере Aimpl::foo()можно вызвать через открытый интерфейс A, но неAimpl::bar()

Еще одним преимуществом является то, что вы можете определить Aimplв отдельном заголовочном файле, который не должен быть включен пользователями A. Все, что вам нужно сделать, это использовать предварительное объявление Aimplдо Aопределения и перенести определения всех функций-членов, ссылающихся pImplна файл .cpp. Это дает вам возможность сохранять Aimplзаголовок закрытым и сократить время компиляции.

Дима
источник
2
Если вы используете этот шаблон, то AImpl даже не нуждается в заголовке. Я просто вставил это в файл реализации для класса А
1800 ИНФОРМАЦИЯ
Ваш разработчик является частным. У меня есть новый вопрос по этому поводу, см. Stackoverflow.com/questions/17680762/…
Роланд
7

Чтобы поместить пример формы в код:

#include<iostream>
#include<string>
#include<cstdlib>

using namespace std;

class IColor
{
public:
    virtual string Color() = 0;
};

class RedColor: public IColor
{
public:
    string Color()
    {
        return "of Red Color";
    }
};

class BlueColor: public IColor
{
public:
    string Color()
    {
        return "of Blue Color";
    }
};


class IShape
{
public:
virtual string Draw() = 0;
};

class Circle: public IShape
{
        IColor* impl;
    public:
        Circle(IColor *obj):impl(obj){}
        string Draw()
        {
            return "Drawn a Circle "+ impl->Color();
        }
};

class Square: public IShape
{
        IColor* impl;
    public:
        Square(IColor *obj):impl(obj){}
        string Draw()
        {
        return "Drawn a Square "+ impl->Color();;
        }
};

int main()
{
IColor* red = new RedColor();
IColor* blue = new BlueColor();

IShape* sq = new Square(red);
IShape* cr = new Circle(blue);

cout<<"\n"<<sq->Draw();
cout<<"\n"<<cr->Draw();

delete red;
delete blue;
return 1;
}

Выход:

Drawn a Square of Red Color
Drawn a Circle of Blue Color

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

NotAgain говорит восстановить Монику
источник
0

для меня я думаю об этом как о механизме, где вы можете менять интерфейсы. В реальном мире у вас может быть класс, который может использовать более одного интерфейса, Bridge позволяет вам менять местами.

j2emanue
источник
0

Вы работаете в страховой компании, в которой вы разрабатываете приложение для документооборота, которое решает различные задачи: бухгалтерский учет, заключение договоров, претензии. Это абстракция. Что касается реализации, вы должны иметь возможность создавать задачи из разных источников: электронная почта, факс, электронная почта.

Вы начинаете свой дизайн с этих классов:

public class Task {...}
public class AccountingTask : Task {...}
public class ContractTask : Task {...}
public class ClaimTask : Task {...}

Теперь, поскольку каждый источник должен обрабатываться определенным образом, вы решаете специализировать каждый тип задачи:

public class EmailAccountingTask : AccountingTask {...}
public class FaxAccountingTask : AccountingTask {...}
public class EmessagingAccountingTask : AccountingTask {...}

public class EmailContractTask : ContractTask {...}
public class FaxContractTask : ContractTask {...}
public class EmessagingContractTask : ContractTask {...}

public class EmailClaimTask : ClaimTask {...}
public class FaxClaimTask : ClaimTask {...}
public class EmessagingClaimTask : ClaimTask {...}

Вы заканчиваете 13 классов. Добавление типа задачи или типа источника становится сложной задачей. Использование шаблона моста упрощает поддержку, отделяя задачу (абстракцию) от источника (что является проблемой реализации):

// Source
public class Source {
   public string GetSender();
   public string GetMessage();
   public string GetContractReference();
   (...)
}

public class EmailSource : Source {...}
public class FaxSource : Source {...}
public class EmessagingSource : Source {...}

// Task
public class Task {
   public Task(Source source);
   (...)
}
public class AccountingTask : Task {...}
public class ContractTask : Task {...}
public class ClaimTask : Task {...}

Добавление типа задачи или источника теперь стало намного проще.

Примечание. Большинство разработчиков не создавали бы иерархию 13 классов заранее для решения этой проблемы. Однако в реальной жизни вы можете заранее не знать количество типов источников и задач; если у вас есть только один источник и два типа задач, вы, вероятно, не отделите задачу от источника. Затем общая сложность возрастает по мере добавления новых источников и типов задач. В какой-то момент вы проведете рефакторинг и, чаще всего, получите мостоподобное решение.

Сильвен Родриг
источник
-5
Bridge design pattern we can easily understand helping of service and dao layer.

Dao layer -> create common interface for dao layer ->
public interface Dao<T>{
void save(T t);
}
public class AccountDao<Account> implement Dao<Account>{
public void save(Account){
}
}
public LoginDao<Login> implement Dao<Login>{
public void save(Login){
}
}
Service Layer ->
1) interface
public interface BasicService<T>{
    void save(T t);
}
concrete  implementation of service -
Account service -
public class AccountService<Account> implement BasicService<Account>{
 private Dao<Account> accountDao;
 public AccountService(AccountDao dao){
   this.accountDao=dao;
   }
public void save(Account){
   accountDao.save(Account);
 }
}
login service- 
public class LoginService<Login> implement BasicService<Login>{
 private Dao<Login> loginDao;
 public AccountService(LoginDao dao){
   this.loginDao=dao;
   }
public void save(Login){
   loginDao.save(login);
 }
}

public class BridgePattenDemo{
public static void main(String[] str){
BasicService<Account> aService=new AccountService(new AccountDao<Account>());
Account ac=new Account();
aService.save(ac);
}
}
}
Сохан Кумават
источник
5
Я понизил голос, потому что чувствую, что это запутанный, плохо отформатированный ответ.
Зимано
1
Полностью согласен, как вы можете публиковать ответы на этом сайте без минимального внимания к отступам и четкости кода
Массимилиано Краус