:::: MENU ::::

Inversion of Control

In my last post about the Clean Architecture (link) we talked about dependencies, and how we’d ideally like them all pointing “inwards”. That is, we want all the dependencies pointing away from code that might change, and towards code that is less likely to change. The reason we should be intentional about dependencies, is to limit how often we need to change our code. Changes will ripple along those dependencies. Additionally, we need to handle the behavior of dependencies when unit testing.

Normally we don’t think about dependencies. If I need to use a library, I import that code up at the top of my file, and use it. The flow of logic (and flow of control) is intuitive; going from my code, to the library I am importing. We all do this. In larger code bases that have been maintained for a little while, after years of adding an import here and there, we’re suddenly calling it “spaghetti code.”

So, what is this inversion of control thing?

Here’s what wikipedia had to say:

In software engineering, inversion of control (IoC) describes a design in which custom-written portions of a computer program receive the flow of control from a generic, reusable library.

Thanks wikipedia writer. That didn’t help. Here’s how I might put it.

Inversion of Control is a pattern that allows us to point a dependency in the opposite direction of the flow of control. This is accomplished by creating [and depending on] an Interface, which is then implemented, and that implementation is provided to our code (as opposed to being created locally).

Not much better, I know. I may have made it more confusing. Basically, we make an interface for “the other guy” to implement. If they change their code later, our code is unaffected. This pattern allows us to be selective about what direction our dependencies point.

Talk’s cheap, let’s see some code.

public class BusinessLogic{
    private DataDAO dataDAO = new DataDAO();

    public void doBusinessLogic(String name){
        List<Data> dataList = dataDAO.getByName(name);

        //Do stuff with the datums. 

    }
}

Here, we’ve done some things right. There’s a separation of concerns with respect to the Data class and DataDAO class. We’re not writing SQL locally, or in the Data class itself. However, writing a unit test for doBusinessLogic might be challenging, requiring us to mock, and inject dataDAO. Also, if our code makes multiple instances of our fake BusinessLogic class, then we’re creating multiple DataDAOs (which are presumably expensive because they probably wrap a database connection). Furthermore, changes to the DataDAO (like changing the signature of getByName) would make us modify the BusinessLogic class and any other class we may have passed dataDAO to. You could say, “The buck doesn’t [necessarily] stop here.” Finally, BusinessLogic is tied to DataDAO; you cannot use doBusinessLogic without having it call DataDao.getByName.

Here’s are IOC version of the same code:

public interface DataService{
        List<Data> getByName(String name);
}
public class DataServiceDAO implements DataService{

       private DataDAO dataDAO = new DataDAO();

        @Override
        public List<Data> getByName(String name) {
            return dataDao.getByName(name);
        }
}
public class BusinessLogic {

    private DataService dataService;

    public BusinessLogic(DataService dataService){
        this.dataService = dataService;
    }

    public void doBusinessLogic(String name){
        List<Data> dataList = dataService.getByName(name);

        //Do stuff with the datums.

    }
}

Using Inversion of Control (IOC) we create the DataService Interface, and pass it into the BusinessLogic class. What has that bought us? Here’s each of the problems we called out earlier:

  • to unit test doBusinessLogic, we can create a TestDataServiceImpl, and pass that in when creating BusinessLogic
  • when creating multiple instances of BusinessLogic, we can pass in the same DataServiceDAO.
  • Changes to DataDAO, will get caught in DataServiceDAO, and not go sneaking around our code. (the buck stops here)
  • BusinessLogic will work with any implementation of DataService, with no changes needed.

The tradeoff is that there is a little more code upfront. This may not be appropriate for code that is running as a single instance and very unlikely to change.

In this implementation we used “Constructor Injection” to allow our calling code to control which DataService implementation we used (inversion of control… get it?). There are other types of injection we could use; for example, we could let the framework create the DataService with annotations “@inject” or “@autowire”. This would pass the control all the way up to the point when we decide what is on the class path. This pattern allows us to be intentional about our dependencies, whichever way we choose to inject them, and is the workhorse enabling a “Clean Architecture”.


So, what do you think ?