Comprensione del modello di progettazione del ponte


24

Non capisco affatto il modello di progettazione "a ponte". Ho visitato vari siti Web, ma non mi hanno aiutato.

Qualcuno può aiutarmi a capirlo?


2
Neanche io lo capisco. In attesa di vedere le risposte :)
Violet Giraffe

Ci sono molti siti Web e libri che descrivono modelli di design là fuori. Non penso che valga la pena ripetere ciò che è già stato scritto, forse puoi fare una domanda specifica. Mi ci è voluto un po 'anche per capirlo, continuando a passare tra fonti ed esempi diversi.
Helena,

Risposte:


16

In OOP usiamo il polimorfismo, quindi un'astrazione può avere molteplici implementazioni. Diamo un'occhiata al seguente esempio:

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

È stato introdotto un nuovo requisito che deve introdurre la prospettiva di accelerazione dei treni, quindi modificare il codice come indicato di seguito.

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

Il codice sopra riportato non è gestibile e manca di riutilizzabilità (supponendo che potremmo riutilizzare il meccanismo di accelerazione per la stessa piattaforma di binario). Il codice seguente applica il modello del ponte e separa le due diverse astrazioni, trasporto del treno e accelerazione .

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*/}

3
Esempio molto bello. Aggiungerei i miei due centesimi: questo è un ottimo esempio di preferire la composizione all'eredità
zzfima

1
Per quello che vale, penso che dovrebbe essere Monoraildato che in realtà non sono due parole, è una sola parola (composta). Un MonoRail sarebbe una sottoclasse di Rail anziché un diverso tipo di rail (che è). Proprio come non SunShineCupCakeSunshineCupcake
useremmo

Questa linea "le due diverse astrazioni, trasporto ferroviario e accelerazione" aiuta, sottolinea quali sono le due gerarchie e ciò che cerchiamo di risolvere è di farle variare in modo indipendente.
wangdq,

11

Mentre la maggior parte dei modelli di design hanno nomi utili, trovo il nome "Bridge" non intuitivo per quanto riguarda ciò che fa.

Concettualmente, si inseriscono i dettagli di implementazione utilizzati da una gerarchia di classi in un altro oggetto, in genere con la propria gerarchia. In tal modo, si rimuove una stretta dipendenza da tali dettagli di implementazione e si consente ai dettagli di tale implementazione di cambiare.

Su piccola scala, l'ho paragonato all'uso di un modello di strategia nel modo in cui è possibile inserire un nuovo comportamento. Invece di limitarsi a racchiudere un algoritmo come spesso si vede in una strategia, l'oggetto dell'implementazione è solitamente più ricco di funzionalità. E quando applichi il concetto a un'intera gerarchia di classi il modello più grande diventa un ponte. (Ancora una volta, odio il nome).

Non è uno schema che userai ogni giorno, ma l'ho trovato utile quando gestisci una potenziale esplosione di classi che può accadere quando hai un (apparente) bisogno di ereditarietà multipla.

Ecco un esempio del mondo reale:

Ho uno strumento RAD che ti consente di rilasciare e configurare i controlli su un'area di progettazione, quindi ho un modello a oggetti come questo:

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

E così via, forse con una dozzina di controlli.

Ma poi viene aggiunto un nuovo requisito per supportare più temi (look-n-feel). Diciamo che abbiamo i seguenti temi: Win32, WinCE, WinPPC, WinMo50, WinMo65. Ogni tema avrebbe valori o implementazioni differenti per le operazioni relative al rendering come DefaultFont, DefaultBackColor, BorderWidth, DrawFrame, DrawScrollThumb, ecc.

Potrei creare un modello a oggetti come questo:

Win32TextboxWidget : TextboxWidget

Win32ListWidget : ListWidget

ecc., per un tipo di controllo

WinCETextboxWidget : TextboxWidget

WinCEListWidget : ListWidget

ecc., per ogni altro tipo di controllo (di nuovo)

Ti viene l'idea: ottieni un'esplosione di classe del numero di widget per il numero di temi. Ciò complica il progettista RAD rendendolo consapevole di ogni tema. Inoltre, l'aggiunta di nuovi temi impone la modifica del progettista RAD. Inoltre, vi è molta implementazione comune in un tema che sarebbe bello ereditare, ma i controlli stanno già ereditando da una base comune ( Widget).

Quindi, invece, ho creato una gerarchia di oggetti separata che implementa il tema. Ogni widget conterrebbe un riferimento all'oggetto che implementa le operazioni di rendering. In molti testi, questa classe ha il suffisso Implma ho deviato da quella convenzione di denominazione.

Quindi ora il mio TextboxWidgetassomiglia a questo:

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

E posso fare in modo che i miei vari pittori ereditino la mia base tematica specifica, cosa che prima non potevo fare:

Win32WidgetPainter
+ DefaultFont
+ DefaultFontSize
+ DefaultColors
+ DrawFrame
+ Etc

Win32TextboxPainter : Win32WidgetPainter

Win32ListPainter : Win32WidgetPainter

Una delle cose belle è che posso caricare dinamicamente le implementazioni in fase di esecuzione, permettendomi di aggiungere tutti i temi che voglio senza cambiare il software di base. In altre parole, la mia "implementazione può variare indipendentemente dall'astrazione".


Non capisco come si supponga che questo sia il modello del ponte? Quando aggiungi un nuovo componente alla gerarchia dei widget, sei costretto ad aggiungere quel nuovo widget a TUTTI i pittori (Win32NewWidgetPainter, PPCNewWidgetPainter). NON si tratta di due gerarchie a crescita indipendente. Per un corretto modello di bridge, non si dovrebbe sottoclassare la classe PlatformWidgetPainter per ciascun widget, ma si dovrebbe ricevere un "descrittore di disegno" del widget.
Mazyod,

Grazie per il feedback, hai ragione. È stato anni fa che l'ho pubblicato e, esaminandolo ora, direi che descrive bene il bridge fino all'ultimo bit da cui sono derivato Win32TextboxPaintere Win32ListPainter da cui provengo Win32WidgetPainter. È possibile avere un albero di eredità sul lato implementazione, ma dovrebbe essere più generico (forse StaticStyleControlPainter, EditStyleControlPaintere ButtonStyleControlPainter) con eventuali operazioni primitive necessarie sovrascritti, se necessario. Questo è più vicino al vero codice su cui basavo l'esempio.
Tcarvin,

3

Il ponte intende separare un'astrazione dalla sua implementazione concreta , in modo che entrambi possano variare in modo indipendente:

  • perfeziona l'astrazione con la sottoclasse
  • fornire implementazioni diverse, anche per sottoclasse, senza dover conoscere né l'astrazione né i suoi perfezionamenti.
  • se necessario, scegliere in fase di esecuzione qual è l' implementazione più adatta .

Il bridge ci riesce usando la composizione:

  • l'astrazione si riferisce (riferimento o puntatore) a un oggetto di implementazione
  • l'astrazione e i suoi perfezionamenti conoscevano solo l'interfaccia di implementazione

inserisci qui la descrizione dell'immagine

Ulteriori osservazioni su una frequente confusione

Questo modello è molto simile al modello dell'adattatore: l' astrazione offre un'interfaccia diversa per un'implementazione e utilizza la composizione per farlo. Ma:

La differenza chiave tra questi modelli sta nelle loro intenzioni
- Gamma & al, in " Modelli di progettazione, elemento di software riutilizzabile OO " , 1995

In questo eccellente libro fondamentale sui modelli di design, gli autori osservano anche che:

  • gli adattatori vengono spesso utilizzati quando viene rilevata un'incompatibilità e l'accoppiamento è imprevisto
  • i ponti vengono utilizzati sin dall'inizio del progetto, quando si prevede che le classi si evolveranno in modo indipendente.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.