W ogóle nie rozumiem wzorca projektowego „pomostowego”. Przeglądałem różne strony internetowe, ale one nie pomogły.
Czy ktoś może mi pomóc w zrozumieniu tego?
W ogóle nie rozumiem wzorca projektowego „pomostowego”. Przeglądałem różne strony internetowe, ale one nie pomogły.
Czy ktoś może mi pomóc w zrozumieniu tego?
Odpowiedzi:
W OOP używamy polimorfizmu, więc abstrakcja może mieć wiele implementacji. Spójrzmy na następujący przykład:
//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;
}
}
Wprowadzono nowy wymóg i należy wprowadzić perspektywę przyspieszenia pociągów, więc zmień kod jak poniżej.
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.
}
}
Powyższy kod nie jest możliwy do utrzymania i nie można go ponownie wykorzystać (zakładając, że moglibyśmy ponownie użyć mechanizmu przyspieszenia dla tej samej platformy gąsienicowej). Poniższy kod stosuje wzór mostu i oddziela dwie różne abstrakcje, transport pociągu i przyspieszenie .
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
bo tak naprawdę nie są to dwa słowa, to jedno (złożone) słowo. MonoRail byłby jakąś podklasą szyny zamiast innego rodzaju szyny (którą jest). Podobnie jak my nie użyłby SunShine
lub CupCake
, byliby Sunshine
iCupcake
Chociaż większość wzorców projektowych ma przydatne nazwy, uważam, że nazwa „Most” nie jest intuicyjna pod względem tego, co robi.
Koncepcyjnie przekazujesz szczegóły implementacji używane przez hierarchię klas do innego obiektu, zwykle z własną hierarchią. W ten sposób usuwasz ścisłą zależność od tych szczegółów implementacji i pozwalasz na zmianę szczegółów tej implementacji.
Na małą skalę porównuję to do użycia wzorca strategii w sposób, w jaki można podłączyć nowe zachowanie. Ale zamiast po prostu zawijać algorytm, co często widać w strategii, obiekt implementacji jest zwykle bardziej wypełniony funkcjami. A kiedy zastosujesz tę koncepcję do całej hierarchii klas, większy wzór stanie się mostem. (Znowu nienawidzę nazwy).
Nie jest to wzór, którego będziesz używać każdego dnia, ale uważam, że jest on pomocny w zarządzaniu potencjalną eksplozją klas, która może się zdarzyć, gdy masz (pozorną) potrzebę wielokrotnego dziedziczenia.
Oto prawdziwy przykład:
Mam narzędzie RAD, które pozwala upuszczać i konfigurować formanty na powierzchni projektowej, więc mam model obiektu taki jak ten:
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
I tak dalej, z może tuzinem kontroli.
Ale potem dodano nowe wymaganie do obsługi wielu motywów (look-n-feel). Powiedzmy, że mamy następujące tematy: Win32
, WinCE
, WinPPC
, WinMo50
, WinMo65
. Każdy motyw miałby inne wartości lub implementacje dla operacji związanych z renderowaniem, takich jak DefaultFont, DefaultBackColor, BorderWidth, DrawFrame, DrawScrollThumb itp.
Mógłbym stworzyć model obiektu taki jak ten:
Win32TextboxWidget : TextboxWidget
Win32ListWidget : ListWidget
itp. dla jednego typu kontroli
WinCETextboxWidget : TextboxWidget
WinCEListWidget : ListWidget
itp., dla każdego typu kontroli (ponownie)
Masz pomysł - dostajesz eksplozję klasową # widżetów razy # motywów. To komplikuje projektanta RAD, uświadamiając mu każdy temat. Ponadto dodanie nowych motywów zmusza projektanta RAD do modyfikacji. Ponadto istnieje wiele typowych implementacji w ramach kompozycji, które dobrze byłoby dziedziczyć, ale formanty już dziedziczą po wspólnej bazie ( Widget
).
Zamiast tego stworzyłem osobną hierarchię obiektów, która implementuje kompozycję. Każdy widżet zawierałby odwołanie do obiektu, który realizuje operacje renderowania. W wielu tekstach ta klasa jest opatrzona sufiksem, Impl
ale odstępowałem od tej konwencji nazewnictwa.
Więc teraz TextboxWidget
wygląda tak:
TextboxWidget : Widget // text box specific
+ Text
+ MaxLength
+ Font
+ ShowEditor
+ Painter // reference to the implementation of the widget rendering operations
+ Etc
Mogę sprawić, że różni malarze odziedziczą moją bazę tematyczną, czego wcześniej nie mogłem zrobić:
Win32WidgetPainter
+ DefaultFont
+ DefaultFontSize
+ DefaultColors
+ DrawFrame
+ Etc
Win32TextboxPainter : Win32WidgetPainter
Win32ListPainter : Win32WidgetPainter
Jedną z fajnych rzeczy jest to, że mogę dynamicznie ładować implementacje w czasie wykonywania, co pozwala mi dodawać tyle motywów, ile chcę, bez zmiany podstawowego oprogramowania. Innymi słowy, moja „implementacja może się różnić niezależnie od abstrakcji”.
Win32TextboxPainter
i Win32ListPainter
od Win32WidgetPainter
. Państwo może mieć drzewo dziedziczenia po stronie realizacji, ale powinien być bardziej ogólny (być może StaticStyleControlPainter
, EditStyleControlPainter
i ButtonStyleControlPainter
) wszelkich niezbędnych operacji prymitywnych nadpisana, ile potrzeba. Jest to bliższe prawdziwemu kodowi, na którym opierałem przykład.
Most zamierza oddzielić abstrakcję od jej konkretnej implementacji , aby oba mogły się różnić niezależnie:
Most osiąga to za pomocą kompozycji:
Dodatkowe uwagi na temat częstego zamieszania
Ten wzorzec jest bardzo podobny do wzorca adaptera: abstrakcja oferuje inny interfejs dla implementacji i wykorzystuje do tego kompozycję. Ale:
Kluczowa różnica między tymi wzorami leży w ich intencjach
- Gamma i in., W „ Wzorcach projektowych, element oprogramowania OO wielokrotnego użytku ” , 1995
W tej doskonałej książce na temat wzorców projektowych autorzy zauważają również, że: