Índice do grimório

Padrão 19 · Comportamentais

Mediator

coordenar destinatários e ações de um protocolo por etapa, sem retransmitir tudo a todos.

Começar pelo código
A criatura lacustre de couraça azul se curva junto à grade de Azurion enquanto Marta usa um gancho para afastar o galho que prende sua nadadeira.
Cena do capítulo 19 · Mediator

01 · Entenda a escolha

Os fundamentos do padrão.

README.md

19 — Mediator: O Conselho das Vozes

Zora coordena um pedido local de abertura: cultivadores respondem pela inspeção, barqueiros pelas amarras e guarda pela autorização/manobra. O projeto representa essas solicitações, confirmações e efeitos por dados, sem simular fluxo de água, estabilidade de embarcação ou recuperação de carga molhada. A coordenação nova não apaga dano anterior.

A alternativa direta primeiro

Com dois colaboradores simples e relação estável, uma chamada direta é mais clara que introduzir um conselho. src/main.ts começa com uma solicitação diretamente registrada. Mediator ganha função neste recorte quando a reação correta depende da etapa e do responsável, sem fazer cada colega conhecer todos os outros.

ConselhoMediator implementa Mediador.notificar(Evento). Barqueiros, Cultivadores e Guarda conhecem esse contrato para comunicar decisões, sem referências entre si. O mediador conhece portas de ação desses colegas e escolhe um destinatário e uma ação por regra. Não retransmite toda mensagem a todos como um barramento genérico.

Executar

Ambiente declarado e utilizado: Node 26.10.0, npm 11.19.1, TypeScript 5.9.3, @types/node 26.0.0. .nvmrc, lock e configuração ESM NodeNext estrita são próprios. Não há imports de outros projetos.

Na pasta book-23/code/19-mediator:

npm ci --ignore-scripts --no-audit --no-fund
npm test
npm start
npm run solution

Na raiz do repositório:

node book-23/scripts/projetos.mjs 19 install
node book-23/scripts/projetos.mjs 19 check
node book-23/scripts/projetos.mjs 19 run
node book-23/scripts/projetos.mjs 19 solution
node book-23/scripts/projetos.mjs 19 capture
node book-23/scripts/projetos.mjs 19 verify

evidence/stdout.txt e solution-stdout.txt são gerados pelos programas reais, sem banners npm; não há linhas de envio acrescentadas editorialmente. manifest.json vincula versões/fontes/testes/saídas por SHA256. runner-commands.json registra os seis comandos do runner; isolation.json registra instalação, testes e programas numa cópia temporária só deste projeto, com comparação byte a byte das duas saídas. Tempos de teste variam; não houve benchmark. As evidências são do produtor e não atribuem aprovação humana ou auditoria independente.

Uma composição, um pedido

montarConselho() cria um mediador e três colegas novos e conecta suas portas uma única vez. A ligação é feita antes das notificações; sem equipe conectada, um pedido é rejeitado. conectar() copia as referências de cada porta e recusa reconexão, impedindo trocar equipe enquanto se mantém o estado de uma operação.

O ciclo é deliberadamente de uso único:

  1. Estado pronto: Barqueiros.pedirAbertura() produz pedido. O mediador entra em aguardando-inspecao e chama somente Cultivadores.solicitarInspecao().
  2. Cultivadores.confirmarInspecao(true) faz entrar em aguardando-amarras e chama somente Barqueiros.solicitarAmarras().
  3. Barqueiros.confirmarAmarras(true) faz entrar em aguardando-autorizacao e chama somente Guarda.solicitarAutorizacao().
  4. Guarda.confirmarAutorizacao(true) chama Guarda.abrir() e só entra em aberto depois de a ação retornar normalmente.

As ações de solicitação apenas incrementam seu contador no colega; não geram confirmação automática. Confirmações são chamadas explícitas posteriores, de responsáveis distintos no exemplo. Confirmação negativa na etapa correspondente encerra como recusado; não solicita a próxima ação. Um evento fora da etapa, duplicado, precoce ou posterior ao encerramento é rejeitado sem novos despachos. Os três valores de confirmação precisam ser booleanos em execução; strings truthy e números não contam como confirmação.

Resultado tem status, estado e motivo. processado significa que o evento válido foi tratado: uma negativa pode ser processada e terminar recusado. rejeitado significa que o evento não podia ser tratado naquele contexto. falhou significa que uma ação dirigida lançou erro. Esses estados não são autenticação ou confirmação de segurança física.

Um segundo pedido exige nova composição completa, com novo mediador e novos colegas. O contexto anterior não tem reset ou troca de pedido. Seus colegas continuam ligados à operação antiga; confirmações antigas não fazem avançar a nova composição. Não há correlação distribuída entre pedidos, rotação de identificadores ou fila de operações concorrentes.

Falha parcial, repetição e reentrância

O mediador registra cada despacho com destinatário, ação e status. Erro síncrono de qualquer ação encerra como falhou, sem despachar etapas posteriores e sem retentativa automática. A mensagem de Error é copiada quando legível; há fallback para valor lançado sem mensagem ou leitura de mensagem que falhe. A exceção é convertida em resultado de falha, sem preservar sua identidade como exceção relançada.

Guarda(true) na montagem de teste simula falha após incrementar o contador de acionamentos e antes de marcar abertura. A tentativa permanece observável, aberta fica falsa e o conselho não anuncia aberto. Nenhuma solicitação ou efeito anterior é revertido. Uma porta externa poderia falhar depois de produzir outro efeito parcial; o mediador não descobre nem desfaz esse efeito por magia. O estado falhou não é um certificado de quanto uma comporta física se moveu.

Durante o despacho de uma ação, uma notificação reentrante ao conselho é rejeitada. O bloqueio é liberado em finally; a ação solicitada deve retornar antes de a confirmação ser recebida por chamada posterior. Isso evita avançar o protocolo dentro de um callback de solicitação. Não há fila de notificações aninhadas ou controle de trabalho arbitrariamente bloqueante.

A repetição não é idempotência geral: eventos já consumidos retornam rejeição em vez de repetir o sucesso. Estados terminais não aceitam nova tentativa; criar um novo contexto é uma decisão explícita fora deste ciclo, sem promessa de que isso seja seguro para um serviço real após resultado incerto.

As portas são síncronas neste contrato. Um retorno void não impede toda função async em TypeScript; o mediador não espera Promises nem trata rejeições delas. Uma implementação que devolve trabalho assíncrono viola esse contrato local; não interprete retorno normal como conclusão assíncrona.

Encapsulamento e custos

Campos # protegem o estado das instâncias em JavaScript, enquanto interfaces e readonly orientam a compilação. Uma interface de evento não autentica o remetente: código que possui o mediador pode chamar notificar diretamente. Quem possui a guarda também pode chamar seu método público abrir e ignorar a coordenação; um teste demonstra esse limite. As pré-condições são regras do caminho mediado, não uma fronteira de segurança física ou distribuída.

Resultados, snapshots, a lista de despachos e seus itens são congelados. Snapshots copiam a lista e conservam apenas campos escalares declarados. A montagem e a equipe copiam/congelam seus recipientes de referências, sem congelar o estado dos colegas, que precisa mudar. Isso não clona portas, funções ou closures, nem protege contra código hostil que altere métodos/protótipos/dependências.

O ciclo fornece no máximo quatro despachos, portanto esse histórico de ações é limitado por sua própria política de uso único. Snapshots copiam os registros existentes; o mediador mantém referências às três portas. Adicionar muitas regras pode aumentar acoplamento e custo de manutenção, mesmo sem aumentar muito a execução deste exemplo. O mediador conhece as regras da cooperação local; concentrar distribuição de água, território, tributação e toda política do conselho faria dele um objeto com responsabilidades excessivas.

Exercício e relações

exercise/README.md pede separar uma política de cotas do mecanismo de abertura. solution/cotas.ts implementa uma política pequena e explícita de prioridade por ordem; a composição a chama fora do mediador. O plano resultante não confirma inspeção, não amarra carga e não autoriza abertura. A solução é uma recusa fundamentada de acrescentar essa nova responsabilidade à base, sem fingir que um método de distribuição já existia no projeto principal.

Observer distribui uma notícia a interessados que reagem por conta própria. Aqui as regras escolhem destinatário/ação de acordo com pré-condições. Facade oferece uma entrada mais simples para um conjunto de serviços; Mediator organiza a interação entre colegas que notificam o contrato comum. Uma composição pode usar intenções de ambos; não há proibição absoluta de chamadas diretas ou regra de que qualquer coordenador seja um Mediator.

Um barramento de mensagens pode ser parte de uma implementação, mas retransmitir a todos não demonstra por si só esta coordenação seletiva. Não há broker, rede, persistência ou framework externo demonstrado. Citações não verificadas e afirmações genéricas de harmonia da fonte não são usadas como evidência.

Arquivos

src/contratos.ts: eventos, resultados e portas. src/conselho.ts: regras e despachos. src/colegas.ts: colegas com referência ao contrato do mediador. src/montagem.ts: raiz de composição. src/main.ts: saída real da alternativa direta e do protocolo. test/mediator.test.ts: destinatários, ordem, recusas, falhas e limites. solution/: política separada, exemplo e testes. diagram.mmd: somente a base, sem a política do exercício.

02 · Leia o mecanismo

O feitiço, linha por linha.

Comece por src/main.ts para ver o programa em uso. Explore os contratos, a implementação e os testes no seletor de arquivos.

src/main.tsTypeScript
import { montarConselho } from "./montagem.js";

// Alternativa direta para dois colaboradores simples e estáveis.
let registroDireto = 0;
const registrarPedido = () => { registroDireto += 1; };
registrarPedido();
console.log(`Chamada direta: ${registroDireto} pedido registrado`);

const { conselho, barqueiros, cultivadores, guarda } = montarConselho();
console.log(`Autorização precoce: ${guarda.confirmarAutorizacao(true).status}`);
console.log(`Pedido: ${barqueiros.pedirAbertura().estado}`);
console.log(`Inspeção: ${cultivadores.confirmarInspecao(true).estado}`);
console.log(`Amarras: ${barqueiros.confirmarAmarras(true).estado}`);
console.log(`Autorização: ${guarda.confirmarAutorizacao(true).estado}`);
for (const despacho of conselho.snapshot().despachos) console.log(`${despacho.destinatario}.${despacho.acao}: ${despacho.status}`);
console.log(`Aberta no modelo: ${guarda.snapshot().aberta} | acionamentos: ${guarda.snapshot().acionamentos}`);
console.log(`Segundo pedido: ${barqueiros.pedirAbertura().status}`);
A execução do programa é feita localmente.

03 · Revele as conexões

A estrutura por trás da magia.

Veja os papéis e suas relações. Amplie cada vista para ler atributos, métodos e conexões com calma.

ConselhoMediator realiza Mediador.
19 · a contrato

Esta vista reúne Mediador, ConselhoMediator. ConselhoMediator realiza Mediador. Membros exibidos são um recorte; construtores e demais membros podem estar omitidos.

Classificadores Barqueiros, Cultivadores, Guarda.
19 · b colegas

Esta vista reúne Barqueiros, Cultivadores, Guarda. Membros exibidos são um recorte; construtores e demais membros podem estar omitidos.

Barqueiros mantém referência a Mediador (notifica pedido e amarras).
19 · c notificações

Esta vista reúne Mediador, Barqueiros, Cultivadores, Guarda. Barqueiros mantém referência a Mediador (notifica pedido e amarras). Cultivadores mantém referência a Mediador (notifica inspeção). Guarda mantém referência a Mediador (notifica autorização). Membros exibidos são um recorte; construtores e demais membros podem estar omitidos. e2 · notifica pedido e amarras e3 · notifica inspeção e4 · notifica autorização

ConselhoMediator mantém referência a Cultivadores (solicita inspeção).
19 · d despachos

Esta vista reúne ConselhoMediator, Barqueiros, Cultivadores, Guarda. ConselhoMediator mantém referência a Cultivadores (solicita inspeção). ConselhoMediator mantém referência a Barqueiros (solicita amarras). Membros exibidos são um recorte; construtores e demais membros podem estar omitidos. e5 · solicita inspeção e6 · solicita amarras

ConselhoMediator mantém referência a Guarda (solicita autorização e abertura).
19 · e limites

Esta vista reúne ConselhoMediator, Guarda. ConselhoMediator mantém referência a Guarda (solicita autorização e abertura). ConselhoMediator: Uma composição, um pedido. Despacho seletivo, sem broadcast. Eventos fora da etapa são rejeitados. Guarda: Só o caminho mediado exige pré-condições; método público não é fronteira de segurança. Membros exibidos são um recorte; construtores e demais membros podem estar omitidos. e7 · solicita autorização e abertura

04 · Faça sua escolha

Sua vez de conjurar.

exercise/README.md

Exercício — Abertura não é distribuição de recursos

O conselho recebe a proposta de acrescentar distribuirCotas ao mediador de abertura. Extraia essa responsabilidade para uma função separada que planeje cotas por uma política explícita. A base deste projeto já é enxuta e não contém aquele método: a tarefa é implementar a expansão fora dela, em vez de instalar e depois fingir remover uma responsabilidade inexistente.

Critérios:

  1. A política recebe unidades disponíveis e pedidos com grupo/quantidade. Aloca por prioridade da ordem recebida, até esgotar o disponível, sem conceder mais que foi solicitado.
  2. O resultado informa solicitado, concedido e restante. Inverta a ordem e mostre que essa escolha muda quem recebe quanto; não chame essa regra de justiça universal.
  3. Disponível é inteiro seguro não negativo; demanda é inteira segura positiva. Nomes de grupos devem ser não vazios e distintos depois de normalizar espaços.
  4. Não mutar entradas; congelar as estruturas de saída declaradas.
  5. Planejar cotas não altera pré-condições de abertura e não substitui confirmação dos colegas.

Resposta comentada

solution/cotas.ts contém planejarCotas, sem importar ou chamar o mediador. solution/main.ts chama a política na composição, e depois executa o protocolo de abertura pelos colegas. A política devolve somente um plano: não transfere água ou material e não garante execução física. A base não foi modificada; a nova função e seu contrato constituem uma responsabilidade separada. Se uma aplicação já tivesse um método equivalente na classe do conselho, a migração incluiria mover o cálculo e atualizar os chamadores — não seria uma extração com zero edições nessa aplicação.

Com dez unidades, prioridade barqueiros8/cultivadores6 concede8/2; ordem inversa concede6/4. É uma regra local visível e contestável, não decisão obrigatória do padrão Mediator. Nenhuma soma de demandas precisa ultrapassar o intervalo seguro: cada concessão é limitada pelo restante disponível. Resultado e itens são congelados, mas não se promete clonagem genérica de recursos ou objetos arbitrários.

solution/cotas.test.ts verifica prioridade, conservação do disponível, entradas/saídas, invalidações e independência do estado de abertura. Execute npm run solution e npm test. Se a necessidade de distribuição ainda não existe, a alternativa mais simples é não acrescentar essa política; um mediador não precisa concentrar todas as decisões do conselho.

Compare depois da sua tentativa.

A resposta e seus testes estão na pasta solution/. O sucesso dos testes fornecidos confirma essa resposta; sua tentativa precisa estar incluída na compilação para ser avaliada.

Do estudo à prática

Experimente localmente.

Node 26.10.0 · npm 11.19.1 · TypeScript 5.9.3

Execute os comandos dentro da pasta deste projeto. O lock instala o compilador local. Os programas não são executados no navegador.

cd book-23/code/19-mediator
npm ci --ignore-scripts --no-audit --no-fund
npm test
npm start

# Depois da sua tentativa
npm run solution
Baixar este projeto completo

Diagrama ampliado