Digamos que temos 1001 clientes que constroem suas dependências diretamente, em vez de aceitarem injeções. Refatorar o 1001 não é uma opção, de acordo com nosso chefe. Na verdade, nem sequer é permitido o acesso à fonte, apenas aos arquivos de classe.
O que devemos fazer é "modernizar" o sistema pelo qual esses 1001 clientes passam. Podemos refatorar tudo o que gostamos. As dependências fazem parte desse sistema. E algumas dessas dependências que devemos mudar para ter uma nova implementação.
O que gostaríamos de fazer é ter a capacidade de configurar diferentes implementações de dependências para satisfazer essa multidão de clientes. Infelizmente, o DI não parece uma opção, pois os clientes não aceitam injeções com construtores ou setters.
As opções:
1) Refatorar a implementação do serviço que os clientes usam para que ele faça o que os clientes precisam agora. Bang, terminamos. Não é flexível. Não é complexo.
2) Refatorar a implementação para delegar seu trabalho a outra dependência que ela adquire por meio de uma fábrica. Agora podemos controlar qual implementação eles usam refatorando a fábrica.
3) Refatorar a implementação para delegar seu trabalho a outra dependência que ela adquire por meio de um localizador de serviço. Agora podemos controlar qual implementação eles usam configurando o localizador de serviço, que pode ser simplesmente uma hashmap
sequência de caracteres para objetos com um pouco de conversão.
4) Algo em que ainda nem pensei.
O objetivo:
Minimize o dano do design causado arrastando o código do cliente mal projetado antigo para o futuro sem adicionar complexidade inútil.
Os clientes não devem saber ou controlar a implementação de suas dependências, mas insistem em construí-las new
. Não podemos controlar, new
mas controlamos a classe que eles estão construindo.
Minha pergunta:
O que deixei de considerar?
você realmente precisa da possibilidade de configurar entre diferentes implementações? Para qual propósito?
Agilidade. Muitas incógnitas. A gerência quer potencial de mudança. Só perca a dependência do mundo exterior. Também testando.
você precisa de uma mecânica de tempo de execução ou apenas uma mecânica de tempo de compilação para alternar entre diferentes implementações? Por quê?
É provável que a mecânica do tempo de compilação seja suficiente. Exceto para testes.
qual granularidade você precisa alternar entre implementações? Tudo de uma vez? Por módulo (cada um contendo um grupo de classes)? Por turma?
Dos 1001, apenas um é executado pelo sistema a qualquer momento. Alterar o que todos os clientes usam ao mesmo tempo provavelmente é bom. O controle individual das dependências provavelmente é importante.
quem precisa controlar o interruptor? Somente sua / sua equipe de desenvolvedores? Um administrador? Cada cliente por conta própria? Ou os desenvolvedores de manutenção para o código do cliente? Então, quão fácil / robusta / infalível a mecânica precisa ser?
Dev para teste. Administrador conforme as dependências externas de hardware são alteradas. Precisa ser fácil de testar e configurar.
Nosso objetivo é mostrar que o sistema pode ser refeito rapidamente e modernizado.
caso de uso real para a opção de implementação?
Uma é que alguns dados serão fornecidos pelo software até que a solução de hardware esteja pronta.
fonte
Respostas:
Bem, não sei se entendi completamente os detalhes técnicos e as diferenças exatas de suas soluções suportadas, mas IMHO você primeiro precisa descobrir que tipo de flexibilidade realmente precisa.
As perguntas que você deve se fazer são:
você realmente precisa da possibilidade de configurar entre diferentes implementações? Para qual propósito?
você precisa de uma mecânica de tempo de execução ou apenas uma mecânica de tempo de compilação para alternar entre diferentes implementações? Por quê?
qual granularidade você precisa alternar entre implementações? Tudo de uma vez? Por módulo (cada um contendo um grupo de classes)? Por turma?
quem precisa controlar o interruptor? Somente sua / sua equipe de desenvolvedores? Um administrador? Cada cliente por conta própria? Ou os desenvolvedores de manutenção para o código do cliente? Então, quão fácil / robusta / infalível a mecânica precisa ser?
Tendo em mente as respostas para essas perguntas, escolha a solução mais simples que você pode pensar, que fornece a flexibilidade necessária. Não implemente flexibilidade que você não tenha certeza "apenas por precaução", se isso significar esforço adicional.
Como resposta às suas respostas: se você tiver pelo menos um caso de uso real em mãos, use-o para validar suas decisões de design. Use esse caso de uso para descobrir qual tipo de solução funciona melhor para você. Experimente se uma "fábrica" ou um "localizador de serviço" fornece o que você precisa ou se precisa de algo mais. Se você acha que as duas soluções são igualmente boas para o seu caso, jogue um dado.
fonte
Só para ter certeza de que estou acertando. Você tem algum serviço feito de algumas classes, digamos,
C1,...,Cn
e vários clientes que ligam diretamentenew Ci(...)
. Portanto, acho que concordo com a estrutura geral da sua solução, que é criar um novo serviço interno com algumas classes novasD1,...,Dn
e modernas e injetar suas dependências (permitindo essas dependências na pilha) e reescreverCi
para não fazer nada além de instanciar e delgate paraDi
. A questão é como fazê-lo e você sugeriu algumas maneiras de2
e3
.Para dar uma sugestão semelhante a
2
. Você pode usar a injeção de dependênciaDi
interna e internamente e, em seguida, criar uma raiz de composição normalR
(usando uma estrutura, se você considerar apropriado), responsável pela montagem do gráfico de objetos. Mova-se paraR
trás de uma fábrica estática e deixe que cada umCi
consiga passarDi
por isso. Então, por exemplo, você pode terEssencialmente, esta é a sua solução 2, mas coleta todas as suas fábricas em um único local, juntamente com o restante da injeção de dependência.
fonte
getInstance()
?Comece com implementações simples, nada extravagantes e singulares.
Se você precisar criar implementações adicionais posteriormente, é uma questão de quando a decisão de implementação ocorre.
Se você precisar da flexibilidade do "tempo de instalação" (cada instalação do cliente usa uma implementação estática única), ainda não precisará fazer nada sofisticado. Você oferece apenas DLLs ou SOs diferentes (ou qualquer que seja o seu formato lib). O cliente só precisa colocar o caminho certo na
lib
pasta ...Se você precisar de flexibilidade de tempo de execução, precisará apenas de um adaptador fino e de um mecanismo de escolha da implementação. Se você usa um contêiner de fábrica, localizador ou IoC, geralmente é discutível. As únicas diferenças significativas entre um adaptador e um localizador são A) Nomeação e B) Se o objeto retornado é um singleton ou uma instância dedicada. E a grande diferença entre um contêiner de IoC e uma fábrica / localizador é quem liga para quem . (Geralmente, é uma questão de preferência pessoal.)
fonte