Wednesday, 16 April 2014

Dependency Injection with Google Guice

One of the problems that we get in software development, specifically in the design of software classes, is that of the connection between classes. If one class uses another directly, even though it works, this can cause problems later on and is known as "close coupling". Here is a mundane example:

//a really dumb message sending class
public class MessageSender {
    public void sendMessage(String message) {
        System.out.println(message);
    }
}

//another dumb class that uses the message sender
public class MessageUser {
    //a reference to the sender object
    private MessageSender messageSender;
    //the message sender object is created in the constructor
    public MessageUser() {
        this.messageSender = new MessageSender();
    }
    //send the message via the sender
    public void sendMessage(String message) {
        messageSender.sendMessage(message);
    }
}

//the main class that uses the message classes
public class MessageMain {
    public static void main(String[] args) {
        MessageUser messageUser = new MessageUser();
        messageUser.sendMessage("This is a test");
    }
}

OK, this is really mundane, but can see that MessageUser cannot use any other way of sending messages. If you want to use another means, say email, you'd have to either change what MessageSender does or change MessageUser to use a different class, call it EMailSender. However, we can now use an interface instead of a class:

public interface MessageSender {
    public void sendMessage(String message);
}

//an implementation of the interface
public class SystemMessageSender implements MessageSender {
    public void sendMessage(String message) {
        System.out.println(message);
    }
}

You still have to change MessageUser, but to use an interface given to, or "injected" into, the MessageUser object via the constructor:

public class MessageUser {
    //a reference to the interface
    private MessageSender messageSender;
    //the interface is sent to the object using the constructor
    public MessageUser(MessageSender messageSender) {
        this.messageSender = messageSender;
    }
    
    public void sendMessage(String message) {
        messageSender.sendMessage(message);
    }
}

This works if we then, in the main class, create the object that implements the MessageSender and pass it to, or inject it into, the MessageUser object:

public class MessageMain {
    public static void main(String[] args) {
        SystemMessageSender messageSender = new SystemMessageSender();
        MessageUser messageUser = new MessageUser(messageSender);
        messageUser.sendMessage("This is a test");
    }
}

Now if we want to have MessageUser send emails we create a class which also implements the MessageSend interface:

//an implementation of the interface
public class EMailMessageSender implements MessageSender {
    public void sendMessage(String message) {
        EMail.textMessage(message);
    }
}

All that then has to be done is create an object of this class in the main class and then pass it to MessageUser, as before:

        EMailMessageSender messageSender = new EMailMessageSender();
        MessageUser messageUser = new MessageUser(messageSender);
        messageUser.sendMessage("This is a test");

This technique, known as Dependency Injection, is quite an important design pattern and is mandatory in certain frameworks, such as Spring.

Introducing Google Guice


So far, so good, and it's difficult to see how this can be really improved upon. However, Guice (pronounced with a J rather than a G) does for dependency injection what Mockito does for unit testing. To introduce Guice into the above example, we have to create another class, an extension of Guice's AbstractModule class:

public class MessageModule extends AbstractModule {
    protected void configure() {
        bind(MessageSender.class).to(SystemMessageSender.class);
    }
}

You can sort-of see what's going on. The module is responsible for creating the class that implements the interface, so whenever the interface is used, the object is bound to it. The magic happens in the Injector, used in the main class:

        Injector injector = Guice.createInjector(new MessageModule());
        MessageUser messageUser = injector.getInstance(MessageUser.class);
        messageUser.sendMessage("This is a test");

Notice that the module hasn't been told about MessageUser, so the Injector is figuring out it's dependencies from the constructor of the class, and this also has to change using the @Inject annotation:

    @Inject
    public MessageUser(MessageSender messageSender) {
        this.messageSender = messageSender;
    }

Now all this doesn't seem like a big deal, if anything we've added lines and classes, but if you've got a lot of classes with umpteen dependencies, Guice can save you a lot of work.

No comments:

Post a Comment