BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Programming Concepts

Dependency Inversion Principle

The sixth and final article in the SOLID Principles series describes the Dependency Inversion Principle (DIP). The DIP states that high level modules should not depend upon low level modules and that abstractions should not depend upon details.

The Principle

The Dependency Inversion Principle (DIP) states that high level modules should not depend upon low level modules. Both should depend upon abstractions. Secondly, abstractions should not depend upon details. Details should depend upon abstractions.

The idea of high level and low level modules categorises classes in a hierarchical manner. High level modules or classes are those that deal with larger sets of functionality. At the highest level they are the classes that implement business rules within the overall design of a solution. Low level modules deal with more detailed operations. At the lowest level they may deal with writing information to databases or passing messages to the operating system. Of course, there are many levels between the highest and the lowest. The DIP applies to any area where dependencies between classes exist.

The DIP can be described more easily with an example. Consider a banking solution. As a part of the software it is necessary to transfer money between accounts. This may involve a class for a bank account with an account number and a balance value. It may include methods that add or remove funds from the account. To control transfers between accounts you may create a higher level TransferManager class. This may have properties for the two accounts involved in the transaction and for the value of the transfer. A possible design is shown below:

Dependency Inversion Principle not applied

The problem with such a design is that the high level TransferManager class is directly dependent upon the lower level BankAccount class. The Source and Destination properties reference the BankAccount type. This makes it impossible to substitute other account types unless they are subclasses of BankAccount. If we later want to add the ability to transfer money from a bank account to pay bills, the BillAccount class would have to inherit from BankAccount. As bills would not support the removal of funds, this is likely to break the rules of the Liskov Substitution Principle (LSP) or require changes to the TransferManager class that do not comply with the Open / Closed Principle (OCP).

Further problems arise should changes be required to low level modules. A change in the BankAccount class may break the TransferManager. In more complex scenarios, changes to low level classes can cause problems that cascade upwards through the hierarchy of modules. As the software grows, this structural problem can be compounded and the software can become fragile or rigid.

Applying the DIP resolves these problems by removing direct dependencies between classes. Instead, higher level classes refer to their dependencies using abstractions, such as interfaces or abstract classes. The lower level classes implement the interfaces, or inherit from the abstract classes. This allows new dependencies to be substituted without impact. Furthermore, changes to lower levels should not cascade upwards as long as they do not involve changing the abstraction.

The effect of the DIP is that classes are loosely coupled. This increases the robustness of the software and improves flexibility. The separation of high level classes from their dependencies raises the possibility of reuse of these larger areas of functionality. Without the DIP, only the lowest level classes may be easily reusable.

Example Code

To demonstrate the application of the DIP, we can review some code that violates it and explain how to refactor to comply with the principle. We will use example code that matches the UML diagram shown earlier in the article:

public class BankAccount
{
    public string AccountNumber { get; set; }
        
    public decimal Balance { get; set; }

    public void AddFunds(decimal value)
    {
        Balance += value;
    }

    public void RemoveFunds(decimal value)
    {
        Balance -= value;
    }
}


public class TransferManager
{
    public BankAccount Source { get; set; }

    public BankAccount Destination { get; set; }

    public decimal Value { get; set; }

    public void Transfer()
    {
        Source.RemoveFunds(Value);
        Destination.AddFunds(Value);
    }
}
22 January 2011