Tackle software dependencies with IoC and Dependency Injection
7 min read
7 min read
Coupling between objects creates "dependency", which per se is not bad at all. You will always have some dependencies among your objects. It becomes bad when it increases abnormally since that will make maintenance a nightmare. So what you'll do is of course to reduce dependencies in your code as much as possible. This means you first have to identify potential sources of bad dependencies which could possibly be avoided.
public class MyClass{Note the usage of an interface here. I'll come back to that later. Composition creates loose coupling because when the interface changes you "just" need to adapt the dependent MyClass.
private ISomeInterface anInterface;
}
public class MyClass{Again, a dummy example that shows the dependency created by instantiating a new object of type PersistencyManager inside the class MyClass (it doesn't matter where).
private PersistencyManager persistencyMan;
public MyClass(){
this.persistencyMan = new PersistencyMan();
}
}
public class MyClass{Here the dependency is also on the Factory. This code however creates loose coupling because the process of creating objects is "outsourced" to another object. MyClass doesn't know how the object is being build.
private PersistencyManager persistencyMan;
public MyClass(){
ConfigFactory myFactory = ConfigFactory.getDefaultFactory();
this.persistencyMan = myFactory.getPersistencyManager(); //or through configuration
}
}
public class MyClass{Here a dependency to a static "Helper" class is created which performs some logic.
public void doSomeStuff(String userInput){
...
MyHelper.helpMeOutWith(userInput);
...
}
}
public class MyCustomerService implements ICustomerService{This may be a simple business logic class (in our service layer if we want so) which takes a customer, applies some validation on it and then delegates the persistency operation to the according DAO (Data Access Object). Note, we depend on interfaces here and the according dependency to the DAO object is created through a factory. This is the way you'd to it without IoC and DI (dependency injection). Now think about a unit test for the validation logic. What are the problems?? Well..we want to test the validation instructions inside the
private ICustomerDao customerDao;
public MyCustomerService(){
this.customerDao = (ICustomerDao)MyDaoFactory.getInstance(CustomerDao.class); //one example of retrieval
...
}
//returns true if validation and persistency succeeded, false otherwise;
//this could be more sophisticated of course, returning a number of validation problems
public boolean saveAndValidateCustomer(Customer aCustomer){
//a set of validation instructions for your customer object
...
if(isAValidCustomer){
customerDao.save(aCustomer);
return true;
}else{
return false;
}
}
}
saveAndValidateCustomer(...)
method but we don't want to test the persistency operation. That's out of scope of the unit test. But as you may have noticed, there is the customerDao.save(...)
operation in it. We have to mock that out because we don't want to save something down or get annoying exceptions because some DB related stuff couldn't be loaded. Good we externalized the instantiation of the DAO into our factory and hopefully this factory depends on some configuration file which in the end determines which interface gets instantiated with which concrete type.public class CustomerServiceTest{Ok, we should be safe now to execute the test. Note you have to initialize your factory with some test objects which point to empty "mock" DAOs where the save operation doesn't have any side effects.
private ICustomerService customerService;
public CustomerServiceTest(){
MyDaoFactory.initializeWithConfig("mytestconfiguration.xml"); //test initialization
}
@Before
public void setUp(){
this.customerService = new CustomerService();
}
@After
public void tearDown(){
this.customerService = null;
}
@Test
public void testSaveAndValidateCustomer(){
Customer aCustomer = new Customer();
//initialize aCustomer by setting its properties to a valid state
boolean isValid = customerService.saveAndValidateCustomer(aCustomer);
assertTrue("The customer object should be valid!", isValid);
//continue and set the customer to an invalid state and check again
}
}
public class MyCustomerService implements ICustomerService{or
private ICustomerDao customerDao;
public MyCustomerService(ICustomerDao customerDao){
this.customerDao = customerDao;
...
}
public boolean saveAndValidateCustomer(Customer aCustomer){
...
}
}
public class MyCustomerService implements ICustomerService{The resulting test case is really simple!
private ICustomerDao customerDao;
public MyCustomerService(){
...
}
public boolean saveAndValidateCustomer(Customer aCustomer){
...
}
public void setCustomerDao(ICustomerDao customerDao){
this.customerDao = customerDao;
}
}
public class CustomerServiceTest{Simple, isn't it? Unit testing becomes really simple in such cases. Normally when people claim their unit tests are too complex and therefore not done it is usually a sign they have too tightly coupled code and they therefore fail to mock out their dependencies.
private ICustomerService customerService;
@Before
public void setUp(){
//in the constructor injection case:
this.customerService = new CustomerService(new MockCustomerDao());
//in the setter injection case:
//this.customerService = new CustomerService();
//this.customerService.setCustomerDao(new MockCustomerDao());
}
@After
public void tearDown(){
this.customerService = null;
}
@Test
public void testSaveAndValidateCustomer(){
//same as before
}
}