O Padrão do Mediador em Java
1. Visão geral
Neste artigo, daremos uma olhada emthe Mediator Pattern, one of the GoF behavioral patterns. Descreveremos seu propósito e explicaremos quando devemos usá-lo.
Como de costume, também forneceremos um exemplo de código simples.
2. Padrão do Mediador
Na programação orientada a objetos, devemos sempre tentardesign the system in such a way that components are loosely coupled and reusable. Essa abordagem torna nosso código mais fácil de manter e testar.
Na vida real, no entanto, muitas vezes precisamos lidar com um conjunto complexo de objetos dependentes. É aí que o Padrão do Mediador pode ser útil.
The intent of the Mediator Pattern is to reduce the complexity and dependencies between tightly coupled objects communicating directly with one another. Isso é conseguido através da criação de um objeto mediador que cuida da interação entre objetos dependentes. Consequentemente, toda a comunicação passa pelo mediador.
Isso promove acoplamentos frouxos, pois um conjunto de componentes trabalhando juntos não precisa mais interagir diretamente. Em vez disso, eles se referem apenas ao único objeto mediador. Dessa forma, também é mais fácil reutilizar esses objetos em outras partes do sistema.
3. Diagrama UML do Mediator Pattern
Vamos agora ver o padrão visualmente:
No diagrama UML acima, podemos identificar os seguintes participantes:
-
Mediator define a interface que os objetosColleague usam para se comunicar
-
Colleague define a classe abstrata contendo uma única referência aoMediator
-
ConcreteMediator encapsula a lógica de interação entre os objetosColleague
-
ConcreteColleague1 eConcreteColleague2 se comunicam apenas por meio doMediator
Como podemos ver,Colleague objects do not refer to each other directly. Instead, all the communication is carried out by the*Mediator*.
Consequentemente,ConcreteColleague1eConcreteColleague2 podem ser reutilizados mais facilmente.
Além disso, no caso de precisarmos alterar a maneira como os objetosColleague trabalham juntos, só temos que alterar a lógicaConcreteMediator. Ou podemos criar uma nova implementação doMediator.
4. Implementação Java
Agora que temos uma idéia clara da teoria, vejamos um exemplo para entender melhor o conceito na prática.
4.1. Cenário de exemplo
Imagine que estamos construindo um sistema de resfriamento simples que consiste em um ventilador, uma fonte de alimentação e um botão. Pressionar o botão liga ou desliga o ventilador. Antes de ligar o ventilador, precisamos ligar a energia. Da mesma forma, temos que desligar a energia logo após o ventilador ser desligado.
Vamos agora dar uma olhada no exemplo de implementação:
public class Button {
private Fan fan;
// constructor, getters and setters
public void press(){
if(fan.isOn()){
fan.turnOff();
} else {
fan.turnOn();
}
}
}
public class Fan {
private Button button;
private PowerSupplier powerSupplier;
private boolean isOn = false;
// constructor, getters and setters
public void turnOn() {
powerSupplier.turnOn();
isOn = true;
}
public void turnOff() {
isOn = false;
powerSupplier.turnOff();
}
}
public class PowerSupplier {
public void turnOn() {
// implementation
}
public void turnOff() {
// implementation
}
}
A seguir, vamos testar a funcionalidade:
@Test
public void givenTurnedOffFan_whenPressingButtonTwice_fanShouldTurnOnAndOff() {
assertFalse(fan.isOn());
button.press();
assertTrue(fan.isOn());
button.press();
assertFalse(fan.isOn());
}
Tudo parece estar funcionando direito. Mas observe comoButton, Fan, and PowerSupplier classes are tightly coupled. OButton opera diretamente emFaneFan interage comButton ePowerSupplier.
Seria difícil reutilizar a classeButton em outros módulos. Além disso, se precisarmos adicionar uma segunda fonte de alimentação em nosso sistema, teremos que modificar a lógica da classeFan.
4.2. Adicionando o padrão do mediador
Agora, vamos implementar o Mediator Pattern para reduzir as dependências entre nossas classes e tornar o código mais reutilizável.
Primeiro, vamos apresentar a classeMediator:
public class Mediator {
private Button button;
private Fan fan;
private PowerSupplier powerSupplier;
// constructor, getters and setters
public void press() {
if (fan.isOn()) {
fan.turnOff();
} else {
fan.turnOn();
}
}
public void start() {
powerSupplier.turnOn();
}
public void stop() {
powerSupplier.turnOff();
}
}
A seguir, vamos modificar as classes restantes:
public class Button {
private Mediator mediator;
// constructor, getters and setters
public void press() {
mediator.press();
}
}
public class Fan {
private Mediator mediator;
private boolean isOn = false;
// constructor, getters and setters
public void turnOn() {
mediator.start();
isOn = true;
}
public void turnOff() {
isOn = false;
mediator.stop();
}
}
Mais uma vez, vamos testar a funcionalidade:
@Test
public void givenTurnedOffFan_whenPressingButtonTwice_fanShouldTurnOnAndOff() {
assertFalse(fan.isOn());
button.press();
assertTrue(fan.isOn());
button.press();
assertFalse(fan.isOn());
}
Nosso sistema de refrigeração funciona conforme o esperado.
Now that we’ve implemented the Mediator Pattern, none of the Button, Fan, or PowerSupplier classes communicate directly. Eles têm apenas uma única referência aoMediator.
Se precisarmos adicionar uma segunda fonte de alimentação no futuro, tudo o que temos que fazer é atualizar a lógicaMediator’s; As classesButtoneFan permanecem intocadas.
Este exemplo mostra com que facilidade podemos separar objetos dependentes e facilitar a manutenção do nosso sistema.
5. Quando usar o padrão do mediador
The Mediator Pattern is a good choice if we have to deal with a set of objects that are tightly coupled and hard to maintain. Desta forma, podemos reduzir as dependências entre os objetos e diminuir a complexidade geral.
Adicionalmente, usando o objeto mediador, extraímos a lógica de comunicação para o único componente, portanto seguimos oSingle Responsibility Principle. Além disso, podemos introduzir novos mediadores sem a necessidade de alterar as partes restantes do sistema. Por isso, seguimos o Princípio Aberto-Fechado.
Sometimes, however, we may have too many tightly coupled objects due to the faulty design of the system. If this is a case, we should not apply the Mediator Pattern. Em vez disso, devemos dar um passo para trás e repensar a maneira como modelamos nossas classes.
Como com todos os outros padrões,we need to consider our specific use case before blindly implementing the Mediator Pattern.
6. Conclusão
Neste artigo, aprendemos sobre o Padrão do Mediador. Explicamos que problema esse padrão resolve e quando deveríamos considerar usá-lo. Também implementamos um exemplo simples do padrão de design.
Como sempre, os exemplos de código completos estão disponíveisover on GitHub.