Bridge
(patrón de diseño)
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.
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 interface 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 interface abstracta.
Mantiene una referencia a un objeto de tipo Implementor.
RefinedAbstraction extiende la interface definida
por Abstraction
Implementor define la interface para la
implementación de clases. Esta interface no se tiene que corresponder
exactamente con la interface de Abstraction; de hecho, las dos interfaces
pueden ser bastante diferente. Típicamente la interface Implementor provee sólo
operaciones primitivas, y Abstraction define operaciones de alto nivel basadas
en estas primitivas.
ConcreteImplementor implementa la interface de
Implementor y define su implementación concreta.
Colaboraciones
Abstraction emite los pedidos de los clientes a su
objeto Implementor.
Consecuencias
1.
Desacopla interface
e implementación: una implementación no es limitada permanentemente a una interface.
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 require 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 sólo tiene que conocer
Abstraction e Implementor.
2. Mejora
la extensibilidad: se puede extender las jerarquías
de Abstraction e Implementor
independientemente.
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 Implementar: en situaciones donde existe sólo
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 interface
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 Implementar 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
Java
/** interfaz que implementan los implementadores especificos **/ public interface Implementador { public abstract void operacion(); } /** primera implementacion de Implementador **/ public class ImplementacionA implements Implementador{ public void operacion() { System.out.println("Esta es la implementacion A"); } } /** segunda implementacion de Implementador **/ public class ImplementacionB implements Implementador{ public void operacion() { System.out.println("Esta es una implementacion de B"); } } /** interfaz de abstracción **/ public interface Abstraccion { public void operacion(); } /** clase refinada que implementa la abstraccion **/ public 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(); } }
No hay comentarios:
Publicar un comentario