Dependency Injection Analogy In Design Pattern

There are many articles already discussing the details of various dependency injection implementations in a C# context, there's no point me writing another one. What I want to add is an analogy that helps you understand the benefits of DI. It's not always clear how DI adds to the efficiency of what you're coding, hopefully this will help a bit by showing you what you would end up with prior to introducing DI.

Let’s start with the following class:

  1. public class CordlessDrill  
  2. {  
  3.     public void Drill()  
  4.     {  
  5.         var drillBit = new DrillBit();  
  6.         drillBit.Turn();  
  7.     }  
  8. }  


The cordless drill we just defined has the ability to drill things, but it depends on a drill bit to do so (obviously). Therefore, we instantiate a drill bit, and we turn it by calling Turn on the new drill bit object. These classes are thought of as ‘tightly-coupled’, as the cordless drill class directly depends on the concretion of a DrillBit object. All good so far, the drill does the job.
But now we’ve come across a different task that needs doing; we need to saw wood. Well the class we’ve just wrote is useless to us, so we create another class – another tool to do this particular job:
  1. public class CordlessSaw  
  2. {  
  3.     public void Saw()  
  4.     {  
  5.         var sawBlade = new SawBlade();  
  6.         sawBlade.Saw();  
  7.     }  


Again, a class tightly-coupled with another class, designed to do a specific job.

We’ve got enough tools to do both jobs so far, but as we tackle new tasks and their requirements, we’ll need new classes. It’s inefficient to keep writing these ‘cordless’ classes (i.e. carry around as many tools as there are tasks), when in fact they share some common functionality. And that’s when it’s time for dependency injection.

If we disregard the actual details (the ‘implementation’) of the power tool’s dependency (i.e. whether it’s a saw or a drill bit), we can focus on the fact that it needs an attachment of some sort and that it will provide power to that attachment – that’s it’s only job. At this point, what does being this vague allow us to do? It allows us to define the attachment just like an  attachment. An abstraction, rather than a concrete implementation. We could translate this, in real terms, to “I know the tool needs an attachment, I don’t care whether it’s a drill bit or saw blade”. Our code can then start to look as in the following code snippet:
  1. public interface IAttachment  
  2. {  
  3.     void DoTask();  
  4. }  
  5.   
  6. public class DrillBit : IAttachment  
  7. {  
  8.     void DoTask()  
  9.     {  
  10.         // drill stuff  
  11.     }  
  12. }  
  13.   
  14. public class SawBlade : IAttachment  
  15. {  
  16.     void DoTask()  
  17.     {  
  18.         // saw stuff  
  19.     }  
  20. }  
  21.   
  22. public class CordlessTool  
  23. {  
  24.     private IAttachment _attachment;  
  25.   
  26.     public CordlessTool(IAttachment attachment)  
  27.     {  
  28.         _attachment = attachment;  
  29.     }  
  30.   
  31.     public void GivePowerToAttachment()  
  32.     {  
  33.         _attachment.DoTask();  
  34.     }  
  35. }  


It’s clear to see the benefits of what we’ve just done. We’ve got a single class now responsible for giving power to the attachments, and doing only that. It’s more maintainable, less cumbersome, it’s easily extendible and last, but not least, is that it can be unit tested without a regard for what IAttachment actually does as part of DoTask() (see ‘mocking’).

There are a few things that haven’t been covered here, as they have been well covered in the other articles I mentioned earlier. For example, which IAttachment is injected into the CordlessTool class is a decision that does need to make at some point. Typically, this is done with an inversion of control container. Also, we’ve opted for constructor injection here – as it most suits the requirements of this example. There are other ways of injecting dependencies.