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

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


person Charles Graham    schedule 26.11.2008    source источник
comment
Пожалуйста, подумайте о том, чтобы принять другой ответ на этот вопрос. Принятый в настоящее время ответ неверен и бесполезен. Новые ответы намного лучше.   -  person jaco0646    schedule 05.02.2020
comment
На этот вопрос прямо отвечает книга GoF.   -  person jaco0646    schedule 25.03.2020


Ответы (12)


Классический пример паттерна Мост используется при определении фигур в среде пользовательского интерфейса (см. запись в Википедии о паттерне моста). Шаблон "Мост" - это составная часть шаблон и Шаблоны стратегии.

Это обычное представление о некоторых аспектах паттерна «Адаптер» в паттерне «Мост». Однако, если процитировать эту статью:

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

person shek    schedule 26.11.2008
comment
Мост не имеет ничего общего с шаблоном или стратегией. Мост - это структурный образец. Шаблон и Стратегия - это модели поведения. - person jaco0646; 05.02.2020

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

Когда:

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

Выполните рефакторинг для:

          ----Shape---                        Color
         /            \                       /   \
Rectangle(Color)   Circle(Color)           Blue   Red
person Anton Shchastnyi    schedule 21.05.2012
comment
Зачем делать наследование цветов? - person vainolo; 03.02.2013
comment
@vainolo, потому что цвет - это интерфейс, а синий, красный - конкретные цвета - person Weltschmerz; 01.03.2014
comment
Это просто рефакторинг. Намерение шаблона моста: отделить абстракцию от ее реализации, чтобы они могли различаться независимо. Где абстракция и где здесь реализация? - person clapas; 06.09.2017
comment
Разве Rectangle (Color) не более абстрактный, чем BlueRectangle? - person Anton Shchastnyi; 07.09.2017
comment
@clapas, Абстракция - это свойство Shape.color, поэтому класс Red и класс Blue являются реализацией, а интерфейс Color - мостом. - person reco; 29.12.2017
comment
Я прочитал десятки неподходящих примеров, в которых просто злоупотребляет наследованием, то есть что-то, что кажется естественным, как «имеет», представлено как «есть». Это лучший ответ, который я нашел. Браво! Кратко, информативно, обрисовано в общих чертах. - person egelev; 07.10.2019

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

person Federico A. Ramponi    schedule 26.11.2008
comment
См. Также диаграмму Антона Щастного ниже для графической иллюстрации этого объяснения. - person NomadeNumerique; 03.02.2014
comment
Я не думаю, что цвет является хорошим примером иерархии реализации, это довольно сбивает с толку. Хороший пример шаблона Bridge в шаблонах проектирования от GoF, реализация которого зависит от платформы: IBM PM, UNIX X и т. Д. - person clapas; 06.09.2017

Когда:

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

Выполните рефакторинг для:

     A         N
  /     \     / \
Aa(N) Ab(N)  1   2
person John Sonmez    schedule 23.02.2012
comment
Я думаю, что это очень прагматичный подход к шаблонам: 1) описать неоптимальный простой дизайн 2) реорганизовать дизайн / код для лучшего факторизации - person Alexey; 10.07.2012
comment
Используйте математическую концепцию, чтобы объяснить шаблон проектирования моста. Очень заинтересован. - person Jian Huang; 18.02.2015
comment
Это просто рефакторинг. Намерение шаблона моста: отделить абстракцию от ее реализации, чтобы они могли различаться независимо. Где абстракция и где здесь реализация? - person clapas; 06.09.2017
comment
Джон красиво изложил это в сообщении в блоге. Считал, что это хорошее чтение для общего обзора. - person Vaibhav Bhalla; 17.01.2019

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

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

person thSoft    schedule 25.05.2010

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

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

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

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

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

См. Также http://c2.com/cgi/wiki?BridgePattern.

person Bill Karwin    schedule 26.11.2008
comment
Привет, Билл. Я не понимаю, почему мы обязательно используем шаблон Bridge в драйверах устройств. Я имею в виду, что мы можем легко делегировать реализацию (чтения, записи, поиска и т. Д.) Правильному классу с помощью полиморфизма, верно? Или, может быть, с посетителем? Почему это должен быть мост? Заранее спасибо. - person stdout; 01.12.2016
comment
@zgulser, да, вы действительно используете полиморфизм. Шаблон «Мост» описывает один из видов использования подклассов для отделения реализации от абстракции. - person Bill Karwin; 01.12.2016
comment
Вы имели в виду развязку реализации формы (например, прямоугольника) от абстракции цвета Let's Day, верно? И я полагаю, вы говорите, что есть множество способов сделать это, и Bridge - лишь один из них. - person stdout; 01.12.2016
comment
Да, у подклассов есть и другие применения. Именно этот способ использования подклассов и делает его шаблоном моста. - person Bill Karwin; 01.12.2016
comment
Я имею в виду развязку от абстрактного интерфейса Shape к конкретной реализации Rectangle. Таким образом, вы можете написать код, которому нужен объект типа Shape, даже если конкретный объект на самом деле является подклассом Shape. - person Bill Karwin; 01.12.2016
comment
Важно отметить, что шаблон «Мост» может быть реализован по-разному в зависимости от возможностей языка / системы. Любой язык, допускающий определение класса, обязательно должен иметь способ определения интерфейса этого класса. Но не все языки позволяют определять интерфейсы независимо от классов. Для тех, кто это делает (например, C #), шаблон Bridge может быть реализован напрямую с использованием таких определений интерфейсов и при этом пропустить по крайней мере один уровень подкласса и создания экземпляров частного объекта. - person C Perkins; 20.01.2017

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

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

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

Используйте паттерн Мост, когда:

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

Ответ @ John Sonmez ясно показывает эффективность шаблона моста в уменьшении иерархии классов.

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

Шаблон адаптера:

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

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

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

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

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

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

Sourcemaking Bridge, статья о шаблоне

адаптер создания исходного кода, статья о шаблоне

Шаблон статьи journaldev Bridge

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

Образец моста в реальном мире (в соответствии с предложением 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 определяют реализацию (переопределенную абстракцию) для абстракции: addGear(): Он содержит Gear - Либо Manual, либо Auto

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

  1. Абстракция и Реализация могут изменяться независимо друг от друга и не связаны во время компиляции.
  2. Сопоставьте ортогональные иерархии - одна для абстракции и одна для реализации.
person Ravindra babu    schedule 29.05.2016
comment
Адаптер заставляет вещи работать после того, как они спроектированы; Мост заставляет их работать раньше, чем они есть. Вы можете изучить подключаемый адаптер. Это вариант адаптера, описанный GoF в разделе «Адаптер» их книги «Шаблоны проектирования». Цель состоит в том, чтобы создать интерфейс для классов, которые еще не существуют. Сменный адаптер - это не мост, поэтому я не думаю, что первый пункт верен. - person c1moore; 23.11.2016
comment
Хотя ручная и автоматическая передача может потребовать разной реализации для грузовика и автомобиля. - person andigor; 30.09.2018

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

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

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

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

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

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

person Dima    schedule 26.11.2008
comment
Если вы используете этот шаблон, AImpl даже не нуждается в заголовке. Я просто вставил его в файл реализации для класса A - person 1800 INFORMATION; 26.11.2008
comment
Ваш разработчик является частным лицом. У меня есть новый вопрос по этому поводу, см. stackoverflow.com/questions/17680762/ - person Roland; 16.07.2013

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

#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

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

person NotAgain says Reinstate Monica    schedule 14.09.2013

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

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

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 классов заранее, чтобы решить эту проблему. Однако в реальной жизни вы можете не знать заранее количество источников и типов задач; если у вас есть только один источник и два типа задач, вы, вероятно, не будете отделять задачу от источника. Затем общая сложность растет по мере добавления новых источников и типов задач. В какой-то момент вы проведете рефакторинг и, чаще всего, получите решение, подобное мосту.

person Sylvain Rodrigue    schedule 02.06.2020

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

person j2emanue    schedule 29.12.2012

person    schedule
comment
Я проголосовал против, потому что считаю, что это запутанный, плохо отформатированный ответ. - person Zimano; 29.03.2018
comment
Полностью согласен, как можно публиковать ответы на этом сайте без минимального внимания к отступам и ясности кода? - person Massimiliano Kraus; 10.07.2018