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

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

14 ответов

Решение

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

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

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

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

Когда:

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

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

          ----Shape---                        Color
         /            \                       /   \
Rectangle(Color)   Circle(Color)           Blue   Red

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

Когда:

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

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

     A         N
  /     \     / \
Aa(N) Ab(N)  1   2

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

статья об образце моста Журдев

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

Пример реального использования Bridge Pattern (в соответствии с предложением meta.stackru.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

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

  1. Абстракция и реализация могут меняться независимо друг от друга, и они не связаны во время компиляции
  2. Карта ортогональных иерархий - одна для абстракции и одна для реализации.

Я использовал шаблон моста на работе. Я программирую на 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 частный заголовок и сократить время компиляции.

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

#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

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

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

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

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

Ключевое различие между шаблонами проектирования Adapter и Bridge заключается в их намерениях. Из шаблонов проектирования , глава 4, раздел «Мост», параграф «Связанные шаблоны» (Gamma et al. 1994):

Шаблон адаптера (139) предназначен для совместной работы несвязанных классов. Обычно он применяется к системам после их проектирования. Мост, с другой стороны, используется заранее в проекте, чтобы позволить абстракциям и реализациям варьироваться независимо.

  1. Слово «независимо» означает, что в этой ситуации применяется шаблон проектирования «Мост», поскольку формы и цвета независимы:
                   ------Shape-----                           Shape       Colour
            /                \              Bridge       / \         / \
       Circle                Square         ----->  Circle Square  Red Blue
        / \                   / \
RedCircle BlueCircle  RedSquare BlueSquare
  1. Но в данной ситуации это неприменимо, потому что цвета зависят от форм:
                   ------Shape-----
            /                \
       Circle                Square
        / \                   / \
RedCircle BlueCircle  RedSquare GreenSquare
  1. И это бесполезно в этой ситуации, потому что есть одна форма и один цвет:
      Shape
  |
Circle
  |
RedCircle

Табличное представление ситуации 1:

      | Shape  | Colour |          | Shape  |  | Colour |
| ------ | ------ |          | ------ |  | ------ |
| circle | red    |  Bridge  | circle |  | red    |
| circle | blue   |  ----->  | square |  | blue   |
| square | red    |
| square | blue   |

Табличное представление ситуации 2:

      | Shape  | Colour |
| ------ | ------ |
| circle | red    |
| circle | blue   |
| square | red    |
| square | green  |

Табличное представление ситуации 3:

      | Shape  | Colour |
| ------ | ------ |
| circle | red    |

Таким образом, шаблон проектирования Bridge в объектно-ориентированном программировании эквивалентен нормализации к нормальной форме проекции-соединения, обозначаемой PJ/NF (Fagin 1979), в реляционных базах данных.

В ситуации 1 схема отношения R(Форма, Цвет) имеет многозначные зависимости ∅ ↠ {Форма} (независимые формы) и ∅ ↠ {Цвет} (независимые цвета), которые не вытекают из набора ключевых зависимостей {KEY({ Форма, цвет})}, поэтому его нет в PJ/NF. Его проекции находятся в PJ/NF, потому что R 1 (Shape) имеет тривиальную функциональную зависимость {Shape} → {Shape}, которая подразумевается набором ключевых зависимостей {KEY({Shape})}, а R 2 (Color) имеет тривиальную функциональную зависимость тривиальная функциональная зависимость {Цвет} → {Цвет}, которая подразумевается набором ключевых зависимостей {KEY({Цвет})}.

В ситуации 2 схема отношения R(Форма, Цвет) имеет тривиальную многозначную зависимость {Форма} ↠ {Цвет}, которая подразумевается набором ключевых зависимостей {КЛЮЧ({Форма, Цвет})}, поэтому она уже находится в ПД/НФ.

В ситуации 3 схема отношения R(Форма, Цвет) имеет функциональные зависимости ∅ → {Форма} (одна фигура) и ∅ → {Цвет} (один цвет), которые подразумеваются набором ключевых зависимостей {KEY({Форма , Цвет}), KEY(∅)}, так что он уже есть в PJ/NF.

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

Допустим, у вас есть другой способ оплаты, например, оплата картой и интернет-банкинг. И есть разные платежные шлюзы, такие как банк CITI и банк HSBC.

Затем вы можете просто добавить члена платежного шлюза в режимы оплаты. И во время выполнения передать эту информацию объекту режима оплаты. И произвести оплату.

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

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

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);
}
}
}
Другие вопросы по тегам