I (think I) understand the purpose of dependency injection, but I'm just not getting why I need something like Guice to do it (well, obviously I don't need Guice, but I mean why it would be beneficial to use it). Let's say I have existing (non-Guice) code that's something like this:
public SomeBarFooerImplementation(Foo foo, Bar bar) { this.foo = foo; this.bar = bar; } public void fooThatBar() { foo.fooify(bar); }
And somewhere higher level, maybe in my main(), I have:
public static void main(String[] args) { Foo foo = new SomeFooImplementation(); Bar bar = new SomeBarImplementation(); BarFooer barFooer = new SomeBarFooerImplementation(foo, bar); barFooer.fooThatBar(); }
Now I've basically got the benefits of dependency injection, right? Easier testability and so forth? Of course if you want you could easily change the main() to get implementation classnames from configuration instead of hardcoding, too.
As I understand it, to do the same in Guice, I'd do something like:
public SomeModule extends AbstractModule { @Override protected void configure() { bind(Foo.class).to(SomeFooImplementation.class); bind(Bar.class).to(SomeBarImplementation.class); bind(BarFooer.class).to(SomeBarFooerImplementation.class); } } @Inject public SomeBarFooerImplementation(Foo foo, Bar, bar) { this.foo = foo; this.bar = bar; } public static void main(String[] args) { Injector injector = Guice.createInjector(new SomeModule()); Foo foo = injector.getInstance(Foo.class); barFooer.fooThatBar(); }
Is that it? It seems to me like just syntactic sugar, and not particularly helpful syntactic sugar. And if there's some advantage to breaking the "new XxxImplementation()" stuff out into a separate module rather than doing it directly in main(), that's easily done without Guice anyway.
So I get the feeling that I'm missing something very basic. Could you please explain to me how the Guice way is advantageous?
Thanks in advance.
Guice solves the problem of having two different String objects you want to inject through "named annotations". You can bind these manually in your module, but there are libraries which will pull these from your configuration that make use of Names.
Spring allows you to omit the @Autowired annotation when there's only one constructor. Guice allows binding to a Provider, as well as injecting a Provider of your class, even when your class has no Provider binding.
install allows for composition: Within its configure method, FooModule may install FooServiceModule (for instance). This would mean that an Injector created based only on FooModule will include bindings and providers in both FooModule and FooServiceModule.
Overview of bindings in Guice. A binding is an object that corresponds to an entry in the Guice map. You add new entries into the Guice map by creating bindings.
In real life and bigger applications the level of components is not just 2 (as in your example). It may be 10 or more. Also requirements and dependencies can change at a whim.
When doing "DI by hand" this means, that you must change all paths to all components and their transitive dependencies by hand. That might just be quite some work.
With "real DI" (i.e. Guice, Spring, CDI) you just change one or two affected components and the wiring (the Module
on Guice) and that's it.
Also: Imagine that some of your deeply nested components are in factories which should be called by other components. In Guice you just inject a Provider<Foo>
and that's it. When doing it by hand you must drag the dependencies of the factories and their instantiations throughout your code.
That's even more frustrating.
Edit To clarify the last point: Image you have some deeply nested logic (10 or more levels). At the bottom your logic shall create a factory based on some value e.g. the actual temperature: if "warm" then a Cooler
is wanted, if "cold" then a Heater
. Both have the interface TemperatureAdjustor
. Both Cooler
and Heater
need a lot of different dependencies to get their job done.
Since the instance type of the factor you cannot create one in main
and hand it down to where it is needed. So you end up handing the dependencies of both factories down to the location where they are needed. ( "a lot dependencies" plus "a lot of dependencies" is "too many to stay sane")
With "real DI" like Guice you can use AssistedInject
to avoid the clutter of dependencies and give only the actual business value (here: temperature) to the factory and be done. The wiring of the factory is done in the module.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With