¿Alguien ha usado el patrón de puente en una aplicación del mundo real? Si es así, ¿cómo lo usaste? ¿Soy yo, o es solo el Patrón Adaptador con una pequeña inyección de dependencia en la mezcla? ¿Realmente merece su propio patrón?
¿Alguien ha usado el patrón de puente en una aplicación del mundo real? Si es así, ¿cómo lo usaste? ¿Soy yo, o es solo el Patrón Adaptador con una pequeña inyección de dependencia en la mezcla? ¿Realmente merece su propio patrón?
Respuestas:
Un ejemplo clásico del patrón Bridge se utiliza en la definición de formas en un entorno de interfaz de usuario (consulte la entrada de Wikipedia del patrón Bridge ). El patrón Puente es un compuesto de los patrones de Plantilla y Estrategia .
Es una vista común algunos aspectos del patrón Adaptador en el patrón Puente. Sin embargo, para citar este artículo :
A primera vista, el patrón Puente se parece mucho al patrón Adaptador en el sentido de que una clase se utiliza para convertir un tipo de interfaz a otra. Sin embargo, la intención del patrón Adaptador es hacer que una o más interfaces de clases se vean iguales a las de una clase en particular. El patrón Bridge está diseñado para separar la interfaz de una clase de su implementación para que pueda variar o reemplazar la implementación sin cambiar el código del cliente.
Hay una combinación de las respuestas de Federico y John .
Cuando:
----Shape---
/ \
Rectangle Circle
/ \ / \
BlueRectangle RedRectangle BlueCircle RedCircle
Refactorizar a:
----Shape--- Color
/ \ / \
Rectangle(Color) Circle(Color) Blue Red
El patrón Bridge es una aplicación del antiguo consejo, "prefiera la composición a la herencia". Se vuelve útil cuando debe subclasificar diferentes tiempos de manera ortogonal entre sí. Digamos que debe implementar una jerarquía de formas de colores. No subclase Forma con Rectángulo y Círculo y luego subclase Rectángulo con Rectángulo Rojo, Rectángulo Azul y Rectángulo Verde y lo mismo para Círculo, ¿verdad? Preferiría decir que cada Forma tiene un Color e implementar una jerarquía de colores, y ese es el Patrón de Puente. Bueno, no implementaría una "jerarquía de colores", pero se entiende la idea ...
Cuando:
A
/ \
Aa Ab
/ \ / \
Aa1 Aa2 Ab1 Ab2
Refactorizar a:
A N
/ \ / \
Aa(N) Ab(N) 1 2
El adaptador y el puente están ciertamente relacionados, y la distinción es sutil. Es probable que algunas personas que piensan que están usando uno de estos patrones realmente estén usando el otro patrón.
La explicación que he visto es que Adaptador se usa cuando intentas unificar las interfaces de algunas clases incompatibles que ya existen . El Adaptador funciona como una especie de traductor de implementaciones que podrían considerarse heredadas .
Mientras que el patrón Bridge se usa para código que es más probable que sea greenfield. Está diseñando el puente para proporcionar una interfaz abstracta para una implementación que debe variar, pero también define la interfaz de esas clases de implementación.
Los controladores de dispositivos son un ejemplo citado a menudo de Bridge, pero yo diría que es un Bridge si está definiendo las especificaciones de la interfaz para los proveedores de dispositivos, pero es un Adaptador si está tomando controladores de dispositivos existentes y haciendo una clase envolvente para Proporcionar una interfaz unificada.
Entonces, en cuanto al código, los dos patrones son muy similares. En cuanto a los negocios, son diferentes.
Ver también http://c2.com/cgi/wiki?BridgePattern
En mi experiencia, Bridge es un patrón recurrente bastante frecuente, porque es la solución siempre que haya dos dimensiones ortogonales en el dominio . Por ejemplo, formas y métodos de dibujo, comportamientos y plataformas, formatos de archivo y serializadores, etc.
Y un consejo: siempre piense en los patrones de diseño desde la perspectiva conceptual , no desde la perspectiva de implementación. Desde el punto de vista correcto, Bridge no se puede confundir con Adapter, porque resuelven un problema diferente, y la composición es superior a la herencia, no por sí misma, sino porque permite manejar las preocupaciones ortogonales por separado.
La intención de Bridge and Adapter es diferente y necesitamos ambos patrones por separado.
Patrón de puente:
Use el patrón Puente cuando:
La respuesta de @ John Sonmez muestra claramente la efectividad del patrón de puente para reducir la jerarquía de clases.
Puede consultar el siguiente enlace de documentación para obtener una mejor visión del patrón del puente con un ejemplo de código
Patrón adaptador :
Diferencias clave
Pregunta SE relacionada con el diagrama UML y el código de trabajo:
Diferencia entre patrón de puente y patrón de adaptador
Artículos utiles:
artículo de patrón de puente de creación de fuente
artículo de patrón de adaptador de creación de fuente
artículo del patrón del puente journaldev
EDITAR:
Ejemplo del mundo real del patrón de puente (según la sugerencia de meta.stackoverflow.com, ejemplo de sitio de documentación incorporado en esta publicación ya que la documentación se pondrá al sol)
El patrón de puente desacopla la abstracción de la implementación para que ambos puedan variar de forma independiente. Se ha logrado con la composición en lugar de la herencia.
Patrón de puente UML de Wikipedia:
Tienes cuatro componentes en este patrón.
Abstraction
: Define una interfaz
RefinedAbstraction
: Implementa la abstracción:
Implementor
: Define una interfaz para la implementación
ConcreteImplementor
: Implementa la interfaz del Implementador.
The crux of Bridge pattern :
Dos jerarquías de clases ortogonales que usan composición (y no herencia). La jerarquía de abstracción y la jerarquía de implementación pueden variar de forma independiente. La implementación nunca se refiere a la abstracción. Abstracción contiene la interfaz de implementación como miembro (a través de la composición). Esta composición reduce un nivel más de jerarquía de herencia.
Caso de uso de palabras reales:
Permita que diferentes vehículos tengan ambas versiones del sistema de engranaje manual y automático.
Código de ejemplo:
/* 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();
}
}
salida:
Car handles Manual gear
Car handles Auto gear
Truck handles Manual gear
Truck handles Auto gear
Explicación:
Vehicle
Es una abstracción. Car
y Truck
son dos implementaciones concretas de Vehicle
.Vehicle
define un método abstracto: addGear()
.Gear
es la interfaz del implementadorManualGear
y AutoGear
son dos implementaciones de Gear
Vehicle
contiene implementor
interfaz en lugar de implementar la interfaz. Compositon
La interfaz del implementador es crucial para este patrón: permite que la abstracción y la implementación varíen independientemente. Car
y Truck
defina la implementación (abstracción redefinida) para la abstracción:: addGear()
Contiene Gear
- O Manual
bienAuto
Caso (s) de uso para el patrón de puente :
He usado el patrón de puente en el trabajo. Programo en C ++, donde a menudo se le llama modismo PIMPL (puntero a la implementación). Se parece a esto:
class A
{
public:
void foo()
{
pImpl->foo();
}
private:
Aimpl *pImpl;
};
class Aimpl
{
public:
void foo();
void bar();
};
En este ejemplo class A
contiene la interfaz y class Aimpl
contiene la implementación.
Un uso para este patrón es exponer solo algunos de los miembros públicos de la clase de implementación, pero no otros. En el ejemplo solo Aimpl::foo()
se puede llamar a través de la interfaz pública de A
, pero noAimpl::bar()
Otra ventaja es que puede definir Aimpl
en un archivo de encabezado separado que no necesita ser incluido por los usuarios de A
. Todo lo que tiene que hacer es utilizar una declaración directa de Aimpl
antes de A
definir, y mover las definiciones de todas las funciones miembro que hacen referencia pImpl
al archivo .cpp. Esto le brinda la capacidad de mantener el Aimpl
encabezado privado y reducir el tiempo de compilación.
Para poner un ejemplo de forma en el 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;
}
El resultado es:
Drawn a Square of Red Color
Drawn a Circle of Blue Color
Tenga en cuenta la facilidad con la que se pueden agregar nuevos colores y formas al sistema sin provocar una explosión de subclases debido a permutaciones.
Está trabajando para una compañía de seguros donde desarrolla una aplicación de flujo de trabajo que gestiona diferentes tipos de tareas: contabilidad, contrato, reclamos. Esta es la abstracción. En el lado de la implementación, debe poder crear tareas de diferentes fuentes: correo electrónico, fax, mensajería electrónica.
Comienzas tu diseño con estas clases:
public class Task {...}
public class AccountingTask : Task {...}
public class ContractTask : Task {...}
public class ClaimTask : Task {...}
Ahora, dado que cada fuente debe manejarse de una manera específica, usted decide especializar cada tipo de tarea:
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 {...}
Terminas con 13 clases. Agregar un tipo de tarea o un tipo de fuente se convierte en un desafío. El uso del patrón de puente produce algo más fácil de mantener al desacoplar la tarea (la abstracción) de la fuente (que es un problema de implementación):
// 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 {...}
Agregar un tipo de tarea o una fuente ahora es mucho más fácil.
Nota: La mayoría de los desarrolladores no crearían la jerarquía de 13 clases por adelantado para manejar este problema. Sin embargo, en la vida real, es posible que no sepa de antemano el número de fuentes y tipos de tareas; Si solo tiene una fuente y dos tipos de tareas, probablemente no desacoplaría la Tarea de la Fuente. Luego, la complejidad general crece a medida que se agregan nuevas fuentes y tipos de tareas. En algún momento, refactorizará y, con mayor frecuencia, terminará con una solución similar a un puente.
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);
}
}
}