Friday, August 7, 2009

Dependency Injection

Dependency Injection (DI), Inversion of Control (IoC) sounds scary as compared to what they are. These two terms are used interchangeably in IT industry. Basically, IoC is a principle and DI is a design pattern which follows this principle. In this pattern (DI), container takes the responsibility to inject the appropriate resources to each component it manages. Lets take it step by step with example. If we look at the below implementation of CallService, we notice that it is tightly coupled with the class HomePhone.


public class CallService {
Callable callMaker = new HomePhone();

public void callClient(int clientId){
// we do not care how to make a call, but we will need callMaker
callMaker.makeCall();
}
// other methods to exposed goes here.
}

interface Callable{
void makeCall();
}

class HomePhone implements Callable{
public void makeCall() {
System.out.println("Calling from HomePhone ...");
}
}

class MobilePhone implements Callable{
public void makeCall() {
System.out.println("Calling from MobilePhone ...");
}
}


Problem is if we want to make a call from MobilePhone, we will have to write another service or will have to update CallService and recompile. BTW, who writes the code like above these days. Atleast we'll write our CallService like this.


public class CallService {
Callable callMaker = null;

public CallService(Callable callMaker){
this.callMaker = callMaker;
}
public void callClient(int clientId){
// we do not care how to make a call, but we will need callMaker
callMaker.makeCall();
}
// other methods to exposed goes here.
}


In this case what we are doing is "injecting" the dependency "Callable" to the component "CallService" via constructor. So, this is one form of dependency injection. But in this case I am asking my CallService client to instantiate a Callable object to be used. Other way is to provide a setter method for callMaker. What if the user of CallService forgets to instantiate the CallMaker before using it ? famus NullPointerException, remember ? There are few light weight containers which takes the responsiblity of injecting appropriate resources before you can use the component [Spring container.] Now questions is what exactly container does ? lets look at the below code which acts (sort of) as a container and manages given components based upon configuration file.


class Container {
private static Container INSTANCE = new Container();
public static Container getInstance() {
return INSTANCE;
}
Map components;

private Container() {
components = new HashMap();
try {
Properties properties = new Properties();
properties.load(new FileInputStream("testPackage/other/properties/container.properties"));
for (Map.Entry entry : properties.entrySet()){
process((String)entry.getKey(), (String)entry.getValue());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void process(String key, String value) throws Exception{
String[] arr = key.split("\\.");
if (arr.length == 1){
Object component = Class.forName(value).newInstance();
components.put(key, component);
}else{
Object component = components.get(arr[0]); // CallService
Object toBeSet = components.get(value); // CallMaker
PropertyUtils.setProperty(component, arr[1], toBeSet); //CallService.setCallMaker(...)
}
}
public Object getComponent(String key){
return components.get(key);
}

}

class CallServiceClient {
public static void main(String[] args){
Container container = Container.getInstance();
CallService service = (CallService)container.getComponent("callservice");
service.callClient(5);
}
}

container.properties
#Define callMaker
callMaker=testPackage.other.HomePhone

#Define callService
callService=testPackage.other.CallService
#inject callMaker to callService
callService.callMaker=callMaker
# end

This is a typical example of how a container manager different components and their dependencies.