Skip to content

Scoped & Unscoped Bindings

Stéphane Nicolas edited this page May 16, 2016 · 27 revisions

In toothpick there are 2 kinds of bindings :

  • unscoped bindings
  • scoped bindings

In the 2 following sections, we will use a common example class and see how scoped and unscoped bindings differ when injecting them :

class A {
  @Inject IFoo foo1;
  @Inject IFoo foo2;
}

class Foo {
  @Inject Scope s;
}

Unscoped Bindings

bind(IFoo.class).to(Foo.class) is a simple association IFoo --> Foo. An unscoped binding expresses no constraints on the creation of the Foo instances, as opposed to a scope binding. An unscoped binding is said to belong to a given scope (in which it will be installed via a Module).

Basically, when a scope defines bind(IFoo.class).to(Foo.class), it means that in this scope and its children scopes : @Inject IFoo foo will return a Foo.

Examples of unscoped bindings:

The scope tree used in this example will be :

Scope s0 : Scope --> S0
  \
   \
  Scope S1 : Scope --> S1 & IFoo --> Foo
    \
     \
    Scope S2 : Scope --> S2

(Remember that the binding of class Scope is always overridden by all scopes.)

Then using the classes A & Foo & the scope tree defined above, we would have :

  • Toothpick.inject(new A(), S0) : There is no binding to inject IFoo as the binding is only defined in child scope S1. The injection of new A() in S0 will fail with an appropriate error message.
  • Toothpick.inject(new A(), S1) : The injection of new A() in S1 will result in the following :
  • a.foo1 will be an instance of Foo;
  • a.foo2 will be an instance of Foo, different from a.foo1;
  • a.foo1.scope & a.foo2.scope will be S1.
  • Toothpick.inject(new A(), S2) : The injection of new A() in S2 will result in the following :
  • a.foo1 will be an instance of Foo;
  • a.foo2 will be an instance of Foo, different from a.foo1;
  • a.foo1.scope & a.foo2.scope will be S2.

Scoped Bindings

A binding is scoped when we call its method scope(). bind(IFoo.class).to(Foo.class).scope() express more than the simple association IFoo --> Foo. A scoped bindings means :

  • there is an association IFoo --> Foo, in the same way as an unscoped binding does;
  • AND the same instance of Foo is recycled/reused for each injection of IFoo
  • AND the instance of Foo will be created inside the scope. All the dependencies of Foo will have to be found at runtime in the scope where the binding is scoped or in its parent scopes.

A scoped binding is said to be scoped in a given scope. We note scoped bindings IFoo --> (Foo) with the target of the binding in parenthesis.

Examples of scoped bindings:

The scope tree used in this example will be :

Scope s0 : Scope --> S0
  \
   \
  Scope S1 : Scope --> S1 & IFoo --> (Foo)  // <-- scoped binding
    \
     \
    Scope S2 : Scope --> S2

(Remember that the binding of class Scope is always overridden by all scopes.)

Then using the classes A & Foo & the scope tree defined above, we would have :

  • Toothpick.inject(new A(), S0) : There is no binding to inject IFoo as the binding is only defined in child scope S1. The injection of new A() in S0 will fail with an appropriate error message.
  • Toothpick.inject(new A(), S1) : The injection of new A() in S1 will result in the following :
  • a.foo1 will be an instance of Foo;
  • a.foo2 will be the same instance of Foo as a.foo1;
  • a.foo1.scope & a.foo2.scope will be S1.
  • Toothpick.inject(new A(), S2) : The injection of new A() in S2 will result in the following :
  • a.foo1 will be an instance of Foo;
  • a.foo2 will be the same instance of Foo as a.foo1, and the same instance as when A was injected in S1.
  • a.foo1.scope & a.foo2.scope will be S1.

The last line a.foo1.scope = a.foo2.scope = S1 is the most important.

Defining a scoped binding IFoo --> (Foo) means that Foo, the target of the injection, must fulfill all its dependencies in the scope where it is scoped or the parents of this scopes OR the dependencies should be unscoped.

//space of creation of Foo instances in the case of a scoped binding in S1.
+--------------------------------------------------------------------+
|    Scope s0 : Scope --> S0                                         |
|         \                                                          |
|          \                                                         |
|   Scope S1 : Scope --> S1 & IFoo --> (Foo)  // <-- scoped binding  |
|          \                                                         |
+-----------\--------------------------------------------------------+
             \
        Scope S2 : Scope --> S2

All the bindings defined in children scopes of S1 are not taken into account : Foo instances must exist, as well as all their transitive dependencies, in S0 & S1. And the instances of Foo created will be recycled in S1 and the scope below S1.

ToothPick will enforce this constraint and will check that a scoped binding doesn't require any dependency that is scoped in a children scope.

Which bindings can be scoped ?

In a module, not all bindings can be scoped. Only the bindings that imply that ToothPick will need a scope to create instances can be scoped.

bind(IFoo.class).to(Foo.class); // can be scoped
bind(IFoo.class).to(new Foo()); // cannot be scoped (it is indeed scoped)
bind(IFoo.class).toProvider(FooProvider.class); // can be scoped, will scope both the provider and produced instances
bind(IFoo.class).toProvider(new FooProvider()); // cannot be scoped (the provider is scoped)
bind(Foo.class); // can be scoped

Links

Clone this wiki locally