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?
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?
Respostas:
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.
somehow
Eu afirmo que esse anti-padrão seja chamado Jack of All Trades, ou talvez Too Many Hats.
Se eu tivesse que dar um nome, acho que chamaria Hydra :
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.
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.
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.
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.
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.
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!
(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.
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 extends
palavra-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.
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.
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.
"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".
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 .