Como evitar uma quantidade louca de interfaces na interface do usuário com injeção de dependência?

9

Problema
Recentemente, li muito sobre Singletons serem ruins e como a injeção de dependência (que entendo como "usando interfaces") é melhor. Quando implementei parte disso com retornos de chamada / interfaces / DI e aderindo ao princípio de segregação de interface, acabei com uma bagunça.

As dependências de um pai da interface do usuário em que basicamente as de todos os seus filhos eram combinadas; portanto, quanto mais alto a hierarquia era um elemento da interface do usuário, mais inchado era o construtor.

No topo da hierarquia da interface do usuário, havia uma classe Application, contendo as informações sobre a seleção atual e uma referência a um modelo 3d que precisa refletir as alterações. A classe de aplicação estava implementando 8 interfaces, e isso era apenas a rotunda de um quinto dos produtos (/ interfaces) por vir!

Atualmente, trabalho com um singleton segurando a seleção atual e os elementos da interface do usuário com uma função para atualizar-se. Essa função desativa a árvore da interface do usuário e os elementos da interface do usuário e, em seguida, acessa o singleton da seleção atual conforme necessário. O código parece mais limpo para mim dessa maneira.

Pergunta
Um singleton pode ser apropriado para este projeto?
Se não, existe uma falha fundamental no meu pensamento e / ou implementação da DI que a torna tão complicada?

Informações adicionais sobre o projeto
Tipo: Cesto de compras para apartamentos, com sinos e assobios
Tamanho: 2 homens-mês para código e interface do usuário
Manutenção: Nenhuma atualização em execução, mas talvez a "versão 2.0" posterior
Ambiente: Usando C # no Unity, que usa uma Entidade Sistema de componentes

Em quase todos os casos, a interação do usuário aciona várias ações. Por exemplo, quando o usuário seleciona um item

  • a parte da interface do usuário que mostra esse item e sua descrição precisa ser atualizada. Para isso, ele também precisa obter algumas informações de um modelo 3d para calcular o preço.
  • na interface do usuário, o preço total geral precisa ser atualizado
  • uma função correspondente em uma classe em um modelo 3d precisa ser chamada para exibir as alterações lá
R. Schmitz
fonte
DI não é apenas sobre o uso de interfaces, é a capacidade de trocar concreções que é especialmente útil na esfera de testes de unidade ...
Robbie Dee
11
Além disso, ainda estou para ver um problema em que um singleton é a solução preferida. O exemplo canônico que as pessoas sempre parecem indicar é um gravador de logs, mas mesmo aqui você pode gravar em vários logs diferentes (erro, depuração etc.).
Robbie Dee
@RobbieDee Sim, o DI é mais, mas não vejo uma situação em que o uso de uma interface não seja o DI. E se um singleton não é a solução preferida - o que também assumo -, por que a solução preferida é tão confusa? Essa é a minha pergunta principal, mas eu queria mantê-la aberta, pois às vezes a regra geral não se aplica.
R. Schmitz
O arranjo de concretização de interface também é um recurso de estruturas de simulação. Eles também estive em linguagens OO para um bom décadas ...
Robbie Dee
Eu não entendo o seu ponto.
R. Schmitz

Respostas:

4

Eu acho que a pergunta é um sintoma, não uma solução.

Recentemente, li muito sobre Singletons serem ruins e como a injeção de dependência (que entendo como "usando interfaces") é melhor. Quando implementei parte disso com retornos de chamada / interfaces / DI e aderindo ao princípio de segregação de interface, acabei com uma bagunça.

Uma solução procurando um problema; isso e mal-entendido provavelmente está corrompendo seu design. Você já leu esta pergunta sobre o DI vs Singleton, conceitos diferentes ou não? Eu li isso simplesmente envolvendo um singleton para que o cliente não precise lidar com um singleton. É apenas um bom encapsulamento antigo. Eu acho que é no local.


quanto mais a hierarquia era um elemento da interface do usuário, mais inchado era o construtor.

Construa os bits menores primeiro e depois passe-os para o construtor da coisa a que pertencem e passe essa coisa maior para o construtor da próxima coisa maior ...

Se você tiver problemas complexos de construção, use o padrão de fábrica ou construtor. Resumindo, existe uma construção complexa inserida em sua própria classe para manter outras classes organizadas, limpas, compreensíveis etc.


A classe de aplicação estava implementando 8 interfaces, e isso era apenas a rotunda de um quinto dos produtos (/ interfaces) por vir!

Isso soa como a ausência de capacidade de extensão. O design do núcleo está ausente e tudo está sendo amontoado a partir do topo. Deveria haver mais construção, composição e herança de baixo para cima.

Gostaria de saber se o seu design é "top pesado". Parece que estamos tentando fazer com que uma classe seja tudo ou qualquer coisa que possa ser. E o fato de ser uma classe de interface do usuário, não uma classe de domínio comercial, que realmente me faz pensar sobre a separação de preocupações.

Revise seu design desde o início e verifique se há uma abstração sólida e básica do produto que pode ser construída para criar produções de categoria mais complexas ou diferentes. Então é melhor você ter coleções personalizadas dessas coisas para ter um lugar para colocar a funcionalidade "nível de coleção" - como o "terceiro modelo" mencionado.


... modelo 3d que precisa refletir mudanças.

Muito disso pode caber em classes de coleção personalizadas. Também pode ser uma estrutura de classe independente devido à profundidade e complexidade. Essas duas coisas não são mutuamente exclusivas.

Leia sobre o padrão de visitantes. É a idéia de ter um monte de funcionalidades conectadas abstratamente a diferentes tipos.


Design e DI

90% de toda a injeção de dependência que você fará é a passagem de parâmetro do construtor. É o que diz o quy que escreveu o livro . Crie bem suas aulas e evite poluir esse processo de pensamento com alguma noção vaga sobre a necessidade de usar um recipiente de DI. Se você precisar, seu design o sugerirá, por assim dizer.


Concentre-se na modelagem do domínio de compras de apartamentos.

Evite a abordagem de Jessica Simpson ao design : "Não sei o que isso significa, mas quero isso".

O seguinte está errado:

  • Eu deveria usar interfaces
  • Eu não deveria usar um singleton
  • Eu preciso de DI (seja lá o que for)
  • Eu deveria usar composição, não herança
  • Eu devo evitar herança
  • Eu preciso usar padrões
radarbob
fonte
Tentei me livrar de todos os singletons que usei e algumas das coisas que você disse aconteceram mais ou menos automaticamente. O resto faz muito sentido também, e eu vou seguir seu conselho e ler o padrão de visitantes, etc. Em suma, uma resposta bem escrita, obrigado!
R. Schmitz
Apenas um ponto, e eu não estou tentando ser um vampiro ajuda aqui, mas foi tipo de parte da pergunta original: O consenso geral (e que se baseia em razões válidas) parece ser que você de fato não é suposto use um singleton. Você escreve "'Eu não devo usar um singleton' está errado" - em que situação seria apropriado usá-lo?
R. Schmitz
Tudo o que estou dizendo é: "depende". Muitas vezes eu vejo máximas tomadas literalmente.
Radarbob
3

"Hierarquia de classe" é um pouco de bandeira vermelha. Hipotético: uma página da web com 5 widgets. A página não é um ancestral de nenhum dos widgets. Pode conter uma referência a esses widgets. Mas não é um ancestral. Em vez da hierarquia de classes, considere usar a composição. Cada um dos 5 widgets pode ser construído por conta própria, sem referência aos outros widgets ou à página inicial. A página superior é então construída com informações suficientes para criar uma página básica e distribuir os objetos do widget (Coleção) que são passados ​​para ela. A página é responsável pelo layout, etc., mas não pela construção e lógica dos widgets.

Depois de usar a composição, DI é seu melhor amigo. O DI permite trocar cada widget por qualquer versão ou tipo de widget que você definir no DI. A construção dos widgets é capturada na DI e é separada da página superior. Talvez até a composição da coleção possa ser definida no seu DI. A página superior executa o layout com base nos widgets passados ​​para ela, como deveria. Não são necessárias alterações no construtor da página superior. A página superior precisa apenas da lógica para executar o layout dos widgets e transmitir informações de / para o widget com base nas interfaces definidas.

Em vez de passar os ouvintes para cima e para baixo na cadeia de composição, injete uma coleção de ouvintes nos widgets que precisam. Injete a coleção em um editor para que eles possam publicar na coleção. Injete a coleção nos ouvintes para que eles possam se adicionar à coleção. A coleção atravessa todos os objetos na cadeia de composição.

scorpdaddy
fonte
Desculpe, "hierarquia de classes" estava errada aqui; na verdade, não era hierarquia de classes, mas hierarquia de interface do usuário. Os 'widgets' não são subclasses da classe de aplicativo, mas a 'página' contém referências a eles para publicar atualizações da interface do usuário. Era muito testável com essa estrutura, mas também havia muita sobrecarga, especialmente nos construtores, apenas transmitindo muitos ouvintes para seus filhos (UI).
R. Schmitz
@ R.Schmitz: A sobrecarga importava? A interface do usuário era lenta e o design que você descreveu como culpado?
26716 Robert Harvey
@ R.Schmitz Você pode falar sobre "transmitir muitos ouvintes"? Talvez minha experiência com DI em C # seja baixa, mas algo me diz que não seria necessário (pelo menos no construtor) com o design adequado.
precisa saber é o seguinte
@RobertHarvey Sem perda de desempenho, trata-se apenas de legibilidade.
R. Schmitz
@ Katana314 Por exemplo, quando um item é adicionado, o 3dmodel precisa ser atualizado e não pertence a nenhuma das categorias de produtos; portanto, é referenciado na classe do aplicativo, que escuta os itens que estão sendo adicionados / removidos / alterados. No final, seria praticamente o mesmo para todas as categorias de produtos. Outras partes da interface do usuário também reagem (e escutam), mas a mensagem precisa ir para o topo, porque é aí que está a referência do modelo 3d e os dados reais (que também são atualizados).
22916 R. Schmitz