Tuesday, June 12, 2012

Object Design Principles - Part 5

Dependency Inversion Principle

The DIP states that high level classes should not depend on low level classes.  Both should depend on abstractions.  Details should depend on abstractions.  Abstractions should not depend on details.  The DIP is about reversing the direction of dependencies from higher level components to lower level components such that lower level components are dependent upon only the interfaces owned by the higher level components.  This is a method for moving to a more loosely coupled architecture.  Basically, you have to depend on a standard interface used by objects and not depend on their details.  This one might be best illustrated by example. 

In the following example, the BadCar class is tightly coupled to the actual implementation of the BadMotor function.  This means that these two objects are married, and changes to one directly impact the other.


public class BadMotor
{
    public Boolean Start()
    {
        Console.Write("Starting");
        return true;
    }
}
 
//tightly coupled to details.
public class BadCar
{
    public BadMotor Motor {get;set;}
 
    public Boolean Start(BadMotor badMotor)
    {
        Motor = badMotor;
        return Motor.Start();
    }
}
 
 
While this does function, it creates a dependency relationship between two objects at their implementation level.   This creates hardships for maintenance and scalability long term.   The proper way to build this relationship would be to invert dependencies onto interfaces to ensure no object has knowledge or visibility into implementation of any other object. Consider the following examples:

public interface IEngine
{
    bool Start();
}
 
public interface IGreenEngine : IEngine
{
    bool IsCharged ();
}
 
public class FourCylEngine : IEngine
{
    public bool Start()
    {
        Console.WriteLine("4Cyl Starting");
        return true;
    }
}
 
public class V8Engine : IEngine
{
    public bool Start()
    {
        Console.WriteLine("V8 Starting");
        return true;
    }
}
 
public class HybridEngine : IGreenEngine
{
    public bool Start()
    {
        if (IsCharged())
            Console.WriteLine("Hybrid Starting");
        return true;
    }
    public bool  IsCharged()
    {
        Console.WriteLine("Hybrid is charged");
        return true;
    }
}
 
public class Car
{
    public Car(IEngine engine)
    {
        Engine = engine;
    }
    public IEngine Engine{get ; set ; }
    public Boolean Start()
    {
            
        return Engine.Start();
    }
}
 
I built this interface dependency and inheritance to ensure that objects are loosely coupled and only share an interface.   This allows for actual base implementations to come and go and even be recognized dynamically without causing lower level objects to update their implementation.   Here is an example of hot swapping and scaling with the previously defined objects and interfaces.

class Program
{
      
    static void Main(string[] args)
    {
        //  Cars are only dependant upon an Engine interface
        Car BigThing = new Car(new V8Engine());
        BigThing.Start();
        //  Cars are only dependant upon an Engine interface
        Car SmallThing = new Car(new FourCylEngine());
        SmallThing.Start();
        // Since we have an interface dependency, it is easy to hot swap.
        BigThing.Engine = new HybridEngine();
        BigThing.Start();
    }
}

The output is as follows:


As you can see, this allows for maintainability and long-term scalability by ensuring that the objects stay out of each other's business. By adhering to the spirit of this principle as well as the previous SOLID principles, you can keep your code base healthy and easy to maintain when the requirement changes come.