Pular para o conteúdo

O Mediator é um padrão de projeto comportamental que centraliza a comunicação entre vários objetos em um único ponto de coordenação: o mediador.

Em vez de cada objeto conversar diretamente com vários outros, todos passam a interagir por meio desse componente central. Isso reduz o acoplamento, evita dependências cruzadas e torna o comportamento do sistema mais previsível.

Esse padrão existe porque, em muitos sistemas, os objetos até começam simples, mas com o tempo passam a depender uns dos outros de forma desorganizada. O resultado costuma ser código difícil de entender, reutilizar e evoluir.

Imagine uma tela de cadastro em um sistema acadêmico. Ela possui campos de texto, caixa de seleção, botões e mensagens de validação.

No início, parece natural que os componentes conversem diretamente:

  • o botão de salvar valida os campos
  • a caixa de seleção habilita ou desabilita outros campos
  • a escolha do tipo de usuário altera partes visíveis do formulário
  • mensagens de erro dependem do estado de vários componentes

Com o tempo, surgem alguns problemas:

  • aumento de dependências entre componentes da interface
  • dificuldade para reutilizar um campo ou botão em outro contexto
  • mudanças pequenas passam a impactar várias classes
  • a lógica de coordenação fica espalhada
  • o comportamento da tela se torna mais difícil de testar

O problema central é este: os componentes precisam colaborar, mas não deveriam conhecer tantos detalhes uns dos outros.

O Mediator resolve isso introduzindo um objeto responsável por coordenar a comunicação entre os demais.

Em vez de um componente chamar diretamente outro componente, ele apenas informa ao mediador que algo aconteceu. O mediador recebe esse evento e decide o que fazer.

Uma forma simples de visualizar a solução é esta:

  1. os componentes mantêm referência apenas ao mediador
  2. cada componente notifica eventos relevantes, como clique, seleção ou alteração de texto
  3. o mediador interpreta o contexto e coordena a reação necessária
  4. os componentes deixam de depender diretamente uns dos outros
  5. a lógica de colaboração fica concentrada em um único ponto

Com isso, os componentes ficam mais independentes, o fluxo de interação fica mais claro e a manutenção tende a ser mais simples.

Pense em uma torre de controle de aeroporto.

Os aviões não negociam diretamente entre si quem vai pousar, decolar ou aguardar. Em vez disso, todos se comunicam com a torre, que coordena as ações com base no contexto geral.

No padrão Mediator, a torre de controle é o mediador. Os aviões são os componentes. Cada avião continua executando sua função, mas a coordenação entre eles passa por um único ponto central.

Use Mediator quando:

  • vários objetos interagem intensamente entre si
  • o acoplamento entre componentes está ficando difícil de controlar
  • você quer reutilizar componentes em outros contextos
  • mudanças em uma classe exigem alterações em várias outras
  • existe uma lógica clara de coordenação entre elementos do sistema

Exemplos reais em software:

  • formulários com validações e campos interdependentes
  • janelas e caixas de diálogo em interfaces gráficas
  • salas de chat com coordenação de mensagens
  • orquestração de componentes em jogos
  • módulos de workflow em sistemas corporativos

Mediator pode ser mal utilizado quando vira apenas um lugar para acumular toda a lógica do sistema sem critério.

Erros frequentes:

  • colocar regras de negócio demais dentro do mediador
  • transformar o mediador em um objeto gigante e difícil de manter
  • usar o padrão quando poucos objetos poderiam colaborar diretamente sem problema
  • criar um mediador excessivamente genérico e pouco claro
  • concentrar responsabilidades que deveriam continuar nos componentes

Se isso acontecer, o mediador pode virar um Objeto Deus, centralizando decisões demais e trocando um problema de acoplamento por um problema de excesso de responsabilidade.

Uma implementação incremental pode seguir estes passos:

  1. identifique o grupo de objetos que hoje se comunicam de forma muito acoplada
  2. defina uma interface de mediador com um método como notificar(remetente, evento)
  3. faça os componentes dependerem da interface do mediador
  4. mova para o mediador a lógica que coordena a colaboração entre componentes
  5. mantenha nos componentes apenas suas responsabilidades próprias
  6. refine o mediador para evitar que ele assuma regras que não pertencem a ele

Antes de implementar, vale perguntar: o problema está na lógica interna de cada componente ou na forma como eles se coordenam? Quando a dor está na coordenação, Mediator costuma ser uma boa escolha.

No exemplo abaixo, uma caixa de diálogo de matrícula centraliza a interação entre checkbox, campo de texto e botão de envio.

interface MediadorFormulario {
void notificar(ComponenteFormulario remetente, String evento);
}
abstract class ComponenteFormulario {
protected MediadorFormulario mediador;
public ComponenteFormulario(MediadorFormulario mediador) {
this.mediador = mediador;
}
}
class CheckboxPossuiBolsa extends ComponenteFormulario {
private boolean marcada;
public CheckboxPossuiBolsa(MediadorFormulario mediador) {
super(mediador);
}
public void marcar(boolean valor) {
this.marcada = valor;
mediador.notificar(this, "alterouBolsa");
}
public boolean isMarcada() {
return marcada;
}
}
class CampoCodigoBolsa extends ComponenteFormulario {
private boolean visivel;
public CampoCodigoBolsa(MediadorFormulario mediador) {
super(mediador);
}
public void mostrar() {
visivel = true;
}
public void esconder() {
visivel = false;
}
public boolean isVisivel() {
return visivel;
}
}
class BotaoEnviar extends ComponenteFormulario {
public BotaoEnviar(MediadorFormulario mediador) {
super(mediador);
}
public void clicar() {
mediador.notificar(this, "enviar");
}
}
class DialogoMatricula implements MediadorFormulario {
private final CheckboxPossuiBolsa checkbox;
private final CampoCodigoBolsa campoCodigo;
private final BotaoEnviar botaoEnviar;
public DialogoMatricula() {
this.checkbox = new CheckboxPossuiBolsa(this);
this.campoCodigo = new CampoCodigoBolsa(this);
this.botaoEnviar = new BotaoEnviar(this);
}
@Override
public void notificar(ComponenteFormulario remetente, String evento) {
if (remetente == checkbox && evento.equals("alterouBolsa")) {
if (checkbox.isMarcada()) {
campoCodigo.mostrar();
} else {
campoCodigo.esconder();
}
}
if (remetente == botaoEnviar && evento.equals("enviar")) {
System.out.println("Formulario processado pela classe mediadora.");
}
}
}

Nesse código, os componentes não se conhecem diretamente. O checkbox não chama o campo de código, e o botão não valida elementos por conta própria. A coordenação fica concentrada em DialogoMatricula.

  • reduz o acoplamento entre componentes
  • centraliza a lógica de interação
  • melhora a reutilização de objetos individuais
  • facilita mudanças na coordenação do fluxo
  • ajuda a organizar dependências complexas
  • adiciona uma camada extra de indireção
  • pode concentrar responsabilidades demais
  • pode dificultar a leitura se o mediador crescer demais
  • exige cuidado para não misturar coordenação com regra de negócio
  • pode ser desnecessário em cenários simples

O Mediator se relaciona com outros padrões e ideias importantes:

  • Observer: ambos lidam com comunicação entre objetos, mas Observer distribui notificações, enquanto Mediator centraliza a coordenação
  • Facade: Facade simplifica o acesso a um subsistema; Mediator organiza a colaboração entre componentes que continuam ativos
  • Command: comandos podem ser disparados por componentes coordenados por um mediador
  • SRP: o padrão ajuda a tirar dos componentes a responsabilidade de conhecer muitos colegas
  • Baixo acoplamento: a principal força do padrão está justamente em reduzir dependências cruzadas

Essas relações mostram que o Mediator não existe para esconder complexidade apenas para o cliente, mas para organizar melhor a colaboração interna entre objetos.

Algumas variações comuns incluem:

  • mediadores explícitos com interface própria
  • componentes que notificam eventos por nomes simbólicos
  • mediadores baseados em eventos ou barramento interno
  • caixas de diálogo que também controlam ciclo de vida dos componentes
  • uso combinado com Observer para distribuir notificações a partir do mediador

Em frameworks modernos, parte desse papel pode aparecer em controladores, presenters, event buses ou serviços de orquestração. Ainda assim, a ideia central continua sendo a mesma: tirar dos componentes a responsabilidade de coordenar uns aos outros diretamente.

Agora veja um exemplo mais realista. Um sistema web de autenticação possui uma tela que alterna entre login e cadastro.

interface Mediator {
void notificar(ComponenteUI remetente, String evento);
}
abstract class ComponenteUI {
protected final Mediator mediator;
protected ComponenteUI(Mediator mediator) {
this.mediator = mediator;
}
}
class CheckboxModo extends ComponenteUI {
private boolean login = true;
public CheckboxModo(Mediator mediator) {
super(mediator);
}
public void alternarModo() {
login = !login;
mediator.notificar(this, "alternouModo");
}
public boolean isLogin() {
return login;
}
}
class CampoTexto extends ComponenteUI {
private final String nome;
private boolean visivel = true;
public CampoTexto(Mediator mediator, String nome) {
super(mediator);
this.nome = nome;
}
public void mostrar() {
visivel = true;
}
public void esconder() {
visivel = false;
}
public String getNome() {
return nome;
}
public boolean isVisivel() {
return visivel;
}
}
class BotaoAcao extends ComponenteUI {
public BotaoAcao(Mediator mediator) {
super(mediator);
}
public void clicar() {
mediator.notificar(this, "clicou");
}
}
class DialogoAutenticacao implements Mediator {
private final CheckboxModo checkboxModo;
private final CampoTexto campoUsuario;
private final CampoTexto campoSenha;
private final CampoTexto campoEmail;
private final BotaoAcao botao;
public DialogoAutenticacao() {
checkboxModo = new CheckboxModo(this);
campoUsuario = new CampoTexto(this, "usuario");
campoSenha = new CampoTexto(this, "senha");
campoEmail = new CampoTexto(this, "email");
botao = new BotaoAcao(this);
campoEmail.esconder();
}
@Override
public void notificar(ComponenteUI remetente, String evento) {
if (remetente == checkboxModo && evento.equals("alternouModo")) {
if (checkboxModo.isLogin()) {
campoEmail.esconder();
System.out.println("Modo login ativado.");
} else {
campoEmail.mostrar();
System.out.println("Modo cadastro ativado.");
}
}
if (remetente == botao && evento.equals("clicou")) {
if (checkboxModo.isLogin()) {
System.out.println("Validando credenciais de login...");
} else {
System.out.println("Validando dados de cadastro...");
}
}
}
public CheckboxModo getCheckboxModo() {
return checkboxModo;
}
public BotaoAcao getBotao() {
return botao;
}
}
public class Main {
public static void main(String[] args) {
DialogoAutenticacao dialogo = new DialogoAutenticacao();
dialogo.getCheckboxModo().alternarModo();
dialogo.getBotao().clicar();
}
}

Nesse exemplo, a caixa de diálogo funciona como mediadora entre os componentes da tela. Cada componente continua simples, enquanto a lógica de alternar entre login e cadastro fica concentrada em DialogoAutenticacao.