Dec 23 2008

Murder mysteries and Dependency Injection

Category: C# | Patternsfossmo @ 07:50

 

imageTo try to explain Dependency Injection and Inversion of Control Containers, I'm going to get inspiration from some old detective novels. When I say old, I think of novels like the ones Agatha Christie wrote. This first post will explain Dependency Injection, and the second one will explain Inversion of Control Containers. To start our story we need some characters.

Please welcome:
The Colonel - This is the old guy with the white mustache and monocle.
The young man - He is in love with the woman, and is always trying to show his interest for her.
The woman - She is a countess and have a very rich father (probably the reason the young man got into her in the first place).
The butler - He is always the killer, likewise in this story. 
The detective - Always traveling around running into mysteries to solve.

We should probably have at least ten characters more, but this will do for our examples. They are all invited to a big mansion to spend the weekend there. 

To get our story going, we need a weapon. Let's create one.

  1:     public class Gun
  2:     {
  3:         public void UseWeaponOn(string target)
  4:         {
  5:             Console.WriteLine(string.Format("{0} is shot dead!", target));
  6:         }
  7:     }

In most of the "Agatha Christie-like" detective novels, the butler is the villain. In this story he is the villain to. Let's create a butler so he can start committing crimes (read: killing ;-) ).

  1:     public class Butler
  2:     {
  3:         private Gun _gun;
  4: 
  5:         public Butler()
  6:         {
  7:             _gun = new Gun();
  8:         }
  9: 
 10:         public void Kill(string target)
 11:         {
 12:             _gun.UseWeaponOn(target);
 13:         }
 14:     }

The butlers first target is the colonel:

  1:     class Program
  2:     {
  3:         static void Main(string[] args)
  4:         {
  5:             Butler butler = new Butler();
  6:             butler.Kill("The Colonel");
  7:             Console.ReadKey();
  8:         }
  9:     }

This is what happens after the code is run:

image

The butler has killed his first victim. But, he has a problem. After killing the colonel, he have to find a new weapon for his next kill, because the detective is snooping around the mansion and he has found the murder weapon. This also gives us a problem. Since the gun is created inside the butler class's constructor, we have to modify the implementation of the class in order to make this change.

Coupling or dependency is the degree to which each program module (class) relies on each one of the other modules (classes). The butler class is tightly coupled to the gun class. When modules are tightly coupled, they cannot be replaced without altering their implementation. In order to avoid tightly coupled classes, we can use interfaces to provide a level of abstraction. Let's create an interface to represent a weapon.

  1:     public interface IWeapon
  2:     {
  3:         void UseWeaponOn(string target);   
  4:     }

Then we can implement the interface in the gun class.

  1:     public class Gun:IWeapon
  2:     {
  3:         public void UseWeaponOn(string target)
  4:         {
  5:             Console.WriteLine(string.Format("{0} is shot dead!", target));
  6:         }
  7:     }

And now we can change the butler class.

  1:     public class Butler
  2:     {
  3:         private IWeapon _weapon;
  4: 
  5:         public Butler()
  6:         {
  7:             _weapon = new Gun();
  8:         }
  9: 
 10:         public void Kill(string target)
 11:         {
 12:             _weapon.UseWeaponOn(target);
 13:         }
 14:     }

There is still a small problem with the coupling. The gun class is still created inside the butler class. How can we change this? Well, here is the solution:

  1:     public class Butler
  2:     {
  3:         private IWeapon _weapon;
  4: 
  5:         public Butler(IWeapon weapon)
  6:         {
  7:             _weapon = weapon;
  8:         }
  9: 
 10:         public void Kill(string target)
 11:         {
 12:             _weapon.UseWeaponOn(target);
 13:         }
 14:     }

We inject the weapon through the butler's constructor and create the gun object outside the butler class.

  1:     class Program
  2:     {
  3:         static void Main(string[] args)
  4:         {
  5:             IWeapon weapon = new Gun();
  6:             Butler butler = new Butler(weapon);
  7:             butler.Kill("The Colonel");
  8:             Console.ReadKey();
  9:         }
 10:     }

Now we have a butler class that is loose coupled to the gun class. It is now easy for us to create a new weapon when the butler starts killing people again. In every old detective novel there need to be a murder committed using poison. Arsenic, of course. We then need to create a new weapon class:

  1:     public class Poison:IWeapon
  2:     {
  3:         public void UseWeaponOn(string target)
  4:         {
  5:             Console.WriteLine(string.Format("{0} is killed by poisoning.", target));
  6:         }
  7:     }

To convince his victim to drink the poison, the butler needs to persuade the poor person to drink it. We need to create a communication class.

  1:     public class Communicate
  2:     {
  3:         public void Speak(string phrase)
  4:         {
  5:             Console.WriteLine(phrase);
  6:         }
  7:     }

To make this loose coupled, we create a interface for the communication class too.

  1:     public interface ICommunicate
  2:     {
  3:         void Speak(string phrase);
  4:     }

This means that we need to change the butler class to also inject the communication class.

  1:     public class Butler
  2:     {
  3:         private IWeapon _weapon;
  4:         private ICommunicate _communicate;
  5: 
  6:         public Butler(IWeapon weapon, ICommunicate communicate)
  7:         {
  8:             _weapon = weapon;
  9:             _communicate = communicate;
 10:         }
 11: 
 12:         public void Kill(string target)
 13:         {
 14:             _weapon.UseWeaponOn(target);
 15:         }
 16: 
 17:         public void Speak(string phrase)
 18:         {
 19:             _communicate.Speak(phrase);
 20:         }
 21:     }

The butler has found a new victim; the young man.

  1:     class Program
  2:     {
  3:         static void Main(string[] args)
  4:         {
  5:             IWeapon weapon = new Poison();
  6:             ICommunicate communicate = new Communicate();
  7:             Butler butler = new Butler(weapon, communicate);
  8:             butler.Speak("Please drink this, Sir!");
  9:             butler.Kill("The young man");
 10:             Console.ReadKey();
 11:         }
 12:     }

The output we get is this:

image 

After a few days the detective gathers all the people living, and working, in the mansion in the big living room. He then starts to explain who the killer is. The colonel was killed by a mistake. The real target was the young man.  It turns out that the butler is secretly in love with the woman, and wanted to get rid of all men interested in her. He killed the young man because he saw the woman get a bit intimate with him.

If the butler hadn't been stopped, he would continue to kill people that were interested in the woman. He would probably use a different weapon next time. A weapon that maybe didn't require him to speak. Instead of us creating a communication object and input a null value, we can do a Poor man's dependency injection. That means overloading the constructor this way:

  1:         public Butler(IWeapon weapon): this(weapon, new Communicate())
  2:         {}

Doing this, the communication object will be created automatically.

A nice quote that fit into this context is:

"It's OK to figure out murder mysteries, but you shouldn't need to figure out code. You should be able to read it."
- Steve McConnell (Code Complete)

In the next part I will try to explain what IoC containers are.

Tags:

Currently rated 5.0 by 10 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Comments

Comments are closed