Mão na massa: Bridge

O padrão deste post é o Bridge!

Problema

Como vimos no post anterior sobre o padrão Adapter, utilizamos o padrão Adapter para liberar o nosso código cliente de detalhes de implementação de Frameworks/API/Biblioteca específicas.

Suponha agora que é necessário fazer um programa que vá funcionar em várias plataformas, por exemplo, Windows, Linux, Mac, etc. O programa fará uso de diversas abstrações de janelas gráficas, por exemplo, janela de diálogo, janela de aviso, janela de erro, etc.

Como podemos representar esta situação? A utilização de um Adapter para adaptar as janelas para as diversas plataformas parece ser boa, criaríamos um Adapter para Windows, Linux e Mac e então utilizaríamos de acordo com a necessidade.

No entanto teríamos que utilizar o adaptador de cada uma das plataforma para cada um dos tipos de abstrações de janelas. Por exemplo, para uma janela de diálogo, teríamos um Adapter para Windows, Linux e Mac, da mesma forma para as outras janelas.

Vamos então partir para uma solução bem legal, o padrão Bridge!

Bridge

Intenção:

“Desacoplar uma abstração da sua implementação, de modo que as duas possam variar independentemente.” [1]

Ou seja, o Bridge fornece um nível de abstração maior que o Adapter, pois são separadas as implementações e as abstrações, permitindo que cada uma varie independentemente.

Para o exemplo as implementações seriam as classes de Janela das plataformas. Vamos iniciar construindo elas, de maneira bem simples. A primeira classe será a interface comum a todas as implementações, chamadas de JanelaImplementada:

public interface JanelaImplementada {

	void desenharJanela(String titulo);

	void desenharBotao(String titulo);

}

Ou seja, nessa classe definimos que todas as janelas desenham uma janela e um botão. Vamos ver agora a classe concreta que desenha a janela na plataforma Windows:

public class JanelaWindows implements JanelaImplementada {

	@Override
	public void desenharJanela(String titulo) {
		System.out.println(titulo + " - Janela Windows");
	}

	@Override
	public void desenharBotao(String titulo) {
		System.out.println(titulo + " - Botão Windows");
	}

}

Também uma classe bem simples, apenas vamos exibir uma mensagem no terminal para saber que tudo correu bem.

Agora vamos então para as abstrações. Elas são abstrações pois não definem uma janela específica, como a JanelaWindows, no entanto utilizarão os métodos destas janelas concretas para construir suas janelas.

Vamos então iniciar a construção da classe abstrata que vai fornecer uma interface de acesso comum para as abstrações de janelas:

public abstract class JanelaAbstrata {

	protected JanelaImplementada janela;

	public JanelaAbstrata(JanelaImplementada j) {
		janela = j;
	}

	public void desenharJanela(String titulo) {
		janela.desenharJanela(titulo);
	}

	public void desenharBotao(String titulo) {
		janela.desenharBotao(titulo);
	}

	public abstract void desenhar();

}

Essa classe possui uma referência para a interface das janelas implementadas, com isso conseguimos variar a implementação de maneira bem simples. Agora veja o exemplo da classe de JanelaDialogo, que abstrai uma janela de diálogo para todas as plataformas:

public class JanelaDialogo extends JanelaAbstrata {

	public JanelaDialogo(JanelaImplementada j) {
		super(j);
	}

	@Override
	public void desenhar() {
		desenharJanela("Janela de Diálogo");
		desenharBotao("Botão Sim");
		desenharBotao("Botão Não");
		desenharBotao("Botão Cancelar");
	}

}

Uma janela de diálogo exibe sempre três botões: Sim, Não e Cancelar. Ou seja, independente de qual plataforma se está utilizando, a abstração é sempre a mesma. Para uma janela de aviso por exemplo, bastaria um botão Ok, então sua implementação seria algo do tipo:

public class JanelaAviso extends JanelaAbstrata {

	public JanelaAviso(JanelaImplementada j) {
		super(j);
	}

	@Override
	public void desenhar() {
		desenharJanela("Janela de Aviso");
		desenharBotao("Ok");
	}

}

Vamos ver então como seria o cliente da nossa aplicação. Ele fará uso apenas da classe que define uma Janela, assim poderá ficar livre de quaisquer detalhes de abstrações ou de implementações:

	public static void main(String[] args) {
		JanelaAbstrata janela = new JanelaDialogo(new JanelaLinux());
		janela.desenhar();
		janela = new JanelaAviso(new JanelaLinux());
		janela.desenhar();

		janela = new JanelaDialogo(new JanelaWindows());
		janela.desenhar();
	}

Note que é bem simples realizar trocas entre as abstrações de janelas e de plataformas. O diagrama UML para este exemplo seria algo do tipo:

Um pouco de teoria

Como vimos pelo exemplo de codificação o Bridge provê um excelente nível de desacoplação dos componentes. Tanto novas abstrações como novas plataformas podem ser acomodadas pelo sistema sem grandes dificuldades, graças a extensibilidade do padrão.

Outro ponto que é comum a maioria dos padrões é a ocultação dos detalhes de implementação do cliente, que fica independente de qualquer variação ou extensão que precise ser feita.

Um ponto que merece um certo cuidado é sobre a instanciação dos objetos, pois vimos que é necessário especificar a abstração e utilizar uma implementação, assim o cliente precisa conhecer bem as classes, e o que elas realizam para saber exatamente o que, quando e como fazer.

Código fonte completo

O código completo pode ser baixado no seguinte repositório Git: https://github.com/MarcosX/Padr-es-de-Projeto.

Os arquivos estão como um projeto do eclipse, então basta colocar no seu Workspace e fazer o import.

Se gostou do post compartilhe com seus amigos e colegas, senão, comente o que pode ser melhorado. Encontrou algum erro no código? Comente também. Possui alguma outra opinião ou alguma informação adicional? Comenta ai! 😀

Referências:

[1] GAMMA, Erich et al. Padrões de Projeto: Soluções reutilizáveis de software orientado a objetos.