Share
Embed
Dependency Injection Made Simple
Presented at DevFest MN 2016.
I'm not entirely sure these slides will make sense without me actually presenting...
Daniel Lew
February 06, 2016
Transcript
92 slides
-
• Data provider depends on database • Image Loader depends
on HTTP • REST depends on HTTP • REST depends on deserializer • Login screen depends on all the above -
"Dependency Injection" is a 25-dollar term for a 5-cent concept.
http://www.jamesshore.com/Blog/Dependency-Injection-Demystified.html ~James Shore -
public class Example { private final Dependency dependency; public
Example() { dependency = new Dependency(); } } VS public class Example { private final Dependency dependency; public Example(Dependency dependency) { this.dependency = dependency; } } -
public class Example { private final Dependency dependency; public
Example() { dependency = new Dependency(); } } VS public class Example { private final Dependency dependency; public Example(Dependency dependency) { this.dependency = dependency; } } -
• Constructor injection public class Example { private final Dependency
dependency; public Example(Dependency dependency) { this.dependency = dependency; } } • Method injection public class Example { private Dependency dependency; public Example() { } public void setDependency(Dependency dependency) { this.dependency = dependency; } } -
Context wrappedContext = new ContextThemeWrapper(context, R.style.MyTheme); View view =
new View(wrappedContext); -
Dependency inversion principle • High-level modules should not depend on
low-level modules. Both should depend on abstractions. • Abstractions should not depend on details. Details should depend on abstractions. -
void copy() { char c; while ((c = readKeyboard()) !=
-1) { writePrinter(c); } } -
What if… • …You want to read from disk? enum
InputDevice { KEYBOARD, DISK } void copy(InputDevice input) { char c; while (true) { if (input == InputDevice.KEYBOARD) { c = readKeyboard(); } else if (input == InputDevice.DISK) { c = readDisk(); } else { throw new IllegalArgumentException("Whoops!"); } if (c == -1) { return; } writePrinter(c); } } -
What if… • …You want to write to the monitor?
enum OutputDevice { PRINTER, MONITOR } void copy(OutputDevice outputDevice) { char c; while ((c = readKeyboard()) != -1) { if (outputDevice == OutputDevice.PRINTER) { writePrinter(c); } else if (outputDevice == OutputDevice.MONITOR) { writeMonitor(c); } else { throw new IllegalArgumentException("Whoops again!"); } } } -
• What do we test in the read / write
modules? • How the hell do we test copy?! -
What if… • You want to change readKeyboard() or writePrinter()?
void copy() { char c; while ((c = readKeyboard()) != -1) { writePrinter(c); } } -
interface Reader { char read(); } interface Writer {
void write(char c); } void copy(Reader reader, Writer writer) { char c; while ((c = reader.read()) != -1) { writer.write(c); } } -
But Why? • Share dependencies • Configure dependencies externally •
Clean separation of modules -
But Why? • Share dependencies • Configure dependencies externally •
Clean separation of modules • Easy testing -
@Test public void testCopy() { TestReader reader = new TestReader("Hello,
world!"); TestWriter writer = new TestWriter(); copy(reader, writer); assertEquals("Hello, world!", writer.getWritten()); } -
But Why? • Share dependencies • Configure dependencies externally •
Clean separation of modules • Easy testing • Easy debugging -
Framework Advantages • Handles injection busywork • Handles logic (e.g.
singletons, lazy loading) • Unified system for dependencies -
Frameworks • Dagger 1 • Dagger 2 • Guice •
Spring • PicoContainer • …And on and on and on… -
// Greeter.java public class Greeter { @Inject public Greeter() {
} public void helloWorld() { System.out.println("Hello, world!"); } } // MyModule.java @Module(injects = Greeter.class) public final class MyModule { } // Retrieving Greeter ObjectGraph objectGraph = ObjectGraph.create(new MyModule()); Greeter greeter = objectGraph.get(Greeter.class); greeter.helloWorld(); -
// Greeter.java public class Greeter { @Inject public Greeter() {
} public void helloWorld() { System.out.println("Hello, world!"); } } // MyModule.java @Module(injects = Greeter.class) public final class MyModule { } // Retrieving Greeter ObjectGraph objectGraph = ObjectGraph.create(new MyModule()); Greeter greeter = objectGraph.get(Greeter.class); greeter.helloWorld(); -
// Greeter.java public class Greeter { @Inject public Greeter() {
} public void helloWorld() { System.out.println("Hello, world!"); } } // MyModule.java @Module(injects = Greeter.class) public final class MyModule { } // Retrieving Greeter ObjectGraph objectGraph = ObjectGraph.create(new MyModule()); Greeter greeter = objectGraph.get(Greeter.class); greeter.helloWorld(); -
// Greeter.java public class Greeter { @Inject public Greeter() {
} public void helloWorld() { System.out.println("Hello, world!"); } } // MyModule.java @Module(injects = Greeter.class) public final class MyModule { } // Retrieving Greeter ObjectGraph objectGraph = ObjectGraph.create(new MyModule()); Greeter greeter = objectGraph.get(Greeter.class); greeter.helloWorld(); -
// Greeter.java public class Greeter { @Inject public Greeter() {
} public void helloWorld() { System.out.println("Hello, world!"); } } // MyModule.java @Module(injects = Greeter.class) public final class MyModule { } // Retrieving Greeter ObjectGraph objectGraph = ObjectGraph.create(new MyModule()); Greeter greeter = objectGraph.get(Greeter.class); greeter.helloWorld(); -
// Text.java public class Text { @Inject public Text() {
} public String getText() { return "Hello, world!"; } } // Greeter.java public class Greeter { private final Text text; @Inject public Greeter(Text text) { this.text = text; } public void sayText() { System.out.println(text.getText()); } } -
// Text.java public class Text { @Inject public Text() {
} public String getText() { return "Hello, world!"; } } // Greeter.java public class Greeter { private final Text text; @Inject public Greeter(Text text) { this.text = text; } public void sayText() { System.out.println(text.getText()); } } -
// Text.java public class Text { @Inject public Text() {
} public String getText() { return "Hello, world!"; } } // Greeter.java public class Greeter { private final Text text; @Inject public Greeter(Text text) { this.text = text; } public void sayText() { System.out.println(text.getText()); } } -
// Text.java public class Text { @Inject public Text() {
} public String getText() { return "Hello, world!"; } } // Greeter.java public class Greeter { private final Text text; @Inject public Greeter(Text text) { this.text = text; } public void sayText() { System.out.println(text.getText()); } } -
@Inject public ManyDependencies( Dep1 never, Dep2 going, Dep3 to, Dep4
give, Dep5 you, Dep6 up ) -
// Greeter.java public class Greeter { @Inject Text text;
public void sayText() { System.out.println(text.getText()); } } -
// Greeter.java public class Greeter { @Inject Text text;
public void sayText() { System.out.println(text.getText()); } } -
public class ManyDependencies { @Inject Dep1 never; @Inject Dep2 going;
@Inject Dep3 to; @Inject Dep4 let; @Inject Dep5 you; @Inject Dep6 down; } -
Providers • Explicitly provide dependencies • Greater control over dependency
• Can’t annotate dependency • Dependency inversion -
Providers @Module(injects = Greeter.class) public final class MyModule { @Provides
Text provideText() { return new Text("Hello, World!"); } @Provides Greeter provideGreeter(Text text) { return new Greeter(text); } } -
Providers @Module(injects = Greeter.class) public final class MyModule { @Provides
Text provideText() { return new Text("Hello, World!"); } @Provides Greeter provideGreeter(Text text) { return new Greeter(text); } } -
Providers @Module(injects = Greeter.class) public final class MyModule { @Provides
Text provideText() { return new Text("Hello, World!"); } @Provides Greeter provideGreeter(Text text) { return new Greeter(text); } } -
Providers @Module(injects = Greeter.class) public final class MyModule { @Provides
Text provideText() { return new Text("Hello, World!"); } @Provides Greeter provideGreeter(Text text) { return new Greeter(text); } } -
Providing Interfaces public interface Reader { char read(); } public
class KeyboardReader implements Reader { @Override public char read() { /* implementation */ } } @Module(injects = Reader.class) public final class MyModule { @Provides Reader provideReader() { return new KeyboardReader(); } } -
Providing Interfaces public interface Reader { char read(); } public
class KeyboardReader implements Reader { @Override public char read() { /* implementation */ } } @Module(injects = Reader.class) public final class MyModule { @Provides Reader provideReader() { return new KeyboardReader(); } } -
Providing Interfaces public interface Reader { char read(); } public
class KeyboardReader implements Reader { @Override public char read() { /* implementation */ } } @Module(injects = Reader.class) public final class MyModule { @Provides Reader provideReader() { return new KeyboardReader(); } } -
Providing Interfaces public interface Reader { char read(); } public
class KeyboardReader implements Reader { @Override public char read() { /* implementation */ } } @Module(injects = Reader.class) public final class MyModule { @Provides Reader provideReader() { return new KeyboardReader(); } } -
Providing Interfaces public interface Reader { char read(); } public
class KeyboardReader implements Reader { @Override public char read() { /* implementation */ } } @Module(injects = Reader.class) public final class MyModule { @Provides Reader provideReader() { return new KeyboardReader(); } } -
Multiple Modules • Compile time @Module(includes = HttpModule.class) public final
class RestModule { } • Runtime ObjectGraph.create(new RestModule(), new HttpModule()); Warning! -
// HttpModule.java @Module(library = true) public final class HttpModule {
@Provides OkHttp provideOkHttp() { return new OkHttp(); } } // RestModule.java @Module(complete = false) public final class RestModule { @Provides Retrofit provideRetrofit(OkHttp okHttp) { return new Retrofit(okHttp); } } -
// HttpModule.java @Module(library = true) public final class HttpModule {
@Provides OkHttp provideOkHttp() { return new OkHttp(); } } // RestModule.java @Module(complete = false) public final class RestModule { @Provides Retrofit provideRetrofit(OkHttp okHttp) { return new Retrofit(okHttp); } } -
// HttpModule.java @Module(library = true) public final class HttpModule {
@Provides OkHttp provideOkHttp() { return new OkHttp(); } } // RestModule.java @Module(complete = false) public final class RestModule { @Provides Retrofit provideRetrofit(OkHttp okHttp) { return new Retrofit(okHttp); } } -
// HttpModule.java @Module(library = true) public final class HttpModule {
@Provides OkHttp provideOkHttp() { return new OkHttp(); } } // RestModule.java @Module(complete = false) public final class RestModule { @Provides Retrofit provideRetrofit(OkHttp okHttp) { return new Retrofit(okHttp); } } -
// HttpModule.java @Module(library = true) public final class HttpModule {
@Provides OkHttp provideOkHttp() { return new OkHttp(); } } // RestModule.java @Module(complete = false) public final class RestModule { @Provides Retrofit provideRetrofit(OkHttp okHttp) { return new Retrofit(okHttp); } } -
// HttpModule.java @Module(library = true) public final class HttpModule {
@Provides OkHttp provideOkHttp() { return new OkHttp(); } } // RestModule.java @Module(complete = false) public final class RestModule { @Provides Retrofit provideRetrofit(OkHttp okHttp) { return new Retrofit(okHttp); } } -
// HttpModule.java @Module(library = true) public final class HttpModule {
@Provides OkHttp provideOkHttp() { return new OkHttp(); } } // RestModule.java @Module(complete = false) public final class RestModule { @Provides Retrofit provideRetrofit(OkHttp okHttp) { return new Retrofit(okHttp); } } -
Module Overrides @Module() public final class MyModule { @Provides Writer
provideWriter() { return new PrinterWriter(); } } @Module( includes = MyModule.class, overrides = true ) public final class TestModule { @Provides Writer provideWriter() { return new TestWriter(); } } -
Module Overrides @Module() public final class MyModule { @Provides Writer
provideWriter() { return new PrinterWriter(); } } @Module( includes = MyModule.class, overrides = true ) public final class TestModule { @Provides Writer provideWriter() { return new TestWriter(); } } -
Module Overrides @Module() public final class MyModule { @Provides Writer
provideWriter() { return new PrinterWriter(); } } @Module( includes = MyModule.class, overrides = true ) public final class TestModule { @Provides Writer provideWriter() { return new TestWriter(); } } -
Module Overrides @Module() public final class MyModule { @Provides Writer
provideWriter() { return new PrinterWriter(); } } @Module( includes = MyModule.class, overrides = true ) public final class TestModule { @Provides Writer provideWriter() { return new TestWriter(); } } -
Module Overrides @Module() public final class MyModule { @Provides Writer
provideWriter() { return new PrinterWriter(); } } @Module( includes = MyModule.class, overrides = true ) public final class TestModule { @Provides Writer provideWriter() { return new TestWriter(); } } -
Module Overrides @Module() public final class MyModule { @Provides Writer
provideWriter() { return new PrinterWriter(); } } @Module( includes = MyModule.class, overrides = true ) public final class TestModule { @Provides Writer provideWriter() { return new TestWriter(); } } -
Other Dagger Features • Singletons @Provides @Singleton Greeter provideGreeter() •
Lazy injecting @Inject Lazy<Text> text; • Qualifiers @Provides @Named("Greeter1") Greeter provideGreeter() -
Dagger + Gradle apply plugin: 'com.neenbedankt.android-apt' dependencies { apt
'com.squareup.dagger:dagger-compiler:1.2.2' compile 'com.squareup.dagger:dagger:1.2.2' } https://bitbucket.org/hvisser/android-apt -
Injecting Android • Activity, Service, Fragment, etc… • Problem: We
don’t control construction! • Solution: ObjectGraph.inject() -
Injecting Activities public class MyActivity extends Activity { @Inject
Dependency dependency; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ObjectGraph objectGraph = ((MyApplication) getApplication()).getObjectGraph(); objectGraph.inject(this); setContentView(R.layout.main); } } -
Injecting Activities public class MyActivity extends Activity { @Inject
Dependency dependency; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ObjectGraph objectGraph = ((MyApplication) getApplication()).getObjectGraph(); objectGraph.inject(this); setContentView(R.layout.main); } } -
Injecting Activities public class MyActivity extends Activity { @Inject
Dependency dependency; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ObjectGraph objectGraph = ((MyApplication) getApplication()).getObjectGraph(); objectGraph.inject(this); setContentView(R.layout.main); } } -
u2020 • Unicode character for “dagger”: † • Injector pattern
• https://github.com/JakeWharton/u2020