Multidões construindo uma implementação. DI sem esperança? Usar localizador de serviço?


14

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 hashmapsequê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, newmas controlamos a classe que eles estão construindo.

Minha pergunta:

O que deixei de considerar?


Perguntas do Doc Brown

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.


1
Não acho que você conseguirá muito armazenando fábricas ou localizadores no estado global. Porque se importar? Você vai testar clientes substituindo a dependência?
Basilevs 02/02

Considere usar o carregador de classes personalizado.
Basilevs 02/02

Por curiosidade, o que esse sistema faz?
Primeiro-

Respostas:


7

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.


Boas perguntas! Por favor, veja a atualização.
Candied_orange 2/02

Atualizado com o caso de uso real.
Candied_orange 2/02

@CandiedOrange: Eu não posso lhe dar um pouco de peixe, só posso tentar ajudá-lo a pescar :-)
Doc Brown

Entendido. Estou apenas olhando para a água, me perguntando se preciso de isca melhor ou um buraco de pesca diferente. Eu adoraria fazer o DI adequado, mas essa situação não parece permitir isso.
Candied_orange

1
@CandiedOrange Não se apegue ao desejo de fazer DI porque é "bom" ou "melhor" do que qualquer outra coisa. A DI é uma solução específica para um problema (ou algumas vezes várias). Mas nem sempre é a solução mais "adequada". Não se apaixone por isso ... trate-o como é. É uma ferramenta.
Svidgen

2

Só para ter certeza de que estou acertando. Você tem algum serviço feito de algumas classes, digamos, C1,...,Cne vários clientes que ligam diretamente new Ci(...). Portanto, acho que concordo com a estrutura geral da sua solução, que é criar um novo serviço interno com algumas classes novas D1,...,Dne modernas e injetar suas dependências (permitindo essas dependências na pilha) e reescrever Cipara não fazer nada além de instanciar e delgate para Di. A questão é como fazê-lo e você sugeriu algumas maneiras de 2e 3.

Para dar uma sugestão semelhante a 2. Você pode usar a injeção de dependência Diinterna e internamente e, em seguida, criar uma raiz de composição normal R(usando uma estrutura, se você considerar apropriado), responsável pela montagem do gráfico de objetos. Mova-se para Rtrás de uma fábrica estática e deixe que cada um Ciconsiga passar Dipor isso. Então, por exemplo, você pode ter

public class OldClassI {
    private final NewClassI newImplementation;

    public OldClassI(Object parameter) {
        this.newImplementation = CompositionRoot.getInstance().getNewClassI(parameter);
    }
}

Essencialmente, 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.


Eu concordo com isto. Só não tenho certeza de como isso não é o mesmo que o localizador de serviço da opção 3. Você espera criar uma nova instância com cada chamada para getInstance()?
Candied_orange

@CandiedOrange Isso provavelmente é apenas um choque de uso, para mim, um localizador de serviço é muito especificamente algo que é essencialmente apenas um hashmap de tipos para objetos, enquanto que a raiz da composição é apenas um objeto com vários métodos que constroem outros objetos.
walpen

1

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 libpasta ...

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.)

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.