dependency injection(控制反转)

来源:互联网 发布:java二维数组遍历 编辑:程序博客网 时间:2024/06/03 17:06

http://fernandocejas.com/

Fernando Cejas

Tasting Dagger 2 on Android

Hey! Finally I decided that was a good time to get back to the blog and share what I have dealing with for the last weeks. In this occasion I would like to talk a bit about my experience with Dagger 2, but first I think that really worth a quick explanation about why I believe that dependency injection is important and why we should definitely use it in our android applications.

By the way, I assume that you have have a basic knowledge about dependency injection in general and tools like Dagger/Guice, otherwise I would suggest you to check some of the very good tutorials out there. Let’s get our hands dirty then!

Why dependency injection?

The first (and indeed most important) thing we should know about it is that has been there for a long time and uses Inversion of Control principle, which basically states that the flow of your application depends on the object graph that is built up during program execution, and such a dynamic flow is made possible by object interactions being defined through abstractions. This run-time binding is achieved by mechanisms such as dependency injection or a service locator.

Said that we can get to the conclusion that dependency injection brings us important benefits:

  • Since dependencies can be injected and configured externally we can reuse those components.
  • When injecting abstractions as collaborators, we can just change the implementation of any object without having to make a lot of changes in our codebase, since that object instantiation resides in one place isolated and decoupled.
  • Dependencies can be injected into a component: it is possible to inject mock implementations of these dependencies which makes testing easier.

One thing that we will see is that we can manage the scope of our instances created, which is something really cool and from my point of view, any object or collaborator in your app should not know anything about instances creation and lifecycle and this should be managed by our dependency injection framework.

What is JSR-330?

Basically dependency injection for Java defines a standard set of annotations (and one interface) for use on injectable classes in order to to maximize reusability, testability and maintainability of java code.
Both Dagger 1 and 2 (also Guice) are based on this standard which brings consistency and an standard way to do dependency injection.

Dagger 1

I will be very quick here because this version is out of the purpose of this article. Anyway, Dagger 1has a lot to offer and I would say that nowadays is the most popular dependency injector used on Android. It has been created by Square inspired by Guice.

Its fundamentals are:

  • Multiple injection points: dependencies, being injected.
  • Multiple bindings: dependencies, being provided.
  • Multiple modules: a collection of bindings that implement a feature.
  • Multiple object graphs: a collection of modules that implement a scope.

Dagger 1 uses compile time to figure out bindings but also uses reflection, and although it is not used to instantiate objects, it is used for graph composition. All this process happens at runtime, where Dagger tries to figure out how everything fits together, so there is a price to pay: inefficiency sometimes and difficulties when debugging.

Dagger 2

Dagger 2 is a fork from Dagger 1 under heavy development by Google, currently version 2.0. It was inspired by AutoValue project (https://github.com/google/auto, useful if you are tired of writing equals and hashcode methods everywhere).
From the beginning, the basic idea behind Dagger 2, was to make problems solvable by using code generation, hand written code, as if we were writing all the code that creates and provides our dependencies ourselves.

If we compare this version with its predecessor, both are quite similar in many aspects but there are also important differences that worth mentioning:

  • No reflection at all: graph validation, configurations and preconditions at compile time.
  • Easy debugging and fully traceable: entirely concrete call stack for provision and creation.
  • More performance: according to google they gained 13% of processor performance.
  • Code obfuscation: it uses method dispatch, like hand written code.

Of course all this cool features come with a price, which makes it less flexible: for instance, there is no dynamism due to the lack of reflection.

Diving deeper

To understand Dagger 2 it is important (and probably a bit hard in the beginning) to know about the fundamentals of dependency injection and the concepts of each one of these guys (do not worry if you do not understand them yet, we will see examples):

  • @Inject: Basically with this annotation we request dependencies. In other words, you use it to tell Dagger that the annotated class or field wants to participate in dependency injection. Thus, Dagger will construct instances of this annotated classes and satisfy their dependencies.
  • @Module: Modules are classes whose methods provide dependencies, so we define a class and annotate it with @Module, thus, Dagger will know where to find the dependencies in order to satisfy them when constructing class instances. One important feature of modules is that they have been designed to be partitioned and composed together (for instance we will see that in our apps we can have multiple composed modules). 
  • @Provide: Inside modules we define methods containing this annotation which tells Dagger how we want to construct and provide those mentioned dependencies.
  • @Component: Components basically are injectors, let’s say a bridge between @Injectand @Module, which its main responsibility is to put both together. They just give you instances of all the types you defined, for example, we must annotate an interface with@Component and list all the @Modules that will compose that component, and if any of them is missing, we get errors at compile time. All the components are aware of the scope of dependencies it provides through its modules. 
  • @Scope: Scopes are very useful and Dagger 2 has has a more concrete way to do scoping through custom annotationsWe will see an example later, but this is a very powerful feature, because as pointed out earlier, there is no need that every object knows about how to manage its own instances. An scope example would be a class with a custom @PerActivity annotation, so this object will live as long as our Activity is alive. In other words, we can define the granularity of your scopes (@PerFragment, @PerUser, etc). 
  • @Qualifier: We use this annotation when the type of class is insufficient to identify a dependency. For example in the case of Android, many times we need different types of context, so we might define a qualifier annotation “@ForApplication” and“@ForActivity”, thus when injecting a context we can use those qualifiers to tell Dagger which type of context we want to be provided.

Shut up and show me the code!

I guess it is too much theory for now, so let’s see Dagger 2 in action, although it is a good idea to first set it up by adding the dependencies in our build.gradle file:

As you can see we are adding the compiler, the runtime library and the apt plugin, which is necessary, otherwise the dagger annotation processor might not work properly, especially I encountered problems on Android Studio.

Our example

A few months ago I wrote an article about how to implement uncle bob’s clean architecture on Android, which I strongly recommend to read so you get a better understanding of what we are gonna do here. Back then, I faced a problem when constructing and providing dependencies of most of the objects involved in my solution, which looked something like this (check out the comments):

As you can see, the way to address this problem is to use a dependency injection framework. We basically get rid of that boilerplate code (which is unreadable and understandable): this class must not know anything about object creation and dependency provision.

So how do we do it? Of course we use Dagger 2 features… Let me picture the structure of my dependency injection graph:

Let’s break down this graphic and explain its parts plus some code.

Application Component: A component whose lifetime is the life of the application. It injects bothAndroidApplication and BaseActivity classes.

As you can see, I use the @Singleton annotation for this component which constraints it to one-per-application. You might be wondering why I’m exposing the Context and the rest of the classes. This is actually an important property of how components work in Dagger: they do not expose types from their modules unless you explicitly make them available. In this case in particular I just exposed those elements to subgraphs and if you try to remove any of them, a compilation error will be triggered.

Application Module: This module provides objects which will live during the application lifecycle, that is the reason why all of @Provide methods use a @Singleton scope.

0 0
原创粉丝点击