Alguém já usou o Bridge Pattern em um aplicativo do mundo real? Se sim, como você o usou? Sou eu, ou é apenas o Padrão do Adaptador com uma pequena injeção de dependência jogada na mistura? Ele realmente merece seu próprio padrão?
Alguém já usou o Bridge Pattern em um aplicativo do mundo real? Se sim, como você o usou? Sou eu, ou é apenas o Padrão do Adaptador com uma pequena injeção de dependência jogada na mistura? Ele realmente merece seu próprio padrão?
Respostas:
Um exemplo clássico do padrão Bridge é usado na definição de formas em um ambiente de interface do usuário (consulte a entrada Wikipedia do padrão Bridge ). O padrão Bridge é um composto dos padrões Template e Strategy .
É uma visão comum alguns aspectos do padrão do adaptador no padrão Bridge. No entanto, para citar este artigo :
À primeira vista, o padrão Bridge se parece muito com o padrão Adapter, pois uma classe é usada para converter um tipo de interface em outro. No entanto, a intenção do padrão do adaptador é fazer com que as interfaces de uma ou mais classes sejam iguais às de uma classe específica. O padrão Bridge foi projetado para separar a interface de uma classe de sua implementação, para que você possa variar ou substituir a implementação sem alterar o código do cliente.
Há uma combinação das respostas de Federico e John .
Quando:
----Shape---
/ \
Rectangle Circle
/ \ / \
BlueRectangle RedRectangle BlueCircle RedCircle
Refatorar para:
----Shape--- Color
/ \ / \
Rectangle(Color) Circle(Color) Blue Red
O padrão Bridge é uma aplicação do antigo conselho, "prefira composição sobre herança". Torna-se útil quando você deve subclassificar momentos diferentes de maneiras ortogonais entre si. Digamos que você deve implementar uma hierarquia de formas coloridas. Você não subclassaria Shape com retângulo e círculo e, em seguida, subclasse Rectangle com RedRectangle, BlueRectangle e GreenRectangle e o mesmo para Circle, não é? Você prefere dizer que cada forma tem uma cor e implementar uma hierarquia de cores, e esse é o padrão de ponte. Bem, eu não implementaria uma "hierarquia de cores", mas você entendeu ...
Quando:
A
/ \
Aa Ab
/ \ / \
Aa1 Aa2 Ab1 Ab2
Refatorar para:
A N
/ \ / \
Aa(N) Ab(N) 1 2
Adapter e Bridge certamente estão relacionados, e a distinção é sutil. É provável que algumas pessoas que pensam estar usando um desses padrões estejam realmente usando o outro padrão.
A explicação que eu vi é que o Adapter é usado quando você está tentando unificar as interfaces de algumas classes incompatíveis que já existem . O adaptador funciona como um tipo de tradutor para implementações que podem ser consideradas herdadas .
Enquanto o padrão Bridge é usado para código com maior probabilidade de ser greenfield. Você está projetando o Bridge para fornecer uma interface abstrata para uma implementação que precisa variar, mas também define a interface dessas classes de implementação.
Drivers de dispositivo é um exemplo frequentemente citado de Bridge, mas eu diria que é um Bridge se você estiver definindo as especificações da interface para fornecedores de dispositivos, mas é um Adaptador se estiver usando drivers de dispositivo existentes e criando uma classe de wrapper para fornecer uma interface unificada.
Portanto, em termos de código, os dois padrões são muito semelhantes. Em termos de negócios, eles são diferentes.
Consulte também http://c2.com/cgi/wiki?BridgePattern
Na minha experiência, o Bridge é um padrão frequentemente recorrente, porque é a solução sempre que há duas dimensões ortogonais no domínio . Por exemplo, formas e métodos de desenho, comportamentos e plataformas, formatos de arquivo e serializadores e assim por diante.
E um conselho: sempre pense nos padrões de design da perspectiva conceitual , não da perspectiva da implementação. Do ponto de vista correto, o Bridge não pode ser confundido com o Adapter, porque resolve um problema diferente, e a composição é superior à herança, não por causa de si mesma, mas porque permite lidar com preocupações ortogonais separadamente.
A intenção do Bridge e do Adapter é diferente e precisamos dos dois padrões separadamente.
Padrão de ponte:
Use o padrão Bridge quando:
A resposta de John Sonmez mostra claramente a eficácia do padrão de ponte na redução da hierarquia de classes.
Você pode consultar o link da documentação abaixo para obter uma melhor visão do padrão de ponte com o exemplo de código
Padrão do adaptador :
Principais diferenças:
Pergunta SE relacionada com diagrama UML e código de trabalho:
Diferença entre o padrão Bridge e o adaptador
Artigos úteis:
artigo de padrão de ponte de criação de fontes
artigo do padrão do adaptador de criação de fontes
artigo de padrão de ponte journaldev
EDITAR:
Exemplo do mundo real do padrão de ponte (conforme sugestão de meta.stackoverflow.com, exemplo de site de documentação incorporado neste post, pois a documentação vai se pôr)
O padrão de ponte desacopla a abstração da implementação para que ambos possam variar independentemente. Foi alcançado com a composição e não com a herança.
Padrão de ponte UML da Wikipedia:
Você tem quatro componentes neste padrão.
Abstraction
: Define uma interface
RefinedAbstraction
: Implementa abstração:
Implementor
: Define uma interface para implementação
ConcreteImplementor
: Implementa a interface do implementador.
The crux of Bridge pattern :
Duas hierarquias de classe ortogonais usando composição (e sem herança). A hierarquia de abstração e a hierarquia de implementação podem variar independentemente. A implementação nunca se refere à abstração. Abstração contém a interface de Implementação como um membro (através da composição). Essa composição reduz mais um nível de hierarquia de herança.
Caso de uso de palavra real:
Permita que veículos diferentes tenham ambas as versões do sistema manual e automático.
Código de exemplo:
/* 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();
}
}
resultado:
Car handles Manual gear
Car handles Auto gear
Truck handles Manual gear
Truck handles Auto gear
Explicação:
Vehicle
é uma abstração. Car
e Truck
são duas implementações concretas de Vehicle
.Vehicle
define um método resumo: addGear()
.Gear
é interface do implementadorManualGear
e AutoGear
são duas implementações de Gear
Vehicle
contém implementor
interface em vez de implementar a interface. Compositon
A interface do implementador é o cerne desse padrão: permite que a abstração e a implementação variem independentemente. Car
e Truck
definir a implementação (abstração redefinido) de abstração: addGear()
: Contém Gear
- De qualquer Manual
ouAuto
Casos de uso para o padrão Bridge :
Eu usei o padrão de ponte no trabalho. Eu programa em C ++, onde geralmente é chamado de idioma PIMPL (ponteiro para implementação). Se parece com isso:
class A
{
public:
void foo()
{
pImpl->foo();
}
private:
Aimpl *pImpl;
};
class Aimpl
{
public:
void foo();
void bar();
};
Neste exemplo class A
contém a interface e class Aimpl
a implementação.
Um uso para esse padrão é expor apenas alguns dos membros públicos da classe de implementação, mas não outros. No exemplo, apenas Aimpl::foo()
pode ser chamado através da interface pública de A
, mas nãoAimpl::bar()
Outra vantagem é que você pode definir Aimpl
em um arquivo de cabeçalho separado que não precise ser incluído pelos usuários do A
. Tudo o que você precisa fazer é usar uma declaração de encaminhamento de Aimpl
antes de A
ser definida e mover as definições de todas as funções de membro referenciadas pImpl
no arquivo .cpp. Isso permite manter o Aimpl
cabeçalho privado e reduzir o tempo de compilação.
Para colocar exemplo de forma no código:
#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;
}
A saída é:
Drawn a Square of Red Color
Drawn a Circle of Blue Color
Observe a facilidade com que novas cores e formas podem ser adicionadas ao sistema sem levar a uma explosão de subclasses devido a permutações.
Você está trabalhando para uma companhia de seguros onde desenvolve um aplicativo de fluxo de trabalho que gerencia diferentes tipos de tarefas: contabilidade, contrato, reclamações. Esta é a abstração. No lado da implementação, você deve poder criar tarefas de diferentes fontes: email, fax, e-messaging.
Você inicia seu design com estas classes:
public class Task {...}
public class AccountingTask : Task {...}
public class ContractTask : Task {...}
public class ClaimTask : Task {...}
Agora, como cada fonte deve ser tratada de uma maneira específica, você decide especializar cada tipo de tarefa:
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 {...}
Você acaba com 13 aulas. Adicionar um tipo de tarefa ou tipo de fonte se torna desafiador. Usar o padrão de ponte produz algo mais fácil de manter, dissociando a tarefa (a abstração) da fonte (que é uma preocupação de implementação):
// 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 {...}
Adicionar um tipo de tarefa ou fonte agora é muito mais fácil.
Nota: A maioria dos desenvolvedores não criaria a hierarquia de 13 classes antecipadamente para lidar com esse problema. No entanto, na vida real, talvez você não conheça com antecedência o número de tipos de fonte e tarefa; se você tiver apenas uma fonte e dois tipos de tarefas, provavelmente não desacoplaria a Tarefa da Origem. Em seguida, a complexidade geral aumenta à medida que novas fontes e tipos de tarefas são adicionados. Em algum momento, você irá refatorar e, na maioria das vezes, acabar com uma solução semelhante a uma 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);
}
}
}