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?
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?
Respostas:
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*/}
Monorail
já 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 SunShine
ou CupCake
, eles seriam Sunshine
eCupcake
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, Impl
mas eu me desviei dessa convenção de nomenclatura.
Então agora minha TextboxWidget
aparê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".
Win32TextboxPainter
e Win32ListPainter
de Win32WidgetPainter
. Você pode ter uma árvore de herança no lado da aplicação, mas deve ser mais genérico (talvez StaticStyleControlPainter
, EditStyleControlPainter
e 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.
A ponte pretende desacoplar uma abstração de sua implementação concreta , para que ambas possam variar independentemente:
A ponte consegue isso usando a composição:
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: