Passando um Contexto ao redor com injeção de dependência

5

No projeto em que estou trabalhando, estou usando o Guice e tentando fazer o máximo possível com a Injeção de Dependência. No entanto, há um pequeno problema; muitos dos meus objetos dependem de um objeto Context . Este é um objeto imutável, mas possui algumas informações que a maioria dos outros objetos precisa para funcionar corretamente. Ele nunca muda para classes que estão todas trabalhando dentro do mesmo Context , mas meu aplicativo deve ser capaz de suportar diferentes Context s ao mesmo tempo, no mesmo escopo. Eu posso pensar em três maneiras de resolver o meu problema:

  1. Atualmente, encontrei-me escrevendo muito código com o AssistedInject, que quase sempre tem métodos como Factory.create(Context c) . Todo o meu código que depende de Context está sendo criado dessa maneira, mesmo que sejam simples.

  2. Estou começando a considerar o uso de Provider s e a alteração de todas as linhas de factory.create(context) a obj = provider.get(); obj.setContext(c); , mas não desejo depender do código para fazer as duas chamadas. O compilador certifica-se de que cada objeto possua o context e que o context não possa ser alterado (nunca será).

  3. Minha pesquisa apontou para este tópico: link A idéia de emparelhar cada Context com um childInjector que tem uma linha bind(Context.class).toInstance(ContextFactory.makeInstance(args)) , mas não tenho certeza de como garantir que tudo está usando o injetor infantil correto. Isso parece ser o melhor, mas além desse segmento (que não é o meu cenário), não sei como fazer isso, ou se é certo para a minha situação.

Aqui está um sscce do que pode ser feito pela opção 3:

public class GuiceChildTest {
    public static void main(String... args) {
        Injector inj = Guice.createInjector(new ParentModule());
        Injector fooChildOne = inj.createChildInjector(new FooModule(new Foo(10)));
        Injector fooChildTwo = inj.createChildInjector(new FooModule(new Foo(20)));

        Bar bar = fooChildOne.getInstance(Bar.class);
        System.out.println(bar.baz());

        bar = fooChildTwo.getInstance(Bar.class);
        System.out.println(bar.baz());
    }

    private static class ParentModule extends AbstractModule {
        @Override
        protected void configure() {
        }
    }

    private static class Bar {
        private MyInterface intf;
        @Inject
        public Bar(MyInterface in) {
            this.intf = in;
        }

        public int baz() {
            return intf.doSomething();
        }
    }

    private static class FooModule extends AbstractModule {
        private Foo f;
        public FooModule(Foo f) {
            this.f = f;
        }

        @Override
        protected void configure() {
            bind(MyInterface.class).toInstance(f);
        }   
    }   

    private static class Foo implements MyInterface {
        private int i;
        public Foo(int i) {
            this.i = i;
        }
        @Override
        public int doSomething() {
            return i;
        }
    }

    private static interface MyInterface {
        int doSomething();
    }
}
    
por durron597 17.06.2013 / 23:23
fonte

1 resposta

3

Vamos abordar os fundamentos de Injeção de Dependência com uma breve revisão do princípio de injeção de dependência

When following this principle, the conventional dependency relationships established from high-level, policy-setting modules to low-level, dependency modules are inverted (i.e. reversed), thus rendering high-level modules independent of the low-level module implementation details. The principle states

  • High-level modules should not depend on low-level modules. Both should depend on abstractions.
  • Abstractions should not depend on details. Details should depend on abstractions.

Parece que as duas primeiras abordagens que você mencionou podem não estar seguindo totalmente os princípios de DI, porque os objetos de maior ordem parecem precisar saber que tipo de Context eles deveriam ter.

E antes de continuar, devo admitir um preconceito que potencialmente ofenda os proponentes fanáticos da DI:

DI can get over-emphasized and zealous adherence to the principles can get in the way of writing maintainable code that's appropriate for the environment that it operates within. And that's not necessarily a dig against DI, but rather against any technique that ends up being blindly applied. I'm not saying that you are blindly applying DI, but there are many who fall into that category.

Spoiler usado para proteger aqueles que são excessivamente sensíveis a tais reivindicações

Portanto, antes de chamar os métodos 1 e amp; 2 "ruim", precisamos perguntar se eles estão atendendo às suas necessidades atuais. Essas abordagens envolvem alguma medida de código clichê que é copiado por aí. Isso pode ser bom se você não encontrar a necessidade de alterar esse modelo com frequência.

Da mesma forma, se os objetos puderem usar um e somente um tipo de Contexto, então você não precisará precisar de se preocupar com DI neste caso, pois não há necessariamente algo a o objeto. "Mas e o teste?" é a réplica habitual a essa afirmação pragmática, que é respondida com uma resposta igualmente pragmática de "Esses objetos podem usar / precisam de um contexto diferente para serem testados?" Se sim, use DI. Se não, então ... continue.

A terceira opção que você propõe deixa você mais longe do código clichê e aproxima você de uma abordagem DI. Sua hesitação parece ser:

but I'm not sure how to ensure that everything is using the correct child injector

Acho que a solução para esse desafio é envolver sua criação de Contexto em uma Fábrica e absolver os objetos de maior ordem de saber o que eles precisam chamar.

Então, se eu entendi seu exemplo de código corretamente, em vez disso:

Bar bar = fooChildOne.getInstance(Bar.class);

você teria algo como:

Bar bar = new Bar(ContextFactory.getInstance(Bar.class));

Na sugestão, estamos passando o Contexto retornado do construtor ContextFactory into Bar . Pode ser 1 trivial para simplificar o exemplo com:

public class Bar(){
    Bar(){
        return Bar(ContextFactory.getInstance(Bar.class));
    }
}

ou:

public class Bar(){
    Bar(){
        this.Context = ContextFactory.getInstance(Bar.class);
    }
}

1 Meu Java está enferrujado, então eu não sei se você pode encadear construtores como o primeiro exemplo.

E você poderia aumentar um pouco as coisas usando reflexão dentro de ContextFactory para determinar o Contexto correto para Bar ou, opcionalmente, passar parâmetros adicionais para o ContextFactory if Bar pode manipular vários tipos de Contexto em operação regular.

TL; DR

Use a terceira opção que você propôs e envolva a criação de Contexto em uma Fábrica .

    
por 17.12.2014 / 19:37
fonte