Is Your Code SOLID: The Dependency Inversion Principle

The mother of all SOLID principles. Nail this one, and you’ll keep your codebase supple — ready for just about any change you throw at it. The Dependency Inversion Principle comes in two flavors:

  • HIGH LEVEL MODULES SHOULD NOT DEPEND UPON LOW LEVEL MODULES. BOTH SHOULD DEPEND UPON ABSTRACTIONS.
  • ABSTRACTIONS SHOULD NOT DEPEND UPON DETAILS. DETAILS SHOULD DEPEND UPON ABSTRACTIONS.

An example should make this clear.

Consider a very simple requirement: read a serial port for ASCII characters, find any appropriate stock symbol and associated data and copy that data onto a TCP/IP socket (don’t laugh – those were real requirements: a trading partner’s trade desk wanted a connection to a former employer’s trading platform, and the exchange had no more ports to grant).

The MacGyver’ed up solution ended up with a dependency graph that looked like this:

More abstractly, this program reads characters from a source (the serial port), does a lookup, and then writes textual data to a destination (a socket). A reasonable one-off, but ultimately a problem. Because the high level module (symbol lookup) depended on low level modules (an RS232 serial port reader and a socket), we could only use it in an environment with an RS232 port and a socket. Any other use would require so much refactoring a rewrite actually started to make sense. It was a very simple hack … err … design that became very expensive to maintain.

Instead, if the symbol lookup module depended on a simple text reader abstraction, which the RS232 Serial Port reader then implemented, and the socket implemented a simple text writer abstraction, we suddenly open all the modules up to a world of other uses and environments:

Improved dependency graph

With this change, all the classes follow the OCP. The behavior of the program is changed by replacing the RS232 Serial Port Reader with some other character reader (i.e., and extension) – no other classes need to change. As long as our line-of-business module (symbol lookup) depends on abstractions (TextReader and TextWriter), the data will flow no matter where it comes from, or goes to.

Usually, DIP isn’t violated in such an over-the-top way as that. It’s often much more subtle:

namespace Numa.Infrastructure.Client
{
    public interface IUIHost
    { 

      //... 

      /// <summary>
      /// Get or Set Environment Configuration Setting
      /// </summary>
      Dictionary<string, string> EnvConfig
      {
        get;
        set;
      }
    }
}

Note the implementation detail: IUIHost.EnvConfig returns a Dictionary<string,string>. Not only does the interface depend on that detail, it forces all of its consumers dependency on it simply by making it part of the interface. A reasonable SOLID refactoring of this interface would probably replace the Dictionary<string , string> class with an IDictionary<string , string> interface.

This simple change does two things to improve the quality of our design:

  • It protects the clients of our abstractions from changes in our implementation of the abstraction. A derived design might deal with huge result sets, and so it loads the data lazily. A specific Dictionary type can’t do that, but something implemented under the IDictionary interface can.
  • It documents a contract between our abstraction and clients of our abstraction – constraining both our designs, and the valid uses of our designs. The change says our interface returns an object that can map a string to another string.

Before we think we’re done, consider this: both the original and the changed code also says the IUIHost.EnvConfig property supplies a type where:

  • String mappings can be added
  • String mappings can be removed
  • String mappings can be cleared

In other words, this interface promises to support those additional features. If we don’t want to make all of those promises, we should choose an abstraction that better describes what we mean (i.e., If we don’t want to allow modifications to the result set, we might consider an IEnumerable or ICollection return type)

Another place we see the DIP violated is in code written from a procedural perspective – code that depends on specific function calls instead of calling through abstractions or interfaces. They are often implemented in types with names like “FooManager” or “FooHelper”. For example:

using System;
using System.Security.Principal; 

public static class AuthorizationService
{ 

  public static bool HasPermission(
      string action,
      IIdentity identity)
     {
        // something
     }
} 

public class MyClass
{ 

   public void DoSomething()
   { 

      bool permitted = AuthorizationService.HasPermission(
         "MyClass.DoSomething",
         System.Threading.Thread.CurrentPrincipal.Identity); 

      if (permitted)
      { 

         // blah 

      } 

   } 

}

MyClass.DoSomething depends on the following implementation details:

  1. where the Identity comes from (the Thread’s CurrentPrincipal)
  2. it depends on where it gets the AuthorizationService from (it’s a global)
  3. the specific implementation of the AuthorizationService.

To understand this code’s resistance to change, try writing a unit test around MyClass.DoSomething() that only invokes a test stub AuthorizationService. It simply can’t be done.

AuthorizationService exposes the detail that there’s only one (implied by the fact that it’s a static class) callers will also depend on that detail, and that its services will always be accessible via a reference to the class name. In other words, it simply can’t be changed without forcing a rebuild of all clients.

An improved design might look something like:

using System;
using System.Security.Principal; 

public interface IAuthorizationService
{ 

   bool HasPermission(string action, IIdentity identity); 

} 

public class MyClass
{ 

   IIdentity _user;
   IAuthorizationService _authSvc; 

   public MyClass(IAuthorizationService authService,
      IIdentity user)
   { 

      _authSvc = authService;
      _user = user; 

   } 

   public void DoSomething()
   { 

      bool permitted = _authSvc.HasPermission(
         "MyClass.DoSomething", _user); 

      if (permitted)
      {
         // blah
      } 

   } 

}

This technique, called Constructor Dependency Injection, breaks MyClass’s dependency on the detail that an AuthorizationService is implemented a particular way, and that it’s supplied in a particular way. By breaking that dependency, our designs become much easier to change and test.

Powered by WordPress with GimpStyle Theme design by Horacio Bella.
Entries and comments feeds. Valid XHTML and CSS.