Design Patterns Dependency Injection Oliver Haase 1
Motivation 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() { … } } 2
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(); } } return movies; } } 3
Problem How to remove MovieLister ’s dependency on ColonDelimitedMovieFinder ? Difference to DocManager example: ‣ MovieLister needs only one MovieFinder instance (service) ‣ DocManager must be able to create many Document objects at will 4
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. 5
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; } } Usage: MovieLister movieLister = new MovieLister(new ColonDelimitedMovieFinder(“movies.txt”)); List<Movie> movies = movieLister.moviesDirectedBy(“Quentin Tarantino”); 7
Setter Injection - Example @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”); 8
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) { this.finder = finder; } public List<Movie> moviesDirectedBy(String arg) { … } } 9
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”); 11
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; } } 13
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. 15
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: 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"); } } 16
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); 17
Guice: Setter Injection @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; } } 18
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. 19
Guice: Setter Injection 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); 20
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 Recommendation: Use setter injection only if necessary. 21
DocManager Reloaded How to apply DI pattern to DocManager example? ⇒ 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. 22
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() { for ( Document doc : docs ) doc.open(); } } 23
DocManager Reloaded Sample Bindings: public class DocManagerModule extends AbstractModule { @Override protected void configure() { bind(DocumentFactory.class).to(LatexDocFactory.class); } } Usage: Injector injector = Guice.createInjector(new DocManagerModule()); DocManager docManager = injector.getInstance(DocManager.class); 24
Recommend
More recommend