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.
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.
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.
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.
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
Navegue pelas camadas
Camada 1/3O cliente
use ← →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.
Estrutura
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()); // okfrom abc import ABC, abstractmethod
# ANTES: interface gorda obriga todo mundo a tudo
class MaquinaEscritorio(ABC):
@abstractmethod
def imprimir(self, doc): ...
@abstractmethod
def digitalizar(self): ...
@abstractmethod
def enviar_fax(self, numero): ...
class ImpressoraSimplesRuim(MaquinaEscritorio):
def imprimir(self, doc): print("imprimindo", doc)
def digitalizar(self): raise NotImplementedError # mentira
def enviar_fax(self, numero): raise NotImplementedError # mentira
# DEPOIS: uma interface por capacidade
class Imprimivel(ABC):
@abstractmethod
def imprimir(self, doc): ...
class Digitalizavel(ABC):
@abstractmethod
def digitalizar(self): ...
class ImpressoraSimples(Imprimivel):
def imprimir(self, doc): print("imprimindo", doc)
class Multifuncional(Imprimivel, Digitalizavel):
def imprimir(self, doc): print("imprimindo", doc)
def digitalizar(self): return "imagem"
# O cliente pede exatamente o que usa
def imprimir_relatorio(p: Imprimivel):
p.imprimir("relatorio mensal")
imprimir_relatorio(ImpressoraSimples())
imprimir_relatorio(Multifuncional())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.
- 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
Cortar por capacidade
Uma interface por papel (`Imprimivel`, `Digitalizavel`), não por objeto do mundo. Nome de capacidade impede a gordura de voltar.
- 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
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ó.
Armadilhas
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.
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.
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.
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
- LSP — Substituição de LiskovUm subtipo deve poder substituir seu supertipo sem que o código que usa o supertipo perceba a diferença — o que é uma exigência sobre comportamento, não apenas sobre assinatura.
- SRP — Responsabilidade ÚnicaUm módulo deve ter apenas uma razão para mudar — ou seja, deve responder a um único interessado do negócio, e não acumular decisões que evoluem por motivos diferentes.
- DIP — Inversão de DependênciaMódulos de alto nível não devem depender de módulos de baixo nível; ambos devem depender de abstrações — e a abstração pertence a quem a usa, não a quem a implementa.