Padrão Comportamental (GoF)

Mediator

Centraliza a comunicação entre objetos num mediador, eliminando referências diretas entre eles — reduzindo o acoplamento de M:N (cada objeto conhece todos os outros) para M:1 (cada objeto conhece apenas o mediador).

Intenção

Substituir referências cruzadas entre objetos por um hub central — o Mediator — que conhece todos os participantes (colegas) e coordena a comunicação entre eles. Cada colega envia suas notificações para o Mediator e recebe instruções dele; os colegas não sabem que os outros existem.

Catalogado pelo GoF (1994) como padrão comportamental, o Mediator aparece em formulários complexos de UI (campos que se habilitam mutuamente), salas de chat, sistemas de controle de tráfego aéreo, pipelines de processamento e qualquer contexto onde N objetos precisariam se referenciar mutuamente — criando um grafo de dependências difícil de manter.

Problema

Imagine um formulário de cadastro com quatro componentes: um checkbox "Pessoa Jurídica", um campo CNPJ, um campo CPF e um botão Enviar. As regras de interação são: ao marcar "Pessoa Jurídica", o campo CNPJ é habilitado e o CPF desabilitado; o botão Enviar só fica ativo quando o campo relevante está preenchido. A abordagem direta acopla cada componente a todos os outros:

// Abordagem ingênua — NÃO faça isso:
class CheckboxPJ {
  constructor(
    private campoCNPJ: CampoCNPJ,   // referência direta
    private campoCPF: CampoCPF,     // referência direta
    private botaoEnviar: BotaoEnviar // referência direta
  ) {}

  alternar(marcado: boolean): void {
    if (marcado) {
      this.campoCNPJ.habilitar();
      this.campoCPF.desabilitar();
    } else {
      this.campoCNPJ.desabilitar();
      this.campoCPF.habilitar();
    }
    this.botaoEnviar.atualizar(marcado);
    // Cada novo componente exige modificar esta classe.
  }
}

Com N componentes, cada um precisa conhecer os outros N-1. O acoplamento cresce quadraticamente (M:N). Testes se tornam difíceis porque isolar um componente exige mockar todos os seus pares. Adicionar um novo componente ao formulário exige modificar todos os componentes existentes que devem reagir a ele.

O Mediator resolve isso: cada componente conhece apenas o Mediator e envia notificações a ele. A lógica de "quem reage ao quê" fica centralizada em um único lugar — o ConcreteMediator.

Solução

O Mediator organiza o código em três participantes:

  1. Mediator (interface): declara o método de comunicação que os colegas usam para notificar o mediador. Ex.: notificar(remetente, evento). Os colegas só conhecem esta interface — não o ConcreteMediator específico.
  2. ConcreteMediator: conhece todos os colegas concretos e implementa a lógica de coordenação. Ao receber uma notificação, decide quais colegas devem ser acionados e como. É o único lugar onde a lógica de interação entre componentes existe.
  3. Colleagues (colegas): os componentes que interagem entre si apenas através do Mediator. Cada colega mantém uma referência ao Mediator (injetada via construtor ou setter) e o notifica quando seu estado muda. Nunca chama outros colegas diretamente.

Resolvendo a referência circular

O Mediator precisa conhecer os colegas, e os colegas precisam conhecer o Mediator — criando uma dependência circular na construção. A solução idiomática é injeção via setter: construa os colegas primeiro, construa o Mediator passando os colegas, depois injete o Mediator nos colegas com setMediador(). Desta forma, nenhum construtor precisa receber um objeto ainda não existente.

Estrutura

         «interface»
           Mediador
  ┌──────────────────────────────────────┐
  │ + notificar(rem: Componente,         │
  │             evento: string): void    │
  └──────────────────────────────────────┘
              ▲
  MediadorFormulario (ConcreteMediator)
  ┌──────────────────────────────────────┐
  │ - checkbox: CheckboxTermos           │
  │ - campo: CampoCodigo                 │
  │ - botao: BotaoEnviar                 │
  │ + notificar(rem, evento): void       │
  │   → aplica regras de coordenação     │
  └──────────────────────────────────────┘
              │ coordena
     ┌────────┼────────────┐
     ▼        ▼            ▼
CheckboxTermos  CampoCodigo  BotaoEnviar
(Colleague)     (Colleague)  (Colleague)
     │              │
     └──────┬────────┘
            │ notifica via
            ▼
          Mediador


Fluxo de comunicação:

  checkbox.aceitar()
    → this.mediador.notificar(this, "termos:aceitos")
      → MediadorFormulario verifica o estado de todos os colegas
        → botao.habilitar()   (se campo também preenchido)
        → botao.desabilitar() (caso contrário)

Exemplos de código

Exemplo 1 — Sala de chat com participantes desacoplados

O exemplo clássico: múltiplos participantes se comunicam por meio de uma sala (o Mediator). Nenhum participante referencia os outros diretamente — eles enviam mensagens para a sala e recebem notificações dela. A lógica de "quem recebe o quê" fica toda no SalaDeChat, não nos participantes.

// ── Mediator interface ────────────────────────────────────────
interface MediadorChat {
  enviarMensagem(mensagem: string, remetente: Participante): void;
}

// ── Colleague ─────────────────────────────────────────────────
// Participante não conhece outros participantes — só o mediador.
class Participante {
  constructor(
    readonly nome: string,
    private readonly mediador: MediadorChat
  ) {}

  enviar(mensagem: string): void {
    console.log(`${this.nome} → sala: "${mensagem}"`);
    this.mediador.enviarMensagem(mensagem, this);
  }

  receber(mensagem: string, remetente: Participante): void {
    console.log(`  [${this.nome} ← ${remetente.nome}]: "${mensagem}"`);
  }
}

// ── ConcreteMediator ──────────────────────────────────────────
class SalaDeChat implements MediadorChat {
  private readonly participantes: Set<Participante> = new Set();

  entrar(p: Participante): void { this.participantes.add(p); }
  sair(p: Participante): void   { this.participantes.delete(p); }

  enviarMensagem(mensagem: string, remetente: Participante): void {
    // Entrega para todos exceto o remetente — lógica centralizada aqui.
    for (const p of this.participantes) {
      if (p !== remetente) {
        p.receber(mensagem, remetente);
      }
    }
  }
}

// ── Uso ──────────────────────────────────────────────────────
const sala  = new SalaDeChat();

const alice = new Participante('Alice', sala);
const bob   = new Participante('Bob',   sala);
const carol = new Participante('Carol', sala);

sala.entrar(alice);
sala.entrar(bob);
sala.entrar(carol);

alice.enviar('Bom dia a todos!');
// Alice → sala: "Bom dia a todos!"
//   [Bob ← Alice]: "Bom dia a todos!"
//   [Carol ← Alice]: "Bom dia a todos!"

sala.sair(carol);

bob.enviar('Carol saiu da sala.');
// Bob → sala: "Carol saiu da sala."
//   [Alice ← Bob]: "Carol saiu da sala."
// (Carol não recebe — já saiu)

Exemplo 2 — Formulário com componentes interdependentes

Componentes de UI que precisam reagir uns aos outros são o caso de uso mais frequente do Mediator em aplicações reais. Neste exemplo, um checkbox e um campo de código controlam o estado de um botão Enviar — toda a lógica de coordenação fica no MediadorFormulario, e os componentes simplesmente notificam quando seu estado muda.

// ── Mediator interface ────────────────────────────────────────
interface Mediador {
  notificar(remetente: Componente, evento: string): void;
}

// ── Classe base dos colegas ───────────────────────────────────
class Componente {
  private mediador: Mediador | null = null;

  // Setter evita referência circular no construtor.
  setMediador(m: Mediador): void { this.mediador = m; }

  protected notify(evento: string): void {
    this.mediador?.notificar(this, evento);
  }
}

// ── Colleagues concretos ──────────────────────────────────────
class CheckboxTermos extends Componente {
  private aceito = false;

  aceitar(): void {
    this.aceito = true;
    console.log('Checkbox: termos aceitos');
    this.notify('termos:aceitos');
  }

  estaAceito(): boolean { return this.aceito; }
}

class CampoCodigo extends Componente {
  private valor = '';

  preencher(codigo: string): void {
    this.valor = codigo;
    console.log(`Campo código: "${codigo}"`);
    this.notify('codigo:preenchido');
  }

  getValor(): string { return this.valor; }
}

class BotaoEnviar extends Componente {
  habilitar(): void   { console.log('Botão Enviar: habilitado'); }
  desabilitar(): void { console.log('Botão Enviar: desabilitado'); }
}

// ── ConcreteMediator ──────────────────────────────────────────
// Toda a lógica de coordenação fica aqui — os colegas não se conhecem.
class MediadorFormulario implements Mediador {
  constructor(
    private readonly checkbox: CheckboxTermos,
    private readonly campo: CampoCodigo,
    private readonly botao: BotaoEnviar
  ) {}

  notificar(_remetente: Componente, evento: string): void {
    const termosOk = this.checkbox.estaAceito();
    const codigoOk = this.campo.getValor().length >= 4;

    console.log(
      `  [Mediador] evento="${evento}" → termos=${termosOk} código=${codigoOk}`
    );

    if (termosOk && codigoOk) {
      this.botao.habilitar();
    } else {
      this.botao.desabilitar();
    }
  }
}

// ── Uso ──────────────────────────────────────────────────────
const checkbox = new CheckboxTermos();
const campo    = new CampoCodigo();
const botao    = new BotaoEnviar();

// Mediator criado após os colegas; depois injetado neles via setter.
const mediador = new MediadorFormulario(checkbox, campo, botao);
checkbox.setMediador(mediador);
campo.setMediador(mediador);
botao.setMediador(mediador);

checkbox.aceitar();
// Checkbox: termos aceitos
//   [Mediador] evento="termos:aceitos" → termos=true código=false
//   Botão Enviar: desabilitado

campo.preencher('AB12');
// Campo código: "AB12"
//   [Mediador] evento="codigo:preenchido" → termos=true código=true
//   Botão Enviar: habilitado

Quando usar

  • Quando muitos objetos se referenciam mutuamente e o grafo de dependências dificulta manutenção, testes e compreensão do código — sinal claro de que M:N precisa virar M:1.
  • Em formulários e diálogos complexos de UI onde campos se habilitam, desabilitam ou se populam mutuamente dependendo do estado de outros campos — o Mediator é o lugar natural para centralizar essas regras de interação.
  • Em sistemas de comunicação entre agentes (chat rooms, salas de conferência, sistemas de despacho) onde os participantes não devem se conhecer diretamente para facilitar entrada, saída e substituição.
  • Para facilitar testes: mockar o Mediator permite testar cada componente de forma isolada sem precisar montar o conjunto completo de colegas.

Quando evitar

  • Para comunicação simples entre dois ou três objetos: se a coordenação é trivial e estável, a abstração do Mediator adiciona complexidade sem benefício perceptível.
  • Quando o Mediator começa a acumular lógica de negócio: se toda regra do domínio migra para o Mediator, ele se torna um god object disfarçado. Quando isso ocorre, é sinal de que a responsabilidade deve ser dividida ou que o domínio precisa ser modelado de forma diferente.
  • Quando a rastreabilidade do fluxo é crítica: centralizar toda a comunicação num único ponto pode dificultar o entendimento de fluxos complexos — "quem notificou quem e quando" exige inspecionar o Mediator inteiro. Em sistemas com muitos eventos, considere ferramentas de rastreio ou logging específico no Mediator.

Prós e contras

Prós

  • Reduz acoplamento M:N para M:1 — cada componente depende apenas do Mediator, não dos demais.
  • Centraliza a lógica de coordenação num único lugar — mais fácil de auditar, modificar e depurar as regras de interação.
  • Colegas podem ser adicionados ou removidos sem impactar os outros colegas — apenas o Mediator precisa ser atualizado.
  • Facilita testes unitários — um mock do Mediator isola completamente o componente que está sendo testado.

Contras

  • O Mediator pode crescer descontroladamente e virar um god object se cada nova regra de coordenação for centralizada nele sem critério.
  • Toda comunicação passa por um único ponto — em sistemas com muitos eventos, o Mediator pode se tornar um gargalo de performance ou compreensão.
  • O Mediator precisa conhecer todos os colegas concretos, criando acoplamento bidirecional entre ele e cada componente.

Armadilhas comuns

1. Mediator vira god object

A armadilha mais frequente: à medida que o sistema cresce, cada nova regra de coordenação é adicionada ao Mediator. Ele rapidamente acumula lógica de múltiplos domínios e se torna uma classe enorme que ninguém quer tocar. O sinal de alerta é quando o Mediator tem mais lógica do que os próprios colegas juntos.

Solução: quando o Mediator cresce demais, decomponha-o em Mediators menores por responsabilidade, ou revise o design — talvez alguns colegas devessem ter mais lógica interna e notificar menos o Mediator.

2. Confundir Mediator com Observer

O Observer define uma dependência um-para-muitos: o Subject publica um evento e N Observers reagem de forma independente — o Subject não conhece os tipos concretos dos Observers, apenas sua interface. A comunicação é unidirecional e desacoplada: o Subject não sabe quem reagiu.

O Mediator define uma coordenação bidirecional: o Mediator conhece explicitamente cada colega concreto e decide, com base em estado de múltiplos colegas, quais ações tomar. A comunicação flui nos dois sentidos — colegas notificam o Mediator, e o Mediator aciona colegas. Use Observer quando o padrão é "broadcaster desconhecido → múltiplos ouvintes desacoplados"; use Mediator quando o padrão é "hub que coordena fluxo entre participantes conhecidos".

3. Referência circular na construção

O Mediator precisa de referências aos colegas, e os colegas precisam de referência ao Mediator — nenhum pode ser criado primeiro sem o outro. A solução padrão é injeção via setter: crie os colegas primeiro (com o Mediator como null ou com um stub), construa o Mediator passando os colegas, depois injete o Mediator nos colegas com setMediador(). Outra abordagem é injetar o Mediator via construtor dos colegas e criar o Mediator num segundo passo após inicializar os colegas.

4. Mediator vs Facade — distinção de direção

A Facade provê uma interface simplificada para um subsistema do ponto de vista externo: um cliente chama a Facade, que coordena o subsistema internamente. A comunicação é unidirecional (cliente → Facade → subsistema). A Facade não conhece o cliente; o subsistema não conhece a Facade de volta.

O Mediator coordena a comunicação interna entre componentes que já existem no sistema: os colegas notificam o Mediator e recebem notificações dele — bidirecional. O Mediator conhece os colegas e eles conhecem o Mediator. São padrões complementares, não alternativos.

Padrões relacionados

O Mediator se relaciona com padrões de comunicação e coordenação:

O Observer e o Mediator são os dois padrões de comunicação entre objetos mais frequentemente confundidos. Observer = broadcast 1→N desacoplado por evento (o Subject não conhece os Observers individualmente); Mediator = hub que conhece os colegas e coordena fluxo bidirecional entre eles. São complementares: um Mediator pode internamente usar o Observer para notificar colegas sobre mudanças. A Facade simplifica o acesso externo a um subsistema (unidirecional); o Mediator coordena interações internas entre componentes que se comunicam mutuamente (bidirecional) — distinção de direção e de quem conhece quem. O Command pode ser roteado através de um Mediator: em vez de cada componente chamar diretamente o receptor de uma ação, ele cria um Command e o entrega ao Mediator, que decide como e para quem despachá-lo.