Problema
Pense na seguinte situação: seria legal ter um aplicativo que trocasse mensagem entre diversas plataformas móveis, um Android enviando mensagem para um iOS, um Symbian trocando mensagens com um Android… O problema é que cada uma destas plataforma implementa maneiras diferentes de receber mensagens.
Obviamente seria uma péssima solução criar vários métodos para cada plataforma. Analise o diagrama abaixo:
Imagine que agora o aplicativo vai incluir a plataforma BlackBerry OS, precisaríamos criar os métodos de comunicação com todas as outras plataforma existentes, além de adicionar métodos em todas as outras plataformas para que elas se comuniquem com o BlackBerry OS.
Esta ideia de relacionamento muitos para muitos pode deixar o design bem complexo, comprometendo a eficiência do sistema, bem como sua manutenibilidade.
Quando uma situação em que um relacionamento muitos para muitos é necessário em Banco de Dados, uma boa prática é criar uma tabela intermediária e deixar que ela relaciona uma entidade com outras várias e vice-e-versa. Esta é a ideia do padrão Mediator.
Mediator
Intenção:
“Definir um objeto que encapsula a forma como um conjunto de objetos interage. O Mediator promove o acoplamento fraco ao evitar que os objetos se refiram uns aos outros explicitamente e permitir variar suas interações independentemente.” [1]
Pela intenção podemos perceber que o Mediator atua como um mediador entre relacionamentos muitos para muitos, ao evitar uma referência explicita aos objetos. Outra vantagem que podemos notar é também que ele concentra a maneira como os objetos interagem.
O padrão Mediator consiste de duas figuras principais: o Mediator e o Colleague. O Mediator recebe mensagens de um Colleague, define qual protocolo utilizar e então envia a mensagem. O Colleague define como receberá uma mensagem e envia uma mensagem para um Mediator.
Vamos então implementar o Colleague que servirá como base para todos os outros:
public abstract class Colleague { protected Mediator mediator; public Colleague(Mediator m) { mediator = m; } public void enviarMensagem(String mensagem) { mediator.enviar(mensagem, this); } public abstract void receberMensagem(String mensagem); }
Simples, define apenas a interface comum de qualquer Colleague. Todos possuem um Mediator, que deve ser compartilhado entre os objetos Colleague. Também define a maneira como todos os objetos Colleague enviam mensagens. O método “receberMensagem()” fica a cargo das subclasses.
Como exemplo de Colleague, vejamos as classes a seguir, que representam as plataformas Android e iOS:
public class IOSColleague extends Colleague { public IOSColleague(Mediator m) { super(m); } @Override public void receberMensagem(String mensagem) { System.out.println("iOs recebeu: " + mensagem); } }
public class AndroidColleague extends Colleague { public AndroidColleague(Mediator m) { super(m); } @Override public void receberMensagem(String mensagem) { System.out.println("Android recebeu: " + mensagem); } }
As classes Colleague concretas também são bem simples, apenas definem como a mensagem será recebida.
Vejamos então como funciona o Mediator. Vamos primeiro definir a interface comum de qualquer Mediator:
public interface Mediator { void enviar(String mensagem, Colleague colleague); }
Ou seja, todo Mediator deverá definir uma maneira de enviar mensagens. Vejamos então como o Mediator concreto seria implementado:
public class MensagemMediator implements Mediator { protected ArrayList<Colleague> contatos; public MensagemMediator() { contatos = new ArrayList<Colleague>(); } public void adicionarColleague(Colleague colleague) { contatos.add(colleague); } @Override public void enviar(String mensagem, Colleague colleague) { for (Colleague contato : contatos) { if (contato != colleague) { definirProtocolo(contato); contato.receberMensagem(mensagem); } } } private void definirProtocolo(Colleague contato) { if (contato instanceof IOSColleague) { System.out.println("Protocolo iOS"); } else if (contato instanceof AndroidColleague) { System.out.println("Protocolo Android"); } else if (contato instanceof SymbianColleague) { System.out.println("Protocolo Symbian"); } } }
O Mediator possui uma lista de objetos Colleague que realizarão a comunicação e um método para adicionar um novo Colleague.
O método “enviar()” percorre toda a lista de contatos e envia mensagens. Note que dentro deste métodos foi feita uma comparação para evitar a mensagem seja enviada para a pessoa que enviou. Para enviar a mensagem primeiro deve ser definido qual protocolo utilizar e em seguida enviar a mensagem.
No nosso exemplo, o método “definirProtocolo()” apenas imprime na tela o tipo do Colleague que enviou a mensagem, utilizar para isso a verificação instanceof.
Desta maneira, o cliente poderia ser algo do tipo:
public static void main(String[] args) { MensagemMediator mediador = new MensagemMediator(); AndroidColleague android = new AndroidColleague(mediador); IOSColleague ios = new IOSColleague(mediador); SymbianColleague symbian = new SymbianColleague(mediador); mediador.adicionarColleague(android); mediador.adicionarColleague(ios); mediador.adicionarColleague(symbian); symbian.enviarMensagem("Oi, eu sou um Symbian!"); System.out.println("========="); android.enviarMensagem("Oi Symbian! Eu sou um Android!"); System.out.println("========="); ios.enviarMensagem("Olá todos, sou um iOs!"); }
O diagrama UML para este exemplo seria o seguinte:
Um pouco de teoria
O padrão Mediator tem como principal objetivo diminuir a complexidade de relacionamentos entre objetos, garantindo assim que todos fiquem mais livres para sofrer mudanças, bem como facilitando a introdução de novos tipos de objetos ao relacionamento.
Outro ganho é a centralização da lógica de controle de comunicação entre os objetos, imagine que o protocolo de comunicação com o Android precisasse ser alterado, a mudança seria em um local bem específico da classe Mediator.
Uma vantagem não muito explorada nesse exemplo é que o Mediator centraliza também o controle dos objetos Colleague. Como citamos no post anterior sobre o padrão Observer, quando o relacionamento entre objetos Observer e Subject fica muito complexo, pode ser necessário utilizar uma classe intermediária que mapeie o relacionamento, facilitando o envio de mensagens aos objetos Observer.
Ao introduzir o Mediator vimos que a complexidade das classes Colleague foi transferida para o Mediator, o que tornou as classes Colleague bem mais simples e fáceis de manter. No entanto isto também pode ser um problema, pois a classe Mediator pode crescer em complexidade e se tornar difícil de manter.
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.