Bridge
Bridge
Bridge es un patrón de diseño estructural que te permite dividir una clase grande, o un grupo de clases estrechamente relacionadas, en dos jerarquías separadas (abstracción e implementación) que pueden desarrollarse independientemente la una de la otra.
Digamos que tienes una clase geométrica Forma con un par de subclases: Círculo y Cuadrado. Deseas extender esta jerarquía de clase para que incorpore colores, por lo que planeas crear las subclases de forma Rojo y Azul. Sin embargo, como ya tienes dos subclases, tienes que crear cuatro combinaciones de clase, como CírculoAzul y CuadradoRojo.

El número de combinaciones de clase crece en progresión geométrica.
Añadir nuevos tipos de forma y color a la jerarquía hará que ésta crezca exponencialmente. Por ejemplo, para añadir una forma de triángulo deberás introducir dos subclases, una para cada color. Y, después, para añadir un nuevo color habrá que crear tres subclases, una para cada tipo de forma. Cuanto más avancemos, peor será.
Solución
Este problema se presenta porque intentamos extender las clases de forma en dos dimensiones independientes: por forma y por color. Es un problema muy habitual en la herencia de clases.
El patrón Bridge intenta resolver este problema pasando de la herencia a la composición del objeto. Esto quiere decir que se extrae una de las dimensiones a una jerarquía de clases separada, de modo que las clases originales referencian un objeto de la nueva jerarquía, en lugar de tener todo su estado y sus funcionalidades dentro de una clase.

Con esta solución, podemos extraer el código relacionado con el color y colocarlo dentro de su propia clase, con dos subclases: Rojo y Azul. La clase Forma obtiene entonces un campo de referencia que apunta a uno de los objetos de color. Ahora la forma puede delegar cualquier trabajo relacionado con el color al objeto de color vinculado. Esa referencia actuará como un puente entre las clases Forma y Color. En adelante, añadir nuevos colores no exigirá cambiar la jerarquía de forma y viceversa.
Imaginemos por ejemplo que tenemos una clase abstracta cuya función es mostrar logs, pudiendo tener implementaciones concretas para los logs de info o warn. Ahora bien imagina que queremos añadir a nuestro sistema la posibilidad de sacar esos logs por diferentes salidas. Sin el patron bridge, extenderiamos varias clases para cada funcionalidad. Por ejemplo InfoLogFile, InfoLogConsole, WarnLogFile, WarnLogConsole. Pero esto hace que cada vez que queramos añadir un nueva implementación de Logger tengamos que crear tantas clases como Outputs tengamos.
Para solucionar esto esta el patron bridge, que lo que hace es separar la parte del Logger de la parte de las implementaciones de salida.

Vamos a verlo en código.
/**
* Abstración, contiene el comportamiento core del conjunto de clases.
* Contiene la referencia al otro conjunto de clases (implementadores).
*/
abstract class Logger {
protected final LoggerOutput loggerOutput;
public Logger(LoggerOutput loggerOutput) {
this.loggerOutput = loggerOutput;
}
public abstract void log(String message);
}
/**
* Concrete de la clase abstracta
*/
public class InfoLogger extends Logger {
public InfoLogger(LoggerOutput loggerOutput) {
super(loggerOutput);
}
@Override
public void log(String message) {
loggerOutput.output(String.format("INFO: %s", message));
}
}
/**
* Concrete de la clase abstracta.
*/
public class ErrorLogger extends Logger {
public ErrorLogger(LoggerOutput loggerOutput) {
super(loggerOutput);
}
@Override
public void log(String message) {
loggerOutput.output(String.format("ERROR: %s", message));
}
}
/**
* Clase que contiene la abstracción pero refinada.
*/
public class WarnLogger extends Logger {
public WarnLogger(LoggerOutput loggerOutput) {
super(loggerOutput);
}
@Override
public void log(String message) {
loggerOutput.output(String.format("WARN: %s", message));
}
}
/**
* Interfaz implementadora, permite tener el segundo grupos de clases.
* Separa parte de la lógica.
*/
public interface LoggerOutput {
void output(String message);
}
/**
* Clase implementadora concreta (Concrete implementor).
*/
public class FileLoggerOutput implements LoggerOutput {
private String path;
public FileLoggerOutput(String path) {
this.path = path;
}
@Override
public void output(String message) {
System.out.printf("File: %s\n%s\n", path, message);
}
}
/**
* Clase implementadora concreta (Concrete implementor).
*/
public class TerminalLoggerOutput implements LoggerOutput {
@Override
public void output(String message) {
System.out.println(message);
}
}
