Design Patterns Dependency Injection Oliver Haase 1 Motivation A - - PowerPoint PPT Presentation

design patterns
SMART_READER_LITE
LIVE PREVIEW

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>


slide-1
SLIDE 1

Oliver Haase

Design Patterns

1

Dependency Injection

slide-2
SLIDE 2

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() { … } }

slide-3
SLIDE 3

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; } }

slide-4
SLIDE 4

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

slide-5
SLIDE 5

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.

slide-6
SLIDE 6

Types of Dependency Injection

There are 3 types of dependency injection:

  • 1. constructor injection
  • 2. setter injection
  • 3. interface injection

6

slide-7
SLIDE 7

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”);

slide-8
SLIDE 8

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”);

slide-9
SLIDE 9

Interface Injection - Example

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) { … } }

slide-10
SLIDE 10

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

slide-11
SLIDE 11

Interface Injection - Example

Usage:

11

MovieLister movieLister = new MovieLister(); movieLister.injectMovieFinder(new ColonDelimitedMovieFinder(“movies.txt”)); List<Movie> movies = movieLister.moviesDirectedBy(“Quentin Tarantino”);

slide-12
SLIDE 12

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

slide-13
SLIDE 13

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; } }

slide-14
SLIDE 14

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

slide-15
SLIDE 15

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.

slide-16
SLIDE 16

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"); } }

slide-17
SLIDE 17

Guice: Instantiation

Instances 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);

slide-18
SLIDE 18

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; } }

slide-19
SLIDE 19

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.

slide-20
SLIDE 20

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);

slide-21
SLIDE 21

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

21

Recommendation: Use setter injection only if necessary.

slide-22
SLIDE 22

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.

slide-23
SLIDE 23

DocManager Reloaded

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(); } }

slide-24
SLIDE 24

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:

slide-25
SLIDE 25

You Want More?

Read more about DI in Martin Fowler’s seminal online article http://martinfowler.com/articles/injection.html

25

slide-26
SLIDE 26

Service Locator

26

slide-27
SLIDE 27

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; } }

slide-28
SLIDE 28

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.

slide-29
SLIDE 29

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) { ... } }

slide-30
SLIDE 30

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.

slide-31
SLIDE 31

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:

slide-32
SLIDE 32

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

slide-33
SLIDE 33

Builder

33

slide-34
SLIDE 34

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 ... }

slide-35
SLIDE 35

Motivating Example (J. Bloch)

35

Question: How to create instances of NutritionFacts? Option 1: telescoping constructors

slide-36
SLIDE 36

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); }

slide-37
SLIDE 37

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:

slide-38
SLIDE 38

Second Try...

38

Question: How to create instances of NutritionFacts? Option 2: JavaBeans Pattern

slide-39
SLIDE 39

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; }

slide-40
SLIDE 40

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:

slide-41
SLIDE 41

Third Try...

41

Question: How to create instances of NutritionFacts? Option 3: constructor/setter combination

slide-42
SLIDE 42

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; }

slide-43
SLIDE 43

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:

slide-44
SLIDE 44

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
slide-45
SLIDE 45

Comparison

Option 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!

slide-46
SLIDE 46

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.

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);

slide-47
SLIDE 47

Option 4 - Builder

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; }

slide-48
SLIDE 48

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); } }

slide-49
SLIDE 49

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:

slide-50
SLIDE 50

Builder - Structure

50

Client c'tor(Builder) use() Product c'tor(<mandatory params>) setOptionalParam1() setOptionalParam2() build() Builder return new Product(self);

  • 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
slide-51
SLIDE 51

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

slide-52
SLIDE 52

Creational Patterns

Discussion

52

slide-53
SLIDE 53

Overview

53 Prototype Dependency Injection Factory Method Abstract Factory Service Locator Builder Singleton

slide-54
SLIDE 54

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