Oliver Haase
Design Patterns
Dependency Injection
Motivation
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() { …
} }
Motivation
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();
}
Problem
How to remove MovieLister ’s dependency on
ColonDelimitedMovieFinder ?
4
Difference to DocManager example:
‣ MovieLister needs only one MovieFinder instance (service)
‣ DocManager must be able to create many Document objects
at will
Idea
Have the dependency (service) be injected by the client Inversion of Control
Hollywood Principle (“don’t call us, we’ll call you”)
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.
Types of Dependency Injection
There are 3 types of dependency injection:
1. constructor injection 2. setter injection
3. interface injection
6
Constructor Injection - Example
@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;
} }
Setter Injection - Example
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”);
Interface Injection - Example
Provider of MovieFinder interface also defines injection interface, e.g. :
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) {
Setter vs. Interface Injection
Only difference between setter and interface injection:
Whether interface provider defines companion injection interface that implementing class must use for injection, or not.
10
Interface Injection - Example
Usage:
MovieLister movieLister = new MovieLister();
movieLister.injectMovieFinder(new ColonDelimitedMovieFinder(“movies.txt”));
List<Movie> movies =
movieLister.moviesDirectedBy(“Quentin Tarantino”);
Dependency Injection Frameworks
‣ DI Frameworks separate out instantiation configuration, i.e.
bindings from abstract interfaces to concrete types.
‣ Configuration usually either in XML or in Java with annotations
‣ Wide-spread DI Frameworks:
• Apache Spring DI
• Google Guice
12
Guice: Constructor Injection
@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;
Guice: Constructor Injection
‣ @Inject annotation tells Guice to create and fill in appropriate MovieFinder instance when creating
MovieLister instance.
‣ Works only if bound type (e.g. ColonDelimitedMovieFinder )
• has zero-args non-private constructor, or
• uses itself constructor injection
14
Guice: Constructor Injection
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.
Guice: Modules
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");
} }
Guice: Instantiation
Instances are created by
‣ creating a Guice injector that uses a previously defined module
‣ having the injector create the application object(s)
Injector injector = Guice.createInjector(new MovieListerModule());
MovieLister lister = injector.getInstance(MovieLister.class);
Guice: Setter Injection
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;
} }
Guice: Setter Injection
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.
Guice: Setter Injection
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 vs. Setter Injection
Constructor Injection
‣ only valid and complete objects are created
‣ better chances for immutability Setter Injection
‣ can lead to unnecessarily mutable objects
‣ injection through easy-to-read methods
‣ necessary if dependencies are not available at creation
time, e.g. cyclic dependencies
DocManager Reloaded
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.
DocManager Reloaded
@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() {
DocManager Reloaded
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:
You Want More?
Read more about DI in Martin Fowler’s seminal online article
http://martinfowler.com/articles/injection.html
Service Locator
26
Motivation
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();
}
Problem & Idea
How to remove MovieLister ’s dependency on
ColonDelimitedMovieFinder ?
28
Pass a service locator into MovieLister that can be queried
for all kinds of services.
Example
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) { ...
But...
… (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.
Example
@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:
Service Locator vs. Abstract Factory
‣ both can create objects of different types (services vs.
products)
‣ product types belong to a product family, services unrelated with each other
‣ abstract factory creates many instances of a product type, service locator only one instance per service type
32
Builder
Motivating Example (J. Bloch)
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
...
}
Motivating Example (J. Bloch)
Question: How to create instances of NutritionFacts ?
Option 1: telescoping constructors
Option 1 - Telescoping C’tors
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);
}
Option 1 - Telescoping C’tors
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;
} }
Sample usage:
Second Try...
38
Question: How to create instances of NutritionFacts ?
Option 2: JavaBeans Pattern
Option 2 - JavaBeans Pattern
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;
}
Option 2 - JavaBeans Pattern
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:
Third Try...
Question: How to create instances of NutritionFacts ?
Option 3: constructor/setter combination
Option 3 - C’tor & Setters
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;
}
Option 3 - C’tor & Setters
public void setSodium(int sodium) { this.sodium = sodium;
}
public void setCarbs(int carbs) { carbs = carbs;
} }
Sample usage:
Comparison
Option 1
‣ only valid and complete objects are created
‣ preserves immutability
‣ hard to read
44
Option 2
‣ creation of incomplete, invalid objects
‣ loss of immutability
‣ easy to read
Comparison
Option 3
‣ only valid and complete objects are created
‣ easy to read
‣ loss of immutability
There is another option that combines the best of all three options!
Option 4 - Builder
46
Idea:
Define a builder that can
‣ be fed with a combination of NutritionFacts values, and
‣ then be used to create a NutritionFacts instance.
Option 3 — Builder
Idea:
Define a builder that can
be fed with a combination of NutritionFacts values, and then be used to create a NutritionFacts instance.
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);
Oliver Haase (HTWG Konstanz) Design Patterns 10 / 16
Option 3 — Builder
p u b l i c c l a s s N u t r i t i o n F a c t s {
p r i v a t e f i n a l i n t s e r v i n g S i z e ; // ( ml ) ma nda to ry p r i v a t e f i n a l i n t s e r v i n g s ; // ( p e r c o n t a i n e r ) ma nda to ry
p r i v a t e f i n a l i n t c a l o r i e s ; // o p t i o n a l
p r i v a t e f i n a l i n t f a t ; // ( g ) o p t i o n a l
p r i v a t e f i n a l i n t sodium ; // (mg) o p t i o n a l
p r i v a t e f i n a l i n t c a r b s ; // ( g ) o p t i o n a l
p u b l i c s t a t i c c l a s s B u i l d e r {
p r i v a t e f i n a l i n t s e r v i n g S i z e ; p r i v a t e f i n a l i n t s e r v i n g s ;
// o p t i o n a l params i n i t i a l i z e d t o d e f a u l t v a l u e s p r i v a t e i n t c a l o r i e s = 0 ;
p r i v a t e i n t f a t = 0 ;
p r i v a t e i n t sodium = 0 ; p r i v a t e i n t c a r b s = 0 ;
p u b l i c B u i l d e r ( i n t s e r v i n g S i z e , i n t s e r v i n g s ) { t h i s . s e r v i n g S i z e = s e r v i n g S i z e ;
t h i s . s e r v i n g s = s e r v i n g s ; }
Oliver Haase (HTWG Konstanz) Design Patterns 11 / 16
Option 4 - Builder
@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;
Option 4 - Builder
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);
} }
Option 4 - Builder
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);
Sample Usage:
Builder - Structure
50
Builder — Structure
Client
c'tor(Builder) use()
Product c'tor(<mandatory params>)
setOptionalParam1() setOptionalParam2() build()
Builder
return
new Product(self);
Oliver Haase (HTWG Konstanz) Design Patterns 14 / 16
Builder — Participants
Builder:
provides c’tor with all mandatory params initializes optional param to default values
provides set operation for each optional parameter
provides built operation that calls Product c’tor, passes in reference to itself
Product: provides c’tor that expects a Builder instance and copies all values into itself
Client:
creates Builder instance, thereby passes in all mandatory params uses set operations to set optional params
calls build operation to have Product instance created
Oliver Haase (HTWG Konstanz) Design Patterns 15 / 16
‣ provides c’tor with all mandatory params
‣ initializes optional params to default values
‣ provides set operation for each optional param
‣ provides build operation that calls Product’s c’tor, passes in reference to itself
provides c'tor that expects a Builder
instance and copies all values into itself
‣ creates Builder instance, thereby passes in all mandatory params
‣ uses set operations to set optional params
‣ calls build operation to have Product instance created
Pros & Cons
‣ Pro:
• creates only valid products
• easy to read
• preserves immutability
• configured builder can be used to create more than one product
builder object = abstract factory
‣ Con:
• more verbose implementation
Creational Patterns
Discussion
52
Overview
Prototype
Dependency Injection Abstract Factory
Service Locator
Builder
Similarities & Commonalities
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