Patrones de Diseño (VIII): Patrones Estructurales - Bridge

El patrón, estructuralmente hablando, es muy parecido al último patrón que analizamos, el Adapter o adaptador; pero su la diferencia radica en su utilidad. Este patrón es quizás uno de los más difíciles de digerir, intentaré explicarlo de una forma clara.

Dada una abstracción, es decir, una clase abstracta o interfaz que representa una funcionalidad sin implementar, nuestro objetivo es conseguir un escenario en el que la modificación de la misma no afecte a su implementación y viceversa. Es decir que si tenemos, por ejemplo, una abstracción que represente un vehículo y la implementación de la misma, la modificación de las cualidades del vehículo no afecte a cómo funciona internamente el mismo y viceversa.

Sin más preámbulos vamos a revisar la plantilla del patrón Bridge:

Bridge


Nombre: Bridge

Problema y Contexto:

Tenemos la necesidad de que la implementación de una abstracción sea modificada en tiempo de ejecución o nuestro sistema requiere que la funcionalidad (parcial o total) de nuestra abstracción esté desacoplada de la implementación para poder modificar tanto una como otra sin que ello obligue a la cambiar las demás clases.

Se aplica cuando:

  • Queremos evitar enlaces permanentes entre una abstracción y una implementación.
  • Tanto las abstracciones como las implementaciones deben ser extensibles por medio de subclases.
  • Queremos que los cambios en la implementación de una abstracción no afecten al cliente.
  • Necesitamos que la implementación de una característica sea compartida entre múltiples objetos.


Solución y Estructura:

Partimos de una abstracción base (clase abstracta o interfaz) que tendrá como atributo un objeto que será el que realice las funciones a implementar y que denominaremos implementador. Nuestra abstracción contendrá todas las operaciones que nuestro sistema requiera.

Por otro lado, tendremos el implementador, que será una interfaz que defina las operaciones necesarias para cubrir la funcionalidad que ofrece nuestra abstracción. Para dotar de funcionalidad a las operaciones definidas podremos crear diferentes implementadores concretos que implementen dicha interfaz.

Por último debemos crear una clase que herede de nuestra abstracción para definir concretamente lo que hacen sus métodos, pero ésta deberá implementar la funcionalidad mediante el atributo que heredó del padre (que almacena un implementador).

La estructura es la siguiente:


Donde:

Client: Como siempre, se encargará de utilizar las abstracción de la que trata el problema.

Abstraction: Abstracción objetivo del problema. Contiene una referencia a un objeto que implemente la interfaz Implementator el cual servirá para definir la funcionalidad de la misma. Puede ser una clase abstracta o una interfaz.

Implementator: Interfaz que define las operaciones necesarias para implemntar la funcionalidad de Abstraction.

RefinedAbstraction: Clase que hereda o implementa (según sea clase abstracta o interfaz respectivamente) de Abstraction. Extiende su funcionalidad basándose en el atributo que almacena al Implementator.

ConcreteImplementor: Implementación concreta de la funcionalidad definida por el Implementor. Puede haber varias implementaciones para la misma funcionalidad.

Consecuencias:

  • POSITIVAS:
    • Una implementación no se limita permanentemente a una interface.
    • La implementación de una abstracción puede ser configurada y/o cambiada en tiempo de ejecución.
    • Desacoplando Abstraction e Implementor también se eliminan las dependencias sobre la implementación en tiempo de compilación.
    • Cambiar una implementación no require recompilar la clase Abstraction ni sus clientes.
    • Las capas de alto nivel de un sistema sólo tiene que conocer Abstraction e Implementor.
    • Se pueden extender las jerarquías de Abstraction e Implementor sin que haya dependencias.
    • Oculta los detalles de implementación a los clientes.
  • NEGATIVAS:
    • Puede ser complicado de entender al principio.
    • Añade complejidad.
    • Problemática al no entender bien su funcionamiento.


Patrones Relacionados: Adapter, Abstract Factory

Ejemplo:

Para ilustrar todo esto vamos a ver un ejemplo que representará la abstracción del envío de un paquete. Nuestra interfaz abstracta representará la agencia de transportes que realizará el envío:

public abstract class EmpresaMensajeria{
protected IEnvio envio;

protected EmpresaMensajeria(IEnvio envio){
this.envio = envio;
}

public void recogerPaquete(){
System.out.println('Se ha recogido el paquete.');
envio.procesarEnvio();
}

public void enviarPaquete(){
envio.enviar();
}

public void entregarPaquete(){
envio.procesarEntrega();
System.out.println('Se ha entregado el paquete.');
}

public void setEnvio(IEnvio envio){
this.envio=envio;
}

public void getEnvio(){
return this.envio;
}
}

A continucación vamos a definir la interfaz IEnvio que representará al implemntador del envío:

public interface IEnvio{
public void procesarEnvio();
public void enviar();
public void procesarEntrega();
}
El siguiente paso es crear implementaciones de la interfaz IEnvio:
public class EnvioMar implements IEnvio{
public void procesarEnvio(){
System.out.println('El paquete se ha cargado en el barco.');
}
public void enviar(){
System.out.println('El paquete va navegando por el mar.');
}
public void procesarEntrega(){
System.out.println('El paquete se ha descargado en el puerto.');
} }
public class EnvioAire implements IEnvio{
public void procesarEnvio(){
System.out.println('El paquete se ha cargado en el avión.');
}
public void enviar(){
System.out.println('El paquete va volando por el aire.');
}
public void procesarEntrega(){
System.out.println('El paquete se ha descargado en el aeropuerto.');
}
}

Ahora crearemos la empresa de transportes refinada:

public class EuroTransport extends EmpresaMensajeria{
private String nif;
public EuroTransport(String nif){
IEnvio envioPorDefecto = new EnvioAire();
super(envioPorDefecto);
this.nif=nif;
}

public EuroTransport(String nif, IEnvio envio){
super(envio);
this.nif=nif;
}

public void identificarse(){
System.out.println("Identificación: "+this.nif);
}
}

Y por último hacemos que un cliente utilice nuestra abstracción:

public static void main(String[] args){
// En primer lugar crearemos el objeto que representa a la emrpesa de mensajerio
EmpresaMensajeria mensajero = new EuroTransport("0854752177");
// Enviaremos un paquete vía aérea, que es la que esta empresa tiene pro defecto
mensajero.recogerPaquete();
mensajero.enviarPaquete();
mensajero.entregarPaquete();
// Ahora le decimos a la empresa que queremos enviar por mar
mensajero.setEnvio(new EnvioMar());
mensajero.recogerPaquete();
mensajero.enviarPaquete();
mensajero.entregarPaquete();
}

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP