Kiedy używasz wzoru mostka? Czym różni się od wzoru adaptera?

154

Czy ktoś kiedykolwiek używał wzorca mostu w prawdziwej aplikacji? Jeśli tak, jak z tego korzystałeś? Czy to ja, czy jest to tylko wzorzec adaptera z niewielkim zastrzykiem zależności wrzuconym do miksu? Czy naprawdę zasługuje na swój własny wzór?

Charles Graham
źródło
Rozważ przyjęcie innej odpowiedzi na to pytanie. Aktualnie zaakceptowana odpowiedź jest niepoprawna i nieprzydatna. Nowsze odpowiedzi są znacznie lepsze.
jaco0646
Książka GoF odpowiedzi wprost na to pytanie.
jaco0646

Odpowiedzi:

76

Klasyczny przykład wzorca Bridge jest używany w definicji kształtów w środowisku interfejsu użytkownika (patrz wpis w Wikipedii dotyczący wzorca mostu ). Wzór Bridge jest kompozyt z szablonu i strategii desenie.

Niektóre aspekty wzorca adaptera we wzorcu mostka są typowym widokiem. Jednak cytując z tego artykułu :

Na pierwszy rzut oka wzorzec Bridge wygląda bardzo podobnie do wzorca Adapter, ponieważ klasa jest używana do konwersji jednego rodzaju interfejsu na inny. Jednak celem wzorca Adapter jest sprawienie, aby interfejs jednej lub kilku klas wyglądał tak samo, jak interfejs określonej klasy. Wzorzec Bridge został zaprojektowany w celu oddzielenia interfejsu klasy od jej implementacji, dzięki czemu można zmieniać lub zastępować implementację bez zmiany kodu klienta.

szek
źródło
1
Bridge nie ma nic wspólnego z szablonem ani strategią. Most jest wzorem strukturalnym. Szablon i strategia to wzorce zachowań.
jaco0646
249

Jest połączenie odpowiedzi Federico i Johna .

Kiedy:

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

Refaktoryzuj na:

          ----Shape---                        Color
         /            \                       /   \
Rectangle(Color)   Circle(Color)           Blue   Red
Anton Shchastnyi
źródło
6
Dlaczego miałbyś dziedziczyć kolory?
vainolo
10
@vainolo, ponieważ kolor to interfejs, a niebieski, czerwony to konkretne kolory
Weltschmerz
3
To tylko refaktoryzacja. Zamiar wzorca mostu: „Oddziel abstrakcję od jej implementacji, aby obie mogły się zmieniać niezależnie”. Gdzie jest abstrakcja, a gdzie implementacja?
clapas
1
Czy Rectangle (Color) nie jest bardziej abstrakcyjnym rozwiązaniem niż BlueRectangle?
Anton Shchastnyi
2
@clapas, Abstrakcja to właściwość „Shape.color”, dlatego klasa Red i klasa Blue to implementacja, a interfejs Color to bridge.
reco
230

Wzorzec Bridge jest zastosowaniem starej rady „przedkładaj kompozycję nad dziedziczenie”. Jest to przydatne, gdy musisz podklasować różne czasy w sposób ortogonalny względem siebie. Powiedzmy, że musisz wdrożyć hierarchię kolorowych kształtów. Nie utworzyłbyś podklasy Shape z Rectangle i Circle, a następnie podklasy Rectangle z RedRectangle, BlueRectangle i GreenRectangle i tak samo dla Circle, prawda? Wolałbyś powiedzieć, że każdy Shape ma kolor i zastosować hierarchię kolorów, a to jest wzór mostu. Cóż, nie wprowadziłbym „hierarchii kolorów”, ale masz pomysł ...

Federico A. Ramponi
źródło
1
Zobacz także diagram Antona Shchastnyi poniżej, aby uzyskać graficzną ilustrację tego wyjaśnienia.
NomadeNumerique
2
Nie sądzę, aby kolor był dobrym przykładem hierarchii implementacji, jest raczej zagmatwany. Jest dobry przykład wzorca mostu w „Wzorcach projektowych” autorstwa GoF, gdzie implementacja jest zależna od platformy: IBM PM, UNIX X itd.
clapas
215

Kiedy:

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

Refaktoryzuj na:

     A         N
  /     \     / \
Aa(N) Ab(N)  1   2
John Sonmez
źródło
3
Myślę, że jest to bardzo pragmatyczne podejście do wzorców: 1) opisać nieoptymalny, prosty projekt 2) projekt / kod refaktora na lepszy faktoryzowany
Alexey
1
Użyj koncepcji matematycznej, aby wyjaśnić wzorzec projektowy Bridge. Bardzo zainteresowany.
Jian Huang
1
To tylko refaktoryzacja. Zamiar wzorca mostu: „Oddziel abstrakcję od jej implementacji, aby obie mogły się zmieniać niezależnie”. Gdzie jest abstrakcja, a gdzie implementacja?
clapas
John ładnie to ujął w poście na blogu . Okazało się, że jest to dobra lektura dla ogólnego przeglądu.
Vaibhav Bhalla
29

Adapter i mostek są z pewnością powiązane, a różnica jest subtelna. Jest prawdopodobne, że niektórzy ludzie, którzy myślą, że używają jednego z tych wzorców, w rzeczywistości używają drugiego.

Wyjaśnienie, które widziałem, jest takie, że Adapter jest używany, gdy próbujesz ujednolicić interfejsy niektórych niezgodnych klas, które już istnieją . Adapter działa jako rodzaj translatora implementacji, które można uznać za starsze .

Podczas gdy wzorzec Bridge jest używany dla kodu, który z większym prawdopodobieństwem będzie typu greenfield. Projektujesz Bridge, aby zapewnić abstrakcyjny interfejs dla implementacji, która musi się różnić, ale także definiujesz interfejs tych klas implementacji.

Sterowniki urządzeń są często cytowanym przykładem Bridge, ale powiedziałbym, że jest to Bridge, jeśli definiujesz specyfikację interfejsu dla dostawców urządzeń, ale jest to adapter, jeśli bierzesz istniejące sterowniki urządzeń i tworzysz klasę opakowania do zapewniają ujednolicony interfejs.

Więc pod względem kodu te dwa wzorce są bardzo podobne. Pod względem biznesowym są różne.

Zobacz także http://c2.com/cgi/wiki?BridgePattern

Bill Karwin
źródło
Hej Bill. Nie rozumiem, dlaczego koniecznie używamy wzorca mostka w sterownikach urządzeń. Chodzi mi o to, że możemy łatwo delegować implementację (czytania, pisania, wyszukiwania itp.) Do właściwej klasy poprzez polimorfizm, prawda? A może z gościem? Dlaczego to musi być Bridge? Z góry dziękuję.
wyjście
1
@zgulser, tak, używasz polimorfizmu. Wzorzec Bridge opisuje jeden rodzaj użycia podklas w celu oddzielenia implementacji od abstrakcji.
Bill Karwin
Miałeś na myśli oddzielenie implementacji Shape (tj. Rectangle) od Let's day Abstrakcja kolorów, prawda? Myślę, że mówisz, że można to zrobić na wiele sposobów, a Bridge jest tylko jednym z nich.
wyjście
Tak, tworzenie podklas ma inne zastosowania. Ten szczególny sposób używania podklas sprawia, że ​​jest to wzorzec Bridge.
Bill Karwin
I mam na myśli oddzielenie od abstrakcyjnego interfejsu Shape do konkretnej implementacji Rectangle. Możesz więc napisać kod, który wymaga obiektu typu „Shape”, mimo że konkretny obiekt jest tak naprawdę jakąś podklasą Shape.
Bill Karwin
27

Z mojego doświadczenia wynika, że ​​Bridge jest dość często powtarzającym się wzorcem, ponieważ jest rozwiązaniem, gdy w domenie występują dwa ortogonalne wymiary . Np. Kształty i metody rysowania, zachowania i platformy, formaty plików i serializatory i tak dalej.

I rada: zawsze myśl o wzorcach projektowych z perspektywy koncepcyjnej , a nie implementacyjnej. Z właściwego punktu widzenia Bridge nie można pomylić z adapterem, ponieważ rozwiązują inny problem, a kompozycja jest lepsza od dziedziczenia nie ze względu na samą siebie, ale dlatego, że pozwala osobno zająć się kwestiami ortogonalnymi.

thSoft
źródło
22

Przeznaczenie Bridge i Adapter jest inne i potrzebujemy obu wzorców osobno.

Wzór mostka:

  1. Jest to wzór strukturalny
  2. Abstrakcja i implementacja nie są związane w czasie kompilacji
  3. Abstrakcja i implementacja - oba mogą się różnić bez wpływu na klienta
  4. Wykorzystuje kompozycję zamiast dziedziczenia.

Użyj wzorca Bridge, gdy:

  1. Chcesz wiązać implementację w czasie wykonywania,
  2. Masz mnogość klas wynikających ze sprzężonego interfejsu i licznych implementacji,
  3. Chcesz udostępnić implementację między wieloma obiektami,
  4. Musisz odwzorować ortogonalne hierarchie klas.

@ John Sonmez odpowiedź jasno pokazuje skuteczność wzorca mostu w redukcji hierarchii klas.

Możesz zapoznać się z poniższym linkiem do dokumentacji, aby uzyskać lepszy wgląd we wzorzec mostu z przykładem kodu

Wzór adaptera :

  1. To pozwala dwóch niepowiązanych interfejsy celem razem pracy przez różne obiekty, ewentualnie grać tę samą rolę.
  2. Modyfikuje oryginalny interfejs.

Kluczowe różnice:

  1. Adapter sprawia, że ​​wszystko działa po ich zaprojektowaniu; Bridge sprawia, że ​​działają, zanim oni.
  2. Bridge jest zaprojektowany z góry, aby abstrakcja i implementacja różniły się niezależnie . Adapter jest doposażany w celu umożliwienia współpracy niepowiązanych klas.
  3. Cel: Adapter umożliwia współpracę dwóch niepowiązanych ze sobą interfejsów. Bridge pozwala na niezależne zmiany abstrakcji i implementacji.

Powiązane pytanie SE z diagramem UML i działającym kodem:

Różnica między wzorem mostka a wzorem adaptera

Przydatne artykuły:

artykuł o wzorach mostów źródłowych

artykuł wzór adaptera do produkcji źródła

artykuł o wzorach mostów Journaldev

EDYTOWAĆ:

Przykładowy wzorzec mostu w świecie rzeczywistym (zgodnie z sugestią meta.stackoverflow.com, przykład witryny z dokumentacją uwzględnioną w tym poście, ponieważ dokumentacja zostanie zachowana)

Wzorzec mostu oddziela abstrakcję od implementacji, dzięki czemu oba mogą się zmieniać niezależnie. Osiągnięto to raczej dzięki kompozycji niż dziedziczeniu.

Wzorzec mostkowy UML z Wikipedii:

Wzór mostka UML z Wikipedii

W tym wzorze masz cztery komponenty.

Abstraction: Definiuje interfejs

RefinedAbstraction: Implementuje abstrakcję:

Implementor: Definiuje interfejs do implementacji

ConcreteImplementor: Implementuje interfejs implementatora.

The crux of Bridge pattern :Dwie ortogonalne hierarchie klas wykorzystujące kompozycję (bez dziedziczenia). Hierarchia abstrakcji i hierarchia implementacji mogą się zmieniać niezależnie. Implementacja nigdy nie odnosi się do abstrakcji. Abstrakcja zawiera interfejs implementacji jako element członkowski (poprzez kompozycję). Ta kompozycja redukuje o jeden poziom hierarchii dziedziczenia.

Prawdziwe słowo Zastosowanie:

Włącz różne pojazdy, aby mieć obie wersje ręcznego i automatycznego systemu zmiany biegów.

Przykładowy kod:

/* 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();
    }
}

wynik:

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

Wyjaśnienie:

  1. Vehicle jest abstrakcją.
  2. Cari Trucksą dwiema konkretnymi implementacjami Vehicle.
  3. Vehicledefiniuje metody abstrakcyjne: addGear().
  4. Gear jest interfejsem implementatora
  5. ManualGeari AutoGearsą dwiema implementacjami Gear
  6. Vehiclezawiera implementorinterfejs, a nie implementuje interfejs. Compositoninterfejsu implementatora jest sednem tego wzorca: pozwala na niezależne zróżnicowanie abstrakcji i implementacji.
  7. Cari Truckzdefiniuj implementację (przedefiniowaną abstrakcję) dla abstrakcji:: addGear()Zawiera Gear- Albo ManualalboAuto

Przypadki użycia dla wzorca mostka :

  1. Abstrakcja i implementacja mogą zmieniać się niezależnie od siebie i nie są związane w czasie kompilacji
  2. Mapuj hierarchie ortogonalne - jedna dla abstrakcji i jedna dla implementacji .
Ravindra babu
źródło
„Adapter sprawia, że ​​rzeczy działają po ich zaprojektowaniu; Bridge sprawia, że ​​działają, zanim się pojawią”. Możesz zajrzeć do adaptera wtykowego. Jest to odmiana adaptera opisana przez firmę GoF w sekcji „Adapter” w książce Wzorce projektowe. Celem jest stworzenie interfejsu dla klas, które jeszcze nie istnieją. Wtykowy adapter nie jest mostkiem, więc nie wydaje mi się, że pierwszy punkt jest ważny.
c1moore
Chociaż ręczna i automatyczna skrzynia biegów może wymagać innej implementacji dla ciężarówki i samochodu
iigor
9

W pracy używałem wzoru mostka. Programuję w C ++, gdzie często nazywany jest idiomem PIMPL (wskaźnik implementacji). To wygląda tak:

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

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

W tym przykładzie class Azawiera interfejs i class Aimplzawiera implementację.

Jednym z zastosowań tego wzorca jest ujawnienie tylko niektórych publicznych członków klasy implementacji, ale innych nie. W tym przykładzie Aimpl::foo()można wywołać tylko za pośrednictwem publicznego interfejsu programu A, ale nieAimpl::bar()

Kolejną zaletą jest to, że możesz zdefiniować Aimplw oddzielnym pliku nagłówkowym, który nie musi być dołączany przez użytkowników A. Wszystko, co musisz zrobić, to użyć deklaracji forward Aimplprzed Azdefiniowaniem i przenieść definicje wszystkich funkcji pImplskładowych, do których istnieją odniesienia, do pliku .cpp. Dzięki temu możesz zachować Aimplprywatność nagłówka i skrócić czas kompilacji.

Dima
źródło
2
Jeśli używasz tego wzorca, AImpl nie potrzebuje nawet nagłówka. Po prostu umieściłem to w pliku implementacyjnym dla klasy A
1800 INFORMACJE
Twój wykonawca jest prywatny. Mam nowe pytanie w związku z tym, patrz stackoverflow.com/questions/17680762/ ...
Roland
7

Aby umieścić przykład kształtu w kodzie:

#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;
}

Wynik to:

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

Zwróć uwagę na łatwość, z jaką nowe kolory i kształty można dodawać do systemu bez powodowania eksplozji podklas z powodu permutacji.

NotAgain mówi Przywróć Monikę
źródło
0

dla mnie myślę o tym jako o mechanizmie, w którym można wymieniać interfejsy. W prawdziwym świecie możesz mieć klasę, która może używać więcej niż jednego interfejsu, Bridge pozwala na zamianę.

j2emanue
źródło
0

Pracujesz dla firmy ubezpieczeniowej, w której tworzysz aplikację workflow, która zarządza różnego rodzaju zadaniami: księgowością, umową, roszczeniami. To jest abstrakcja. Po stronie wdrożenia musisz mieć możliwość tworzenia zadań z różnych źródeł: e-mail, faks, e-mailing.

Rozpoczynasz projektowanie od tych klas:

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

Teraz, ponieważ każde źródło musi być obsługiwane w określony sposób, decydujesz się na specjalizację każdego typu zadania:

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 {...}

Skończysz z 13 zajęciami. Dodanie typu zadania lub typu źródła staje się trudne. Użycie wzorca mostka daje coś łatwiejszego do utrzymania poprzez oddzielenie zadania (abstrakcji) od źródła (co jest problemem implementacyjnym):

// 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 {...}

Dodawanie typu zadania lub źródła jest teraz znacznie łatwiejsze.

Uwaga: większość programistów nie utworzyłaby z góry hierarchii 13 klas, aby rozwiązać ten problem. Jednak w prawdziwym życiu możesz nie znać z góry liczby źródeł i typów zadań; jeśli masz tylko jedno źródło i dwa typy zadań, prawdopodobnie nie oddzielisz zadania od źródła. Następnie ogólna złożoność rośnie w miarę dodawania nowych źródeł i typów zadań. W pewnym momencie dokonasz refaktoryzacji i najczęściej skończysz z rozwiązaniem podobnym do mostu.

Sylvain Rodrigue
źródło
-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);
}
}
}
sohan kumawat
źródło
5
Głosowałem w dół, ponieważ uważam, że jest to zawiła, źle sformatowana odpowiedź.
Zimano
1
Całkowicie zgadzam się, w jaki sposób można dodawać odpowiedzi na tej stronie bez minimalnej uwagi do kodu wcięcia i jasności
Massimiliano Kraus