Non capisco affatto il modello di progettazione "a ponte". Ho visitato vari siti Web, ma non mi hanno aiutato.
Qualcuno può aiutarmi a capirlo?
Non capisco affatto il modello di progettazione "a ponte". Ho visitato vari siti Web, ma non mi hanno aiutato.
Qualcuno può aiutarmi a capirlo?
Risposte:
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*/}
Monorail
dato 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 SunShine
CupCake
Sunshine
Cupcake
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 Impl
ma ho deviato da quella convenzione di denominazione.
Quindi ora il mio TextboxWidget
assomiglia 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".
Win32TextboxPainter
e Win32ListPainter
da cui provengo Win32WidgetPainter
. È possibile avere un albero di eredità sul lato implementazione, ma dovrebbe essere più generico (forse StaticStyleControlPainter
, EditStyleControlPainter
e ButtonStyleControlPainter
) con eventuali operazioni primitive necessarie sovrascritti, se necessario. Questo è più vicino al vero codice su cui basavo l'esempio.
Il ponte intende separare un'astrazione dalla sua implementazione concreta , in modo che entrambi possano variare in modo indipendente:
Il bridge ci riesce usando la composizione:
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: