PrincípioIntermediário5 min de leitura9 seções1996, Data aproximada

ISP — Segregação de Interfaces

Nenhum cliente deve ser forçado a depender de métodos que não usa: é melhor várias interfaces pequenas e específicas do que uma grande e genérica.

Em 10 segundos

Se uma implementação precisa lançar 'não suportado', a interface é grande demais. Divida por capacidade: quem só imprime implementa só `Imprimivel`, e quem só precisa imprimir depende só disso.

Antes de ter nome

É a aplicação, à interface, da mesma coesão que o SRP pede ao módulo — separar o que clientes diferentes usam por motivos diferentes.

  • #solid
  • #interface
  • #acoplamento
  • #design
Analogia

O cardápio do restaurante

Um restaurante que serve só almoço não recebe o cardápio completo com café da manhã, jantar e carta de vinhos, com metade das páginas riscadas. Ele recebe o cardápio do almoço. Quem chega para almoçar não precisa folhear o que não está disponível — e mudar a carta de vinhos não obriga a reimprimir o cardápio do almoço.

01Conceito

O problema

Interfaces tendem a engordar: cada necessidade nova acrescenta um método, e ninguém remove os antigos por medo de quebrar alguém.

O custo aparece dos dois lados. Quem implementa é obrigado a fornecer o que não faz; quem consome fica acoplado a métodos que nunca chama.

O acoplamento de quem consome é o mais subestimado. Depender de uma interface com quinze métodos significa que qualquer alteração em qualquer um deles pode forçar recompilação, revisão e novos testes — mesmo num cliente que usa um método só.

O princípio tem uma relação direta com LSP: a exceção de 'não suportado' é simultaneamente uma violação de Liskov (o subtipo não cumpre o contrato) e o sintoma clássico de uma interface mal segregada. Corrigir pelo ISP costuma resolver a violação de Liskov de tabela.

Vale também a comparação com SRP: são o mesmo raciocínio em superfícies diferentes. O SRP fala do módulo e de quem pede mudanças nele; o ISP fala do contrato e de quem depende dele. Ambos perguntam 'quem é o interessado?' — e ambos separam quando as respostas divergem.

02Conceito

Qual é o tamanho certo

O princípio diz para segregar, não para atomizar. O critério não é a quantidade de métodos, é a coesão de uso entre os clientes.

A pergunta que resolve: os clientes usam esses métodos juntos? Se todos que chamam abrir também chamam fechar, os dois pertencem à mesma interface, por mais que sejam operações distintas. Se um grupo de clientes usa só leitura e outro só escrita, há duas interfaces disfarçadas de uma.

Uma técnica útil é definir a interface do lado do cliente, não do lado da implementação. Em vez de perguntar 'o que este objeto faz?', pergunte 'o que este cliente precisa?'. As interfaces nascem menores e mais estáveis, porque refletem necessidades reais em vez de capacidades disponíveis.

O exagero também tem custo: uma interface por método produz assinaturas com cinco tipos e um sistema difícil de navegar. Se cada divisão nova não elimina uma dependência real de alguém, ela provavelmente é só ruído.

Sem o padrão × com o padrão

Interfaces por capacidade

  • Imprimivel, Digitalizavel e Faxeavel separadas
  • cada classe assina só o que cumpre
  • quem só imprime depende só de Imprimivel
  • mudar o fax não afeta quem imprime

O custo é mais tipos para nomear e manter — e a necessidade de compor várias interfaces quando um objeto realmente faz várias coisas.

A segregação faz o sistema de tipos voltar a dizer a verdade. O que antes era exceção em tempo de execução vira erro de compilação — ou simplesmente deixa de ser possível.
Como se organiza
  • interface Imprimivelum método — o que a maioria dos clientes precisa

    • ImpressoraSimplesimplementa só isto

    • Multifuncionalimplementa isto e mais

  • interface Digitalizavelcapacidade separada

    • Multifuncionala mesma classe, outro papel

A Multifuncional aparece nas duas árvores porque assina os dois papéis. A impressora simples aparece em uma só — e o tipo passa a refletir exatamente o que cada aparelho faz.
03Anatomia

Navegue pelas camadas

Camada 1/3O cliente

Camada 1

O cliente

define o tamanho da interface

Quem consome é o ponto de partida: a interface deve ter exatamente o que aquele uso exige. Interfaces desenhadas a partir das necessidades dos clientes nascem pequenas e mudam pouco.

No código

function imprimirRelatorio(p: Imprimivel) { p.imprimir('...') }

Se violar: cliente que recebe uma interface com quinze métodos para chamar um fica exposto a mudanças que não lhe dizem respeito.

04Estrutura

Estrutura

Diagrama de classes
05Implementação

Código

// ANTES: interface gorda obriga todo mundo a tudo
interface MaquinaEscritorio {
  imprimir(doc: string): void;
  digitalizar(): string;
  enviarFax(numero: string): void;
}

class ImpressoraSimplesRuim implements MaquinaEscritorio {
  imprimir(doc: string): void { console.log("imprimindo", doc); }
  digitalizar(): string { throw new Error("não suportado"); }   // mentira
  enviarFax(_n: string): void { throw new Error("não suportado"); } // mentira
}

// DEPOIS: uma interface por capacidade
interface Imprimivel {
  imprimir(doc: string): void;
}
interface Digitalizavel {
  digitalizar(): string;
}
interface Faxeavel {
  enviarFax(numero: string): void;
}

class ImpressoraSimples implements Imprimivel {
  imprimir(doc: string): void { console.log("imprimindo", doc); }
}

class Multifuncional implements Imprimivel, Digitalizavel {
  imprimir(doc: string): void { console.log("imprimindo", doc); }
  digitalizar(): string { return "imagem"; }
}

// O cliente pede exatamente o que usa — nem mais, nem menos
function imprimirRelatorio(p: Imprimivel): void {
  p.imprimir("relatório mensal");
}

imprimirRelatorio(new ImpressoraSimples()); // ok
imprimirRelatorio(new Multifuncional());    // ok
06No mundo real

Casos de uso

Repositório com leitura e escrita separadas

Cenário

Um repositório de pedidos expõe consultas e comandos, e a maior parte do sistema só lê — relatórios, telas de listagem, exportações.

Como o padrão entra

Separar em interfaces de leitura e de escrita permite que a maioria dos consumidores dependa só da leitura, e deixa explícito no tipo quem pode alterar dados.

Trade-off: Duas interfaces sobre a mesma implementação exigem coordenação: mudanças de modelo afetam ambas, e é preciso decidir onde ficam operações mistas como 'buscar e travar'.

Ciclo de vida em plugins

Cenário

Uma plataforma de plugins define uma interface com inicialização, encerramento, tratamento de eventos e configuração — e a maioria dos plugins usa uma dessas coisas.

Como o padrão entra

Cada capacidade vira uma interface opcional. O host verifica quais o plugin implementa e chama apenas essas. Plugins simples ficam com poucas linhas.

Trade-off: O host precisa checar capacidades em tempo de execução, o que traz de volta uma forma de verificação de tipo — controlada e explícita, mas ainda assim presente.

Serviço de notificação com canais desiguais

Cenário

Um serviço envia notificações por e-mail, SMS e push; só o push tem ações interativas e só o e-mail tem anexos.

Como o padrão entra

A interface base cobre o envio simples; capacidades extras ficam em interfaces próprias. Quem precisa de anexo pede o tipo que os suporta.

Trade-off: O código que escolhe o canal dinamicamente precisa lidar com capacidades variáveis, e a lógica de fallback ('se não suportar anexo, mande o link') tem de morar em algum lugar.

Passo a passoComo segregar sem atomizar
  1. 1

    Olhar pelos clientes

    Liste quem consome a interface e quais métodos cada um chama. Subconjuntos disjuntos são o sinal de que há mais de um contrato escondido.

  2. 2

    Cortar por capacidade

    Uma interface por papel (`Imprimivel`, `Digitalizavel`), não por objeto do mundo. Nome de capacidade impede a gordura de voltar.

  3. 3

    Assinar só o que se cumpre

    Cada implementação pega apenas as interfaces que honra de verdade. 'Não suportado' deixa de ser opção — vira erro de tipo.

  4. 4

    Pedir o mínimo no cliente

    Quem só imprime depende só de `Imprimivel`. Reunir tudo de novo com interseção no parâmetro desfaz a segregação.

A segregação que o cliente desfaz

Compila, roda, passa no code review. E quebra depois.

As interfaces pequenas existem no papel. O cliente, porém, exige a interseção de todas — e a impressora simples volta a ficar de fora. ISP aplicado na declaração, anulado no uso.

interface Imprimivel { imprimir(doc: string): void; }
interface Digitalizavel { digitalizar(): string; }
interface Faxeavel { enviarFax(n: string): void; }

// "Segregamos as interfaces"... e o cliente pede o combo inteiro.
type MaquinaCompleta = Imprimivel & Digitalizavel & Faxeavel;

function imprimirRelatorio(m: MaquinaCompleta): void {
  m.imprimir("relatorio"); // so precisava de Imprimivel
}

// ImpressoraSimples nao encaixa mais. Voltou a obrigacao de
// digitalizar e fax — so que agora com tres tipos no caminho.

O que quebra

  • Ao plugar impressora simplesO tipo não aceita: o chamador pediu fax e scanner que nunca vai usar.
  • Na mudança do faxQuem só imprime recompila e revisa de novo — o acoplamento que a segregação deveria cortar voltou pela assinatura do cliente.
  • Na implementaçãoAlguém reintroduz throw new Error('não suportado') só para satisfazer o combo exigido pelo cliente.

A saída: O parâmetro do cliente deve ser o menor contrato que ele usa: Imprimivel. Composição de interfaces fica no objeto que realmente tem várias capacidades, não em quem chama um método só.

08Cuidado

Armadilhas

  1. Atomizar em interfaces de um método

    Levar o princípio ao extremo produz dezenas de interfaces minúsculas e assinaturas que listam cinco tipos para descrever um colaborador. O critério é coesão de uso: métodos que os clientes sempre chamam juntos pertencem à mesma interface.

  2. Segregar o contrato e manter a classe gorda

    Dividir as interfaces sem revisar a implementação resolve o acoplamento de quem consome e deixa intacto o problema de quem implementa — uma classe que faz coisas demais continua sendo uma classe que faz coisas demais. ISP e SRP costumam precisar ser aplicados juntos.

  3. Nomear interfaces por objeto, não por papel

    'MaquinaEscritorio' convida a acumular tudo que uma máquina de escritório poderia fazer. 'Imprimivel' delimita naturalmente. O nome é o que impede a interface de voltar a engordar seis meses depois.

09Decisão

Quando usar × quando evitar

Use quando

3
  • Implementações lançam exceção por não saberem cumprir parte do contrato.
  • Clientes distintos usam subconjuntos disjuntos dos métodos.
  • Mudanças na interface afetam código que não a usa de fato.

Evite quando

3
  • Fragmentar sem que isso elimine dependência real de alguém.
  • Todos os clientes usam todos os métodos.
  • O agrupamento é coeso e quebrá-lo espalharia um conceito único.

Testar o que ficou

3 armadilhas de ISP — Segregação de Interfaces viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.

Onde isto aparece de verdade

Você já usa isto, talvez sem saber o nome.

  • io.Reader / io.Writer do GoInterfaces de um método só: quem precisa apenas ler depende de Reader e ignora o resto — o cliente nunca carrega método que não usa.
  • streams do NodeReadable, Writable e Duplex são contratos separados; um consumidor de leitura não é forçado a conhecer a metade de escrita da API.
  • props específicas × objeto inteiroPassar ao componente só as props que ele usa, em vez do objeto de domínio completo, é segregação de interface aplicada ao React.

O padrão em uma linha

Sem nome de domínio, sem cerimônia. Só a ideia.

// Interface só com o que o cliente usa.
interface Legivel { ler(id: string): Promise<T>; }

O que ele cobra

1 salto a mais entre a chamada e o efeito.

  • Mais interfaces pequenas para nomear, manter e compor no lugar de uma só
  • Fragmentar demais espalha um contrato coeso em cacos que ninguém usa isolados

Não vale se: todos os clientes usam a interface inteira do mesmo jeito — segregar aí só multiplica tipos sem aliviar ninguém.

Onde isto aparece

Continue por aqui