Revision: 53d2378a94e9
Author: cgruber <[email protected]>
Date: Thu Jul 25 09:46:26 2013
Log: Created wiki page through web user interface. Credit to Nicholas
Glorioso for the text (with minor edits)
http://code.google.com/p/google-guice/source/detail?r=53d2378a94e9&repo=wiki
Added:
/CyclicDependencies.wiki
=======================================
--- /dev/null
+++ /CyclicDependencies.wiki Thu Jul 25 09:46:26 2013
@@ -0,0 +1,183 @@
+#summary Resolving cyclic dependencies
+
+
+= Resolving Cyclic Dependencies =
+
+Say that your application has a few classes including a Store, a Boss, and
a Clerk.
+
+{{{
+public class Store {
+ private final Boss boss;
+ //...
+
+ @Inject public Store(Boss boss) {
+ this.boss = boss;
+ //...
+ }
+
+ public void incomingCustomer(Customer customer) {...}
+ public Customer getNextCustomer() {...}
+}
+
+public class Boss {
+ private final Clerk Clerk;
+ @Inject public Boss(Clerk Clerk) {
+ this.Clerk = Clerk;
+ }
+}
+
+public class Clerk {
+ // Nothing interesting here
+}
+}}}
+
+Right now, the dependency chain is all good: constructing a Store results
in constructing a Boss, which results in constructing a Clerk. However, to
get the Clerk to get a Customer to do his selling, he'll need a reference
to the Store to get those customer//...
+
+{{{
+public class Store {
+ private final Boss boss;
+ //...
+
+ @Inject public Store(Boss boss) {
+ this.boss = boss;
+ //...
+ }
+ public void incomingCustomer(Customer customer) {...}
+ public Customer getNextCustomer() {...}
+}
+
+public class Boss {
+ private final Clerk Clerk;
+ @Inject public Boss(Clerk Clerk) {
+ this.Clerk = Clerk;
+ }
+}
+
+public class Clerk {
+ private final Store shop;
+ @Inject Clerk(Store shop) {
+ this.shop = shop;
+ }
+
+ void doSale() {
+ Customer sucker = shop.getNextCustomer();
+ //...
+ }
+}
+}}}
+
+which leads to a cycle: Clerk -> Store -> Boss -> Clerk. In trying to
construct a Clerk, an Store will be constructed, which needs a Boss, which
needs a Clerk again!
+
+There are a few ways to resolve this cycle:
+
+== Eliminate the cycle (Recommended) ==
+
+Cycles often reflect insufficiently granular decomposition. To eliminate
such cycles, extract the Dependency Case into a separate class.
+
+In this example, the work of managing the incoming customers can be
extracted into another class, say {{{CustomerLine}}}, and that can be
injected into the Clerk and Store.
+
+{{{
+public class Store {
+ private final Boss boss;
+ private final CustomerLine line;
+ //...
+
+ @Inject public Store(Boss boss, CustomerLine line) {
+ this.boss = boss;
+ this.line = line;
+ //...
+ }
+
+ public void incomingCustomer(Customer customer) { line.add(customer); }
+}
+//...
+public class Clerk {
+ private final CustomerLine line;
+
+ @Inject Clerk(CustomerLine line) {
+ this.line = line;
+ }
+
+ void doSale() {
+ Customer sucker = line.getNextCustomer();
+ //...
+ }
+}
+}}}
+
+While both Store and Clerk dependend on the CustomerLine, there's no cycle
in the dependency graph (although you may want to make sure that the Store
and Clerk both use the same !CustomerLine instance). This also means that
your Clerk will be able to sell cars when your shop has a big tent sale:
just inject a different !CustomerLine.
+
+== Break the cycle with a Provider ==
+
+[InjectingProviders Injecting a Guice provider] will allow you to add a
_seam_ in the dependency graph. The Clerk will still depend on the Store,
but the Clerk doesn't look at the Store until he needs one.
+{{{
+//...
+public class Clerk {
+ private final Provider<Store> shopProvider;
+ @Inject Clerk(Provider<Store> shopProvider) {
+ this.shopProvider = shopProvider;
+ }
+
+ void doSale() {
+ Customer sucker = shopProvider.get().getNextCustomer();
+ //...
+ }
+}
+}}}
+
+Note here, that unless Store is bound as a Singleton or in some other
scope to be reused, the shopProvider.get() call will end up constructing a
new Store, which will construct a new Boss, which will construct a new
Clerk again!
+
+== Factory Methods to tie two objects together ==
+
+When your dependencies are tied together a bit closer, untangling them
with the above methods don't work. Situations like this come up when using
something like a View/Presenter paradigm:
+
+{{{
+public class FooPresenter {
+ @Inject public FooPresenter(FooView view) {
+ //...
+ }
+
+ public void doSomething() {
+ view.doSomethingCool();
+ }
+}
+public class FooView {
+ @Inject public FooView(FooPresenter presenter) {
+ //...
+ }
+
+ public void userDidSomething() {
+ presenter.theyDidSomething();
+ }
+ //...
+}
+}}}
+
+Each of those objects needs the other object. Here, you can use
AssistedInject to get around it:
+
+{{{
+public class FooPresenter {
+ privat final FooView view;
+ @Inject public FooPresenter(FooView.Factory viewMaker) {
+ view = viewMaker.create(this);
+ }
+
+ public void doSomething() {
+ //...
+ view.doSomethingCool();
+ }
+}
+public class FooView {
+ @Inject public FooView(@Assisted FooPresenter presenter) {...}
+
+ public void userDidSomething() {
+ presenter.theyDidSomething();
+ }
+
+ public static interface Factory {
+ FooView create(FooPresenter presenter)
+ }
+}
+}}}
+
+Such situations also come up when attempting to use Guice to manifest
business object models, which may have cycles that reflect different types
of relationships. AssistedInject is also quite good for such cases.
--
You received this message because you are subscribed to the Google Groups
"google-guice-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/google-guice-dev.
For more options, visit https://groups.google.com/groups/opt_out.