[Pesquisar este blog]

segunda-feira, 15 de maio de 2017

Os Princípios SOLID::ISP (parte V)

Parte I | Parte II | Parte III | Parte IV  | Parte V | Parte VI
Os princípios de projeto e programação orientada a objetos enunciados por Martin em 1995, depois denominados como SOLID, são um importante guia para orientação tanto a construção de sistemas fáceis de manter, como para reorganizar (refactor) código existente, além de serem consistentes com para desenvolvimento de software.


Os cinco princípios que formam o acrônimo SOLID:
  • Single Responsability Principle
  • Open-Closed Principle
  • Liskov's Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion Principle

Nos post anteriores foram tratados os princípios da responsabilidade única (SRP), do aberto-fechado (OCP) e da substituição de Liskov (LSP). Neste artigo falaremos sobre o Princípio da Segregação de Interfaces (ISP).

ISP::Interface Segregation Principle

Não dependa daquilo que você não necessita!
O Princípio da Segregação da Interface ou Interface Segregation Principle, aqui referido apenas como ISP, afirma que é preferível dispor de muitas interfaces de programação de cliente específicas do que uma única interface de programação de cliente de propósito geral.

Entende-se como interface de programação de cliente o conjunto de operações que um interface especifica, as quais deverão ser implementadas (ou realizadas) por uma ou mais classes. Abaixo temos uma interface Java denominada IVeiculo, dotada de três operações, todas implicitamente públicas e abstratas.

public interface IVeiculo {
    int getNumeroMaximoOcupantes();
    int getNumeroAtualOcupantes();
    void setNumeroOcupantes(int ocupantes);
}

Para que esta interface seja útil, a mesma deverá ser implementada por uma classe, isto é, um classe pode optar por realizar esta interface, substituindo todos os métodos presentes na interface, como no outro exemplo que segue:

public classe MeuVeiculo implements IVeiculo {
    public static final int MAX_OCUP = 5;
    public int nOcupantes = 0;
    public int getNumeroMaximoOcupantes() {
        return MAX_OCUP;
    }
    public int getNumeroAtualOcupantes() {
        return nOcupantes;
    }
    public void setNumeroOcupantes(int ocupantes) {
        if (ocupantes<0 || ocupantes>MAX_OCUP) {
            throw new RuntimeException("número ocupantes inválido");
        }
        nOcupantes = ocupantes;
    }
}

Neste caso, a classe concreta MeuVeiculo só necessitou implementar três métodos para realizar aqueles especificados pela interface IVeiculo, “contratada” na sua declaração. Espera-se que todos estes métodos sejam, de fato, úteis nesta implementação.

É este o foco do ISP: os clientes (as classes concretas que implementam interfaces) não devem ser forçados a depender de funcionalidades das quais não necessitam, ou seja, uma classe que realiza uma interface não deveria implementar métodos sem necessidade. Assim, ao invés de classes e interfaces complexas, e com numerosas operações, este princípio direciona para a construção de múltiplos elementos menores e mais coesos.

Esta pode ser a situação de uma interface dotada de muitas operações, que, em geral, criará a necessidade da construção de classes clientes dotadas das implementações de tais métodos. 

Classes complexas, com muitas operações, são inevitáveis em algumas situações, mas é igualmente comum que seus clientes utilizem apenas um subconjunto de suas funcionalidades. Mesmo que indiretamente, tais clientes dependem de funcionalidades que eles não utilizam, assim fica a questão: o que acontece se tais classes complexas mudam?

Considere um caixa eletrônico bancário (Automatic Teller Machine - ATM), em cuja tela são exibidas múltiplas mensagens conforme as operações executadas. A não aplicação de SRP, OCP e LSP podem levar a criação de uma interface como a que segue, com muitas operações!

public interface ATMMessenger {
    void askForCard();
    void tellInvalidCard();
    void askForPin();
    void tellInvalidPin();
    :
    void askForWithdrawAmount();
    void tellInsuficientBalance();
    void tellBalance();
    :
    void tellAmountDeposited();
}

Ao acrescentar uma nova operação nesta interface, muitas classes cliente deverão ser modificadas e recompiladas. Mas isto seria necessário?

A separação de uma interface complexa (de propósito geral) em muitas interfaces específicas simplifica o problema. Esta separação é o que se denomina segregação no ISP. Como no exemplo que segue:

public interface LoginMessenger {
    void askForCard();
    void tellInvalidCard();
    void askForPin();
    void tellInvalidPin();
}
public interface WithDrawMessenger {
    void askForWithdrawAmount();
    void askForFeeConfirmation();
    void tellInsuficientBalance();
    void tellBalance();
}

Nesta nova situação, de interfaces com maior foco e menos operações, a adição de uma operação nova reduz o escopo de alterações às classes que efetivamente implementam tais funcionalidades. Ou seja, somente aquelas que dependem das funcionalidades da interface modificada.

Observe abaixo a comparação entre as implementações hipotéticas de uma única e grande interface geral em relação à realização de interfaces específicas, ainda considerando o exemplo do caixa eletrônico bancário.


Conclusões

O esforço da divisão de uma interface complexa, com muitos métodos, em várias interfaces menores, com grupos melhor correlacionados de operações, simplifica tanto a construção do código como sua manutenção.

Na construção torna-se possível selecionar e implementar apenas as interfaces necessárias, reduzindo-se o esforço de codificação. Se todas as interfaces são necessárias, a divisão não causa qualquer problema (não requer mais código ou qualquer manobra especial). Na manutenção, a alteração de interfaces menores causa menor impacto no código, o que é um grande benefício.

Fica claro que é melhor não depender das coisas das quais não se necessita.

Assim, a garantia do ISP leva a:
  • Baixo acoplamento e alta coesão;
  • Promoção do projeto ortogonal (ortogonalidade do projeto) onde:
    • Módulos centrados em sua necessidade (SRP) e
    • Dependências reduzidas entre eles (LSP e ISP).


Para Saber Mais

  • LARMAN, Craig. Utilizando UML e padrões: uma introdução à análise e ao projeto orientados à objetos e ao Processo Unificado. Porto Alegre: Bookman, 2007.
  • MARTIN, R. C.; et. al. Clean Code: a handbook of agile software craftsmanship. Boston: Pearson Education, 2009.
  • MARTIN, R. C. The Clean Coder. Upper Sadle River: Prentice-Hall, 2011.
  • MARTIN, R. C. Principles of OOD. Disponível em http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod, recuperado em 17/02/2017.
  • MARTIN, R. C. Get a SOLID start. Disponível em http://objectmentor.com, recuperado em 17/02/2017.
  • PAGE-JONES, Meilir. Fundamentos do Desenho Orientado a Objetos. São Paulo: Makron Books, 2001.
  • SOMMERVILLE, I. Software Engineering. 9th. Ed. Boston: Addison-Wesley, 2011.