Wann verwenden Sie das Brückenmuster? Wie unterscheidet es sich vom Adaptermuster?


154

Hat jemand jemals das Brückenmuster in einer realen Anwendung verwendet? Wenn ja, wie haben Sie es verwendet? Bin ich es oder ist es nur das Adaptermuster mit einer kleinen Abhängigkeitsinjektion, die in die Mischung geworfen wird? Hat es wirklich ein eigenes Muster verdient?


Bitte akzeptieren Sie eine andere Antwort auf diese Frage. Die aktuell akzeptierte Antwort ist falsch und nicht hilfreich. Neuere Antworten sind weit überlegen.
jaco0646

Das GoF-Buch beantwortet diese Frage direkt.
jaco0646

Antworten:


76

Ein klassisches Beispiel für das Bridge-Muster wird bei der Definition von Formen in einer UI-Umgebung verwendet (siehe Wikipedia-Eintrag Bridge-Muster ). Die Brücke Muster ist ein Verbund der Vorlage und Strategie - Muster.

Es ist eine gängige Ansicht, einige Aspekte des Adaptermusters im Brückenmuster. Um jedoch aus diesem Artikel zu zitieren :

Auf den ersten Blick ähnelt das Bridge-Muster dem Adapter-Muster sehr, da eine Klasse verwendet wird, um eine Art von Schnittstelle in eine andere zu konvertieren. Mit dem Adaptermuster sollen jedoch die Schnittstellen einer oder mehrerer Klassen mit denen einer bestimmten Klasse identisch aussehen. Das Bridge-Muster dient dazu, die Schnittstelle einer Klasse von ihrer Implementierung zu trennen, sodass Sie die Implementierung variieren oder ersetzen können, ohne den Clientcode zu ändern.


1
Bridge hat nichts mit Vorlage oder Strategie zu tun. Brücke ist ein Strukturmuster. Vorlage und Strategie sind Verhaltensmuster.
jaco0646

248

Es gibt eine Kombination aus Federicos und Johns Antworten.

Wann:

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

Refactor zu:

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

6
Warum sollten Sie Farben vererben?
Vainolo

10
@vainolo weil Farbe eine Schnittstelle ist und Blau, Rot sind konkrete Farben
Weltschmerz

3
Dies ist nur ein Refactoring. Absicht des Brückenmusters: "Entkoppeln Sie eine Abstraktion von ihrer Implementierung, damit die beiden unabhängig voneinander variieren können." Wo ist die Abstraktion und wo ist die Implementierung hier?
Clapas

1
Ist Rectangle (Color) nicht abstrakter als das BlueRectangle-Ding?
Anton Shchastnyi

2
@clapas, Abstraktion ist "Shape.color" der Eigenschaft, daher sind Klasse Rot und Klasse Blau Implementierung und Farbschnittstelle Brücke.
Reco

230

Das Brückenmuster ist eine Anwendung des alten Hinweises "Komposition gegenüber Vererbung bevorzugen". Dies ist praktisch, wenn Sie verschiedene Zeiten orthogonal zueinander unterordnen müssen. Angenommen, Sie müssen eine Hierarchie farbiger Formen implementieren. Sie würden Shape nicht mit Rectangle und Circle unterordnen und dann Rectangle mit RedRectangle, BlueRectangle und GreenRectangle und das Gleiche für Circle, oder? Sie würden es vorziehen , zu sagen , dass jede Form hat eine Farbe und eine Hierarchie von Farben zu implementieren, und das ist die Brücke - Muster. Nun, ich würde keine "Hierarchie der Farben" implementieren, aber Sie bekommen die Idee ...


1
Siehe auch Anton Shchastnyi Diagramm unten für eine grafische Darstellung dieser Erklärung.
NomadeNumerique

2
Ich denke nicht, dass eine Farbe ein gutes Beispiel für eine Implementierungshierarchie ist, sie ist ziemlich verwirrend. Es gibt ein gutes Beispiel für das Bridge-Muster in "Design Patterns" des GoF, bei dem die Implementierung plattformabhängig ist: IBMs PM,
UNIXs

215

Wann:

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

Refactor zu:

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

3
Ich denke, es ist eine sehr pragmatische Herangehensweise an Muster: 1) suboptimales direktes Design beschreiben 2) Refactor-Design / Code für ein besser faktorisiertes
Alexey

1
Verwenden Sie das Mathematikkonzept, um das Bridge-Entwurfsmuster zu erklären. Sehr interessiert.
Jian Huang

1
Dies ist nur ein Refactoring. Absicht des Brückenmusters: "Entkoppeln Sie eine Abstraktion von ihrer Implementierung, damit die beiden unabhängig voneinander variieren können." Wo ist die Abstraktion und wo ist die Implementierung hier?
Clapas

John bringt es schön in einen Blog- Beitrag. Ich fand es eine gute Lektüre für einen Überblick auf hoher Ebene.
Vaibhav Bhalla

29

Adapter und Brücke sind sicherlich verwandt, und die Unterscheidung ist subtil. Es ist wahrscheinlich, dass einige Leute, die glauben, eines dieser Muster zu verwenden, tatsächlich das andere Muster verwenden.

Die Erklärung, die ich gesehen habe, ist, dass Adapter verwendet wird, wenn Sie versuchen, die Schnittstellen einiger bereits vorhandener inkompatibler Klassen zu vereinheitlichen . Der Adapter fungiert als eine Art Übersetzer für Implementierungen, die als Legacy angesehen werden können .

Während das Bridge-Muster für Code verwendet wird, bei dem es sich eher um eine grüne Wiese handelt. Sie entwerfen die Bridge so, dass sie eine abstrakte Schnittstelle für eine Implementierung bereitstellt, die variieren muss. Sie definieren jedoch auch die Schnittstelle dieser Implementierungsklassen.

Gerätetreiber sind ein häufig genanntes Beispiel für Bridge, aber ich würde sagen, es ist eine Bridge, wenn Sie die Schnittstellenspezifikation für Gerätehersteller definieren, aber es ist ein Adapter, wenn Sie vorhandene Gerätetreiber verwenden und eine Wrapper-Klasse erstellen Bereitstellung einer einheitlichen Schnittstelle.

In Bezug auf den Code sind die beiden Muster also sehr ähnlich. In geschäftlicher Hinsicht sind sie anders.

Siehe auch http://c2.com/cgi/wiki?BridgePattern


Hey Bill. Ich verstehe nicht, warum wir in Gerätetreibern unbedingt das Bridge-Muster verwenden. Ich meine, wir können die Implementierung (Lesen, Schreiben, Suchen usw.) einfach über Polymorphismus an die richtige Klasse delegieren, oder? Oder vielleicht mit einem Besucher? Warum muss es Bridge sein? Danke im Voraus.
Stdout

1
@zgulser, ja, Sie verwenden Polymorphismus. Das Bridge-Muster beschreibt eine Art der Verwendung von Unterklassen, um die Implementierung von der Abstraktion zu entkoppeln.
Bill Karwin

Sie meinten, die Formimplementierung (dh das Rechteck) von der Farbabstraktion zu entkoppeln, oder? Und ich glaube, Sie sagen, es gibt verschiedene Möglichkeiten, dies zu tun, und Bridge ist nur eine davon.
Stdout

Ja, Unterklassen haben andere Verwendungszwecke. Diese besondere Art der Verwendung von Unterklassen macht es zum Bridge-Muster.
Bill Karwin

Und die Entkopplung, die ich meine, ist von der abstrakten Formschnittstelle zu einer konkreten Rechteckimplementierung. Sie können also Code schreiben, der ein Objekt vom Typ "Form" benötigt, obwohl das konkrete Objekt tatsächlich eine Unterklasse von Form ist.
Bill Karwin

27

Nach meiner Erfahrung ist Bridge ein häufig wiederkehrendes Muster, da es die Lösung ist, wenn zwei orthogonale Dimensionen in der Domäne vorhanden sind . ZB Formen und Zeichenmethoden, Verhaltensweisen und Plattformen, Dateiformate und Serializer usw.

Und ein Rat: Denken Sie immer an Entwurfsmuster aus konzeptioneller Sicht , nicht aus Sicht der Implementierung. Aus der richtigen Sicht kann Bridge nicht mit Adapter verwechselt werden, da sie ein anderes Problem lösen und die Zusammensetzung der Vererbung nicht aus eigenen Gründen überlegen ist, sondern weil sie es ermöglicht, orthogonale Bedenken separat zu behandeln.


22

Die Absicht von Bridge und Adapter ist unterschiedlich und wir benötigen beide Muster getrennt.

Brückenmuster:

  1. Es ist ein Strukturmuster
  2. Abstraktion und Implementierung sind zur Kompilierungszeit nicht gebunden
  3. Abstraktion und Implementierung - beide können ohne Auswirkungen auf den Kunden variieren
  4. Verwendet Komposition über Vererbung.

Verwenden Sie das Brückenmuster, wenn:

  1. Sie möchten eine Laufzeitbindung der Implementierung,
  2. Sie haben eine Vielzahl von Klassen, die sich aus einer gekoppelten Schnittstelle und zahlreichen Implementierungen ergeben.
  3. Sie möchten eine Implementierung für mehrere Objekte freigeben.
  4. Sie müssen orthogonale Klassenhierarchien zuordnen.

Die Antwort von @ John Sonmez zeigt deutlich die Wirksamkeit des Brückenmusters bei der Reduzierung der Klassenhierarchie.

Unter dem folgenden Dokumentationslink finden Sie einen besseren Einblick in das Brückenmuster anhand eines Codebeispiels

Adaptermuster :

  1. Es ermöglicht zwei nicht miteinander verbundenen Schnittstellen, über die verschiedenen Objekte zusammenzuarbeiten und möglicherweise dieselbe Rolle zu spielen.
  2. Es ändert die ursprüngliche Schnittstelle.

Hauptunterschiede:

  1. Mit dem Adapter funktionieren die Dinge, nachdem sie entworfen wurden. Bridge lässt sie arbeiten, bevor sie es sind.
  2. Bridge wurde von vornherein so konzipiert, dass Abstraktion und Implementierung unabhängig voneinander variieren können . Der Adapter wird nachgerüstet, damit nicht verwandte Klassen zusammenarbeiten.
  3. Die Absicht: Mit dem Adapter können zwei nicht miteinander verbundene Schnittstellen zusammenarbeiten. Mit Bridge können Abstraktion und Implementierung unabhängig voneinander variieren.

Verwandte SE-Frage mit UML-Diagramm und Arbeitscode:

Unterschied zwischen Brückenmuster und Adaptermuster

Nützliche Artikel:

Artikel zur Herstellung von Brückenmustern

Artikel zur Herstellung von Quellenadaptern

journaldev Brückenmuster Artikel

BEARBEITEN:

Beispiel für ein Bridge-Muster in der realen Welt (Gemäß dem Vorschlag von meta.stackoverflow.com wurde ein Beispiel für eine Dokumentationssite in diesen Beitrag aufgenommen, da die Dokumentation untergehen wird.)

Das Brückenmuster entkoppelt die Abstraktion von der Implementierung, sodass beide unabhängig voneinander variieren können. Es wurde eher mit Komposition als mit Vererbung erreicht.

Brückenmuster UML aus Wikipedia:

Brückenmuster UML aus Wikipedia

Sie haben vier Komponenten in diesem Muster.

Abstraction: Es definiert eine Schnittstelle

RefinedAbstraction: Es implementiert Abstraktion:

Implementor: Es definiert eine Schnittstelle für die Implementierung

ConcreteImplementor: Es implementiert die Implementiererschnittstelle.

The crux of Bridge pattern :Zwei orthogonale Klassenhierarchien mit Komposition (und ohne Vererbung). Die Abstraktionshierarchie und die Implementierungshierarchie können unabhängig voneinander variieren. Die Implementierung bezieht sich niemals auf Abstraktion. Die Abstraktion enthält die Implementierungsschnittstelle als Mitglied (durch Komposition). Diese Zusammensetzung reduziert eine weitere Ebene der Vererbungshierarchie.

Echtes Wort Anwendungsfall:

Ermöglichen Sie verschiedenen Fahrzeugen, sowohl Versionen des manuellen als auch des automatischen Getriebesystems zu haben.

Beispielcode:

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

Ausgabe:

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

Erläuterung:

  1. Vehicle ist eine Abstraktion.
  2. Carund Trucksind zwei konkrete Implementierungen von Vehicle.
  3. Vehicledefiniert eine abstrakte Methode : addGear().
  4. Gear ist Implementiererschnittstelle
  5. ManualGearund AutoGearsind zwei Implementierungen von Gear
  6. Vehicleenthält eine implementorSchnittstelle, anstatt die Schnittstelle zu implementieren. Compositonder Implementiererschnittstelle ist der Kern dieses Musters: Es ermöglicht, dass Abstraktion und Implementierung unabhängig voneinander variieren.
  7. Carund Truckdefinieren Sie die Implementierung (neu definierte Abstraktion) für die Abstraktion addGear():: Sie enthält Gear- Entweder ManualoderAuto

Anwendungsfall (e) für das Brückenmuster :

  1. Abstraktion und Implementierung können sich unabhängig voneinander ändern und sind beim Kompilieren nicht gebunden
  2. Orthogonale Hierarchien zuordnen - eine für die Abstraktion und eine für die Implementierung .

"Mit dem Adapter funktionieren die Dinge, nachdem sie entworfen wurden. Mit Bridge funktionieren sie, bevor sie funktionieren." Vielleicht möchten Sie sich den steckbaren Adapter ansehen. Es handelt sich um eine Variante des Adapters, die von der GoF im Abschnitt "Adapter" ihres Entwurfsmusterbuchs beschrieben wird. Der Zweck besteht darin, eine Schnittstelle für Klassen zu erstellen, die noch nicht existieren. Ein steckbarer Adapter ist keine Bridge, daher glaube ich nicht, dass der erste Punkt gültig ist.
c1moore

Obwohl manuelle und automatische Ausrüstung möglicherweise unterschiedliche Implementierung für LKW und Auto erfordern
andigor

9

Ich habe das Brückenmuster bei der Arbeit verwendet. Ich programmiere in C ++, wo es oft als PIMPL-Idiom (Zeiger auf die Implementierung) bezeichnet wird. Es sieht aus wie das:

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

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

In diesem Beispiel class Aenthält die Schnittstelle und class Aimpldie Implementierung.

Eine Verwendung für dieses Muster besteht darin, nur einige der öffentlichen Mitglieder der Implementierungsklasse verfügbar zu machen, andere jedoch nicht. Im Beispiel Aimpl::foo()kann nur über die öffentliche Schnittstelle von aufgerufen werden A, aber nichtAimpl::bar()

Ein weiterer Vorteil ist, dass Sie Aimplin einer separaten Header-Datei definieren können, die von den Benutzern von nicht enthalten sein muss A. Sie müssen lediglich eine Vorwärtsdeklaration verwenden, Aimplbevor Adefiniert wurde, und die Definitionen aller Mitgliedsfunktionen, auf die verwiesen wird, pImplin die CPP-Datei verschieben. Auf diese Weise können Sie den AimplHeader privat halten und die Kompilierungszeit verkürzen.


2
Wenn Sie dieses Muster verwenden, benötigt der AImpl nicht einmal einen Header. Ich habe es gerade in die Implementierungsdatei für die A-Klasse
eingefügt

Ihr Implementierer ist privat. Ich habe eine neue Frage dazu, siehe stackoverflow.com/questions/17680762/…
Roland

7

So fügen Sie ein Formbeispiel in den Code ein:

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

Die Ausgabe ist:

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

Beachten Sie die Leichtigkeit, mit der neue Farben und Formen zum System hinzugefügt werden können, ohne dass es aufgrund von Permutationen zu einer Explosion von Unterklassen kommt.


0

Für mich ist es ein Mechanismus, mit dem Sie Schnittstellen austauschen können. In der realen Welt haben Sie möglicherweise eine Klasse, die mehr als eine Schnittstelle verwenden kann. Mit Bridge können Sie tauschen.


0

Sie arbeiten für eine Versicherungsgesellschaft, in der Sie eine Workflow-Anwendung entwickeln, die verschiedene Aufgaben verwaltet: Buchhaltung, Vertrag, Schadensfälle. Dies ist die Abstraktion. Auf der Implementierungsseite müssen Sie in der Lage sein, Aufgaben aus verschiedenen Quellen zu erstellen: E-Mail, Fax, E-Messaging.

Sie beginnen Ihr Design mit diesen Klassen:

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

Da nun jede Quelle auf eine bestimmte Weise behandelt werden muss, entscheiden Sie sich, jeden Aufgabentyp zu spezialisieren:

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

Sie haben 13 Klassen. Das Hinzufügen eines Aufgabentyps oder eines Quelltyps wird zu einer Herausforderung. Die Verwendung des Brückenmusters führt zu einer einfacheren Wartung, indem die Aufgabe (die Abstraktion) von der Quelle entkoppelt wird (was ein Implementierungsproblem darstellt):

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

Das Hinzufügen eines Aufgabentyps oder einer Quelle ist jetzt viel einfacher.

Hinweis: Die meisten Entwickler würden die 13 Klassenhierarchie nicht im Voraus erstellen, um dieses Problem zu beheben. Im wirklichen Leben kennen Sie die Anzahl der Quell- und Aufgabentypen jedoch möglicherweise nicht im Voraus. Wenn Sie nur eine Quelle und zwei Aufgabentypen haben, würden Sie die Aufgabe wahrscheinlich nicht von der Quelle entkoppeln. Dann wächst die Gesamtkomplexität, wenn neue Quellen und Aufgabentypen hinzugefügt werden. Irgendwann werden Sie umgestalten und erhalten meistens eine brückenartige Lösung.


-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
Ich habe abgelehnt, weil ich denke, dass dies eine verschlungene, schlecht formatierte Antwort ist.
Zimano

1
Stimmen Sie voll und ganz zu, wie Sie Antworten auf dieser Website veröffentlichen können, ohne die Einrückung und Klarheit des Codes zu beachten
Massimiliano Kraus
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.