design patternshaase/lehre/patterns/... · constructor vs. setter injection constructor injection...
TRANSCRIPT
Oliver Haase
Design Patterns
1
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
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; }}
ProblemHow 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”)
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.
Types of Dependency Injection
There are 3 types of dependency injection:
1. constructor injection
2. setter injection
3. interface injection
6
Constructor Injection - Example
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”);
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 - ExampleProvider 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) { … }}
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 - ExampleUsage:
11
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
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; }}
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
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.
Guice: ModulesBindings 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: InstantiationInstances are created by
‣creating a Guice injector that uses a previously defined module
‣having the injector create the application object(s)
17
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
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.
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 InjectionConstructor Injection
‣ only valid and complete objects are created
‣ better chances for immutabilitySetter 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
21
Recommendation: Use setter injection only if necessary.
DocManager ReloadedHow 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
23
@ThreadSafe // assuming that concrete Document is threadsafepublic 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(); } }
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
25
Service Locator
26
Motivation
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; }}
Problem & IdeaHow to remove MovieLister’s dependency on ColonDelimitedMovieFinder?
28
⇒ Pass a service locator into MovieLister that can be queried
for all kinds of services.
Example
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) { ... }}
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
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:
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
33
Motivating Example (J. Bloch)
34
Suppose a class NutritionFacts that describes food items. A few specifications are mandatory, many are optional:
@Immutablepublic 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)
35
Question: How to create instances of NutritionFacts?
Option 1: telescoping constructors
Option 1 - Telescoping C’tors
36
@Immutablepublic 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
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:
Second Try...
38
Question: How to create instances of NutritionFacts?
Option 2: JavaBeans Pattern
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; }
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...
41
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
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:
ComparisonOption 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
ComparisonOption 3
‣ only valid and complete objects are created
‣ easy to read
‣ loss of immutability
45
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
pub l i c c l a s s Nu 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 ) � mandatory
p r i v a t e f i n a l i n t s e r v i n g s ; // ( pe r c o n t a i n e r ) � mandatory
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
pub l i c s t a t i c c l a s s Bu 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 to 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 ;
pub l i c Bu 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
47
@Immutablepublic 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; }
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
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:
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 referenceto itself
Product: provides c’tor that expects a Builder instance and copiesall 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 inreference 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
51
‣ 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
• more verbose usage than telescoping c'tors
• additional object needed to create product
⇒ additional runtime and memory cost
Creational Patterns Discussion
52
Overview
53
Prototype
DependencyInjection
Factory Method
Abstract Factory
ServiceLocator
Builder
Singleton
Similarities & Commonalities
54
Prototype
DependencyInjection
Factory Method
Abstract Factory
ServiceLocator
Builder
Singleton
: patterns that use a dedicated object to create new objects
A B : A can be used to feed B into client code