Pular para o conteúdo

ISP - Interface Segregation Principle

Robert C. Martin enuncia: “Clients should not be forced to depend upon interfaces that they do not use.” O princípio nasceu de um problema real na Xerox, onde uma classe Job monolítica servia a diversos clientes, e qualquer mudança forçava recompilação e redeploy de todos, mesmo os que usavam uma fração da interface.

A ideia central: uma interface grande cria um acoplamento acidental. Se o cliente A depende de uma interface que também declara métodos usados só pelo cliente B, então A fica acoplado transitivamente às mudanças motivadas por B. Ao segregar a interface por papéis (role interfaces), cada cliente depende apenas do subconjunto que lhe interessa, e a superfície de acoplamento encolhe.

Interfaces gordas produzem três males:

  • Implementações incompletas: uma classe obrigada a implementar métodos irrelevantes recorre a throw new UnsupportedOperationException() ou corpos vazios — o que, de quebra, viola LSP - Liskov Substitution Principle.
  • Acoplamento e recompilação desnecessários: mudança num método usado por um cliente força os demais a recompilar/reimplementar.
  • Baixa clareza de intenção: o contrato não comunica qual papel cada cliente exerce.

O excesso de segregação leva à explosão de interfaces minúsculas e ao aumento da complexidade de composição. Se toda operação vira uma interface de método único, você troca uma abstração grande difícil por dezenas de peças difíceis de orquestrar.

Não aplique ISP quando a interface já é coesa e todos os clientes usam todos os métodos, ou em contratos estáveis e pequenos onde a segregação só adicionaria cerimônia.

Uma interface Funcionario gorda força implementações a lidar com operações que não fazem sentido para elas.

public interface Funcionario {
BigDecimal calcularSalario();
void registrarPonto();
void aprovarFerias(String matriculaSubordinado);
void definirMetasEquipe(List<String> metas);
}
public class Desenvolvedor implements Funcionario {
@Override
public BigDecimal calcularSalario() {
return new BigDecimal("12000");
}
@Override
public void registrarPonto() {
System.out.println("Ponto registrado");
}
@Override
public void aprovarFerias(String matriculaSubordinado) {
throw new UnsupportedOperationException("Desenvolvedor não aprova férias");
}
@Override
public void definirMetasEquipe(List<String> metas) {
throw new UnsupportedOperationException("Desenvolvedor não define metas de equipe");
}
}

Desenvolvedor é obrigado a implementar comportamentos de gestão que não possui, recorrendo a exceções — acoplamento acidental e violação de LSP embutida.

public interface Remunerado {
BigDecimal calcularSalario();
}
public interface Ponto {
void registrarPonto();
}
public interface Gestor {
void aprovarFerias(String matriculaSubordinado);
void definirMetasEquipe(List<String> metas);
}
@Component
public class Desenvolvedor implements Remunerado, Ponto {
@Override
public BigDecimal calcularSalario() {
return new BigDecimal("12000");
}
@Override
public void registrarPonto() {
System.out.println("Ponto registrado");
}
}
@Component
public class GerenteEngenharia implements Remunerado, Ponto, Gestor {
@Override
public BigDecimal calcularSalario() {
return new BigDecimal("22000");
}
@Override
public void registrarPonto() {
System.out.println("Ponto registrado");
}
@Override
public void aprovarFerias(String matriculaSubordinado) {
System.out.println("Férias aprovadas para " + matriculaSubordinado);
}
@Override
public void definirMetasEquipe(List<String> metas) {
System.out.println("Metas definidas: " + metas);
}
}
@Service
public class FolhaPagamento {
private final List<Remunerado> remunerados;
public FolhaPagamento(List<Remunerado> remunerados) {
this.remunerados = remunerados;
}
public BigDecimal totalFolha() {
return remunerados.stream()
.map(Remunerado::calcularSalario)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}

FolhaPagamento depende apenas de Remunerado — não sabe nada sobre ponto ou gestão. Cada implementação assume só os papéis que de fato exerce, sem métodos-fantasma.

classDiagram
    class Remunerado {
        <<interface>>
        +calcularSalario() BigDecimal
    }
    class Ponto {
        <<interface>>
        +registrarPonto()
    }
    class Gestor {
        <<interface>>
        +aprovarFerias(String)
        +definirMetasEquipe(List)
    }
    class Desenvolvedor {
        +calcularSalario() BigDecimal
        +registrarPonto()
    }
    class GerenteEngenharia {
        +calcularSalario() BigDecimal
        +registrarPonto()
        +aprovarFerias(String)
        +definirMetasEquipe(List)
    }
    class FolhaPagamento {
        +totalFolha() BigDecimal
    }

    Remunerado <|.. Desenvolvedor
    Ponto <|.. Desenvolvedor
    Remunerado <|.. GerenteEngenharia
    Ponto <|.. GerenteEngenharia
    Gestor <|.. GerenteEngenharia
    FolhaPagamento --> Remunerado
  1. Sob qual perspectiva o ISP deve ser avaliado: da implementação ou do cliente?
Resposta

Do cliente. A pergunta é se algum cliente é forçado a depender de mais do que usa, não simplesmente se a interface é “grande”.

  1. Por que uma interface gorda tende a induzir violações de LSP?
Resposta

Porque implementações que não têm sentido para certos métodos acabam usando UnsupportedOperationException ou corpos vazios, quebrando a substitutabilidade esperada do contrato.

  1. Qual é a granularidade recomendada para segregar interfaces?
Resposta

Por papel do cliente (role interface), não por método individual. Métodos sempre usados juntos pelo mesmo cliente devem permanecer na mesma interface.

  1. No exemplo, por que FolhaPagamento não deve depender de Funcionario?
Resposta

Porque FolhaPagamento só precisa de calcularSalario(). Depender da interface gorda Funcionario a acoplaria a mudanças em ponto e gestão, com as quais não tem relação. Depender de Remunerado minimiza a superfície de acoplamento.