Коли ви використовуєте шаблон моста? Чим вона відрізняється від моделі адаптера?


154

Хто-небудь коли-небудь користувався цим Мостик мостів" у додатку в реальному світі? Якщо так, як ти ним користувався? Це я, чи це просто Адаптерний шаблон з невеликою ін'єкцією залежності, кинутий у суміш? Чи справді вона заслуговує на свій власний зразок?


Просимо прийняти іншу відповідь на це питання. На даний момент прийнята відповідь невірна і не є корисною. Новіші відповіді набагато вищі.
jaco0646

Книга GoF відповідає на це питання безпосередньо.
jaco0646

Відповіді:


76

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

Загальним є перегляд деяких аспектів схеми адаптера в мостовій схемі. Однак, цитую цю статтю :

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


1
Bridge не має нічого спільного з шаблоном або стратегією. Міст - структурна структура. Шаблон та стратегія - це поведінкові моделі.
jaco0646

249

Існує поєднання відповідей Федеріко та Джона .

Коли:

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

Рефактор до:

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

6
Чому б ти робив спадок для кольорів?
vainolo

10
@vainolo, оскільки Колір - це інтерфейс, а Синій, Червоний - це конкретні кольори
Weltschmerz

3
Це просто рефакторинг. Намір схеми моста: "Розв'яжіть абстракцію від її реалізації, щоб ці два могли змінюватись незалежно." Де абстракція і де тут реалізація?
клапас

1
Чи не прямокутник (колір) більш абстрактний, ніж річ BlueRectangle?
Антон Щастний

2
@clapas, Абстракція є "Shape.color" властивості, тому клас Red та клас Blue - це реалізація, а Color Interface - міст.
відновлення

230

Шаблон Bridge - це застосування старої поради, «віддайте перевагу складу над спадщиною». Це стає зручним, коли ви повинні підкласувати різні часи способами, які є ортогональними один з одним. Скажімо, ви повинні реалізувати ієрархію кольорових фігур. Ви б не підклас Форма з прямокутником і колом, а потім підклас Прямокутник з 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
Це просто рефакторинг. Намір схеми моста: "Розв'яжіть абстракцію від її реалізації, щоб ці два могли змінюватись незалежно." Де абстракція і де тут реалізація?
клапас

Джон це чудово розміщує в дописі блогу . Знайшов, що це добре читати для огляду високого рівня.
Vaibhav Bhalla

29

Адаптер і міст, безумовно, пов'язані, і відмінність є тонкою. Цілком ймовірно, що деякі люди, які думають, що використовують один із цих шаблонів, насправді використовують інший шаблон.

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

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

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

Отож, що стосується коду, дві моделі дуже схожі. Бізнес-розумний, вони різні.

Дивіться також http://c2.com/cgi/wiki?BridgePattern


Гей, Білл. Я не розумію, чому ми обов'язково використовуємо шаблон Bridge в драйверах пристроїв. Я маю на увазі, що ми можемо легко делегувати реалізацію (читати, писати, шукати тощо) правильному класу за допомогою поліморфізму? Або з відвідувачем, можливо? Чому це повинен бути міст? Заздалегідь спасибі.
stdout

1
@zgulser, так, ти справді використовуєш поліморфізм. Шаблон Bridge описує один із типів використання підкласів, щоб відокремити реалізацію від абстракції.
Білл Карвін

Ви мали на увазі розв'язування реалізації форми (тобто прямокутник) від дня, коли ми будемо абстрагуватися кольором? І я вважаю, ви говорите, що існують різні способи, і Брідж - це лише один із них.
stdout

Так, підкласифікація має інші напрямки. Саме цей спосіб використання підкласів визначає модель Bridge.
Білл Карвін

Я маю на увазі розв'язку - від абстрактного інтерфейсу Shape до конкретної реалізації прямокутника. Таким чином, ви можете написати код, який потребує об'єкта типу "Shape", навіть якщо конкретний об'єкт - це справді якийсь підклас Shape.
Білл Карвін

27

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

І порада: завжди думайте про моделі дизайну з концептуальної точки зору , а не з точки зору реалізації. З правильної точки зору, Бридж не можна плутати з Адаптером, оскільки вони вирішують іншу проблему, і композиція перевершує спадщину не заради себе, а тому, що дозволяє окремо вирішувати ортогональні проблеми.


22

Призначення Bridge і Adapter відрізняється, і нам потрібні обидва шаблони окремо.

Шаблон мосту:

  1. Це структурна закономірність
  2. Абстракція та реалізація не пов'язані під час компіляції
  3. Абстракція та реалізація - обидва можуть змінюватись без впливу на клієнта
  4. Використовує склад над спадщиною.

Використовуйте шаблон мосту, коли:

  1. Ви хочете прив’язати впровадження часу,
  2. У вас є розповсюдження класів в результаті сполученого інтерфейсу та численних реалізацій,
  3. Ви хочете поділитися реалізацією між кількома об'єктами,
  4. Потрібно зіставити ортогональні ієрархії класів.

@ Відповідь Джона Сонмеса чітко показує ефективність мостового шаблону в зменшенні ієрархії класів.

Ви можете посилатися на посилання нижче на документацію, щоб отримати кращу інформацію про модель моста на прикладі коду

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

  1. Це дозволяє двом непов'язаним інтерфейсам працювати разом через різні об'єкти, можливо, граючи однакову роль.
  2. Це змінює оригінальний інтерфейс.

Основні відмінності:

  1. Адаптер змушує роботу працювати після їх розробки; Міст змушує їх працювати до того, як вони є.
  2. Міст розроблений вперед, щоб абстракція та реалізація залежали незалежно . Адаптер переобладнаний для того, щоб зв'язати непов’язані класи разом.
  3. Завдання: Адаптер дозволяє двом незв’язаним інтерфейсам працювати разом. Bridge дозволяє абстракції та реалізації змінюватися незалежно.

Питання SE щодо діаграми UML та робочого коду:

Різниця між схемою моста та схемою адаптера

Корисні статті:

стаття шаблон мосту джерело

стаття шаблону адаптера для створення джерела

journaldev міст шаблон статті

Редагувати:

Приклад реального світу мосту (відповідно до пропозиції meta.stackoverflow.com), включений приклад сайту документації в цій публікації, оскільки документація буде встановлена ​​на сонце)

Модель мосту відв'язує абстрагування від реалізації, щоб обидва могли змінюватись незалежно. Це було досягнуто композицією, а не спадщиною.

Модель UML з Вікіпедії:

Модель UML з Вікіпедії

У вас є чотири компоненти в цій схемі.

Abstraction: Він визначає інтерфейс

RefinedAbstraction: Він реалізує абстракцію:

Implementor: Він визначає інтерфейс для реалізації

ConcreteImplementor: Він реалізує інтерфейс Implementor.

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. Карта ортогональних ієрархій - одна для абстрагування та одна для реалізації .

"Адаптер змушує роботу працювати після їх розробки; 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 навіть не потребує заголовка. Я просто помістив його у файл реалізації програми для класу A
1800 ІНФОРМАЦІЯ

Ваш виконавець приватний. У мене є нове питання щодо цього, див. Stackoverflow.com/questions/17680762/…
Roland

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

Зауважте, легкість, з якою нові кольори та форми можна додавати в систему, не призводячи до вибуху підкласів через перестановки.


0

для мене я вважаю це механізмом, де можна обмінятися інтерфейсами. У реальному світі у вас може бути клас, який може використовувати більше одного інтерфейсу, Bridge дозволяє обмінятися.


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 класів наперед для вирішення цієї проблеми. Однак у реальному житті ви можете заздалегідь не знати кількості джерел та типів завдань; якщо у вас є лише одне джерело та два типи завдань, ви, ймовірно, не розв'язувати Завдання з Source. Потім загальна складність зростає із додаванням нових джерел та типів завдань. У якийсь момент ви зробите рефактор і, найчастіше, закінчите мостоподібне рішення.


-4
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
Повністю згоден, як можна розміщувати відповіді на цьому веб-сайті без мінімальної уваги до відступу та чіткості
Масміліміано Краус
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.