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;
}
Mapcomponents;
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.