Comprender el patrón de diseño del puente

24

No entiendo el patrón de diseño "puente" en absoluto. He visitado varios sitios web, pero no me han ayudado.

¿Alguien puede ayudarme a entender esto?


fuente
2
Yo tampoco lo entiendo. Estamos ansiosos por ver las respuestas :)
Violet Giraffe
Hay muchos sitios web y libros que describen patrones de diseño. No creo que tenga valor repetir lo que ya se ha escrito, tal vez pueda hacer una pregunta específica. Me tomó un tiempo entenderlo también, cambiando constantemente entre diferentes fuentes y ejemplos.
Helena

Respuestas:

16

En OOP usamos polimorfismo para que una abstracción pueda tener múltiples implementaciones. Veamos el siguiente ejemplo:

//trains abstraction
public interface Train
{ 
    move();
}
public class MonoRail:Train
{
    public override move()
    {
        //use one track;
    }
}
public class Rail:Train
{
    public override move()
    {
        //use two tracks;
    }
}

Se introdujo un nuevo requisito y debe incorporar la perspectiva de aceleración de los trenes, por lo tanto, cambie el código como se muestra a continuación.

    public interface Train
    { 
        void move();
    }
    public class MonoRail:Train
    {
        public override void move()
        {
            //use one track;
        }
    }
    public class ElectricMonoRail:MonoRail
    {
        public override void move()
        {
            //use electric engine on one track.
        }
    }
    public class DieselMonoRail: MonoRail
    {
        public override void move()
        {
            //use diesel engine on one track.
        }
    }
    public class Rail:Train
    {
        public override void move()
        {
            //use two tracks;
        }
    }
    public class ElectricRail:Rail
    {
        public override void move()
        {
            //use electric engine on two tracks.
        }
    }
    public class DieselRail: Rail
    {
        public override void move()
        {
            //use diesel engine on two tracks.
        }
    }

El código anterior no es mantenible y carece de reutilización (suponiendo que podamos reutilizar el mecanismo de aceleración para la misma plataforma de seguimiento). El siguiente código aplica el patrón del puente y separa las dos abstracciones diferentes, el transporte del tren y la aceleración .

public interface Train
{ 
    void move(Accelerable engine);
}
public interface Accelerable
{
    public void accelerate();
}
public class MonoRail:Train
{
    public override void move(Accelerable engine)
    {
        //use one track;
        engine.accelerate(); //engine is pluggable (runtime dynamic)
    }
}
public class Rail:Train
{
    public override void move(Accelerable engine)
    {
        //use two tracks;
        engine.accelerate(); //engine is pluggable (runtime dynamic)
    }
}
public class ElectricEngine:Accelerable{/*implementation code for accelerable*/}
public class DieselEngine:Accelerable{/*implementation code for accelerable*/}
Thurein
fuente
3
Muy buen ejemplo. Añadiría mis dos centavos: este es un muy buen ejemplo de preferir la composición a la herencia
zzfima
1
Por lo que vale, creo que debería serlo, Monorailya que no son realmente dos palabras, es una sola palabra (compuesta). Un MonoRail sería una subclase de Rail en lugar de un tipo diferente de rail (que es). Al igual que no SunShineCupCakeSunshineCupcake
usaríamos
Esta línea "las dos abstracciones diferentes, transporte de trenes y aceleración" ayuda a señalar cuáles son las dos jerarquías y lo que intentamos resolver es hacer que varíen de forma independiente.
wangdq
11

Si bien la mayoría de los patrones de diseño tienen nombres útiles, considero que el nombre "Puente" no es intuitivo con respecto a lo que hace.

Conceptualmente, inserta los detalles de implementación utilizados por una jerarquía de clases en otro objeto, generalmente con su propia jerarquía. Al hacerlo, está eliminando una dependencia estrecha de esos detalles de implementación y permitiendo que los detalles de esa implementación cambien.

En pequeña escala, comparo esto con el uso de un patrón de estrategia en la forma en que puede conectar un nuevo comportamiento. Pero en lugar de simplemente envolver un algoritmo como se ve a menudo en una estrategia, el objeto de implementación generalmente está más lleno de funciones. Y cuando aplica el concepto a toda una jerarquía de clases, el patrón más grande se convierte en un Puente. (De nuevo, odio el nombre).

No es un patrón que usará todos los días, pero lo he encontrado útil al manejar una explosión potencial de clases que puede ocurrir cuando tiene una (aparente) necesidad de herencia múltiple.

Aquí hay un ejemplo del mundo real:

Tengo una herramienta RAD que le permite colocar y configurar controles en una superficie de diseño, por lo que tengo un modelo de objetos como este:

Widget // base class with design surface plumbing
+ Top
+ Left
+ Width
+ Height
+ Name
+ SendToBack
+ BringToFront
+ OnPropertyEdit
+ OnSelect
+ Validate
+ ShowEditor
+ Paint
+ Etc

TextboxWidget : Widget // text box specific
+ Text
+ MaxLength
+ Font
+ ShowEditor // override base to show a property editor form specific to a Textbox
+ Paint // override to render a textbox onto the surface    
+ Etc

ListWidget : Widget // list specific
+ Items
+ SelectedItem
+ ShowEditor // override base to show a property editor form specific to a List
+ Paint // override to render a list onto the surface
+ Etc

Y así sucesivamente, con quizás una docena de controles.

Pero luego se agrega un nuevo requisito para admitir múltiples temas (look-n-feel). Digamos que tenemos los siguientes temas: Win32, WinCE, WinPPC, WinMo50, WinMo65. Cada tema tendría diferentes valores o implementaciones para operaciones relacionadas con la representación como DefaultFont, DefaultBackColor, BorderWidth, DrawFrame, DrawScrollThumb, etc.

Podría crear un modelo de objeto como este:

Win32TextboxWidget : TextboxWidget

Win32ListWidget : ListWidget

etc., para un tipo de control

WinCETextboxWidget : TextboxWidget

WinCEListWidget : ListWidget

etc., para cada tipo de control (de nuevo)

Se entiende la idea: se obtiene una explosión de clase del número de widgets por el número de temas. Esto complica al diseñador de RAD haciéndolo consciente de todos y cada uno de los temas. Además, agregar nuevos temas obliga a modificar el diseñador RAD. Además, hay mucha implementación común dentro de un tema que sería genial heredar, pero los controles ya están heredando de una base común ( Widget).

Entonces, en cambio, lo que hice fue crear una jerarquía de objetos separada que implementa el tema. Cada widget contendría una referencia al objeto que implementa las operaciones de representación. En muchos textos, esta clase tiene el sufijo de una Implpero me desvié de esa convención de nomenclatura.

Así que ahora mi TextboxWidgetaspecto es el siguiente:

TextboxWidget : Widget // text box specific
+ Text
+ MaxLength
+ Font
+ ShowEditor
+ Painter // reference to the implementation of the widget rendering operations
+ Etc

Y puedo hacer que mis diversos pintores hereden mi base de temas específicos, lo que no podía hacer antes:

Win32WidgetPainter
+ DefaultFont
+ DefaultFontSize
+ DefaultColors
+ DrawFrame
+ Etc

Win32TextboxPainter : Win32WidgetPainter

Win32ListPainter : Win32WidgetPainter

Una de las cosas buenas es que puedo cargar dinámicamente las implementaciones en tiempo de ejecución, lo que me permite agregar tantos temas como quiera sin cambiar el software principal. En otras palabras, mi "implementación puede variar independientemente de la abstracción".

tcarvin
fuente
No entiendo cómo se supone que este sea el patrón del puente. Cuando agrega un nuevo componente a la jerarquía de widgets, se ve obligado a agregar ese nuevo widget a TODOS los pintores (Win32NewWidgetPainter, PPCNewWidgetPainter). Esto NO son dos jerarquías que crecen independientemente. Para un patrón de puente adecuado, no debería subclasificar la clase PlatformWidgetPainter para cada widget, sino que debería recibir un "descriptor de dibujo" de Widget.
Mazyod
Gracias por los comentarios, tienes razón. Hace años publiqué esto, y al revisarlo ahora, diría que describe bien el puente hasta el último bit de donde derivaba Win32TextboxPaintery Win32ListPainter de dónde provenía Win32WidgetPainter. Usted puede tener un árbol de herencia en el lado de la aplicación, pero debe ser más genérico (tal vez StaticStyleControlPainter, EditStyleControlPaintery ButtonStyleControlPainter) con las operaciones primitivas necesarias anulado, según sea necesario. Esto está más cerca del código real en el que estaba basando el ejemplo.
tcarvin
3

El puente tiene la intención de desacoplar una abstracción de su implementación concreta , de modo que ambos puedan variar independientemente:

  • refinar la abstracción con subclases
  • Proporcionar diferentes implementaciones, también mediante subclases, sin tener que conocer ni la abstracción ni sus refinamientos.
  • si es necesario, elija en tiempo de ejecución cuál es la implementación más adecuada .

El puente logra esto usando la composición:

  • la abstracción se refiere (referencia o puntero) a un objeto de implementación
  • la abstracción y sus refinamientos solo conocen la interfaz de implementación

ingrese la descripción de la imagen aquí

Observaciones adicionales sobre una confusión frecuente

Este patrón es muy similar al patrón adaptador: la abstracción ofrece una interfaz diferente para una implementación y utiliza la composición para hacerlo. Pero:

La diferencia clave entre estos patrones radica en sus intenciones
: Gamma y otros, en " Patrones de diseño, elemento del software OO reutilizable " , 1995

En este excelente libro seminal sobre patrones de diseño, los autores también observan que:

  • los adaptadores se usan a menudo cuando se descubre una incompatibilidad y el acoplamiento es imprevisto
  • Los puentes se utilizan desde el comienzo del diseño, cuando se espera que las clases evolucionen de forma independiente.
Christophe
fuente