¿Cuándo usas el patrón de puente? ¿Cómo es diferente del patrón del adaptador?

154

¿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?

Charles Graham
fuente
Por favor considere aceptar una respuesta diferente a esta pregunta. La respuesta actualmente aceptada es incorrecta e inútil. Las respuestas más nuevas son muy superiores.
jaco0646
El libro de GoF responde a esta pregunta directamente.
jaco0646

Respuestas:

76

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.

shek
fuente
1
Bridge no tiene nada que ver con Plantilla o Estrategia. El puente es un patrón estructural. Plantilla y estrategia son patrones de comportamiento.
jaco0646
249

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
Anton Shchastnyi
fuente
66
¿Por qué harías herencia de colores?
vainolo
10
@vainolo porque el color es una interfaz y el azul, el rojo son colores concretos
Weltschmerz
3
Esto es solo una refactorización. Intención del patrón de puente: "Desacoplar una abstracción de su implementación para que los dos puedan variar independientemente". ¿Dónde está la abstracción y dónde está la implementación aquí?
clapas
1
¿No es Rectangle (Color) más abstracto que BlueRectangle?
Anton Shchastnyi
2
@clapas, Abstraction es "Shape.color" de la propiedad, por lo tanto, la clase Red y la clase Blue son implementaciones, y la interfaz Color es bridge.
reco
230

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 ...

Federico A. Ramponi
fuente
1
Vea también el diagrama de Anton Shchastnyi a continuación para obtener una ilustración gráfica de esta explicación.
NomadeNumerique
2
No creo que un color sea un buen ejemplo para una jerarquía de implementación, es bastante confuso. No es un buen ejemplo del patrón de Puente en "Patrones de diseño" por el GoF, donde la implementación depende de la plataforma: PM de IBM, de UNIX X, etc
clapas
215

Cuando:

        A
     /     \
    Aa      Ab
   / \     /  \
 Aa1 Aa2  Ab1 Ab2

Refactorizar a:

     A         N
  /     \     / \
Aa(N) Ab(N)  1   2
John Sonmez
fuente
3
Creo que es un enfoque muy pragmático de los patrones: 1) describe un diseño directo subóptimo 2) diseño / código de refactorización para uno mejor factorizado
Alexey
1
Use el concepto matemático para explicar el patrón de diseño del Puente. Muy interesado.
Jian Huang
1
Esto es solo una refactorización. Intención del patrón de puente: "Desacoplar una abstracción de su implementación para que los dos puedan variar independientemente". ¿Dónde está la abstracción y dónde está la implementación aquí?
clapas
John lo pone muy bien en una publicación de blog . Me pareció una buena lectura para una visión general de alto nivel.
Vaibhav Bhalla
29

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

Bill Karwin
fuente
Hola Bill No entiendo por qué necesariamente usamos el patrón Bridge en los controladores de dispositivos. Quiero decir que podemos delegar fácilmente la implementación (de lectura, escritura, búsqueda, etc.) a la clase correcta a través del polimorfismo, ¿verdad? ¿O con un visitante quizás? ¿Por qué tiene que ser Bridge? Gracias por adelantado.
stdout
1
@zgulser, sí, usas polimorfismo. El patrón Bridge describe un tipo de uso de subclases para desacoplar la implementación de la abstracción.
Bill Karwin el
Se refería a desacoplar la implementación de la forma (es decir, el rectángulo) de la abstracción del color, ¿verdad? Y creo que estás diciendo que hay varias formas de hacerlo y que Bridge es solo una de ellas.
stdout
Sí, la subclase tiene otros usos. Esta forma particular de usar subclases es lo que lo convierte en el patrón Bridge.
Bill Karwin
Y el desacoplamiento que quiero decir es de la interfaz de forma abstracta a una implementación concreta de rectángulo. Por lo tanto, puede escribir código que necesite un objeto del tipo "Forma", aunque el objeto concreto sea realmente una subclase de Forma.
Bill Karwin
27

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.

thSoft
fuente
22

La intención de Bridge and Adapter es diferente y necesitamos ambos patrones por separado.

Patrón de puente:

  1. Es un patrón estructural
  2. La abstracción y la implementación no están vinculadas en tiempo de compilación
  3. Abstracción e implementación: ambas pueden variar sin impacto en el cliente
  4. Utiliza la composición sobre la herencia.

Use el patrón Puente cuando:

  1. Desea vinculación en tiempo de ejecución de la implementación,
  2. Tiene una proliferación de clases como resultado de una interfaz acoplada y numerosas implementaciones,
  3. Desea compartir una implementación entre varios objetos,
  4. Necesita mapear jerarquías de clases ortogonales.

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 :

  1. Se permite que dos interfaces no relacionadas con el trabajo en conjunto a través de los diferentes objetos, posiblemente jugando mismo papel.
  2. Modifica la interfaz original.

Diferencias clave

  1. El adaptador hace que las cosas funcionen después de que están diseñadas; El puente los hace trabajar antes que ellos.
  2. Bridge está diseñado por adelantado para permitir que la abstracción y la implementación varíen de forma independiente . El adaptador se actualiza para que las clases no relacionadas funcionen juntas.
  3. La intención: el adaptador permite que dos interfaces no relacionadas trabajen juntas. Bridge permite que Abstracción e implementación varíen independientemente.

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:

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:

  1. Vehicle Es una abstracción.
  2. Cary Truckson dos implementaciones concretas de Vehicle.
  3. Vehicledefine un método abstracto: addGear().
  4. Gear es la interfaz del implementador
  5. ManualGeary AutoGearson dos implementaciones de Gear
  6. Vehiclecontiene implementorinterfaz en lugar de implementar la interfaz. CompositonLa interfaz del implementador es crucial para este patrón: permite que la abstracción y la implementación varíen independientemente.
  7. Cary Truckdefina la implementación (abstracción redefinida) para la abstracción:: addGear()Contiene Gear- O ManualbienAuto

Caso (s) de uso para el patrón de puente :

  1. La abstracción y la implementación pueden cambiar de forma independiente entre sí y no están vinculadas en tiempo de compilación
  2. Mapa de jerarquías ortogonales: una para abstracción y otra para implementación .
Ravindra babu
fuente
"El adaptador hace que las cosas funcionen después de su diseño; Bridge las hace funcionar antes de que lo sean". Es posible que desee buscar en el adaptador enchufable. Es una variación del Adaptador descrito por el GoF en la sección "Adaptador" de su libro de Patrones de diseño. El propósito es crear una interfaz para clases que aún no existen. Un adaptador enchufable no es un puente, por lo que no creo que el primer punto sea válido.
c1moore
A pesar de cambios manual y automática podría requerir una implementación diferente para camiones y coches
andigor
9

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 Acontiene la interfaz y class Aimplcontiene 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 Aimplen 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 Aimplantes de Adefinir, y mover las definiciones de todas las funciones miembro que hacen referencia pImplal archivo .cpp. Esto le brinda la capacidad de mantener el Aimplencabezado privado y reducir el tiempo de compilación.

Dima
fuente
2
Si usa este patrón, el AImpl ni siquiera necesita un encabezado. Simplemente lo puse en línea en el archivo de implementación para la clase A
1800 INFORMACIÓN
Su implementador es privado. Tengo una nueva pregunta con respecto a esto, consulte stackoverflow.com/questions/17680762/…
Roland
7

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.

NotAgain dice Reinstate Monica
fuente
0

Para mí, lo considero un mecanismo en el que puedes intercambiar interfaces. En el mundo real, es posible que tenga una clase que pueda usar más de una interfaz, Bridge le permite intercambiar.

j2emanue
fuente
0

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.

Sylvain Rodrigue
fuente
-5
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);
}
}
}
Sohan Kumawat
fuente
55
Voté en contra porque siento que esta es una respuesta enrevesada y mal formateada.
Zimano
1
Totalmente de acuerdo, ¿cómo puede publicar respuestas en este sitio sin una mínima atención a la sangría y claridad del código?
Massimiliano Kraus