El patrón Bridge, también conocido como Handle/Body, es una técnica usada en programación para desacoplar una abstracción de su implementación, de manera que ambas puedan ser modificadas independientemente sin necesidad de alterar por ello la otra.
Esto es, se desacopla una abstracción de su implementación para que puedan variar independientemente.
Aplicabilidad
Se usa el patrón Bridge cuando:
- Se desea evitar un enlace permanente entre la abstracción y su implementación. Esto puede ser debido a que la implementación debe ser seleccionada o cambiada en tiempo de ejecución.
- Tanto las abstracciones como sus implementaciones deben ser extensibles por medio de subclases. En este caso, el patrón Bridge permite combinar abstracciones e implementaciones diferentes y extenderlas independientemente.
- Cambios en la implementación de una abstracción no deben impactar en los clientes, es decir, su código no debe tener que ser recompilado.
- (En C++) Se desea esconder la implementación de una abstracción completamente a los clientes. En C++, la representación de una clase es visible en la interfaz de la clase.
- Se desea compartir una implementación entre múltiples objetos (quizá usando contadores), y este hecho debe ser escondido a los clientes.
Estructura
Participantes
- Abstraction define una interfaz abstracta. Mantiene una referencia a un objeto de tipo Implementor.
- RefinedAbstraction extiende la interfaz definida por Abstraction
- Implementor define la interfaz para la implementación de clases. Esta interfaz no se tiene que corresponder exactamente con la interfaz de Abstraction; de hecho, las dos interfaces pueden ser bastante diferente. Típicamente la interfaz Implementor provee solo operaciones primitivas, y Abstraction define operaciones de alto nivel basadas en estas primitivas.
- ConcreteImplementor implementa la interfaz de Implementor y define su implementación concreta.
Colaboraciones
- Abstraction reenvía las peticiones de los clientes a su objeto Implementor.
Consecuencias
- 1.Desacopla interfaz e implementación: una implementación no es limitada permanentemente a una interfaz. La implementación de una abstracción puede ser configurada en tiempo de ejecución. Además le es posible a un objeto cambiar su implementación en tiempo de ejecución. Desacoplando Abstraction e Implementor también elimina las dependencias sobre la implementación en tiempo de compilación. Cambiar una clase de implementación no requiere recompilar la clase Abstraction ni sus clientes. Esta propiedad es esencial cuando te debes asegurar la compatibilidad binaria entre diferentes versiones de una biblioteca de clases. Es más, este desacoplamiento fomenta las capas, que pueden conducir a un sistema mejor estructurado. La parte de alto nivel de un sistema solo tiene que conocer Abstraction e Implementor.
- 2.Mejora la extensibilidad: se puede extender las jerarquías de Abstraction e Implementor independientemente. Así, por ejemplo, se puede crear una jerarquía en cadena (iterativa) de RedefinedAbstraction y que cada una de ellas vaya añadiendo una capa independiente de lógica de negocio delegando en los métodos de Implementor que se necesiten cada vez.
- 3.Esconde los detalles de la implementación a los clientes.
Implementación
Consideremos las siguientes cuestiones de implementación cuando se aplica este patrón:
- 1.Sólo un Implementor: en situaciones donde existe solo una implementación, crear una clase Implementor abstracta no es necesario. Esto es un caso especial del patrón; hay una relación uno-a-uno entre Abstraction e Implementor. Sin embargo, esta separación es aún muy útil cuando un cambio en la implementación de una clase no debe afectar a sus clientes existente, es decir, ellos no deben ser recompilados, sólo relinkeados. En C++, la interfaz de la clase Implementor puede ser definida en un archivo header privado el cual no es proveído a los clientes. Esto permite esconder una implementación de una clase completamente de sus clientes.
- 2 Creando el objeto Implementor adecuado: ¿Cómo, cuándo y dónde que clase Implementor instanciar cuando hay más de una?Si Abstraction conoce todas las clases ConcreteImplementor puede instanciar una de ellas en su constructor; puede decidir cuál instanciar dependiendo de los parámetros del constructor.
Otra aproximación es elegir una implementación inicial por defecto y cambiarla después acorde al uso.
También es posible delegar la decisión a otro objeto en conjunto.
- 3 Compartiendo implementadores: el estilo Handle/Body en C++ puede ser usado para compartir implementaciones de muchos objetos. Body almacena una cuenta de referencia que la clase Handle incrementa y decrementa.
- 4 Usando herencia múltiple. Se puede usar herencia múltiple en C++ para asociar una interfaz con su implementación.
Creamos una clase Abstracción padre que sea abstracta, además de abstracciones concretas mediante clases que heredan de ella. Por otro lado se tienen las clases que implementan la funcionalidad con una estructura similar: una clase ImplementaciónAbstracta padre, y todas las clases hijas necesarias que implementan la funcionalidad de todas las maneras necesarias.
La relación se da entre la clase abstracta Abstracción y la clase abstracta Implementación, delegando la primera la implementación en la segunda, que a su vez la delega en las implementaciones concretas.
Código en java
interface Implementador {
void operacion();
}
/** primera implementacion de Implementador **/
class ImplementacionA implements Implementador{
public void operacion() {
System.out.println("Esta es la implementacion A");
}
}
/** segunda implementacion de Implementador **/
class ImplementacionB implements Implementador{
public void operacion() {
System.out.println("Esta es una implementacion de B");
}
}
/** interfaz de abstracción **/
interface Abstraccion {
void operacion();
}
/** clase refinada que implementa la abstraccion **/
class AbstraccionRefinada implements Abstraccion{
private Implementador implementador;
public AbstraccionRefinada(Implementador implementador){
this.implementador = implementador;
}
public void operacion(){
implementador.operacion();
}
}
/** aplicacion que usa el patrón Bridge **/
public class EjemploBridge {
public static void main(String[] args) {
Abstraccion[] abstracciones = new Abstraccion[2];
abstracciones[0] = new AbstraccionRefinada(new ImplementacionA());
abstracciones[1] = new AbstraccionRefinada(new ImplementacionB());
for(Abstraccion abstraccion:abstracciones)
abstraccion.operacion();
}
}