Quantas interfaces há em uma classe? [fechadas]

28

Eu provavelmente consideraria um cheiro de código ou mesmo um antipadrão ter uma classe que implementa 23 interfaces. Se é realmente um antipadrão, como você o chamaria? Ou simplesmente não segue o princípio da responsabilidade única?

Jonas Elfström
fonte
3
Bem, é questionável de qualquer maneira. Se a classe tiver 23 métodos, todos eles cumprem uma única responsabilidade (nem todas as classes precisam de tantos métodos, mas não é impossível - especialmente se a maioria for wrappers de conveniência de três linhas em torno de outros métodos), você simplesmente terá interfaces muito refinadas; )
A classe em questão é um tipo de entidade e as interfaces são principalmente para propriedades. Existem 113 desses e apenas quatro métodos.
Jonas Elfström
6
Eu pensei que as interfaces são essencialmente usadas para digitar duck em linguagens estaticamente compiladas. Qual é o problema? :)
ashes999
2
apenas uma pergunta: todas as 113 propriedades são preenchidas para cada instância desta entidade? Ou é algum tipo de "entidade comum" usada em diferentes contextos, com apenas algumas propriedades preenchidas?
David
11
Vamos voltar à compreensão conceitual do que constitui uma "classe". Uma classe é uma coisa única, com uma responsabilidade e um papel claramente definidos. Você pode definir seu objeto de 23 interfaces dessa maneira? Você pode postar uma única frase (não Joycean) que resuma adequadamente sua classe? Nesse caso, 23 interfaces estão bem. Caso contrário, é muito grande, muito complicado.
Stephen Gross

Respostas:

36

Família Real: Eles não fazem nada particularmente louco, mas têm um bilhão de títulos e estão relacionados à maioria dos outros membros da realeza de alguma forma.

Engenheiro Mundial
fonte
:-)))))))))))))
Emilio Garavaglia
Eu salientarsomehow
Lobo
23

Eu afirmo que esse anti-padrão seja chamado Jack of All Trades, ou talvez Too Many Hats.

Mike Partridge
fonte
2
E o canivete suíço?
FrustratedWithFormsDesigner
Parece que esse termo já é usado para interfaces com muitos métodos :-) Embora isso faça sentido aqui também.
Jalayn
11
@FrustratedWithFormsDesigner Um canivete suíço nunca teria o mau gosto de ter uma interface chamada IValue que contém 30 propriedades, das quais 20 são precedidas pela nova palavra-chave modificadora.
Jonas Elfström
3
+1 para muitos chapéus ... para adequadamente esconder divindade multi-headed
Newtopian
11
Um canivete suíço é um modelo de reutilização. ou seja, uma única "lâmina" pode executar vários métodos, portanto é provavelmente uma analogia ruim!
James Anderson
17

Se eu tivesse que dar um nome, acho que chamaria Hydra :

Hidra

Na mitologia grega, a Hidra de Lernaean (em grego: Λερναία Ὕδρα) era uma antiga besta aquática cônica, semelhante a uma serpente sem nome, com traços reptilianos (como o nome indica) que possuía muitas cabeças - os poetas mencionam mais cabeças do que os pintores de vasos poderiam tinta e, para cada cabeça cortada, cresciam mais duas - e um hálito venenoso tão virulento que até seus rastros eram mortais.

De particular relevância é o fato de que ele não apenas tem muitas cabeças, mas continua crescendo cada vez mais e é impossível matá-las por causa disso. Essa tem sido minha experiência com esse tipo de design; os desenvolvedores continuam colocando cada vez mais interfaces nele até que haja muitos para caber na tela e, a essa altura, ele se tornou tão profundamente arraigado no design e nas suposições do programa que dividi-lo é uma perspectiva sem esperança (se você tentar, você realmente acabam precisando de mais interfaces para preencher a lacuna).

Um dos primeiros sinais de destruição iminente devido a uma "Hidra" é a transmissão frequente de uma interface para outra, sem muita verificação de sanidade e frequentemente para um alvo que não faz sentido, como em:

public void CreateWidget(IPartLocator locator, int widgetTypeId)
{
    var partsNeeded = locator.GetPartsForWidget(widgetTypeId);
    return ((IAssembler)locator).BuildWidget(partsNeeded);
}

É óbvio, quando retirado do contexto, que há algo suspeito nesse código, mas a probabilidade de isso acontecer aumenta com o número de "cabeças" que um objeto cresce, porque os desenvolvedores sabem intuitivamente que estão sempre lidando com a mesma criatura.

Boa sorte nunca fazer qualquer tipo de manutenção em um objeto que implementa 23 interfaces. As chances de você não causar danos colaterais no processo são praticamente nulas.

Aaronaught
fonte
16

O objeto de Deus vem à mente; um único objeto que sabe fazer TUDO. Isso decorre da baixa adesão aos requisitos de "coesão" das duas principais metodologias de projeto; se você possui um objeto com 23 interfaces, possui um objeto que sabe 23 coisas diferentes para seus usuários e essas 23 coisas provavelmente não estão na mesma linha de uma única tarefa ou área do sistema.

No SOLID, embora o criador desse objeto tenha obviamente tentado seguir o Princípio de Segregação de Interface, ele violou uma regra anterior; Princípio de responsabilidade única. Há uma razão para ser "SÓLIDO"; S sempre vem em primeiro lugar quando se pensa em design, e todas as outras regras seguem.

No GRASP, o criador dessa classe negligenciou a regra "Alta Coesão"; O GRASP, diferentemente do SOLID, ensina que um objeto não precisa ter uma única responsabilidade, mas no máximo deve ter duas ou três responsabilidades muito próximas.

KeithS
fonte
Não tenho certeza de que esse seja o antipadrão de Objeto de Deus, já que Deus não é limitado por nenhuma interface. Ser limitado por tantas interfaces pode reduzir a onipotência de Deus. ;) Talvez este seja um objeto Quimera?
FrustratedWithFormsDesigner
Deus tem muitos rostos e pode ser muitas coisas para muitas pessoas. Se a funcionalidade das interfaces combinadas torna o objeto "onisciente", não está realmente constrangendo a Deus, é?
Keiths
11
A menos que o que criou as interfaces as modifique. Então, Deus pode ter que ser reimplementado para se adaptar às mudanças na interface.
FrustratedWithFormsDesigner
3
Dói que você pense que fui eu quem criou a turma.
Jonas Elfström
O uso de "você" é mais o plural de você, referindo-se à sua equipe de desenvolvimento. ALGUÉM nessa equipe, passado ou presente, criou esta classe. Eu vou editar, no entanto.
Keiths
8

23 é apenas um número! No cenário mais provável, é alto o suficiente para causar alarme. No entanto, se perguntarmos, qual é o maior número de métodos / interfaces antes que ele possa chamar uma tag como "antipadrão"? São 5, 10 ou 25? Você percebe que o número não é realmente uma resposta, porque se 10 for bom, 11 também pode ser - e, em seguida, qualquer número inteiro depois disso.

A verdadeira questão é sobre complexidade. E devemos ressaltar que código longo, número de métodos ou tamanho grande da classe por qualquer medida NÃO é realmente a definição de complexidade. Sim, um código cada vez maior (número maior de métodos) dificulta a leitura e a compreensão de um novo iniciante. Ele também lida com funcionalidades potencialmente variadas, grande número de exceções e algoritmos bastante evoluídos para vários cenários. Isso não significa que é complexo - é apenas difícil de digerir.

Por outro lado, um código de tamanho relativamente pequeno, que se pode esperar ler dentro de algumas horas, ainda pode ser complexo. Aqui é quando eu acho que o código é (desnecessariamente) complexo.

Toda sabedoria do design orientado a objetos pode ser usada aqui para definir "complexo", mas eu restringiria aqui para mostrar quando "tantos métodos" é uma indicação de complexidade.

  1. Conhecimento mútuo. (aka coupling) Muitas vezes, quando as coisas são escritas como classes, todos pensamos que é um código orientado a objetos "bom". Mas a suposição sobre a outra classe essencialmente quebra o verdadeiro encapsulamento necessário. Quando você tem métodos que "vazam" detalhes profundos sobre o estado interno dos algoritmos - e o aplicativo é construído com premissa-chave sobre o estado interno da classe de serviço.

  2. Muitas repetições (invasão) Quando métodos que têm nomes semelhantes, mas que contradizem o trabalho - ou que contradizem nomes com funcionalidade semelhante. Muitas vezes, os códigos evoluem para oferecer suporte a interfaces ligeiramente diferentes para diferentes aplicativos.

  3. Papéis demais Quando a classe continua adicionando funções secundárias e continua se expandindo mais como as pessoas gostam, apenas para saber que a classe agora é realmente de duas classes. Surpreendentemente, tudo isso começa com genuínorequisitos e nenhuma outra classe existe para fazer isso. Considere isso, existe uma classe Transaction, que informa detalhes da transação. Parece bom até agora, agora alguém exige conversão de formato no "horário da transação" (entre o UTC e o similares); mais tarde, as pessoas adicionam regras para verificar se algo estava em determinadas datas para validar transações inválidas. - Não vou escrever a história toda, mas, eventualmente, a classe de transação constrói todo o calendário com ele e então as pessoas começam a usar parte "apenas do calendário"! Isso é muito complexo (para imaginar) por que eu instanciarei a "classe de transação" para ter a funcionalidade que um "calendário" me forneceria!

  4. (In) consistência da API Quando eu faço book_a_ticket () - reserve um ingresso! Isso é muito simples, independentemente de quantas verificações e processos serão necessários para que isso aconteça. Agora fica complexo quando o fluxo de tempo começa a afetar isso. Normalmente, seria permitido "pesquisar" e possível status disponível / indisponível; em seguida, para reduzir o tempo necessário, você começa a salvar alguns ponteiros de contexto dentro do ticket e depois refere - se a isso para reservar o ticket. A pesquisa não é a única funcionalidade - as coisas pioram após muitas dessas "funcionalidades secundárias". No processo, o significado de book_a_ticket () implica book_that_ticket ()! e isso pode ser inimaginavelmente complexo.

Provavelmente, existem muitas situações que você veria em um código muito evoluído e tenho certeza de que muitas pessoas podem adicionar cenários, onde "tantos métodos" simplesmente não fazem sentido ou não fazem o que você obviamente pensaria. Este é o anti-padrão.

Minha experiência pessoal é que, quando projetos que começam razoavelmente de baixo para cima, muitas coisas para as quais deveria haver classes genuínas por conta própria - permanecem enterradas ou pior ainda permanecem divididas entre diferentes classes e o aumento de acoplamentos. Na maioria das vezes, o que teria merecido 10 classes, mas tem apenas 4, é provável que muitos deles possuam um número confuso e grande número de métodos. Chame de DEUS e DRAGÃO, é isso que é MAU.

MAS você se depara com MUITAS GRANDES classes, que são perfeitamente consistentes, elas têm 30 métodos - e ainda são muito LIMPAS. Eles podem ser bons.

Dipan Mehta
fonte
A classe em perguntas possui 23 interfaces e elas contêm, no total, 113 propriedades públicas e quatro métodos. Apenas alguns deles são apenas para leitura. C # tem propriedades implementadas automaticamente, é por isso que diferem entre os métodos e propriedades msdn.microsoft.com/en-us/library/bb384054.aspx
Jonas Elfstrom
7

As classes devem ter exatamente o número certo de interfaces; nem mais nem menos.

Dizer "muitos" seria impossível sem verificar se todas essas interfaces servem ou não a um propósito útil nessa classe. Declarar que um objeto implementa uma interface significa que você deve implementar seus métodos se espera que a classe seja compilada. (Presumo que a classe que você está vendo faz.) Se tudo na interface for implementado e essas implementações fizerem algo em relação às entranhas da classe, seria difícil dizer que a implementação não deveria estar lá. O único caso em que consigo pensar em onde uma interface que atenda a esses critérios não pertenceria é externo: quando ninguém a usa.

Algumas linguagens permitem que as interfaces sejam "subclassificadas" usando um mecanismo como a extendspalavra-chave do Java , e quem a escreveu pode não saber disso. Também pode ser que todos os 23 sejam suficientemente distantes que agregá-los não faça sentido.

Blrfl
fonte
Achei que essa pergunta era bastante agnóstica. O código real está em c #.
Jonas Elfström 3/11
Eu não fiz nenhum c #, mas parece suportar uma forma semelhante de herança de interface.
Blrfl
Sim, uma interface pode "herdar" outras interfaces e assim por diante.
Jonas Elfström 3/11
Acho interessante que muitas discussões se concentrem no que as classes devem fazer, e não nas instâncias . Um robô com uma variedade de sensores de imagem e áudio conectados a um chassi comum deve ser modelado como uma entidade que possui localização e orientação, e que pode ver, ouvir e associar imagens e sons. Toda a lógica real por trás dessas funções pode estar em outras classes, mas o próprio robô deve oferecer suporte a métodos como "veja se algum objeto está à frente", "escute qualquer ruído na área" ou "veja se o objeto à frente parece ser gerando os sons detectados ".
Supercat 11/14
Pode ser que um robô em particular deva subdividir sua funcionalidade em subsistemas de uma maneira específica, mas na medida em que o código prático que usa o robô não precise saber ou se preocupar com a subdivisão da funcionalidade em um robô em particular. Se os "olhos" e "ouvidos" fossem expostos ao mundo externo como objetos separados, mas os sensores correspondentes estivessem em um braço de extensão compartilhado, o código externo poderia pedir aos "ouvidos" para ouvir sons no nível do solo ao mesmo tempo que pediu aos "olhos" que olhassem acima de um obstáculo.
Supercat 11/11
1

Parece que eles têm um par "getter" / "setter" para cada propriedade, como é normal para um "bean" típico e promoveu todos esses métodos para a interface. Então, que tal chamá-lo de " tem feijão ". Ou a "flatulência" mais rabelaisiana após os efeitos conhecidos de muitos feijões.

James Anderson
fonte
Está em C # e possui Propriedades Auto-Implementadas. No total, essas 23 interfaces contêm 113 dessas propriedades.
Jonas Elfström 10/11
1

O número de interfaces geralmente aumenta quando um objeto genérico é usado em ambientes diferentes. Em C #, as variantes de IComparable, IEqualityComparer e IComparer permitem a classificação em configurações distintas; portanto, você pode acabar implementando todas elas, algumas delas talvez mais de uma vez, desde que você possa implementar as versões genéricas fortemente tipadas, bem como as versões não genéricas. , além disso, você pode implementar mais de um dos genéricos.

Vamos dar um exemplo de exemplo, digamos uma loja on-line onde você pode comprar créditos com os quais você pode comprar outra coisa (sites de banco de imagens costumam usar esse esquema). Você pode ter uma classe "Valuta" e uma classe "Créditos" que herdam da mesma base. O Valuta possui algumas sobrecargas de operador e rotinas de comparação que permitem realizar cálculos sem se preocupar com a propriedade "Moeda" (adicionando libras a dólares, por exemplo). Os créditos são muito mais simples, mas têm algum outro comportamento distinto. Para poder compará-los, você pode implementar o IComparable e o IComparable e as outras variantes das interfaces de comparação em ambos (mesmo que eles usem uma implementação comum, seja na classe base ou em outro local).

Ao implementar a serialização, ISerializable, IDeserializationCallback é implementado. Implementando pilhas de desfazer refazer: IClonable é adicionado. Funcionalidade IsDirty: IObservable, INotifyPropertyChanged. Permitindo que os usuários editem os valores usando cadeias: IConvertable ... A lista pode continuar indefinidamente ...

Nas línguas modernas, vemos uma tendência diferente que ajuda a separar esses aspectos e colocá-los em suas próprias classes, fora da classe principal. A classe ou aspecto externo é então associado à classe de destino usando anotação (atributos). Freqüentemente é possível tornar as classes de aspecto externo mais ou menos genéricas.

O uso de atributos (anotação) está sujeito a reflexão. Uma desvantagem é uma pequena perda de desempenho (inicial). Uma desvantagem (geralmente emocional) é que princípios como o encapsulamento precisam ser relaxados.

Sempre existem outras soluções, mas para cada solução bacana há uma troca ou um problema. Por exemplo, o uso de uma solução ORM pode exigir que todas as propriedades sejam declaradas virtuais. As soluções de serialização podem exigir construtores padrão em suas classes. Se você estiver utilizando injeção de dependência, poderá acabar implementando 23 interfaces em uma única classe.

A meu ver, 23 interfaces não precisam ser ruins por definição. Pode haver um esquema bem pensado por trás disso, ou alguma determinação de princípio, como evitar o uso de reflexão ou crenças extremas de encapsulamento.

Sempre que alternar um trabalho ou precisar construir uma arquitetura existente. Meu conselho é primeiro se familiarizar totalmente, não tente refatorar tudo muito rapidamente. Ouça o desenvolvedor original (se ele ainda estiver lá) e tente descobrir os pensamentos e idéias por trás do que você vê. Ao fazer perguntas, não faça isso com o objetivo de quebrá-las, mas para aprender ... Sim, todo mundo tem seus próprios martelos de ouro, mas quanto mais martelos você puder coletar, mais fácil se dará com os colegas.

Louis Somers
fonte
0

"Demasiado" é subjetivo: estilo de programação? desempenho? conformidade com os padrões? precedentes? simplesmente senso de conforto / confiança?

Desde que seu código se comporte corretamente e não apresente problemas de manutenção, 23 pode até ser a nova norma. Um dia eu poderia dizer: "Você pode fazer um excelente trabalho com 23 interfaces, veja: Jonas Elfström".

Kris
fonte
0

Eu diria que o limite deve ser um número menor que 23 - mais ou menos como 5 ou 7,
no entanto, isso não inclui nenhum número de interfaces que essas interfaces herdam ou qualquer número de interfaces implementadas pelas classes base.

(Portanto, N + qualquer número de interfaces herdadas, onde N <= 7.)

Se sua classe implementa muitas interfaces, provavelmente é uma classe divina .

Danny Varod
fonte