Error Messages – Dos and Dont’s Dependency Injection with StructureMap
Jul 27

SOLID is a combination of five important design principles:
SSingle Responsibility Principle
OOpen Closed Principle
LLiskov Substitution Principle
IInterface Segregation Principle
DDependency Inversion Principle

This post is about the Dependency Inversion Principle.

Definition:

The Dependency Inversion Principle states that higher level components shouldn’t depend on lower level ones, instead they should depend on abstractions.

Or:
Abstractions shouldn’t depend on details. Details should depend on abstractions.

Benefits:

Unit testing is much easier as you can really test the class you want to test in isolation. If done right, the dependencies of your class under test can simply be faked and therefore, the implementations of it’s dependencies don’t effect your tests.

You can easily create a new implementation, it just has to implement an interface.

Some kind of code duplication can be removed as applying the dependency inversion principle enables you to consolidate mutiple rather equal algorythms into a single generic one.

A bad Example:

Let’s say you work for a company that has three different crms and wants to export some customers from crm 1 to crm 2, crm 1 is capable of exporting its customers as a csv file. We’ll import that.

public class CustomerFileImporter
{
    private string _filePath;

    public CustomerFileImporter(string filePath)
    {
        if (!File.Exists(filePath))
        {
            throw new ArgumentException("File does not exist.");
        }

        _filePath = filePath;
    }

    public void Import()
    {
        var customersFromFile = LoadFromFile();
        var existingCustomers = LoadExistingCustomersFromDb(customersFromFile);

        var updated = HandleCustomerUpdate(existingCustomers, customersFromFile);
        var created = HandleCustomerCreation(existingCustomers, customersFromFile);

        WriteChangesToDb(updated, created);
    }

    private void WriteChangesToDb(IList<Customer> updated,
                                  IList<Customer> created)
    {
        // ....
    }

    private IList<Customer> HandleCustomerCreation(IList<Customer> existing,
                                                   IList<Customer> imported)
    {
        // .....
    }

    private IList<Customer> HandleCustomerUpdate(IList<Customer> existing,
                                                 IList<Customer> imported)
    {
        // .....
    }

    private IList<Customer> LoadFromFile()
    {
        // import the file and
        // return the customers
    }

    private IList<Customer> LoadExistingCustomersFromDb(IList<Customer> imported)
    {
        // do some fancy db stuff
        // and return the customers
    }
}

What’s the problem?

Well, right now, nothing. It does exactly what it has to do. But as we all know “Change is the only constant”, it will only be a matter of time until the requirements change because “The import worked so great!”.

And now, a change is requested, crm 3 should be a possible source too. Sadly, crm 3 doesn’t provide any way to export the customers as a csv file.

How to fix it?

The lazy approach would be to simply copy and paste the CustomerFileImporter, rename it CustomerDbImporter and do the necessary changes.

The better approach would be to rely more on abstractions. Just create an interface, let’s say ICustomerImportSource, with the method LoadCustomers() that returns an IList<Customer>. All we have to change in the above code is to accept an source as constructor argument instead of a filepath and change the call from LoadFromFile to source.LoadCustomers().

All the logic that was in the LoadFromFile method is now in the LoadCustomers method of the ICustomerImportSource’s implementation that might be called Crm1CustomerImportSource. The now requested change is just another implementation of ICustomerImportSource.

As the import is no longer restricted to files, we should rename the CustomerFileImporter to just CustomerImporter.

More changes

I said Change is the only constant right? Well, the next request is “just make it possible to import from any crm into any other”.

As you might have expected, just let the CustomerImporter take an ICustomerImportTarget too, implement the necessary logic for all three crms there, implement the ICustomerImportSource for crm 2 and be done.

The result

public class CustomerImporter
{
    private ICustomerImportSource _source;
    private ICustomerImportTarget _target;

    public CustomerImporter(ICustomerImportSource source,
                            ICustomerImportTarget target)
    {
        _source = source;
        _target = target;
    }

    public void Import()
    {
        var customersFromSource = _source.Load();
        var existingCustomers = _target.LoadExisting(customersFromSource);

        var updated = HandleCustomerUpdate(existingCustomers, customersFromSource);
        var created = HandleCustomerCreation(existingCustomers, customersFromSource);

        _target.HandleChanges(updated, created);
    }

    private IList<Customer> HandleCustomerCreation(IList<Customer> existing,
                                                   IList<Customer> imported)
    {
        // .....
    }

    private IList<Customer> HandleCustomerUpdate(IList<Customer> existing,
                                                 IList<Customer> imported)
    {
        // .....
    }
}

public interface ICustomerImportSource
{
    IList<Customer> Load();
}

public interface ICustomerImportTarget
{
    IList<Customer> LoadExisting(IList<Customer> imported);
    void HandleChanges(IList<Customer> updated, IList<Customer> created);
}

Conclusion

In most cases, only very little effort is required to convert a static solution that depends on concrete implementations into something more generic that needs only abstractions to fulfill its duty. The dependency inversion principle makes it possible.

Would I do such a thing upfront? Well, in most cases yes, its almost zero effort and clearly separates the responsibilities of each class.

Actually, I’d go one step further and almost always use interfaces. The reason is simple, easier unit testing with very little effort and if you use an IoC container there is no pain with the instantiation and the like. More on that in a later post.

Bookmark and Share

5 Responses to “SOLID – D: Dependency Inversion Principle”

  1. [...] SOLID – O: Open Closed Principle July 20th, 2009 Sebastian Leave a comment Go to comments SOLID is a combination of five important design principles: S – Single Responsibility Principle O – Open Closed Principle L – Liskov Substitution Principle I – Interface Segregation Principle D – Dependency Inversion Principle [...]

  2. SOLID – D: Dependency Inversion Principle | Coding Efficiency…

    Thank you for submitting this cool story – Trackback from DotNetShoutout…

  3. 9eFish says:

    SOLID – D: Dependency Inversion Principle | Coding Efficiency…

    9efish.感谢你的文章 – Trackback from 9eFish…

  4. [...] SOLID – L: Liskov Substitution Principle July 21st, 2009 Sebastian Leave a comment Go to comments SOLID is a combination of five important design principles: S – Single Responsibility Principle O – Open Closed Principle L – Liskov Substitution Principle I – Interface Segregation Principle D – Dependency Inversion Principle [...]

  5. [...] Dependency Inversion Principle (ISP) – Defende que componentes maiores devem depender não de componentes menores, mas sim de uma abstração deles. [...]

Leave a Reply

preload preload preload