Existe um princípio de interface "peça apenas o que você precisa"?

9

Eu cresci usando um princípio para projetar e consumir interfaces que diz basicamente: "peça apenas o que você precisa".

Por exemplo, se eu tiver vários tipos que podem ser excluídos, farei uma Deletableinterface:

interface Deletable {
   void delete();
}

Então eu posso escrever uma classe genérica:

class Deleter<T extends Deletable> {
   void delete(T t) {
      t.delete();
   }
}

Em outro lugar no código, sempre solicitarei a menor responsabilidade possível para atender às necessidades do código do cliente. Portanto, se eu precisar excluir apenas um File, ainda pedirei um Deletable, não um File.

Esse princípio é de conhecimento comum e já tem um nome aceito? É controverso? Isso é discutido em livros didáticos?

glenviewjeff
fonte
11
Acoplamento solto, talvez? Ou interfaces estreitas?
tdammers

Respostas:

16

Eu acredito que isso se refere ao que Robert Martin chama de Princípio de Segregação de Interface . As interfaces são separadas em pequenas e concisas para que os consumidores (clientes) precisem apenas conhecer os métodos que lhes interessam. Você pode conferir mais sobre o SOLID .

Vadim
fonte
4

Para expandir a resposta muito boa de Vadim, responderei à pergunta "é controverso" com "não, não realmente".

Em geral, a segregação de interface é uma coisa boa, reduzindo o número geral de "razões para mudar" dos vários objetos envolvidos. O princípio principal é que, quando uma interface com vários métodos deve ser alterada, por exemplo, para adicionar um parâmetro a um dos métodos de interface, todos os consumidores da interface devem pelo menos ser recompilados, mesmo que não usem o método que mudou. "Mas é apenas uma recompilação!", Eu ouvi você dizer; isso pode ser verdade, mas lembre-se de que normalmente qualquer coisa que você recompilar deve ser enviada como parte de um patch de software, independentemente da importância da mudança no binário. Essas regras foram originalmente conceituadas no início dos anos 90, quando a estação de trabalho de desktop média era menos poderosa do que o telefone no bolso, a discagem de 14,4 baud era incrível e 3,5 "disquetes de 1,44 MB" eram a principal mídia removível. Mesmo na era atual do 3G / 4G, os usuários de internet sem fio costumam ter planos de dados com limites; portanto, ao liberar uma atualização, menos binários precisam ser baixados, melhor.

No entanto, como todas as boas idéias, a segregação de interface pode ficar ruim se implementada incorretamente. Primeiramente, existe a possibilidade de que, segregando interfaces e mantendo o objeto que implementa essas interfaces (cumprindo as dependências) relativamente inalterado, você possa acabar com uma "Hydra", parente do antipadrão "Objeto Deus", onde o objeto a natureza onisciente e onipotente do objeto está oculta do dependente pelas interfaces estreitas. Você acaba com um monstro de muitas cabeças que é pelo menos tão difícil de manter quanto o Objeto de Deus, além da sobrecarga de manter todas as suas interfaces. Não há um número difícil de interfaces que você não deve exceder, mas cada interface que você implementa em um único objeto deve ser precedida pela resposta à pergunta: "Essa interface contribui para o objeto '

Segundo, uma interface por método pode não ser necessária, apesar do que o SRP possa lhe dizer. Você pode acabar com "código ravioli"; tantos pedaços pequenos que é difícil rastrear para descobrir exatamente onde as coisas realmente acontecem. Também não é necessário dividir uma interface com dois métodos se todos os usuários atuais dessa interface precisarem dos dois métodos. Mesmo que uma das classes dependentes precise apenas de um dos dois métodos, geralmente é aceitável não dividir a interface se seus métodos tiverem coesão muito alta (bons exemplos são "métodos antônimos", que são exatamente opostos um do outro).

A segregação da interface deve ser baseada nas classes que dependem da interface:

  • Se houver apenas uma classe dependente da interface, não separe. Se a classe não usa um ou mais dos métodos de interface e é o único consumidor da interface, é provável que você não deva ter exposto esses métodos em primeiro lugar.

  • Se houver mais de uma classe que depende da interface e todos os dependentes usarem todos os métodos da interface, não separe; se você precisar alterar a interface (para adicionar um método ou alterar uma assinatura), todos os consumidores atuais serão afetados pela alteração, independentemente de você segregar ou não (embora, se você estiver adicionando um método que pelo menos um dependente não precisará, considere cuidadosamente se a alteração for implementada como uma nova interface, possivelmente herdada da existente).

  • Se houver mais de uma classe dependente da interface e eles não usarem os mesmos métodos, é um candidato à segregação. Veja a "coerência" da interface; todos os métodos promovem um objetivo de programação único e muito específico? Se você conseguir identificar mais de um objetivo principal para a interface (e seus implementadores), considere dividir as interfaces nessas linhas para criar interfaces menores com menos "motivos para mudar".

KeithS
fonte
Também vale a pena notar que a segregação de interface pode ser boa e dandy se alguém estiver usando uma linguagem / sistema OOP que pode permitir que o código especifique uma combinação precisa de interfaces, mas pelo menos no .NET eles podem causar algumas dores de cabeça severas, já que não há uma solução decente. maneira de especificar uma coleção de "coisas que implementam o IFoo e o IBar, mas que de outra forma podem não ter nada em comum".
Supercat 24/02
Os parâmetros de tipo genérico podem ser definidos com critérios, incluindo a implementação de várias interfaces, mas você tem razão em que expressões que exigem um tipo estático normalmente não podem suportar a especificação de mais de uma. Se for necessário que um tipo estático implemente IFoo e IBar, e você controle essas duas interfaces, pode ser uma boa ideia implementá-lo IBaz : IFoo, IBare exigir isso.
7118 KeithS
Se o código do cliente precisar de algo que possa ser usado como IFooe IBar, definir uma composição IFooBarpode ser uma boa ideia, mas se as interfaces forem bem divididas, é fácil acabar exigindo dezenas de tipos de interface distintos. Considere os seguintes recursos que as coleções podem ter: Enumerar, reportar Contagem, Ler enésimo elemento, Escrever enésimo elemento, Inserir antes de enésimo elemento, Excluir enésimo elemento, Novo item (ampliar coleção e retornar índice de novo espaço) e Incluir. Nove métodos: ECRWIDNA. Eu provavelmente poderia descrever dezenas de tipos que naturalmente suportariam muitas combinações diferentes.
Supercat 7/14
Matrizes, por exemplo, suportariam ECRW. Um arraylist suportaria ECRWIDNA. Uma lista de thread-safe pode suportar ECRWNA [embora A geralmente seja útil apenas para preencher previamente a lista]. Um wrapper de matriz somente leitura pode suportar ECR. Uma interface de lista covariante pode suportar ECRD. Uma interface não genérica pode fornecer suporte C ou CD com segurança de tipo. Se Swap fosse uma opção, alguns tipos poderiam suportar CS, mas não D (por exemplo, matrizes), enquanto outros suportariam CDS. Tentar definir tipos distintos de interface para cada combinação necessária de habilidades seria um pesadelo.
Supercat 7/14
Agora imagine que se queira a capacidade de agrupar uma coleção com um objeto que possa fazer tudo o que a coleção pode fazer, mas que registra todas as transações. Quantos invólucros seriam necessários? Se todas as coleções herdadas de uma interface comum que incluíssem propriedades para identificar suas habilidades, um wrapper seria suficiente. Se todas as interfaces forem distintas, no entanto, seria necessário dezenas.
Supercat 7/14