-
Notifications
You must be signed in to change notification settings - Fork 113
Modules & Bindings
Modules define bindings.
The class toothpick.config.Module defines a small DSL to conveniently express bindings, and bindings can be expressed in various ways :
class SimpleModule extends Module {
SimpleModule() {
bind(IFoo.class).to(Foo.class); // case 1
bind(IFoo.class).to(new Foo()); // case 2
bind(IFoo.class).toProvider(FooProvider.class); // case 3
bind(IFoo.class).toProvider(new FooProvider()); // case 4
bind(Foo.class); // case 5
}
}
The various binding modes are :
- case 1: Every
@Inject IFoowill be assigned a new instance ofFoo. - case 2: Every
@Inject IFoowill be assigned the same instance ofFoo. The instance defined in the module. - case 3: Every
@Inject IFoowill be assigned a new instance ofFooproduced by a new instance ofFooProvider. - case 4: Every
@Inject IFoowill be assigned a new instance ofFooproduced by the same instance ofFooProvider. The instance defined in the module. - case 5 : Every
@Inject Foowill be assigned a new instance ofFoo.
In the example above, in the cases 1, 3 and 5, ToothPick is responsible for creating the instances during injection. Whereas in cases 2 and 4, the developer herself is creating the instances of Foo, either directly or via a provider that she defines.
A good rule to remember is :
As soon as ToothPick creates an object, it will be injected.
which also implies that :
As soon as a developer creates an object with ToothPick, the developer has to take care of injecting this object dependencies.
This means that if we define the class :
class Foo {
@Inject Scope s;
}
In the cases 1, 3 and 5, all instances of Foo that are created during injection by ToothPick will themselves be injected. All injected methods and fields will be called/assigned and their annotated constructor or the default constructor will be used to create the instances of Foo.
In the case 2 and 4, the developer will have to assign the field s to a scope, the developer can do it manually or by asking ToothPick to do it : ToothPick.inject( foo, s ).
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 it :
class A {
@Inject IFoo foo1;
@Inject IFoo foo2;
@Inject IBar bar1;
@Inject IBar bar2;
}
bind(IFoo.class).to(Foo.class) is a simple association IFoo --> Foo. Unscoped bindings are inherited by children scopes and can be [overridden in children scopes](#Binding overrides in children scopes). 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.
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.
Suppose we define the following scopes :
Scope s0
\
\
Scope S1 : IFoo --> new Foo & IBar --> Bar
\
\
Scope S2
Then using the class A defined above, we would have :
-
Toothpick.inject(new A(), S0): There is no binding to inject eitherIFooorIBaras the bindings are only defined in children scope. The injection ofnew A()inS0will fail with an appropriate error message. -
Toothpick.inject(new A(), S1): The injection ofnew A()inS1will result in the following : -
a.foo1will be an instance ofFoo(the instance created during the binding tonew Foo(); -
a.foo2will be the same instance ofFoobecause we have boundIFooto a single instance ofFoo; -
a.bar1anda.bar2will be both be instances ofBar.class, and will be different from each other. -
Toothpick.inject(new A(), S2): The injection ofnew A()inS2will result in the exact same thing as inS1above. -
a.foo1will be an instance ofFoo(the instance created during the binding tonew Foo(); -
a.foo2will be the same instance ofFoobecause we have boundIFooto a single instance ofFoo; -
a.bar1anda.bar2will be both be instances ofBar.class, and will be different from each other.
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
Foois recycled/reused for each injection ofIFoo - AND the instance of
Foowill be created inside the scope. All the dependencies ofFoowill 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.