Oliver Haase
Design Patterns
1
Design Patterns Dependency Injection Oliver Haase 1 Motivation A - - PowerPoint PPT Presentation
Design Patterns Dependency Injection Oliver Haase 1 Motivation A simple, motivating example (by Martin Fowler): public interface MovieFinder { /** * returns all movies of this finders source * @return all movies */ List<Movie>
Oliver Haase
1
2
A simple, motivating example (by Martin Fowler):
public interface MovieFinder { /** * returns all movies of this finder’s source * @return all movies */ List<Movie> findAll(); } public class ColonDelimitedMovieFinder implements MovieFinder { private final String fileName; public ColonDelimitedMovieFinder(String fileName) { this.fileName = fileName; /** * returns all movies listed in file <code>fileName</code> */ @Override List<Movie> findAll() { … } }
3
A simple, motivating example (by Martin Fowler):
@ThreadSafe //assuming that ColonDelimitedMovieFinder is threadsafe public class MovieLister { private final MovieFinder finder; public MovieLister() { finder = new ColonDelimitedMovieFinder(“movies.txt”); } public List<Movie> moviesDirectedBy(String arg) { List<Movie> movies = finder.findAll(); for ( Iterator<Movie> it = movies.iterator(); it.hasNext(); ) { Movie movie = it.next(); if ( !movie.getDirector().equals(arg) ) { it.remove(); } } return movies; } }
How to remove MovieLister’s dependency on
ColonDelimitedMovieFinder?
4
Difference to DocManager example:
(service)
at will
Have the dependency (service) be injected by the client ⇒ Inversion of Control ⇒ Hollywood Principle (“don’t call us, we’ll call you”)
5
Please note: DI is first and foremost a design pattern that can be implemented by hand. DI is, however, often equated with DI frameworks, e.g. Spring DI, Google Guice.
There are 3 types of dependency injection:
6
7
@ThreadSafe //assuming that finder instance is threadsafe public class MovieLister { private final MovieFinder finder; public MovieLister(MovieFinder finder) { this.finder = finder; } public List<Movie> moviesDirectedBy(String arg) { List<Movie> movies = finder.findAll(); for ( Iterator<Movie> it = movies.iterator(); it.hasNext(); ) { Movie movie = it.next(); if ( !movie.getDirector().equals(arg) ) { it.remove(); } } return movies; } }
Usage:
MovieLister movieLister = new MovieLister(new ColonDelimitedMovieFinder(“movies.txt”)); List<Movie> movies = movieLister.moviesDirectedBy(“Quentin Tarantino”);
8
@ThreadSafe //assuming that finder instance is threadsafe public class MovieLister { private MovieFinder finder; public MovieLister() {} public void setFinder(MovieFinder finder) { this.finder = finder; } public List<Movie> moviesDirectedBy(String arg) { List<Movie> movies = finder.findAll(); for ( Iterator<Movie> it = movies.iterator(); it.hasNext(); ) { Movie movie = it.next(); if ( !movie.getDirector().equals(arg) ) { it.remove(); } } return movies; } }
Usage:
MovieLister movieLister = new MovieLister(); movieLister.setFinder(new ColonDelimitedMovieFinder(“movies.txt”)); List<Movie> movies = movieLister.moviesDirectedBy(“Quentin Tarantino”);
Provider of MovieFinder interface also defines injection interface, e.g. :
9
public interface MovieFinderInjector { void injectMovieFinder(MovieFinder finder); }
Each class that needs to get a MovieFinder injected has to implement injector interface:
@ThreadSafe //assuming that finder instance is threadsafe public class MovieLister implements MovieFinderInjector { private MovieFinder finder; public MovieLister() {} @Override public void injectMovieFinder(MovieFinder finder) { this.finder = finder; } public List<Movie> moviesDirectedBy(String arg) { … } }
Only difference between setter and interface injection: ⇒ Whether interface provider defines companion injection interface that implementing class must use for injection, or not.
10
Usage:
11
MovieLister movieLister = new MovieLister(); movieLister.injectMovieFinder(new ColonDelimitedMovieFinder(“movies.txt”)); List<Movie> movies = movieLister.moviesDirectedBy(“Quentin Tarantino”);
bindings from abstract interfaces to concrete types.
annotations
12
13 @ThreadSafe //assuming that finder instance is threadsafe public class MovieLister { private final MovieFinder finder; @Inject public MovieLister(MovieFinder finder) { this.finder = finder; } public List<Movie> moviesDirectedBy(String arg) { List<Movie> movies = finder.findAll(); for ( Iterator<Movie> it = movies.iterator(); it.hasNext(); ) { Movie movie = it.next(); if ( !movie.getDirector().equals(arg) ) { it.remove(); } } return movies; } }
appropriate MovieFinder instance when creating
MovieLister instance.
14
15 public class ColonDelimitedMovieFinder implements MovieFinder { private final String fileName; @Inject public ColonDelimitedMovieFinder(@Named("FILE NAME") String fileName) { this.fileName = fileName; } ... }
@Named annotation needed for instance binding, .i.e. binding of a
type to an instance of that type.
Bindings are defined in modules, i.e. Java classes that inherit from com.google.inject.AbstractModule and whose
configure method contains the bindings:
16 public class MovieListerModule extends AbstractModule { @Override protected void configure() { bind(MovieFinder.class).to(ColonDelimitedMovieFinder.class); bind(String.class).annotatedWith(Names.named("FILE NAME")) .toInstance("movies.txt"); } }
Instances are created by
module
17 Injector injector = Guice.createInjector(new MovieListerModule()); MovieLister lister = injector.getInstance(MovieLister.class);
18 @ThreadSafe //assuming that finder instance is threadsafe public class MovieLister { private MovieFinder finder; @Inject public void setFinder(MovieFinder finder) { this.finder = finder; } public List<Movie> moviesDirectedBy(String arg) { List<Movie> movies = finder.findAll(); for ( Iterator<Movie> it = movies.iterator(); it.hasNext(); ) { Movie movie = it.next(); if ( !movie.getDirector().equals(arg) ) { it.remove(); } } return movies; } }
19 public class ColonDelimitedMovieFinder implements MovieFinder { private String fileName; @Inject public void setFileName(@Named("FILE NAME") String fileName) { this.fileName = fileName; } @Override public List<Movie> findAll() { ... } }
MovieListerModule remains the same as before, because
mappings also remain the same.
20
Object instantiation can also remain the same ⇒ Guice automatically calls setter methods to inject necessary dependencies.
Injector injector = Guice.createInjector(new MovieListerModule()); MovieLister lister = injector.getInstance(MovieLister.class);
Or, objects can be instantiated as usually, and then Guice can fill in dependencies using setter methods:
Injector injector = Guice.createInjector(new MovieListerModule()); MovieLister lister = new MovieLister(); injector.injectMembers(lister);
Constructor Injection
Setter Injection
time, e.g. cyclic dependencies
21
Recommendation: Use setter injection only if necessary.
How to apply DI pattern to DocManager example?
22
⇒ Inject concrete DocumentFactory as a service into
DocManager
So ... … if client knows when to create objects, but doesn’t know (neither care) how, then ... … inject client with factory that can be used to get instances as needed.
23 @ThreadSafe // assuming that concrete Document is threadsafe public class DocManager { private final Collection<Document> docs; private final DocumentFactory docFactory; @Inject public DocManager(DocumentFactory docFactory) { this.docFactory = docFactory; docs = new ConcurrentLinkedQueue<Document>(); } public void createDoc() { Document doc = docFactory.newDocument(); docs.add(doc); doc.open(); } public void openDocs() { for ( Document doc : docs ) doc.open(); } }
24 public class DocManagerModule extends AbstractModule { @Override protected void configure() { bind(DocumentFactory.class).to(LatexDocFactory.class); } } Injector injector = Guice.createInjector(new DocManagerModule()); DocManager docManager = injector.getInstance(DocManager.class);
Usage: Sample Bindings:
Read more about DI in Martin Fowler’s seminal online article http://martinfowler.com/articles/injection.html
25
26
27
Back to Martin Fowler’s DI example:
@ThreadSafe //assuming that ColonDelimitedMovieFinder is threadsafe public class MovieLister { private final MovieFinder finder; public MovieLister() { finder = new ColonDelimitedMovieFinder(“movies.txt”); } public List<Movie> moviesDirectedBy(String arg) { List<Movie> movies = finder.findAll(); for ( Iterator<Movie> it = movies.iterator(); it.hasNext(); ) { Movie movie = it.next(); if ( !movie.getDirector().equals(arg) ) { it.remove(); } } return movies; } }
How to remove MovieLister’s dependency on
ColonDelimitedMovieFinder?
28
⇒ Pass a service locator into MovieLister that can be queried for all kinds of services.
29 public interface ServiceLocator { MovieFinder getMovieFinder(String fileName); ... } @ThreadSafe //assuming that ColonDelimitedMovieFinder is threadsafe public class MovieLister { private final MovieFinder finder; private final ServiceLocator serviceLocator; public MovieLister(ServiceLocator serviceLocator) { finder = serviceLocator.getMovieFinder(“movies.txt”); } public List<Movie> moviesDirectedBy(String arg) { ... } }
… (a) there’s now a dependency on the service locator... ⇒ yes, but only on one object for all services.
30
…(b) how does service locator get into MovieLister? ⇒ e.g. with dependency injection.
31 @ThreadSafe //assuming that ColonDelimitedMovieFinder is threadsafe public class MovieLister { private final MovieFinder finder; private final ServiceLocator serviceLocator; @Inject public MovieLister(ServiceLocator serviceLocator) { finder = serviceLocator.getMovieFinder(“movies.txt”); } public List<Movie> moviesDirectedBy(String arg) { ... } }
Using Guice dependency injection:
products)
with each other
service locator only one instance per service type
32
33
34
Suppose a class NutritionFacts that describes food items. A few specifications are mandatory, many are optional:
@Immutable public class NutritionFacts { private final int servingSize; // (ml) - mandatory private final int servings; // (per container) - mandatory private final int calories; // - optional private final int fat; // (g) - optional private final int sodium; // (mg) - optional private final int carbs; // (g) - optional ... }
35
Question: How to create instances of NutritionFacts? Option 1: telescoping constructors
36 @Immutable public class NutritionFacts { private final int servingSize; // (ml) - mandatory private final int servings; // (per container) - mandatory private final int calories; // - optional private final int fat; // (g) - optional private final int sodium; // (mg) - optional private final int carbs; // (g) - optional public NutritionFacts(int servingSize, int servings) { this(servingSize, servings, 0); } public NutritionFacts(int servingSize, int servings, int calories) { this(servingSize, servings, calories, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat) { this(servingSize, servings, calories, fat, 0); }
37 public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) { this(servingSize, servings, calories, fat, sodium, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbs) { this.servingSize = servingSize; this.servings = servings; this.calories = calories; this.fat = fat; this.sodium = sodium; this.carbs = carbs; } } NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);
Sample usage:
38
Question: How to create instances of NutritionFacts? Option 2: JavaBeans Pattern
39 public class NutritionFacts { private int servingSize = -1; // (ml) - mandatory private int servings = -1; // (per container) - mandatory private int calories = 0; // - optional private int fat = 0; // (g) - optional private int sodium = 0; // (mg) - optional private int carbs = 0; // (g) - optional public NutritionFacts() {} public void setServingSize(int servingSize) { this.servingSize = servingSize; } public void setServings(int servings) { this.servings = servings; } public void setCalories(int calories) { this.calories = calories; }
40 public void setFat(int fat) { this.fat = fat; } public void setSodium(int sodium) { this.sodium = sodium; } public void setCarbs(int carbs) { carbs = carbs; } } NutritionFacts cocaCola = new NutritionFacts(); cocaCola.setServingSize(240); cocaCola.setServings(8); cocaCola.setCalories(100); cocaCola.setSodium(35); cocaCola.setCarbs(27);
Sample usage:
41
Question: How to create instances of NutritionFacts? Option 3: constructor/setter combination
42 public class NutritionFacts { private final int servingSize; // (ml) - mandatory private final int servings; // (per container) - mandatory private int calories = 0; // - optional private int fat = 0; // (g) - optional private int sodium = 0; // (mg) - optional private int carbs = 0; // (g) - optional public NutritionFacts(int servingSize, int servings) { this.servingSize = servingSize; this.servings = servings; } public void setCalories(int calories) { this.calories = calories; } public void setFat(int fat) { this.fat = fat; }
43 public void setSodium(int sodium) { this.sodium = sodium; } public void setCarbs(int carbs) { carbs = carbs; } } NutritionFacts cocaCola = new NutritionFacts(240, 8); cocaCola.setCalories(100); cocaCola.setSodium(35); cocaCola.setCarbs(27);
Sample usage:
Option 1
44
Option 2
Option 3
45
There is another option that combines the best of all three options!
46
Idea: Define a builder that can
Client c'tor(NutritionFactsBuilder) NutritionFacts c'tor(servingSize, servings) setCalories(int calories) setFat(int Fat) setSodium(int sodium) setCarbs(int carbs) build() NutritionFactsBuilder return new NutritionFacts(self);
47 @Immutable public class NutritionFacts { private final int servingSize; // (ml) - mandatory private final int servings; // (per container) - mandatory private final int calories; // - optional private final int fat; // (g) - optional private final int sodium; // (mg) - optional private final int carbs; // (g) - optional public static class Builder { private final int servingSize; private final int servings; // optional params initialized to default values private int calories = 0; private int fat = 0; private int sodium = 0; private int carbs = 0; public Builder(int servingSize, int servings) { this.servingSize = servingSize; this.servings = servings; }
48 public void setCalories(int calories) { this.calories = calories; } public void setFat(int fat) { this.fat = fat; } public void setSodium(int sodium) { this.sodium = sodium; } public void setCarbs(int carbs) { this.carbs = carbs; } public NutritionFacts build() { return new NutritionFacts(this); } }
49 private NutritionFacts(Builder builder) { servingSize = builder.servingSize; servings = builder.servings; calories = builder.calories; fat = builder.fat; sodium = builder.sodium; carbs = builder.carbs; } } NutritionFacts.Builder cocaColaBuilder = new NutritionFacts.Builder(240, 8); cocaColaBuilder.setCalories(100); cocaColaBuilder.setSodium(35); cocaColaBuilder.setCarbs(27); NutritionFacts cocaCola = cocaColaBuilder.build();
Sample Usage:
50
Client c'tor(Builder) use() Product c'tor(<mandatory params>) setOptionalParam1() setOptionalParam2() build() Builder return new Product(self);
reference to itself provides c'tor that expects a Builder instance and copies all values into itself
51
product
⇒ builder object = abstract factory
⇒ additional runtime and memory cost
52
53 Prototype Dependency Injection Factory Method Abstract Factory Service Locator Builder Singleton
54 Prototype Dependency Injection Factory Method Abstract Factory Service Locator Builder Singleton : patterns that use a dedicated object to create new objects A B : A can be used to feed B into client code