Qualcuno ha mai usato Bridge Pattern in un'applicazione del mondo reale? Se è così, come l'hai usato? Sono io, o è solo il modello adattatore con una piccola iniezione di dipendenza gettata nel mix? Merita davvero il suo modello?
Qualcuno ha mai usato Bridge Pattern in un'applicazione del mondo reale? Se è così, come l'hai usato? Sono io, o è solo il modello adattatore con una piccola iniezione di dipendenza gettata nel mix? Merita davvero il suo modello?
Risposte:
Un classico esempio del modello Bridge viene utilizzato nella definizione delle forme in un ambiente UI (vedere la voce Wikipedia del modello Bridge ). Il modello Bridge è un composto dei modelli Template e Strategy .
È una visione comune alcuni aspetti del modello Adapter nel modello Bridge. Tuttavia, per citare da questo articolo :
A prima vista, il modello Bridge assomiglia molto al modello Adapter in quanto una classe viene utilizzata per convertire un tipo di interfaccia in un altro. Tuttavia, l'intento del modello Adapter è di rendere le interfacce di una o più classi uguali a quelle di una determinata classe. Il modello Bridge è progettato per separare l'interfaccia di una classe dalla sua implementazione in modo da poter variare o sostituire l'implementazione senza modificare il codice client.
C'è una combinazione delle risposte di Federico e John .
Quando:
----Shape---
/ \
Rectangle Circle
/ \ / \
BlueRectangle RedRectangle BlueCircle RedCircle
Rifattore per:
----Shape--- Color
/ \ / \
Rectangle(Color) Circle(Color) Blue Red
Il modello Bridge è un'applicazione del vecchio consiglio, "preferisci la composizione rispetto all'eredità". È utile quando devi sottoclassare diverse volte in modo ortogonale tra loro. Supponiamo che tu debba implementare una gerarchia di forme colorate. Non dovresti sottoclassare Shape con Rectangle e Circle e quindi sottoclassare Rectangle con RedRectangle, BlueRectangle e GreenRectangle e lo stesso per Circle, vero? Preferiresti dire che ogni forma ha un colore e implementare una gerarchia di colori, e questo è il modello a ponte. Bene, non implementerei una "gerarchia di colori", ma hai avuto l'idea ...
Quando:
A
/ \
Aa Ab
/ \ / \
Aa1 Aa2 Ab1 Ab2
Rifattore per:
A N
/ \ / \
Aa(N) Ab(N) 1 2
Adapter e Bridge sono sicuramente correlati e la distinzione è sottile. È probabile che alcune persone che pensano di utilizzare uno di questi schemi stiano effettivamente utilizzando l'altro modello.
La spiegazione che ho visto è che Adapter viene utilizzato quando si tenta di unificare le interfacce di alcune classi incompatibili che già esistono . L'adattatore funziona come una sorta di traduttore per implementazioni che potrebbero essere considerate legacy .
Considerando che il modello Bridge viene utilizzato per il codice che ha più probabilità di essere greenfield. Stai progettando il Bridge per fornire un'interfaccia astratta per un'implementazione che deve variare, ma definisci anche l'interfaccia di quelle classi di implementazione.
I driver di dispositivo sono un esempio spesso citato di Bridge, ma direi che è un Bridge se si definiscono le specifiche dell'interfaccia per i fornitori di dispositivi, ma è un Adapter se si stanno prendendo i driver di dispositivo esistenti e si sta creando una classe wrapper fornire un'interfaccia unificata.
Quindi, per quanto riguarda il codice, i due modelli sono molto simili. Per quanto riguarda il business, sono diversi.
Vedi anche http://c2.com/cgi/wiki?BridgePattern
Nella mia esperienza, Bridge è un modello abbastanza frequente, perché è la soluzione ogni volta che ci sono due dimensioni ortogonali nel dominio . Ad esempio forme e metodi di disegno, comportamenti e piattaforme, formati di file e serializzatori e così via.
E un consiglio: pensa sempre ai modelli di progettazione dal punto di vista concettuale , non dal punto di vista dell'implementazione. Dal giusto punto di vista, Bridge non può essere confuso con Adapter, perché risolvono un problema diverso e la composizione è superiore all'eredità non per il bene di se stessa, ma perché consente di gestire separatamente le preoccupazioni ortogonali.
L'intento di Bridge e Adapter è diverso e abbiamo bisogno di entrambi i modelli separatamente.
Modello del ponte:
Usa il modello Bridge quando:
La risposta di John Sonmez mostra chiaramente l'efficacia del modello a ponte nella riduzione della gerarchia di classi.
È possibile fare riferimento al seguente collegamento alla documentazione per ottenere una migliore comprensione del modello di ponte con un esempio di codice
Modello di adattatore :
Differenze chiave:
Domanda SE relativa con diagramma UML e codice di lavoro:
Differenza tra il modello Bridge e il modello Adapter
Articoli utili:
articolo modello bridge di origine
articolo modello adattatore sorgente
articolo modello ponte journaldev
MODIFICARE:
Esempio del mondo reale di Bridge Pattern (Come suggerito da meta.stackoverflow.com, esempio di sito di documentazione incorporato in questo post poiché la documentazione sta per tramontare)
Il modello a ponte disaccoppia l'astrazione dall'implementazione in modo che entrambi possano variare in modo indipendente. È stato realizzato con la composizione piuttosto che con l'eredità.
Bridge pattern UML da Wikipedia:
Hai quattro componenti in questo modello.
Abstraction
: Definisce un'interfaccia
RefinedAbstraction
: Implementa l'astrazione:
Implementor
: Definisce un'interfaccia per l'implementazione
ConcreteImplementor
: Implementa l'interfaccia dell'attuatore.
The crux of Bridge pattern :
Due gerarchie di classi ortogonali che utilizzano la composizione (e nessuna eredità). La gerarchia di astrazione e la gerarchia di implementazione possono variare in modo indipendente. L'implementazione non si riferisce mai all'astrazione. L'astrazione contiene l'interfaccia di implementazione come membro (attraverso la composizione). Questa composizione riduce un ulteriore livello di gerarchia ereditaria.
Caso d'uso parola reale:
Consentire a veicoli diversi di avere entrambe le versioni del sistema di cambio manuale e automatico.
Codice di esempio:
/* Implementor interface*/
interface Gear{
void handleGear();
}
/* Concrete Implementor - 1 */
class ManualGear implements Gear{
public void handleGear(){
System.out.println("Manual gear");
}
}
/* Concrete Implementor - 2 */
class AutoGear implements Gear{
public void handleGear(){
System.out.println("Auto gear");
}
}
/* Abstraction (abstract class) */
abstract class Vehicle {
Gear gear;
public Vehicle(Gear gear){
this.gear = gear;
}
abstract void addGear();
}
/* RefinedAbstraction - 1*/
class Car extends Vehicle{
public Car(Gear gear){
super(gear);
// initialize various other Car components to make the car
}
public void addGear(){
System.out.print("Car handles ");
gear.handleGear();
}
}
/* RefinedAbstraction - 2 */
class Truck extends Vehicle{
public Truck(Gear gear){
super(gear);
// initialize various other Truck components to make the car
}
public void addGear(){
System.out.print("Truck handles " );
gear.handleGear();
}
}
/* Client program */
public class BridgeDemo {
public static void main(String args[]){
Gear gear = new ManualGear();
Vehicle vehicle = new Car(gear);
vehicle.addGear();
gear = new AutoGear();
vehicle = new Car(gear);
vehicle.addGear();
gear = new ManualGear();
vehicle = new Truck(gear);
vehicle.addGear();
gear = new AutoGear();
vehicle = new Truck(gear);
vehicle.addGear();
}
}
produzione:
Car handles Manual gear
Car handles Auto gear
Truck handles Manual gear
Truck handles Auto gear
Spiegazione:
Vehicle
è un'astrazione. Car
e Truck
sono due implementazioni concrete di Vehicle
.Vehicle
definisce un metodo astratto: addGear()
.Gear
è l'interfaccia implementatoreManualGear
e AutoGear
sono due implementazioni di Gear
Vehicle
contiene l' implementor
interfaccia anziché implementare l'interfaccia. Compositon
dell'interfaccia dell'attuatore è il punto cruciale di questo modello: consente all'astrazione e all'implementazione di variare in modo indipendente. Car
e Truck
definire l'implementazione (astrazione ridefinita) per l'astrazione:: addGear()
Contiene Gear
- O Manual
oppureAuto
Casi d'uso per il modello Bridge :
Ho usato il modello del ponte al lavoro. Programma in C ++, dove viene spesso chiamato linguaggio PIMPL (puntatore all'implementazione). Sembra così:
class A
{
public:
void foo()
{
pImpl->foo();
}
private:
Aimpl *pImpl;
};
class Aimpl
{
public:
void foo();
void bar();
};
In questo esempio class A
contiene l'interfaccia e class Aimpl
contiene l'implementazione.
Un uso per questo modello è quello di esporre solo alcuni membri pubblici della classe di implementazione, ma non altri. Solo nell'esempio Aimpl::foo()
può essere chiamato tramite l'interfaccia pubblica di A
, ma nonAimpl::bar()
Un altro vantaggio è che è possibile definire Aimpl
in un file di intestazione separato che non deve essere incluso dagli utenti di A
. Tutto quello che devi fare è utilizzare una dichiarazione forward di Aimpl
prima che A
sia definita e spostare le definizioni di tutte le funzioni membro che fanno riferimento pImpl
nel file .cpp. Questo ti dà la possibilità di mantenere l' Aimpl
intestazione privata e ridurre i tempi di compilazione.
Per inserire un esempio di forma nel codice:
#include<iostream>
#include<string>
#include<cstdlib>
using namespace std;
class IColor
{
public:
virtual string Color() = 0;
};
class RedColor: public IColor
{
public:
string Color()
{
return "of Red Color";
}
};
class BlueColor: public IColor
{
public:
string Color()
{
return "of Blue Color";
}
};
class IShape
{
public:
virtual string Draw() = 0;
};
class Circle: public IShape
{
IColor* impl;
public:
Circle(IColor *obj):impl(obj){}
string Draw()
{
return "Drawn a Circle "+ impl->Color();
}
};
class Square: public IShape
{
IColor* impl;
public:
Square(IColor *obj):impl(obj){}
string Draw()
{
return "Drawn a Square "+ impl->Color();;
}
};
int main()
{
IColor* red = new RedColor();
IColor* blue = new BlueColor();
IShape* sq = new Square(red);
IShape* cr = new Circle(blue);
cout<<"\n"<<sq->Draw();
cout<<"\n"<<cr->Draw();
delete red;
delete blue;
return 1;
}
L'output è:
Drawn a Square of Red Color
Drawn a Circle of Blue Color
Nota la facilità con cui nuovi colori e forme possono essere aggiunti al sistema senza portare a un'esplosione di sottoclassi a causa di permutazioni.
Lavori per una compagnia assicurativa in cui sviluppi un'applicazione per il flusso di lavoro che gestisce diversi tipi di attività: contabilità, contratto, sinistri. Questa è l'astrazione. Dal punto di vista dell'implementazione, devi essere in grado di creare attività da diverse fonti: e-mail, fax, e-mail.
Inizi il tuo design con queste classi:
public class Task {...}
public class AccountingTask : Task {...}
public class ContractTask : Task {...}
public class ClaimTask : Task {...}
Ora, poiché ogni origine deve essere gestita in un modo specifico, decidi di specializzare ogni tipo di attività:
public class EmailAccountingTask : AccountingTask {...}
public class FaxAccountingTask : AccountingTask {...}
public class EmessagingAccountingTask : AccountingTask {...}
public class EmailContractTask : ContractTask {...}
public class FaxContractTask : ContractTask {...}
public class EmessagingContractTask : ContractTask {...}
public class EmailClaimTask : ClaimTask {...}
public class FaxClaimTask : ClaimTask {...}
public class EmessagingClaimTask : ClaimTask {...}
Si finisce con 13 lezioni. L'aggiunta di un tipo di attività o di un tipo di origine diventa problematica. L'uso del modello bridge produce qualcosa di più facile da mantenere disaccoppiando l'attività (l'astrazione) dalla fonte (che è una preoccupazione di implementazione):
// Source
public class Source {
public string GetSender();
public string GetMessage();
public string GetContractReference();
(...)
}
public class EmailSource : Source {...}
public class FaxSource : Source {...}
public class EmessagingSource : Source {...}
// Task
public class Task {
public Task(Source source);
(...)
}
public class AccountingTask : Task {...}
public class ContractTask : Task {...}
public class ClaimTask : Task {...}
L'aggiunta di un tipo di attività o di una fonte è ora molto più semplice.
Nota: la maggior parte degli sviluppatori non creerebbe la gerarchia di 13 classi in anticipo per gestire questo problema. Tuttavia, nella vita reale, potresti non conoscere in anticipo il numero di fonti e tipi di attività; se hai solo una fonte e due tipi di attività, probabilmente non disaccoppi l'attività dalla fonte. Quindi, la complessità complessiva aumenta man mano che vengono aggiunte nuove fonti e tipi di attività. Ad un certo punto, refatterai e, molto spesso, finirai con una soluzione a ponte.
Bridge design pattern we can easily understand helping of service and dao layer.
Dao layer -> create common interface for dao layer ->
public interface Dao<T>{
void save(T t);
}
public class AccountDao<Account> implement Dao<Account>{
public void save(Account){
}
}
public LoginDao<Login> implement Dao<Login>{
public void save(Login){
}
}
Service Layer ->
1) interface
public interface BasicService<T>{
void save(T t);
}
concrete implementation of service -
Account service -
public class AccountService<Account> implement BasicService<Account>{
private Dao<Account> accountDao;
public AccountService(AccountDao dao){
this.accountDao=dao;
}
public void save(Account){
accountDao.save(Account);
}
}
login service-
public class LoginService<Login> implement BasicService<Login>{
private Dao<Login> loginDao;
public AccountService(LoginDao dao){
this.loginDao=dao;
}
public void save(Login){
loginDao.save(login);
}
}
public class BridgePattenDemo{
public static void main(String[] str){
BasicService<Account> aService=new AccountService(new AccountDao<Account>());
Account ac=new Account();
aService.save(ac);
}
}
}