dependency injection
TRANSCRIPT
Dependency Injection
Practice, Practice, Practice
A young concert violinist got lost on his way to a performance. He stoped an old man on the corner and asked him how to get to Carnige Hall. The old man looked at the violinist and the violin tucked under his arm, and said: Practice, son, Practice!
Theory-Practice: a positive feedback system
Why DI?
public class Emailer {
private SpellChecker spellChecker;
public Emailer() {
this.spellChecker = new SpellChecker();
}
public void send(String text) { .. }
}
How about testing?
public class MockSpellChecker extends SpellChecker {
private boolean didCheckSpelling = false;
public boolean checkSpelling(String text) {
didCheckSpelling = true;
return true;
}
public boolean verifyDidCheckSpelling(){
return didCheckSpelling;
}
}
And what about the Open-Closed Principle?
The Factory Pattern
ClientFactoryServicepublic class EmailerFactory {
/* The Factory is explicit about the object it produces...*/
public Emailer newItalianEmailer() {
return new Emailer(new ItalianSpellChecker());
}
}
Emailer service = new EmailerFactory().newItalianEmailer();
@Test
public void testEmailer() {
MockSpellChecker spellChecker = ...
Emailer emailer = new Emailer(spellChecker);
emailer.send("Fatte DI in ufficio!");
assert ...;
}
What about the client?
public class EmailerClient {private Emailer emailer = new EmailerFactory().newItalianEmailer();public void sendEmail() {emailer.send(createMessage());}}
public class EmailerFactory {private static Emailer instance; // did you see the problem here?public Emailer newEmailer() {if (null == instance)return new Emailer(..);return instance;}static void set(Emailer mock) {instance = mock;}}
@Testpublic void testEmailClient() {MockEmailer mock = new MockEmailer();EmailerFactory.set(mock);new EmailClient().sendEmail();assert mock.correctlySent();}
@Testpublic void testEmailClient() {MockEmailer mock = new MockEmailer();EmailerFactory.set(mock);try {new EmailClient().sendEmail();assert mock.correctlySent();} finally {EmailerFactory.set(null);}}
The Service Locator Pattern
Is a generalization of the Factory
Emailer emailer = (Emailer) new ServiceLocator().get("ItalianEmailer");
Emailer emailer = (Emailer) new ServiceLocator().get("JapaneseEmailerWithPhoneAndEmail");
(as a Factory) suffers from same problems: testability and shared state
Do you remember the ServiceManager anti-pattern?
DI
Hollywood Principle
Behaviorally focused
Modular
Separation of concerns, Demeter's law, Decoupling
TESTABLE
My main(String... args) is better than yours...
public class EmailerClient {
private final Emailer emailer;
public EmailerClient(Emailer emailer){
this.emailer = emailer;
}
public void sendEmail() {
emailer.send(createMessage());
}
...
}
public static void main(String... args) throws Exception { // by hand
(new ApplicationFactory(args).create()).start();
}
public static void main(String... args) throws Exception { // using Guice
Injector injector = Guice.createInjector(new ApplicationModule(args));
(injector.getInstance(Application.class)).start();
}
What have changed?
Client code (EmailerClient) doesn't invoke the injector (separation of concerns)
The root-object is explicit (Application.class)
The injector obtains instances within current execution context (scoping)
How to identify a dependency
Dependency:
Contract
Variants
public interface SpellChecker {
boolean check(String text);
}
class ItalianSpellChecker implements SpellChecker {
public boolean check(String text) {/* check italian */ ...}
}
class EnglishSpellChecker implements SpellChecker {
public boolean check(String text) {/* check english */ ...}
}
(new Emailer(new EnglishSpellChecker())).send(Hello);
(new Emailer(new ItalianSpellChecker())).send(Ciao);
KEY:
Unique
Arbitrary
Explicit
String Keys (Spring)
Pros
Unique
Arbitrary
Explicit
Cons
Error-prone
No compile-time safe
You can't use Vim but IntelliJ Idea
Must be carefully choosen
Examples:
Identifying by Type
Pros
Compile-time safe
Right direction
Cons
Not unique
Not arbitrary
Explicitness?
Inflexible
Examples:
SpellChecker.class identifies EnglishSpellChecker or ItalianSpellChecker
Emailer.class identifies itself
Combinatorial Keys (Guice)
Key = [contract, variant]
Key1: [Emailer.class, english]
Key2: [Emailer.class, italian]
Key3: [Emailer.class, English]
Or better:
Key1: [Emailer.class, English.class]
Key2: [Emailer.class, Italian.class]
@Retention( RUNTIME )
@Target( { PARAMETER } )
public @interface English {
...
}
public class Emailer{private final SpellChecker spellChecker;@Injectpublic SpellCheckerClient(@English SpellChecker spellChecker) {this.spellChecker = spellChecker;}}
public class SpellingModule extends AbstractModule {@Overrideprotected void configure() { bind(SpellChecker.class).annotatedWith(English.class).to(EnglishSpellChecker.class);}
Injection Idioms
Setter injection
public class Emailer {
private SpellChecker spellChecker;
public void setSpellChecker(SpellChecker spellChecker) {
this. spellChecker = spellChecker;
}
}
Interface Injection (Deprecated)
Method Decoration (via AOP)
Constructor Injection
public class Emailer {
private final SpellChecker spellChecker; // potentially immutable
public Emailer(SpellChecker spellChecker){
this.spellChecker = spellChecker; // freezing the object graph
}
}
Constructor vs Setter
Injection
Constructor
Clear contract
Immutability support or temporal encapsulation
Ready for use/ Object validity
No need for noisy setters
Setter
Esplicitness (type indipendent)
Doesn't suffers from the pyramid/telescoping problem
Doesn't suffers from the circular dependency and some in-construction problems
GOF's Creational Patterns
Abstract Factory
Factory Method
Builder
Prototype
Singleton
The Reinjection Problem
When a long-lived dependent needs a short-lived dependencies
public class Granny {
private Apple apple;
public Granny(Apple apple) {
this.apple = apple;
}
public void eat() {
apple.consume();
apple.consume();
}
}
/**Provides specific object and scoped intances of those objects.*/public interface Provider {T get();}
public class AppleProvider implements Provider{public Apple get(){return new Apple();}
}
public class Granny {private Provider appleProvider;
public Granny(Provider ap) {this.appleProvider = ap;}
public void eat() {appleProvider.get().consume();appleProvider.get().consume();}}
Or using the builder pattern..
public class Granny {private AppleBuilder builder;
public Granny(AppleBuilder b) {this.builder = b;}
public void eat() {builder.buildRedApple().consume();builder.buildGreenApple().consume();}}
public class AppleBuilder {public Apple buildRedApple() {return new RedApple();}
public Apple buildGreenApple() {return new GreenApple();}}
and its Jochua's version...
The Contextual Injection Problem
public class NewsletterManager {
private final List recipients;
private final AssistedProvider deliverer;
public NewsletterManager(List rs,AssistedProvider dp){
this.recipients = rs;
this.deliverer = dp;
}
public void send(Newsletter letter) {
for (Recipient recipient : recipients) {
Deliverer d = deliverer.get(letter);
d.deliverTo(recipient);
}
}
}
public interface AssistedProvider {
T get(C context);
}
public class DelivererProvider implements AssistedProvider {
public Deliverer get(Newsletter letter) {
return new Deliverer(letter);
}
}
Scope
Is a fixed duration of time or method calls in which an object exists
A context under which a given key refers to the same instance
Mostly used: Singleton and No-Scope
Interesting scopes: Transaction, Web, Cache, Thread, Grid, Custom...
Why Scope?
Applies the Hollywood Principle to the state of the objects
The injector manages the latent state of your objects
The injector ensures that the services get new instance of dependencies as needed
Implicity separates state by context
Reduces the necessity for state-aware application logic (separation of concerns infrastructure/business logic)
Defining a Custom Scope
Guice
public interface Scope {
Provider scope(Key key, Provider unscoped);
}
public class TransactionScope implements Scope {
private final ThreadLocal, Object>>();
public Provider scope(
final Key key, final Provider unscoped) {
return new Provider() {
public T get() {
Map