Verständnis des Brückendesignmusters

24

Ich verstehe das "Brücken" -Designmuster überhaupt nicht. Ich habe verschiedene Websites durchgesehen, aber sie haben nicht geholfen.

Kann mir jemand helfen, das zu verstehen?


quelle
2
Ich verstehe es auch nicht. Ich freue mich auf Antworten :)
Violet Giraffe
Es gibt viele Websites und Bücher, die Designmuster beschreiben. Ich glaube nicht, dass es sinnvoll ist, das, was bereits geschrieben wurde, zu wiederholen. Vielleicht können Sie eine bestimmte Frage stellen. Ich brauchte eine Weile, um es zu verstehen und zwischen verschiedenen Quellen und Beispielen zu wechseln.
Helena

Antworten:

16

In OOP verwenden wir Polymorphismus, sodass eine Abstraktion mehrere Implementierungen haben kann. Schauen wir uns das folgende Beispiel an:

//trains abstraction
public interface Train
{ 
    move();
}
public class MonoRail:Train
{
    public override move()
    {
        //use one track;
    }
}
public class Rail:Train
{
    public override move()
    {
        //use two tracks;
    }
}

Eine neue Anforderung wurde eingeführt und muss die Beschleunigungsperspektive der Züge berücksichtigen. Ändern Sie daher den Code wie folgt.

    public interface Train
    { 
        void move();
    }
    public class MonoRail:Train
    {
        public override void move()
        {
            //use one track;
        }
    }
    public class ElectricMonoRail:MonoRail
    {
        public override void move()
        {
            //use electric engine on one track.
        }
    }
    public class DieselMonoRail: MonoRail
    {
        public override void move()
        {
            //use diesel engine on one track.
        }
    }
    public class Rail:Train
    {
        public override void move()
        {
            //use two tracks;
        }
    }
    public class ElectricRail:Rail
    {
        public override void move()
        {
            //use electric engine on two tracks.
        }
    }
    public class DieselRail: Rail
    {
        public override void move()
        {
            //use diesel engine on two tracks.
        }
    }

Der obige Code ist nicht wartbar und nicht wiederverwendbar (vorausgesetzt, wir könnten den Beschleunigungsmechanismus für dieselbe Gleisplattform wiederverwenden). Der folgende Code wendet das Brückenmuster an und trennt die beiden unterschiedlichen Abstraktionen Zugtransport und Beschleunigung .

public interface Train
{ 
    void move(Accelerable engine);
}
public interface Accelerable
{
    public void accelerate();
}
public class MonoRail:Train
{
    public override void move(Accelerable engine)
    {
        //use one track;
        engine.accelerate(); //engine is pluggable (runtime dynamic)
    }
}
public class Rail:Train
{
    public override void move(Accelerable engine)
    {
        //use two tracks;
        engine.accelerate(); //engine is pluggable (runtime dynamic)
    }
}
public class ElectricEngine:Accelerable{/*implementation code for accelerable*/}
public class DieselEngine:Accelerable{/*implementation code for accelerable*/}
Thurein
quelle
3
Sehr schönes Beispiel. Ich würde meine zwei Cent addieren: Dies ist ein sehr gutes Beispiel dafür, wie man Kompositionen der Vererbung
vorzieht
1
Für was es wert ist, denke ich sollte es sein, Monorailda es nicht wirklich zwei Wörter ist, es ist ein einzelnes (zusammengesetztes) Wort. Eine MonoRail wäre eine Unterklasse von Rail anstelle einer anderen Art von Rail (die es ist). Genau wie wir SunShineoder nicht verwenden würden CupCake, würden sie Sunshineund seinCupcake
ErikE
Diese Zeile "Die zwei verschiedenen Abstraktionen, Zugtransport und Beschleunigung" zeigt, was die beiden Hierarchien sind und was wir zu lösen versuchen, ist, sie unabhängig voneinander zu variieren.
Wangdq
11

Während die meisten Entwurfsmuster hilfreiche Namen haben, finde ich den Namen "Bridge" in Bezug auf das, was er tut, nicht intuitiv.

Konzeptionell übertragen Sie die von einer Klassenhierarchie verwendeten Implementierungsdetails in ein anderes Objekt, normalerweise mit einer eigenen Hierarchie. Auf diese Weise entfernen Sie eine enge Abhängigkeit von diesen Implementierungsdetails und ermöglichen, dass sich die Details dieser Implementierung ändern.

Im Kleinen vergleiche ich dies mit der Verwendung eines Strategiemusters, mit dem Sie ein neues Verhalten einfügen können. Anstatt jedoch nur einen Algorithmus zu verpacken, wie dies häufig in einer Strategie zu sehen ist, ist das Implementierungsobjekt in der Regel mehr mit Features gefüllt. Und wenn Sie das Konzept auf eine gesamte Klassenhierarchie anwenden, wird das größere Muster zur Brücke. (Auch hier hasse den Namen).

Es ist kein Muster, das Sie jeden Tag verwenden werden, aber ich fand es hilfreich, wenn Sie eine potenzielle Explosion von Klassen verwalten, die auftreten kann, wenn Sie (offensichtlich) mehrere Vererbungen benötigen.

Hier ist ein reales Beispiel:

Ich habe ein RAD-Tool, mit dem Sie Steuerelemente auf einer Entwurfsoberfläche ablegen und konfigurieren können. Daher habe ich ein Objektmodell wie das folgende:

Widget // base class with design surface plumbing
+ Top
+ Left
+ Width
+ Height
+ Name
+ SendToBack
+ BringToFront
+ OnPropertyEdit
+ OnSelect
+ Validate
+ ShowEditor
+ Paint
+ Etc

TextboxWidget : Widget // text box specific
+ Text
+ MaxLength
+ Font
+ ShowEditor // override base to show a property editor form specific to a Textbox
+ Paint // override to render a textbox onto the surface    
+ Etc

ListWidget : Widget // list specific
+ Items
+ SelectedItem
+ ShowEditor // override base to show a property editor form specific to a List
+ Paint // override to render a list onto the surface
+ Etc

Und so weiter, mit vielleicht einem Dutzend Kontrollen.

Dann wird eine neue Anforderung hinzugefügt, um mehrere Themen zu unterstützen (Look-n-Feel). Lassen Sie uns sagen , dass wir die folgenden Themen haben: Win32, WinCE, WinPPC, WinMo50, WinMo65. Jedes Design hätte andere Werte oder Implementierungen für renderbezogene Operationen wie DefaultFont, DefaultBackColor, BorderWidth, DrawFrame, DrawScrollThumb usw.

Ich könnte ein Objektmodell wie dieses erstellen:

Win32TextboxWidget : TextboxWidget

Win32ListWidget : ListWidget

usw. für einen Kontrolltyp

WinCETextboxWidget : TextboxWidget

WinCEListWidget : ListWidget

usw., für jeden anderen Kontrolltyp (wieder)

Sie bekommen die Idee - Sie bekommen eine Klassenexplosion der Anzahl der Widgets, mal der Anzahl der Themen. Dies erschwert den RAD-Designer, indem er ihn auf jedes Thema aufmerksam macht. Wenn Sie neue Designs hinzufügen, muss der RAD-Designer geändert werden. Darüber hinaus gibt es eine Menge gemeinsamer Implementierungen innerhalb eines Themas, deren Vererbung sich lohnen würde, aber die Steuerelemente erben bereits von einer gemeinsamen Basis ( Widget).

Stattdessen habe ich eine separate Objekthierarchie erstellt, die das Thema implementiert. Jedes Widget enthält einen Verweis auf das Objekt, das die Rendervorgänge implementiert. In vielen Texten wird diese Klasse mit einem angefügt, Implaber ich habe von dieser Namenskonvention abgewichen.

So TextboxWidgetsieht mein jetzt so aus:

TextboxWidget : Widget // text box specific
+ Text
+ MaxLength
+ Font
+ ShowEditor
+ Painter // reference to the implementation of the widget rendering operations
+ Etc

Und ich kann meine verschiedenen Maler meine themenspezifische Basis erben lassen, was ich vorher nicht konnte:

Win32WidgetPainter
+ DefaultFont
+ DefaultFontSize
+ DefaultColors
+ DrawFrame
+ Etc

Win32TextboxPainter : Win32WidgetPainter

Win32ListPainter : Win32WidgetPainter

Eines der schönen Dinge ist, dass ich die Implementierungen zur Laufzeit dynamisch laden kann, so dass ich so viele Themen hinzufügen kann, wie ich möchte, ohne die Kernsoftware zu ändern. Mit anderen Worten, meine "Implementierung kann unabhängig von der Abstraktion variieren".

tcarvin
quelle
Ich verstehe nicht, wie das das Brückenmuster sein soll? Wenn Sie der Widget-Hierarchie eine neue Komponente hinzufügen, müssen Sie dieses neue Widget ALLEN Malern (Win32NewWidgetPainter, PPCNewWidgetPainter) hinzufügen. Dies sind NICHT zwei unabhängig voneinander wachsende Hierarchien. Für ein korrektes Bridge-Muster würden Sie die PlatformWidgetPainter-Klasse nicht für jedes Widget in eine Unterklasse einteilen, sondern ein Widget "Zeichnungsdeskriptor" erhalten.
Mazyod
Danke für das Feedback, du hast recht. Es war Jahre her , dass ich das geschrieben, und es jetzt bewerten den ich würde sagen , es Brücke gut beschreibt , bis das letzte Bit , wo ich abgeleitet Win32TextboxPainterund Win32ListPainter aus Win32WidgetPainter. Sie können einen Vererbungsbaum auf der Implementierungsseite haben, aber es sollte mehr Generika (vielleicht sein StaticStyleControlPainter, EditStyleControlPainterund ButtonStyleControlPainter) mit allen benötigten Grundoperationen überschrieben je nach Bedarf. Dies ist näher an dem realen Code, auf den ich das Beispiel gestützt habe.
Tcarvin
3

Die Brücke beabsichtigt, eine Abstraktion von ihrer konkreten Implementierung zu entkoppeln , so dass beide unabhängig voneinander variieren können:

  • Verfeinern Sie die Abstraktion mit Unterklassen
  • Bereitstellung verschiedener Implementierungen, auch durch Unterklassen, ohne die Abstraktion oder deren Verfeinerungen kennen zu müssen.
  • Wählen Sie bei Bedarf zur Laufzeit die am besten geeignete Implementierung aus .

Die Brücke erreicht dies durch Komposition:

  • Die Abstraktion bezieht sich (Referenz oder Zeiger) auf ein Implementierungsobjekt
  • Die Abstraktion und ihre Verfeinerungen kannten nur die Implementierungsschnittstelle

Bildbeschreibung hier eingeben

Zusätzliche Bemerkungen zu einer häufigen Verwirrung

Dieses Muster ist dem Adaptermuster sehr ähnlich: Die Abstraktion bietet eine andere Schnittstelle für eine Implementierung und verwendet dazu die Komposition. Aber:

Der Hauptunterschied zwischen diesen Mustern liegt in ihren Absichten
- Gamma & al, in " Design Patterns, Element wiederverwendbarer OO-Software " , 1995

In diesem ausgezeichneten wegweisenden Buch über Entwurfsmuster stellen die Autoren außerdem fest, dass:

  • Adapter werden häufig verwendet, wenn eine Inkompatibilität festgestellt wird und die Kopplung unvorhergesehen ist
  • Brücken werden von Anfang an verwendet, wenn erwartet wird, dass sich die Klassen unabhängig voneinander entwickeln.
Christophe
quelle