Compreendendo o padrão de design da ponte

24

Eu não entendo o padrão de design da "ponte". Passei por vários sites, mas eles não ajudaram.

Alguém pode me ajudar a entender isso?


fonte
2
Eu também não entendo. Ansioso para ver respostas :)
Violet Giraffe
Existem muitos sites e livros descrevendo padrões de design por aí. Não acho que valha a pena repetir o que já foi escrito; talvez você possa fazer uma pergunta específica. Também demorei um pouco para entender, alternando entre diferentes fontes e exemplos.
Helena

Respostas:

16

No POO, usamos polimorfismo para que uma abstração possa ter várias implementações. Vejamos o seguinte exemplo:

//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;
    }
}

Um novo requisito foi introduzido e precisa trazer a perspectiva de aceleração dos trens; portanto, altere o código conforme abaixo.

    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.
        }
    }

O código acima não é sustentável e não pode ser reutilizado (supondo que possamos reutilizar o mecanismo de aceleração para a mesma plataforma de trilhos). O código a seguir aplica o padrão de ponte e separa as duas abstrações diferentes, transporte de trem e aceleração .

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
fonte
3
Muito bom exemplo. Eu gostaria de acrescentar meus dois centavos: isso é muito bom exemplo de preferir composição sobre herança
zzfima
11
Pelo que vale, acho que deveria ser, Monorailjá que não são realmente duas palavras, é uma única palavra (composta). Um MonoRail seria uma subclasse de Rail em vez de um tipo diferente de Rail (que é). Assim como não seria utilizar SunShineou CupCake, eles seriam SunshineeCupcake
ErikE
Essa linha "as duas abstrações diferentes, transporte de trem e aceleração" ajuda a apontar quais são as duas hierarquias e o que tentamos resolver é fazê-las variar independentemente.
wangdq
11

Embora a maioria dos padrões de design tenha nomes úteis, acho que o nome "Bridge" não é intuitivo em relação ao que faz.

Conceitualmente, você envia os detalhes da implementação usados ​​por uma hierarquia de classes para outro objeto, normalmente com sua própria hierarquia. Ao fazer isso, você está removendo uma forte dependência desses detalhes de implementação e permitindo que os detalhes dessa implementação sejam alterados.

Em pequena escala, comparo isso ao uso de um padrão de estratégia na maneira como você pode conectar um novo comportamento. Mas, em vez de apenas agrupar um algoritmo, como geralmente é visto em uma estratégia, o objeto de implementação geralmente é mais preenchido. E quando você aplica o conceito a uma hierarquia de classes inteira, o padrão maior se torna uma Bridge. (Novamente, odeie o nome).

Não é um padrão que você usará todos os dias, mas achei útil ao gerenciar uma possível explosão de classes que pode acontecer quando você tem uma necessidade (aparente) de herança múltipla.

Aqui está um exemplo do mundo real:

Eu tenho uma ferramenta RAD que permite soltar e configurar controles em uma superfície de design, então eu tenho um modelo de objeto 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

E assim por diante, com talvez uma dúzia de controles.

Mas, em seguida, um novo requisito é adicionado para oferecer suporte a vários temas (aparência). Vamos dizer que temos os seguintes temas: Win32, WinCE, WinPPC, WinMo50, WinMo65. Cada tema teria valores ou implementações diferentes para operações relacionadas à renderização, como DefaultFont, DefaultBackColor, BorderWidth, DrawFrame, DrawScrollThumb, etc.

Eu poderia criar um modelo de objeto como este:

Win32TextboxWidget : TextboxWidget

Win32ListWidget : ListWidget

etc., para um tipo de controle

WinCETextboxWidget : TextboxWidget

WinCEListWidget : ListWidget

etc., um para o outro tipo de controle (novamente)

Você entendeu a idéia - você obtém uma explosão de classe do número de widgets vezes o número de temas. Isso complica o designer da RAD, tornando-o ciente de todos os temas. Além disso, adicionar novos temas força o designer do RAD a ser modificado. Além disso, há muita implementação comum em um tema que seria ótimo herdar, mas os controles já estão herdando de uma base comum ( Widget).

Então, em vez disso, o que fiz foi criar uma hierarquia de objetos separada que implementa o tema. Cada widget manteria uma referência ao objeto que implementa as operações de renderização. Em muitos textos, essa classe possui um sufixo, Implmas eu me desviei dessa convenção de nomenclatura.

Então agora minha TextboxWidgetaparência fica assim:

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

E posso fazer com que meus vários pintores herdem minha base específica de tema, o que eu não podia fazer antes:

Win32WidgetPainter
+ DefaultFont
+ DefaultFontSize
+ DefaultColors
+ DrawFrame
+ Etc

Win32TextboxPainter : Win32WidgetPainter

Win32ListPainter : Win32WidgetPainter

Uma das coisas boas é que posso carregar dinamicamente as implementações em tempo de execução, permitindo adicionar tantos temas quanto eu quiser, sem alterar o software principal. Em outras palavras, minha "implementação pode variar independentemente da abstração".

tcarvin
fonte
Eu não entendo como isso é suposto ser o padrão da ponte? Quando você adiciona um novo componente à hierarquia do Widget, você é forçado a adicionar esse novo Widget a TODOS os Painters (Win32NewWidgetPainter, PPCNewWidgetPainter). NÃO são duas hierarquias em crescimento independente. Para um padrão de ponte adequado, você não deve subclassificar a classe PlatformWidgetPainter para cada widget, em vez disso, receber um widget "descritor de desenho".
Mazyod
Obrigado pelo feedback, você está certo. Era anos atrás, que eu postei isso, e revisá-la agora eu diria que descreve ponte bem até o último bit onde eu derivado Win32TextboxPaintere Win32ListPainter de Win32WidgetPainter. Você pode ter uma árvore de herança no lado da aplicação, mas deve ser mais genérico (talvez StaticStyleControlPainter, EditStyleControlPaintere ButtonStyleControlPainter) com quaisquer operações primitivas necessários substituídas conforme necessário. Isso está mais próximo do código real em que eu estava baseando o exemplo.
tcarvin
3

A ponte pretende desacoplar uma abstração de sua implementação concreta , para que ambas possam variar independentemente:

  • refine a abstração com subclassificação
  • fornecer implementações diferentes, também por subclassificação, sem precisar conhecer a abstração nem seus refinamentos.
  • se necessário, escolha em tempo de execução qual é a implementação mais adequada .

A ponte consegue isso usando a composição:

  • a abstração se refere (referência ou ponteiro) a um objeto de implementação
  • a abstração e seus refinamentos apenas conheciam a interface de implementação

insira a descrição da imagem aqui

Comentários adicionais sobre uma confusão frequente

Esse padrão é muito semelhante ao padrão do adaptador: a abstração oferece uma interface diferente para uma implementação e usa a composição para fazer isso. Mas:

A principal diferença entre esses padrões está em suas intenções
- Gamma & al, em " Design patterns, element of reusable OO software " , 1995

Neste excelente livro seminal sobre padrões de design, os autores também observam que:

  • adaptadores são frequentemente usados ​​quando uma incompatibilidade é descoberta e o acoplamento é imprevisto
  • pontes são usadas desde o início do projeto, quando se espera que as classes evoluam independentemente.
Christophe
fonte