Sunday, April 13, 2008

Dependency Inversion Principle

Dependency injection is a pretty hot topic in the .NET development community at the moment with the Patterns and Practices team moving to integrate Unity with Enterprise Library 4.0. Tom Hollander recently blogged about the future of dependency injection within Enterprise Library and raised some questions about the value of this move. He has some valid points and I agree that many of the block APIs look very similar in a dependency injection world. However, the real strength of dependency injection is that it is built on the principal of dependency inversion.

If you've never read about dependency inversion before, I can recommend this article, published in the March '08 MSDN magazine by James Kovacs. I can also suggest this whitepaper, published by the team at Object Mentor. Dependency inversion is about breaking down class dependencies to make software more maintainable, reusable and testable. Dependency inversion is the principal that enables patterns like Model View Presenter (MCP) and really opens up your software to test driven development.

I've put together a small example of how dependency inversion can be used to implement a simple payroll service using Microsoft .NET. The class diagram helps to visualise the object dependencies. The goal is to break the concrete dependency between the PayService and the EmployeeService so that the services can evolve and be tested in isolation. The class implementation is followed up with some simple unit tests that illustrate how to expand test coverage.

I believe that the introduction of dependency injection to Enterprise Library will help to promote and expand the use of dependency inversion in software and that we'll see a lot more test driven development in the community. Dependency inversion is not a difficult concept to grasp but it can really help you along the road to better quality and more maintainable software.

IoC

namespace Staff.Interface {
using System.Collections.Generic;
public class Employee {}
public interface IEmployeeService {
IList<Employee> GetEmployees();
}
}

namespace Payroll.Interface {
public interface IPayService {
void PayEmployees();
}
}

namespace Staff {
using System.Collections.Generic;
using Staff.Interface;

public class EmployeeService : IEmployeeService {
public IList<Employee> GetEmployees() {
return new List<Employee>();
}
}
}

namespace Payroll {
using Staff.Interface;
using Payroll.Interface;

public class PayService : IPayService {
private IEmployeeService service;

public PayService(IEmployeeService service) {
this.service = service;
}

public void PayEmployees() {
foreach (Employee e in service.GetEmployees()) {
}
}
}
}
namespace Payroll.Test {
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using Staff.Interface;
using Payroll;

public class MockReturnNull : IEmployeeService {
public IList<Employee> GetEmployees() {
return null;
}
}

public class MockThrowDbException : IEmployeeService {
public IList<Employee> GetEmployees() {
throw new System.InvalidOperationException();
}
}

public class MockReturn20Employees : IEmployeeService {
public IList<Employee> GetEmployees() {
return new List<Employee>();
}
}

[TestClass]
public class PayServiceTest {
[TestMethod]
public void PayEmployeesNull() {
MockReturnNull emp = new MockReturnNull();
PayService pay = new PayService(emp);
pay.PayEmployees();
}
[TestMethod]
public void PayEmployeesException() { }
}
}

No comments:

Post a Comment