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:
-
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. - 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.
- 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)
<?php
// ── Mediator interface ────────────────────────────────────────
interface MediadorChat
{
public function enviarMensagem(string $mensagem, Participante $remetente): void;
}
// ── Colleague ─────────────────────────────────────────────────
class Participante
{
public function __construct(
public readonly string $nome,
private readonly MediadorChat $mediador
) {}
public function enviar(string $mensagem): void
{
echo "{$this->nome} → sala: \"{$mensagem}\"\n";
$this->mediador->enviarMensagem($mensagem, $this);
}
public function receber(string $mensagem, Participante $remetente): void
{
echo " [{$this->nome} ← {$remetente->nome}]: \"{$mensagem}\"\n";
}
}
// ── ConcreteMediator ──────────────────────────────────────────
class SalaDeChat implements MediadorChat
{
/** @var Participante[] */
private array $participantes = [];
public function entrar(Participante $p): void
{
$this->participantes[] = $p;
}
public function sair(Participante $p): void
{
$this->participantes = array_values(array_filter(
$this->participantes,
fn($x) => $x !== $p
));
}
public function enviarMensagem(string $mensagem, Participante $remetente): void
{
foreach ($this->participantes as $p) {
if ($p !== $remetente) {
$p->receber($mensagem, $remetente);
}
}
}
}
// ── Uso ──────────────────────────────────────────────────────
$sala = new SalaDeChat();
$alice = new Participante('Alice', $sala);
$bob = new Participante('Bob', $sala);
$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."
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
<?php
// ── Mediator interface ────────────────────────────────────────
interface Mediador
{
public function notificar(Componente $remetente, string $evento): void;
}
// ── Classe base dos colegas ───────────────────────────────────
abstract class Componente
{
private ?Mediador $mediador = null;
public function setMediador(Mediador $m): void
{
$this->mediador = $m;
}
protected function notify(string $evento): void
{
$this->mediador?->notificar($this, $evento);
}
}
// ── Colleagues concretos ──────────────────────────────────────
class CheckboxTermos extends Componente
{
private bool $aceito = false;
public function aceitar(): void
{
$this->aceito = true;
echo "Checkbox: termos aceitos\n";
$this->notify('termos:aceitos');
}
public function estaAceito(): bool { return $this->aceito; }
}
class CampoCodigo extends Componente
{
private string $valor = '';
public function preencher(string $codigo): void
{
$this->valor = $codigo;
echo "Campo código: \"{$codigo}\"\n";
$this->notify('codigo:preenchido');
}
public function getValor(): string { return $this->valor; }
}
class BotaoEnviar extends Componente
{
public function habilitar(): void { echo "Botão Enviar: habilitado\n"; }
public function desabilitar(): void { echo "Botão Enviar: desabilitado\n"; }
}
// ── ConcreteMediator ──────────────────────────────────────────
class MediadorFormulario implements Mediador
{
public function __construct(
private readonly CheckboxTermos $checkbox,
private readonly CampoCodigo $campo,
private readonly BotaoEnviar $botao
) {}
public function notificar(Componente $remetente, string $evento): void
{
$termosOk = $this->checkbox->estaAceito();
$codigoOk = strlen($this->campo->getValor()) >= 4;
echo " [Mediador] evento=\"{$evento}\"" .
" → termos=" . ($termosOk ? 'true' : 'false') .
" código=" . ($codigoOk ? 'true' : 'false') . "\n";
if ($termosOk && $codigoOk) {
$this->botao->habilitar();
} else {
$this->botao->desabilitar();
}
}
}
// ── Uso ──────────────────────────────────────────────────────
$checkbox = new CheckboxTermos();
$campo = new CampoCodigo();
$botao = new BotaoEnviar();
$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.