Desarrollo

El patrón de diseño Bridge

Publicado el
Etiquetas: patterns design patterns
El patrón de diseño Bridge
Comparte

El patrón de diseño Bridge es una técnica de diseño que se utiliza para separar la abstracción de su implementación. Esta separación permite que ambas puedan variar de forma independiente, lo que ofrece una gran flexibilidad en el diseño del software.

En esencia, el patrón Bridge se basa en dos abstracciones: una que define la interfaz del cliente y otra que define la implementación. Estas dos abstracciones se mantienen separadas y se comunican entre sí mediante un objeto de conexión, conocido como puente (bridge).

Este puente actúa como un intermediario que conecta las dos abstracciones, permitiendo que se comuniquen y se intercambien información entre sí. De esta forma, las implementaciones pueden variar sin afectar la interfaz del cliente y viceversa.

Una de las ventajas principales de utilizar el patrón Bridge es que permite crear un software más flexible y escalable. Gracias a la separación entre la interfaz y la implementación, es posible cambiar o actualizar una de las partes sin afectar a la otra, lo que resulta especialmente útil en sistemas grandes y complejos.

Además, este patrón también puede mejorar la modularidad y la reutilización del código, ya que las abstracciones y las implementaciones pueden ser reutilizadas en diferentes contextos y combinaciones.

Se recomienda utilizar el patrón Bridge cuando se quiere separar la interfaz de usuario de la implementación de un sistema, o cuando se quiere conectar diferentes partes de un sistema de forma flexible y escalable.

  +-------------+     +-------------+
  | Abstraction |<----| Implementor |
  +-------------+     +-------------+
         |                    |
  +----------------------------+
  |         Bridge             |
  +----------------------------+
         |                    |
+---------------------+    +---------------+
| Refined Abstraction |<---| Concrete Impl |
+---------------------+    +---------------+

En el diagrama, la clase Abstraction representa la abstracción principal y contiene una referencia a un objeto Implementor que define su implementación. La clase Implementor es la interfaz implementada por las clases que proporcionan la implementación concreta. La clase Bridge es la clase que conecta la abstracción y la implementación. La clase Refined Abstraction es una subclase de Abstraction que agrega funcionalidad específica. Finalmente, Concrete Impl es una implementación concreta de Implementor.

Ejemplo en Python

¡Por supuesto! A continuación, te mostraré un ejemplo de cómo se puede implementar el patrón de diseño Bridge en Python.

Supongamos que tenemos una aplicación que dibuja diferentes formas geométricas, como rectángulos, círculos, triángulos, etc. Además, tenemos diferentes tipos de implementaciones para cada forma: algunas se dibujan en una ventana de la aplicación, otras se dibujan en un archivo, y otras se dibujan en una impresora. En lugar de crear subclases para cada combinación de forma y tipo de implementación, podemos utilizar el patrón de diseño Bridge para desacoplar ambas dimensiones y permitir que varíen de manera independiente.

Primero, crearemos las clases base para las formas y para las implementaciones. La clase Shape será la clase base para todas las formas, mientras que la clase DrawingAPI será la clase base para todas las implementaciones.

class Shape:
    def __init__(self, drawing_api):
        self.drawing_api = drawing_api

    def draw(self):
        pass


class DrawingAPI:
    def draw_circle(self, x, y, radius):
        pass

    def draw_rectangle(self, x1, y1, x2, y2):
        pass

Luego, podemos definir las subclases de las formas que utilizan una implementación específica a través del puente:

class CircleShape(Shape):
    def __init__(self, x, y, radius, drawing_api):
        super().__init__(drawing_api)
        self.x = x
        self.y = y
        self.radius = radius

    def draw(self):
        self.drawing_api.draw_circle(self.x, self.y, self.radius)


class RectangleShape(Shape):
    def __init__(self, x1, y1, x2, y2, drawing_api):
        super().__init__(drawing_api)
        self.x1 = x1
        self.y1 = y1
        self.x2 = x2
        self.y2 = y2

    def draw(self):
        self.drawing_api.draw_rectangle(self.x1, self.y1, self.x2, self.y2)

Finalmente, podemos implementar diferentes tipos de implementaciones de dibujo y utilizarlas en nuestras formas a través del puente:

class DrawingAPI1(DrawingAPI):
    def draw_circle(self, x, y, radius):
        print(f'DrawingAPI1.circle at {x},{y} radius {radius}')

    def draw_rectangle(self, x1, y1, x2, y2):
        print(f'DrawingAPI1.rectangle at {x1},{y1}:{x2},{y2}')


class DrawingAPI2(DrawingAPI):
    def draw_circle(self, x, y, radius):
        print(f'DrawingAPI2.circle at {x},{y} radius {radius}')

    def draw_rectangle(self, x1, y1, x2, y2):
        print(f'DrawingAPI2.rectangle at {x1},{y1}:{x2},{y2}')

Con esto, podemos crear formas y utilizar diferentes implementaciones para dibujarlas:

circle = CircleShape(1, 2, 3, DrawingAPI1())
circle.draw() # Output: DrawingAPI1.circle at 1,2 radius 3

rectangle = RectangleShape(1, 2, 3, 4, DrawingAPI2())
rectangle.draw() # Output: DrawingAPI2.rectangle at 1,2:3,4

En resumen, el patrón Bridge permite separar la abstracción de la implementación y permite que ambas evolucionen de manera independiente. Esto lo hace útil cuando se desea cambiar una de las partes sin afectar la otra. Además, el patrón Bridge también puede ayudar a reducir la complejidad y aumentar la flexibilidad del código.

En Python, se puede implementar el patrón Bridge utilizando clases y objetos para representar tanto la abstracción como la implementación. Los ejemplos que se han mostrado muestran cómo se puede utilizar esta estructura para crear diferentes tipos de formas y colores, y cómo se pueden combinar en diferentes formas sin afectar la funcionalidad general.

Ejemplo en C

Supongamos que tenemos una aplicación que debe mostrar diferentes formas de entretenimiento (películas, series, documentales, etc.) en diferentes dispositivos (televisores, proyectores, pantallas de computadora, etc.). Además, queremos que la aplicación sea fácilmente extensible para agregar nuevos tipos de entretenimiento y nuevos dispositivos en el futuro.

Para implementar esto, podemos utilizar el patrón Bridge. Empezaremos definiendo la clase abstracta Entretenimiento, que representa cualquier forma de entretenimiento que se pueda mostrar:

public abstract class Entretenimiento
{
    protected IDispositivo dispositivo;

    public Entretenimiento(IDispositivo dispositivo)
    {
        this.dispositivo = dispositivo;
    }

    public abstract void Mostrar();
}

Esta clase tiene una referencia al dispositivo que se utilizará para mostrar el entretenimiento, y un método abstracto Mostrar() que será implementado por las subclases.

A continuación, crearemos una subclase para cada tipo de entretenimiento que queremos mostrar. Por ejemplo, aquí está la clase Pelicula:

public class Pelicula : Entretenimiento
{
    private string titulo;

    public Pelicula(string titulo, IDispositivo dispositivo) : base(dispositivo)
    {
        this.titulo = titulo;
    }

    public override void Mostrar()
    {
        Console.WriteLine("Mostrando la película " + titulo + " en " + dispositivo.GetType().Name);
    }
}

Esta clase implementa el método Mostrar() de la clase base y muestra el título de la película y el tipo de dispositivo en el que se está mostrando.

Ahora definimos la interfaz IDispositivo que representa cualquier dispositivo en el que se puede mostrar el entretenimiento:

public interface IDispositivo
{
    void Encender();
    void Apagar();
}

Esta interfaz tiene dos métodos: Encender() y Apagar().

A continuación, creamos una subclase para cada tipo de dispositivo que queremos utilizar. Por ejemplo, aquí está la clase Television:

public class Television : IDispositivo
{
    public void Encender()
    {
        Console.WriteLine("Encendiendo la televisión");
    }

    public void Apagar()
    {
        Console.WriteLine("Apagando la televisión");
    }
}

Esta clase implementa la interfaz IDispositivo y define cómo encender y apagar una televisión.

Finalmente, podemos utilizar estas clases para mostrar el entretenimiento en diferentes dispositivos. Por ejemplo, aquí está el código que muestra una película en una televisión:

IDispositivo dispositivo = new Television();
Entretenimiento pelicula = new Pelicula("El Padrino", dispositivo);
pelicula.Mostrar();

En este ejemplo, creamos una instancia de Television y la pasamos como argumento al constructor de Pelicula. Luego, llamamos al método Mostrar() de Pelicula, que muestra el título de la película y el tipo de dispositivo en el que se está mostrando.

Este ejemplo muestra cómo el patrón Bridge nos permite separar la abstracción (el entretenimiento) de su implementación (el dispositivo en el que se muestra). Esto nos permite cambiar y extender fácilmente ambos sin afectar el otro.

Ejemplo en Java

El patrón Bridge se utiliza para separar la abstracción de su implementación, de modo que ambas puedan variar de forma independiente. Se utiliza cuando una clase tiene varias implementaciones posibles y queremos evitar una explosión combinatoria de subclases.

En este ejemplo, crearemos una abstracción Shape que puede ser dibujada en diferentes dispositivos de dibujo. Tendremos dos implementaciones concretas de esta abstracción: Rectangle y Circle.

En primer lugar, vamos a crear una interfaz Shape que será implementada por todas las formas que queramos dibujar:

public interface Shape {
    void draw();
}

A continuación, creamos dos clases que implementan la interfaz Shape, una para dibujar círculos y otra para dibujar rectángulos:

public class Circle implements Shape {
    private DrawAPI drawAPI;

    public Circle(DrawAPI drawAPI) {
        this.drawAPI = drawAPI;
    }

    @Override
    public void draw() {
        drawAPI.drawCircle();
    }
}

public class Rectangle implements Shape {
    private DrawAPI drawAPI;

    public Rectangle(DrawAPI drawAPI) {
        this.drawAPI = drawAPI;
    }

    @Override
    public void draw() {
        drawAPI.drawRectangle();
    }
}

Ambas clases reciben como parámetro en su constructor una implementación de la interfaz DrawAPI, que es la que se encarga de realizar el dibujo concreto de la forma.

A continuación, creamos la interfaz DrawAPI que define los métodos que deben implementar las clases que se encargan del dibujo:

public interface DrawAPI {
    void drawCircle();
    void drawRectangle();
}

Y finalmente, implementamos dos clases concretas que implementan la interfaz DrawAPI:

public class RedCircle implements DrawAPI {
    @Override
    public void drawCircle() {
        System.out.println("Drawing Circle[ color: red, radius: 10 ]");
    }

    @Override
    public void drawRectangle() {
        // No hace nada
    }
}

public class GreenRectangle implements DrawAPI {
    @Override
    public void drawCircle() {
        // No hace nada
    }

    @Override
    public void drawRectangle() {
        System.out.println("Drawing Rectangle[ color: green, width: 20, height: 10 ]");
    }
}

En este caso, las implementaciones de DrawAPI se encargan de dibujar un círculo o un rectángulo de un color determinado.

Por último, creamos una clase BridgeDemo que crea dos formas, un círculo y un rectángulo, con dos implementaciones diferentes de DrawAPI, una que dibuja en rojo y otra en verde:

public class BridgeDemo {
    public static void main(String[] args) {
        Shape redCircle = new Circle(new RedCircle());
        Shape greenRectangle = new Rectangle(new GreenRectangle());

        redCircle.draw();
        greenRectangle.draw();
    }
}

Este programa producirá la siguiente salida:

Drawing Circle[ color: red, radius: 10 ]
Drawing Rectangle[ color: green, width: 20, height: 10 ]

En resumen, el patrón Bridge se utiliza cuando queremos separar la abstracción de una clase de su implementación concreta, de forma que ambas puedan variar independientemente. En este ejemplo, la interfaz Shape representa la abstracción, y las clases que implementan DrawAPI representan las implementaciones concretas. Al utilizar la interfaz DrawAPI en lugar de una implementación concreta en la definición de Shape, podemos cambiar fácilmente la forma en que se dibuja sin modificar el código de las formas concretas. Además, gracias al puente entre la abstracción y la implementación, podemos agregar fácilmente nuevas formas y nuevas formas de dibujar sin necesidad de modificar las clases existentes. En general, el patrón Bridge se utiliza para lograr un alto nivel de flexibilidad y extensibilidad en un sistema, lo que lo hace especialmente útil en aplicaciones grandes y complejas donde es importante manejar el cambio de manera efectiva.

Javier Sanchez Toledano
Escrito por Javier Sanchez Toledano Sígueme

Soy Licenciado en Informática e Ingeniero en Sistemas Computacionales.
Soy auditor líder certificado por ICA en la Norma ISO 9000, desarrollo sistemas de gestión de la calidad con un enfoque de mejora continua, creo tableros de control con indicadores clave para mejorar la toma de decisiones basadas en datos.

Comentarios