Saturday 21 May 2016

Dependency Inversion Principle: Part 3 - Dependency Inversion Methods

After various parts of the class have been abstracted, the next thing to do is inverting the dependencies. The actual implementation of the abstraction, or dependencies, need to be provided from outside of the class. By doing so, the class now does not depend on the implementation, but only the abstraction of it.

In section one, I use constructor dependency injection as the dependency inversion method. In this section, I will discuss various methods of dependency inversion methods.

Here the list of dependency inversion methods:
  1. Using Dependency Injection
  2. Using Global States
  3. Using Indirection
Below, I will explain each of the methods.

1. Using Dependency Injection

Using Dependency Injection(DI), is where the dependency is injected directly to a class via its public members. The dependency can be injected into class's constructor (Contructor Injection), set property (Setter Injection), method (Method Injection), events, index properties, fields and basically any members of the class which are public. I generally don't recommended to use fields because exposing fields are not considered a good practice in Object-Oriented programming, as you can achieve the same thing using properties. Using index properties for dependency injection is also a rare case, so I will not explain it further.
Constructor Injection
I used mostly Constructor Injection. Using Constructor Injection can also leverage on some features in IoC container, such as auto-wiring or type discovery. I will discuss about IoC container in section 5 later. Below is an example of Construction Injection:
public class HighLevelModule
{
  private readonly IOperation _operation;

  public HighLevelModule(IOperation operation)
  {
    _operation = operation;
  }

  public void Call()
  {
    _operation.Send();
  }
}
Setter Injection
Setter and Method Injection are used to inject dependencies after the construction of the object. This can be seen as disadvantages when using along with IoC container(will be discussed in section 5). However if you don't use IoC container, they achieve the same thing as the Constructor Injection. The other benefit of Setter or Method Injection is to allow you to alter the dependency on the runtime, and they can be used in complement of Constructor injection. Below is an example of Setter Injection, which allow you to inject one dependencies at a time:
public class HighLevelModule
{
  public IOperation Operation { get; set; }

  public void Call()
  {
    Operation.Send();
  }
}
Method Injection
The Method Injection allow you to set multiple dependencies at the same time. Below is the example of Method Injection:
public class HighLevelModule
{
  private readonly IOperation _operationOne;
  private readonly IOperation _operationTwo;

  public void SetOperations(IOperation operationOne, IOperation operationTwo)
  {
    _operationOne = operationOne;
    _operationTwo = operationTwo;
  }

  public void Call()
  {
    _operationOne.Send();
    _operationTwo.Send();
  }
}
When using Method Injection, the dependencies that are passed as arguments will be persisted in the class, e.g as fields or properties, for later use. When passing some classes or interfaces in the method and to be used just in the method, this does not count as Method Injection.
Using Events
Using Events is limited only for delegate type injection, and this is only appropriate where subscription and notificatin model is required, and the delegate must not return any value, or just returning void. The caller wil subsribe a delegate to the class that implements the event, and there can be multiple subscribers. The event injection can be performed after the object construction. Injecting events via constructor is uncommon. Below is an example of event injection.
public class Caller
{ 
  public void CallerMethod()
  {
    var module = new HighLevelModule();
    module.SendEvent += Send ;

    ...
  }

  public void Send()
  {
    //this is the method injected into HighLevelModule
  }
}

public class HighLevelModule
{
  public event Action SendEvent = delegate {};

  public void Call()
  {
    SendEvent();
  }
}

In general, my mantra is always use Constructor Injection if nothing compelling to use Setter or Method Injection, and this is alaso to enable us to use IoC container later.

2. Using Global States

Instead of injected directly into the class, the dependency can be retrieved from a global state from inside the class. The dependency can be injected into the global states and later accessed from inside the class.

  public class Helper
  {
    public static IOperation GlobalStateOperation { get; set;}
  }

  public class HighLevelModule
  {
    public void Call()
    {
       Helper.GlobalStateOperation.Send();
    }
  }
   
  public class Caller
  {
    public void CallerMethod()
    {
      Helper.GlobalStateOperation = new LowLevelModule();

      var highLevelModule = new HighLevelModule();
      highLevelModule.Call();
    }
  }  
}

Global states can be represented as properties, methods or even fields.The important bit is that the underlying value has public setter and getter. The setter and getter can be in the form of methods instead of properties.

If the global state has only getter (e.g. singleton), the dependency is not inverted. Using global states to invert dependencies is not recommended, as it makes dependencies less obvious, and hides them inside the class.

3. Using Indirection

If you are using Indirection, you don't pass dependency directly into the class. Instead you pass an object that capable of creating or passing the implementation of the abstraction for you. This also means that you create another dependency for the class. The type of object you pass into the class can be:
  • Registry/Container object
  • Factory object
You can choose whether to pass the object directly (Dependency Injection) or using Global States.
Registry/Container object
If you use a register, this often called Service Locator Pattern, then you can query the register to return an implementation of an abstraction(e.g. interface). However, you will need to register the implementation first from outside the class. You can also use a container to wrap up the registry like many IoC container frameworks do. A container normally has additional features such type discovery or auto-wiring, so when you register an interface and its implementation, you don't need to specify the dependencies of the implementation class. When you query the interface, the container will be able to return the implementation class instance by resolving all its dependencies first. Of course, you will need to register all the dependencies first.

In the early days when IoC container frameworks just sprung up, the container was often implemented as a Global state or Singleton instead of explicitly passing it into a class, and this was now considered as anti-pattern. Here is an example of using a container type object:

public interface IOperation
{
  void Send();
}

public class LowLevelModule: IOperation
{
  public LowLevelModule()
  {
    Initiate();
  }

  private void Initiate()
  {
    //do initiation before sending
  }
  
  public void Send()
  {
    //perform sending operation
  }
}

public class HighLevelModule
{
  private readonly Container _container;

  public HighLevelModule(Container container)
  {
    _container = container;
  }

  public void Call()
  {
    IOperation operation = _container.Resolvel<IOperation>();
    operation.Send();
  }
}

public class Caller
{
  public void UsingContainerObject()
  {
     //registry the LowLevelModule as implementation of IOperation
     var register  = new Registry();
     registry.For<IOperation>.Use<LowLevelModule>();

     //wrap-up registry in a container
     var container = new Container(registry);
      
     //inject the container into HighLevelModule
     var highLevelModule = new HighLevelModule(container);
     highLevelModule.Call();     
  }
}


You can even make HighLevelModule depends on the abstraction of the container, but this step is not necessary.

Moreover, using a container or registry everywhere in the classes may not be a good idea, as this makes it less obvious what the class's dependencies are.
Factory object
The difference between using a register/container and a factory object is that when using a register/container you need to register the implementation class before you can query it, while using a factory you don't need to do that as the instantiation is hardcoded in the factory implementation. Factory object is not neccesary to have 'factory' as parts of its name. It can be just a normal class that return an abstraction (e.g interface).

Moreover, because LowLevelModule instantiation is hardcoded in the factory implementation, having HighLevelModule depends on the factory will not invert the LowLevelModule dependency. In order to invert the dependency, HighLevelModule needs to depend on the factory abstraction instead, and the factory object needs to implement that abstraction. Here is an example of using a factory object:

public interface IOperation
{
  void Send();
}


public class LowLevelModule: IOperation
{
  public LowLevelModule()
  {
    Initiate();
  }

  private void Initiate()
  {
    //do initiation before sending
  }
  
  public void Send()
  {
    //perform sending operation
  }
}

public interface IModuleFactory
{
   IOperation CreateModule();
}

public class ModuleFactory: IModuleFactory
{
  public IOperation CreateModule()
  {
      //LowLevelModule is the implementation of the IOperation, 
      //and it is hardcoded in the factory. 
      return new LowLevelModule();
  }
}

public class HighLevelModule
{
  private readonly IModuleFactory _moduleFactory;

  public HighLevelModule(IModuleFactory moduleFactory)
  {
    _moduleFactory = moduleFactory;
  }

  public void Call()
  {
    IOperation operation = _moduleFactory.CreateModule();
    operation.Send();
  }
}

public class Caller
{
  public void CallerMethod()
  {
     //create the factory as the implementation of abstract factory
     IModuleFactory moduleFactory = new ModuleFactory();
      
     //inject the factory into HighLevelModule
     var highLevelModule = new HighLevelModule(moduleFactory);   
     highLevelModule.Call();  
  }
}

My recommendation is to use indirection sparingly. Service Locator Pattern, is seen as anti-pattern nowadays. However from time to time you may need to use a factory object to create the dependencies for you. My Ideal is to stay clear from using Indirection, unless it is proven necessary.

Besides the implementation of abstraction (interface, abstract class or delegate), we normally can also inject dependencies for primitive type such as Boolean, int, double, string or just a class which contain only properties.

No comments:

Post a Comment