Czy ktoś kiedykolwiek używał wzorca mostu w prawdziwej aplikacji? Jeśli tak, jak z tego korzystałeś? Czy to ja, czy jest to tylko wzorzec adaptera z niewielkim zastrzykiem zależności wrzuconym do miksu? Czy naprawdę zasługuje na swój własny wzór?
Czy ktoś kiedykolwiek używał wzorca mostu w prawdziwej aplikacji? Jeśli tak, jak z tego korzystałeś? Czy to ja, czy jest to tylko wzorzec adaptera z niewielkim zastrzykiem zależności wrzuconym do miksu? Czy naprawdę zasługuje na swój własny wzór?
Odpowiedzi:
Klasyczny przykład wzorca Bridge jest używany w definicji kształtów w środowisku interfejsu użytkownika (patrz wpis w Wikipedii dotyczący wzorca mostu ). Wzór Bridge jest kompozyt z szablonu i strategii desenie.
Niektóre aspekty wzorca adaptera we wzorcu mostka są typowym widokiem. Jednak cytując z tego artykułu :
Na pierwszy rzut oka wzorzec Bridge wygląda bardzo podobnie do wzorca Adapter, ponieważ klasa jest używana do konwersji jednego rodzaju interfejsu na inny. Jednak celem wzorca Adapter jest sprawienie, aby interfejs jednej lub kilku klas wyglądał tak samo, jak interfejs określonej klasy. Wzorzec Bridge został zaprojektowany w celu oddzielenia interfejsu klasy od jej implementacji, dzięki czemu można zmieniać lub zastępować implementację bez zmiany kodu klienta.
Jest połączenie odpowiedzi Federico i Johna .
Kiedy:
----Shape---
/ \
Rectangle Circle
/ \ / \
BlueRectangle RedRectangle BlueCircle RedCircle
Refaktoryzuj na:
----Shape--- Color
/ \ / \
Rectangle(Color) Circle(Color) Blue Red
Wzorzec Bridge jest zastosowaniem starej rady „przedkładaj kompozycję nad dziedziczenie”. Jest to przydatne, gdy musisz podklasować różne czasy w sposób ortogonalny względem siebie. Powiedzmy, że musisz wdrożyć hierarchię kolorowych kształtów. Nie utworzyłbyś podklasy Shape z Rectangle i Circle, a następnie podklasy Rectangle z RedRectangle, BlueRectangle i GreenRectangle i tak samo dla Circle, prawda? Wolałbyś powiedzieć, że każdy Shape ma kolor i zastosować hierarchię kolorów, a to jest wzór mostu. Cóż, nie wprowadziłbym „hierarchii kolorów”, ale masz pomysł ...
Kiedy:
A
/ \
Aa Ab
/ \ / \
Aa1 Aa2 Ab1 Ab2
Refaktoryzuj na:
A N
/ \ / \
Aa(N) Ab(N) 1 2
Adapter i mostek są z pewnością powiązane, a różnica jest subtelna. Jest prawdopodobne, że niektórzy ludzie, którzy myślą, że używają jednego z tych wzorców, w rzeczywistości używają drugiego.
Wyjaśnienie, które widziałem, jest takie, że Adapter jest używany, gdy próbujesz ujednolicić interfejsy niektórych niezgodnych klas, które już istnieją . Adapter działa jako rodzaj translatora implementacji, które można uznać za starsze .
Podczas gdy wzorzec Bridge jest używany dla kodu, który z większym prawdopodobieństwem będzie typu greenfield. Projektujesz Bridge, aby zapewnić abstrakcyjny interfejs dla implementacji, która musi się różnić, ale także definiujesz interfejs tych klas implementacji.
Sterowniki urządzeń są często cytowanym przykładem Bridge, ale powiedziałbym, że jest to Bridge, jeśli definiujesz specyfikację interfejsu dla dostawców urządzeń, ale jest to adapter, jeśli bierzesz istniejące sterowniki urządzeń i tworzysz klasę opakowania do zapewniają ujednolicony interfejs.
Więc pod względem kodu te dwa wzorce są bardzo podobne. Pod względem biznesowym są różne.
Zobacz także http://c2.com/cgi/wiki?BridgePattern
Z mojego doświadczenia wynika, że Bridge jest dość często powtarzającym się wzorcem, ponieważ jest rozwiązaniem, gdy w domenie występują dwa ortogonalne wymiary . Np. Kształty i metody rysowania, zachowania i platformy, formaty plików i serializatory i tak dalej.
I rada: zawsze myśl o wzorcach projektowych z perspektywy koncepcyjnej , a nie implementacyjnej. Z właściwego punktu widzenia Bridge nie można pomylić z adapterem, ponieważ rozwiązują inny problem, a kompozycja jest lepsza od dziedziczenia nie ze względu na samą siebie, ale dlatego, że pozwala osobno zająć się kwestiami ortogonalnymi.
Przeznaczenie Bridge i Adapter jest inne i potrzebujemy obu wzorców osobno.
Wzór mostka:
Użyj wzorca Bridge, gdy:
@ John Sonmez odpowiedź jasno pokazuje skuteczność wzorca mostu w redukcji hierarchii klas.
Możesz zapoznać się z poniższym linkiem do dokumentacji, aby uzyskać lepszy wgląd we wzorzec mostu z przykładem kodu
Wzór adaptera :
Kluczowe różnice:
Powiązane pytanie SE z diagramem UML i działającym kodem:
Różnica między wzorem mostka a wzorem adaptera
Przydatne artykuły:
artykuł o wzorach mostów źródłowych
artykuł wzór adaptera do produkcji źródła
artykuł o wzorach mostów Journaldev
EDYTOWAĆ:
Przykładowy wzorzec mostu w świecie rzeczywistym (zgodnie z sugestią meta.stackoverflow.com, przykład witryny z dokumentacją uwzględnioną w tym poście, ponieważ dokumentacja zostanie zachowana)
Wzorzec mostu oddziela abstrakcję od implementacji, dzięki czemu oba mogą się zmieniać niezależnie. Osiągnięto to raczej dzięki kompozycji niż dziedziczeniu.
Wzorzec mostkowy UML z Wikipedii:
W tym wzorze masz cztery komponenty.
Abstraction
: Definiuje interfejs
RefinedAbstraction
: Implementuje abstrakcję:
Implementor
: Definiuje interfejs do implementacji
ConcreteImplementor
: Implementuje interfejs implementatora.
The crux of Bridge pattern :
Dwie ortogonalne hierarchie klas wykorzystujące kompozycję (bez dziedziczenia). Hierarchia abstrakcji i hierarchia implementacji mogą się zmieniać niezależnie. Implementacja nigdy nie odnosi się do abstrakcji. Abstrakcja zawiera interfejs implementacji jako element członkowski (poprzez kompozycję). Ta kompozycja redukuje o jeden poziom hierarchii dziedziczenia.
Prawdziwe słowo Zastosowanie:
Włącz różne pojazdy, aby mieć obie wersje ręcznego i automatycznego systemu zmiany biegów.
Przykładowy kod:
/* 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();
}
}
wynik:
Car handles Manual gear
Car handles Auto gear
Truck handles Manual gear
Truck handles Auto gear
Wyjaśnienie:
Vehicle
jest abstrakcją. Car
i Truck
są dwiema konkretnymi implementacjami Vehicle
.Vehicle
definiuje metody abstrakcyjne: addGear()
.Gear
jest interfejsem implementatoraManualGear
i AutoGear
są dwiema implementacjami Gear
Vehicle
zawiera implementor
interfejs, a nie implementuje interfejs. Compositon
interfejsu implementatora jest sednem tego wzorca: pozwala na niezależne zróżnicowanie abstrakcji i implementacji. Car
i Truck
zdefiniuj implementację (przedefiniowaną abstrakcję) dla abstrakcji:: addGear()
Zawiera Gear
- Albo Manual
alboAuto
Przypadki użycia dla wzorca mostka :
W pracy używałem wzoru mostka. Programuję w C ++, gdzie często nazywany jest idiomem PIMPL (wskaźnik implementacji). To wygląda tak:
class A
{
public:
void foo()
{
pImpl->foo();
}
private:
Aimpl *pImpl;
};
class Aimpl
{
public:
void foo();
void bar();
};
W tym przykładzie class A
zawiera interfejs i class Aimpl
zawiera implementację.
Jednym z zastosowań tego wzorca jest ujawnienie tylko niektórych publicznych członków klasy implementacji, ale innych nie. W tym przykładzie Aimpl::foo()
można wywołać tylko za pośrednictwem publicznego interfejsu programu A
, ale nieAimpl::bar()
Kolejną zaletą jest to, że możesz zdefiniować Aimpl
w oddzielnym pliku nagłówkowym, który nie musi być dołączany przez użytkowników A
. Wszystko, co musisz zrobić, to użyć deklaracji forward Aimpl
przed A
zdefiniowaniem i przenieść definicje wszystkich funkcji pImpl
składowych, do których istnieją odniesienia, do pliku .cpp. Dzięki temu możesz zachować Aimpl
prywatność nagłówka i skrócić czas kompilacji.
Aby umieścić przykład kształtu w kodzie:
#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;
}
Wynik to:
Drawn a Square of Red Color
Drawn a Circle of Blue Color
Zwróć uwagę na łatwość, z jaką nowe kolory i kształty można dodawać do systemu bez powodowania eksplozji podklas z powodu permutacji.
Pracujesz dla firmy ubezpieczeniowej, w której tworzysz aplikację workflow, która zarządza różnego rodzaju zadaniami: księgowością, umową, roszczeniami. To jest abstrakcja. Po stronie wdrożenia musisz mieć możliwość tworzenia zadań z różnych źródeł: e-mail, faks, e-mailing.
Rozpoczynasz projektowanie od tych klas:
public class Task {...}
public class AccountingTask : Task {...}
public class ContractTask : Task {...}
public class ClaimTask : Task {...}
Teraz, ponieważ każde źródło musi być obsługiwane w określony sposób, decydujesz się na specjalizację każdego typu zadania:
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 {...}
Skończysz z 13 zajęciami. Dodanie typu zadania lub typu źródła staje się trudne. Użycie wzorca mostka daje coś łatwiejszego do utrzymania poprzez oddzielenie zadania (abstrakcji) od źródła (co jest problemem implementacyjnym):
// 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 {...}
Dodawanie typu zadania lub źródła jest teraz znacznie łatwiejsze.
Uwaga: większość programistów nie utworzyłaby z góry hierarchii 13 klas, aby rozwiązać ten problem. Jednak w prawdziwym życiu możesz nie znać z góry liczby źródeł i typów zadań; jeśli masz tylko jedno źródło i dwa typy zadań, prawdopodobnie nie oddzielisz zadania od źródła. Następnie ogólna złożoność rośnie w miarę dodawania nowych źródeł i typów zadań. W pewnym momencie dokonasz refaktoryzacji i najczęściej skończysz z rozwiązaniem podobnym do mostu.
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);
}
}
}