So why do you want to use Guice? Well let's start with what how you are currently writing code.
The Problem:
Currently you might be writing code that looks like this:
public class DataProcessor{
public DataProcessor(){}
public void processData(D data)
{
//Do something with data
// Save data
DbWriter myDbWriter = new DbWriter();
myDbWriter.write(data);
}
}
The problem with this code is that your DataProcessor class is tightly coupled with the class DBWriter. When you try to test this class, you will find that you cannot write a test on processData without relying on the DBWriter implementation.Dependency Injection:
To solve the problem, we will "inject" the dependency into the constructor.
public class DataProcessor{
private DbWriter myDbWriter;
public DataProcessor(DBWriter dbWriter){
myDbWriter = dbWriter;
}
public void processData(D data)
{
//Do something with data
// Save data
myDbWriter.write(data);
}
}
Using dependency injection allows us to resolve this dependency at run-time. Now we can mock out the DBWriter class with our own stubbed implementation in our tests. This is an example of what our test class might look like:
public class DataProcessorTest{
private DataProcessor classUnderTest;
@Test
public void testProcessData()
{
mockDbWriter = new DbWriterStub();
classUnderTest = new DataProcessor(mockDbWriter);
classUnderTest.processData();
// Check mock to make sure we saved the data
}
}
Our tests no longer relies on our DbWriter class. If DbWriter changes, this test will not fail. There are few libraries out there to easily create mocks. EasyMock is my favorite.
Guice:
So if you are thinking ahead you might realize that strictly implementing dependency injection introduces a cascading problem. If we are passing in every dependency into every class, where do we instantiate all the objects? Short answer: main().2
public static void main(String [] args){
Application myApplication = new Application(new A(new B(...)));
myApplication.start();
}
Believe it or not, I think this situation is ideal. You have a separation of concerns with the object creation and program execution1. All of the classes can be tested independently, and are more easily reused.
Instead of specifying every dependency in main, we can specify the dependencies using Guice modules:
import com.google.inject.AbstractModule;
public class MyGuiceModule extends AbstractModule{
protected void configure() {
bind(Applicaiton.class).to(MyApplication.class);
bind(A.class).to(AImpl.class);
bind(B.class).to(BImpl.class);
}
}
Extra boilerplate is required for Guice. Activate your module from main like so:
public static void main(String [] args){
Injector injector = Guice.createInjector(new MyGuiceModule());
Application myApplication = injector.getInstance(myApplication.class);
myApplication.start();
}And you have to specify that you want your classes to be injected by using the @Inject annotation on your constructor.
@Inject
public DataProcessor(DBWriter dbWriter){
myDbWriter = dbWriter;
}
Using the Guice modules puts all of your object creation into independent, reusable modules. Want to change an implementation for a database interface? Simply swap out the old implementation for the new one in the Guice module. The rest of your code will not break, and all you have left to do is add tests for your new database implementation. In order to use Guice, you are forced to structure your classes so that they are independent from each other. You will be less vulnerable to changes in your system.I only gave one use-case for Guice. There are many other built in features including method and field injection, defining scopes (singletons), etc- which you can read about on their website.
For android developers out there I recommend a different dependency injection framework by Square called Dagger. It has less features than Guice, but it uses a annotation processor to build the object graph at compile time, instead of using reflection at run-time. This decreases the run-time overhead on the already stressed hardware of smartphones.
1 Robert Martin's Clean Code
2 You can also use factories, but that's a lot of boilerplate to do that for every dependency.