How to mange sets of dependency injections?

63 views
Skip to first unread message

dhoffer

unread,
Sep 14, 2012, 10:00:06 AM9/14/12
to google...@googlegroups.com
I've got a couple of projects that use Guice that used to use manual DI.  So currently I have one module class with all the bindings.  I often use the annotatedWith(Names.named("MyClass")) approach so I can specify which implementation should be injected.

But this brings me to the problem.  What if I have the case where several injections much change as a set?  E.g. lets say I'm implementing a couple of pricing strategies where each strategy creates 5 concrete classes...it's critical that all 5 of those are from the same pricing strategy...not 4 from one and 1 from another.  In the old days, pre-Guice, I could just go to my 'application construction method' find the 5 relevant classes put them right next to each other in code...add comments/etc.  E.g. everything was all in one place so it was manageable to find what types are being created and switch things out.

Now post-Guice I have no centralized control of anything...as the module file doesn't say what is created it just says if 'you' find X use Y.  The real control is in each java class file's @Inject constructor where I add the  @Named("MyClass") annotation.

How can I achieve a more centralized control over the exact classes instantiated?


Christian Gruber

unread,
Sep 14, 2012, 10:16:24 AM9/14/12
to google...@googlegroups.com
I feel like I need a bit more clarity of what you're trying to do before commenting properly.  There are a few ways to think about what you describe here, but without a concrete example in code, it's hard to reason about what might be the best solution.  Can you create a simplified example in code and paste it here, so we can better help?
--
You received this message because you are subscribed to the Google Groups "google-guice" group.
To view this discussion on the web visit https://groups.google.com/d/msg/google-guice/-/PHofIXp_71AJ.
To post to this group, send email to google...@googlegroups.com.
To unsubscribe from this group, send email to google-guice...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/google-guice?hl=en.

Stuart McCulloch

unread,
Sep 14, 2012, 10:25:16 AM9/14/12
to google...@googlegroups.com
On 14 Sep 2012, at 15:16, Christian Gruber wrote:

I feel like I need a bit more clarity of what you're trying to do before commenting properly.  There are a few ways to think about what you describe here, but without a concrete example in code, it's hard to reason about what might be the best solution.  Can you create a simplified example in code and paste it here, so we can better help?

IMHO the real control should be in the module (ie. what gets bound to which key) and not in the concrete class - that should just declare what it needs (ie. its injected dependencies). If you have duplicate graphs of objects which only differ in price strategy then that sounds like the "robot-legs" problem: http://code.google.com/p/google-guice/wiki/FrequentlyAskedQuestions#How_do_I_build_two_similar_but_slightly_different_trees_of_objec

David Hoffer

unread,
Sep 14, 2012, 10:40:51 AM9/14/12
to google...@googlegroups.com
I'm quite new to Guice so perhaps I'm doing things wrong but I use this pattern a lot:

@Inject
public MyClass(@Named("LowPricingStrategyA") IPricingStrategyA pricingStrategyA,
                                        ICalculator calculator) {
        this.pricingStrategyA = pricingStrategyA ;
        this.calculator = calculator;
   }

then in the module:
protected void configure() {
        bind( IPricingStrategyA.class).annotatedWith(Names.named("LowPricingStrategyA")).to(LowPricingStrategyA.class);
        bind( IPricingStrategyA.class).annotatedWith(Names.named("HighPricingStrategyA")).to(HighPricingStrategyA.class);
        bind( ICalculator.class).to(APRCalculator.class);
...

Note that for IPricingStrategyA I have two possible bindings LowPricingStrategyA & HighPricingStrategyA, the module doesn't know/specify which will be used, that is determined by the @Named("LowPricingStrategyA") annotation in MyClass.  Now imagine the 'pricing strategy group' has 5 pairs of classes A, B, C, D & E...a High and a Low for each.

What I'm wondering is how to get the control of this centralized instead of in 5 different classes.

-Dave

Cédric Beust ♔

unread,
Sep 14, 2012, 11:46:14 AM9/14/12
to google...@googlegroups.com
How about using a factory as a way to group related implementations?

public class IPricingStrategyFactory {
  create1()
  create2()
}

bind(IPricingStrategyFactory.class).annotatedWith(LowPrices.class).to(LowPriceFactory.class);
bind(IPricingStrategyFactory.class).annotatedWith(HiPrices.class).to(HiPriceFactory.class);

@Inject
@HiPrices
IPricingStrategyFactory factory;

object1 = factory.create1();
object2 = factory.create2();

-- 
Cédric

David Hoffer

unread,
Sep 14, 2012, 12:20:27 PM9/14/12
to google...@googlegroups.com
I thought about that but I think that just moves the problem around.

This would only work/help in the case where only one class gets injected with the factory.  So all this would do is combine 5 injection annotations into one.  

In my case the problem is not so much that I don't like 5 annotations it's that those 5 annotations are in 5 different places/classes, it seems this approach wouldn't help that.  E.g. I would now have 5 places where I annotate to inject this factory instead.

-Dave

Jared Bunting

unread,
Sep 14, 2012, 12:02:07 PM9/14/12
to google...@googlegroups.com
I'm also not an expert, but one of my guiding rules is, like Stuart said, to keep decisions about which implementation to use at the Module level, not at the injected class level.  So in this case, I would probably do something like this:

@Inject
public MyClass(IPricingStrategyA pricingStrategyA,
                                        ICalculator calculator) {
        this.pricingStrategyA = pricingStrategyA ;
        this.calculator = calculator;
   }

then in the module:
protected void configure() {
        bind( IPricingStrategyA.class).to(LowPricingStrategyA.class);
        bind( ICalculator.class).to(APRCalculator.class);
...

If I have some clients that need the low strategy and some that need the high, I might do something like this:

@Inject
public CommercialSales(@Named("commercial") IPricingStrategyA pricingStrategyA,
                                        ICalculator calculator) {
        this.pricingStrategyA = pricingStrategyA ;
        this.calculator = calculator;
   }

@Inject
public FederalSales(@Named("federal") IPricingStrategyA pricingStrategyA,
                                        ICalculator calculator) {
        this.pricingStrategyA = pricingStrategyA ;
        this.calculator = calculator;
   }

then in the module:
protected void configure() {
        bind( IPricingStrategyA.class).annotatedWith(Names.named("commercial")).to(LowPricingStrategyA.class);
        bind( IPricingStrategyA.class).annotatedWith(Names.named("federal")).to(HighPricingStrategyA.class);
        bind( ICalculator.class).to(APRCalculator.class);
...

(although in reality I would probably use a custom annotation, rather than @Named, or at the very least, constants for the strings...but you get the point)

In both of these patterns, you can see that the decision about what implementation to use is all contained in the module, thus keeping it in the same location.

This could also be accomplished with the robot legs pattern, and there's probably some good arguments for doing it that way.

-Jared

David Hoffer

unread,
Sep 14, 2012, 12:57:59 PM9/14/12
to google...@googlegroups.com
I think that makes sense.  I like the guiding rule as you put it..."keep decisions about which implementation to use at the Module level, not at the injected class level."  

Just not sure I can in practice I know how to use Guice fully following that rule...as without class level annotations I don't know how you could ever use more than one concrete instance of an interface in any one application.  (That was my original use of annotations so I could have lots of IProcessor instances, for instance.  Now I have groups of instances that must be switched out as a group.)

So it seems that the @Named("xxx") annotation gets in the way of that rule.  Even in your case what determines what will be injected is the commercial/federal annotations.  

Regarding custom annotations...does that solve this?  I have not used them yet.  Also I'm not familiar with the robot legs pattern...I'll have to investigate that.

-Dave

Cédric Beust ♔

unread,
Sep 14, 2012, 1:02:22 PM9/14/12
to google...@googlegroups.com
On Fri, Sep 14, 2012 at 9:57 AM, David Hoffer <dhof...@gmail.com> wrote:
Regarding custom annotations...does that solve this?

No, they are just a more type safe way of annotating your injections. I prefer this approach over @Named as a general rule because I don't like to rely on strings, but it's not always possible (e.g. if you use your own injection marker annotation, which can't take a generic enum as a parameter, only a specific one).

-- 
Cédric

David Hoffer

unread,
Sep 15, 2012, 9:45:14 AM9/15/12
to google...@googlegroups.com
As I got into the requirements of this more...turns out its even harder than I thought.  It turns out different parts of the application will need different pricing strategies at the same time, e.g. different functionality will use different pricing strategies.

The good news is my app is 100% IoC so in the manual IoC way I would just new up a separate instance that has been injected with the new pricing scheme and all is good.  With Guice how do I do this?

In other words...I need two near top level object graphs...one injected with A, B & C and another injected with X, Y & Z.  Currently I just have one module file...is the solution to have two module files each defining the bindings for each object graph?

How does Guice work?  Once it creates an instance from ModuleA does it always get ALL its dependencies from that same ModuleA?  So what about common objects that could be shared between the modules?  Do I need 3 module files, one for the shared bindings?  How do I link ModuleA & ModuleB to ModuleShared?  Or maybe I just have duplicates of the shared bindings...that shouldn't hurt.  Perhaps this isn't how Guice works at all.

Guidance is greatly appreciated here.

-Dave

Jared Bunting

unread,
Sep 14, 2012, 2:31:29 PM9/14/12
to google...@googlegroups.com
Well, if you notice in my example, I didn't remove the annotations from the injected class.  I simple changed the annotation to indicate the role that was played by the class (federal vs commercial) rather than specifying the implementation.  Then the module maps from role -> implementation.

So, @Named("xxx") annotations don't get in the way of the rule, but what "xxx" actually is might get in the way.

As a quick example to help aid in understanding.  Say that we want to move commercial sales to the high pricing strategy.  All I would need to do is modify one line in the module (or maybe more if there are multiple classes involved in "high pricing strategy"):

        bind( IPricingStrategyA.class).annotatedWith(Names.named("commercial")).to(LowPricingStrategyA.class);
becomes
        bind( IPricingStrategyA.class).annotatedWith(Names.named("commercial")).to(HighPricingStrategyA.class);



-Jared

David Hoffer

unread,
Sep 23, 2012, 2:45:29 PM9/23/12
to google...@googlegroups.com
Sorry for the delay...got all jammed up.  Yes this does make sense.  I've refactored thins a bunch and don't use named annotations nearly as much.  I'll try to follow these rules you described.

Thanks,
-Dave
Reply all
Reply to author
Forward
0 new messages