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?
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?
Antworten:
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*/}
Monorail
da 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 SunShine
oder nicht verwenden würden CupCake
, würden sie Sunshine
und seinCupcake
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, Impl
aber ich habe von dieser Namenskonvention abgewichen.
So TextboxWidget
sieht 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".
Win32TextboxPainter
und Win32ListPainter
aus Win32WidgetPainter
. Sie können einen Vererbungsbaum auf der Implementierungsseite haben, aber es sollte mehr Generika (vielleicht sein StaticStyleControlPainter
, EditStyleControlPainter
und 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.
Die Brücke beabsichtigt, eine Abstraktion von ihrer konkreten Implementierung zu entkoppeln , so dass beide unabhängig voneinander variieren können:
Die Brücke erreicht dies durch Komposition:
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: