Zrozumienie wzorca projektowego mostu

24

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?


źródło
2
Ja też tego nie rozumiem. Czekamy na odpowiedzi :)
Violet Giraffe
Istnieje wiele stron internetowych i książek opisujących wzorce projektowe. Nie sądzę, żeby powtarzanie tego, co już zostało napisane, ma wartość, być może możesz zadać konkretne pytanie. Zajęło mi również trochę czasu, aby to zrozumieć, ciągle zmieniając źródła i przykłady.
Helena

Odpowiedzi:

16

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*/}
Thurein
źródło
3
Bardzo fajny przykład. Dodałbym moje dwa centy: jest to bardzo dobry przykład preferowania kompozycji nad dziedziczeniem
zzfima,
1
Wydaje mi się, że warto, Monorailbo 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 SunShinelub CupCake, byliby SunshineiCupcake
Erike
Ta linia „dwie różne abstrakty, transport i przyspieszenie pociągu” pomaga wskazać, jakie są dwie hierarchie, i staramy się je rozwiązać, aby były niezależne.
wangdq
11

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, Implale odstępowałem od tej konwencji nazewnictwa.

Więc teraz TextboxWidgetwyglą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”.

tcarvin
źródło
Nie rozumiem, jak to ma być wzór mostu? Gdy dodajesz nowy komponent do hierarchii widgetów, jesteś zmuszony dodać ten nowy widget do WSZYSTKICH malarzy (Win32NewWidgetPainter, PPCNewWidgetPainter). To NIE są dwie niezależnie rosnące hierarchie. Aby uzyskać prawidłowy wzorzec mostu, nie należy podklasować klasy PlatformWidgetPainter dla każdego widżetu, zamiast tego należy otrzymać „deskryptor rysowania” widżetu.
Mazyod
Dzięki za opinie, masz rację. To było rok temu, że pisał to i przeglądając go teraz chciałbym powiedzieć, że dobrze opisuje most aż do ostatniego kawałka gdzie pochodzi Win32TextboxPainteri Win32ListPainter od Win32WidgetPainter. Państwo może mieć drzewo dziedziczenia po stronie realizacji, ale powinien być bardziej ogólny (być może StaticStyleControlPainter, EditStyleControlPainteri ButtonStyleControlPainter) wszelkich niezbędnych operacji prymitywnych nadpisana, ile potrzeba. Jest to bliższe prawdziwemu kodowi, na którym opierałem przykład.
tcarvin
3

Most zamierza oddzielić abstrakcję od jej konkretnej implementacji , aby oba mogły się różnić niezależnie:

  • doprecyzuj abstrakcję za pomocą podklasy
  • zapewniać różne implementacje, także przez podklasowanie, bez konieczności poznawania abstrakcji ani jej udoskonaleń.
  • w razie potrzeby wybierz w czasie wykonywania najbardziej odpowiednią implementację .

Most osiąga to za pomocą kompozycji:

  • abstrakcja odnosi się (odwołanie lub wskaźnik) do obiektu implementacji
  • abstrakcja i jej udoskonalenia znają tylko interfejs implementacji

wprowadź opis zdjęcia tutaj

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:

  • adaptery są często używane, gdy wykryta zostanie niezgodność, a sprzężenie jest nieprzewidziane
  • mosty są używane od początku projektowania, kiedy oczekuje się, że klasy będą ewoluować niezależnie.
Christophe
źródło