[ProgClub list] What is IoC?

Stuart Laughlin stuart at bistrotech.net
Tue Oct 11 08:35:08 AEDT 2011


Here is part two. This one is on Dependency Injection. I made sure the
code compiles this time. By the end of part two you probably have
enough information to know what IoC is in a concrete sense, but I
still haven't broached the subject of IoC containers.

~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~


Dependency Injection
====================

So far we have considered a common software design practice, and we
have identified some problems with it. When we design a class to
instantiate its own dependencies, we are susceptible to nasty
dependency chains, we inhibit the class's ability to polymorph, and we
diminish our ability to specify behavior at the application level. In
all likelihood you have experienced these problems firsthand and have
developed your own techniques to avoid or compensate for them. Perhaps
you've even been applying the principle of Inversion of Control and
simply were not aware of it in formal terms. At any rate, one way to
solve these problems, and the way we will address them here, is by
applying the Inversion of Control principle. In C# and other static
languages, a common technique of applying IoC is Dependency Injection.
As the name suggests, Dependency Injection is a technique whereby we
provide a class instance with all of the components upon which it
depends. As developers have employed this technique, a couple patterns
have emerged which provide useful conventions for dependency
injection. These patterns have been termed Constructor Injection and
Setter Injection. Since constructor injection seems to be the more
commonplace and useful, we will focus on that first.


Inversion of Control via Dependency Injection via Constructor Injection
-----------------------------------------------------------------------

Perhaps the best way to provide a class instance with its required
dependencies is to pass them in on the constructor. The conventional
way to do this is to create a constructor parameter and a private
readonly instance field for each dependency. The constructor then maps
each constructor parameter to the corresponding field. Returning to
our previous code example, where our Notifier depends upon an instance
of EmailSender, we might try something like this...

public class Notifier {
    private readonly EmailSender sender;

    public Notifier(EmailSender sender) {
        this.sender = sender;
    }

    public void NotifyUsers(IEnumerable<User> users, string message) {
        foreach (var user in users) {
            this.sender.Send(message, user);
        }
    }
}

While this demonstrates constructor injection, it doesn't actually
solve any of the problems we've observed. Our Notifier class needs to
be more abstract, and the easiest way to achieve this is to program to
an interface.

	public interface ISender
	{
		void Send (string message, User user);
	}

	public class Notifier
	{
		private readonly ISender sender;

		public Notifier (ISender sender)
		{
			this.sender = sender;
		}

		public void NotifyUsers (IEnumerable<User> users, string message)
		{
			foreach (var user in users) {
				this.sender.Send (message, user);
			}
		}
	}
	
Now we can make EmailSender implement ISender and pass an instance of
EmailSender into Notifier from our Main method.

	public class EmailSender : ISender
	{
		public void Send (string message, User user)
		{
			string host = getHostFromConfig();
			/* send email using SmtpClient or similar */
		}
	}

	public class MainClass
	{
		public static void Main(string[] args)
		{
			var process = new SomeProcess();
			var result = process.Run();
			
			var admins = new List<User> {
                                new User { Email = "sclaughl" },
                                new User { Email = "jj5" }
            };
			
			var notifier = new Notifier(new EmailSender());
			notifier.NotifyUsers(admins, result);
		}
	}

Let's evaluate the problems we identified earlier. What does
Notifier's dependency chain look like? At this point it doesn't
instantiate any classes at all. Rather than depending upon
EmailSender, it now depends on ISender, which carries no dependencies
at all [1]. Our ISender can reside in the same assembly as Notifier
with a slim set of references, while our EmailSender can reside in a
separate assembly with references to all the requisite System.Net.Mail
classes. What about polymorphism? At this point we can pass any
ISender into Notifier -- SmsSender, ConsoleSender, TestSender,
LogSender, etc. -- and get our desired functionality. Finally, we are
controlling Notifier's behavior from our Main method, rather than
relying on Notifier to make its own decisions about how to behave.

Next time we will explore Setter Injection and strategies for managing
the myriad dependencies that need to be injected in a non-trivial
application.


1: Actually Notifier depends on System.dll for
System.Collection.Generic.IEnumerable<> and whichever assembly
contains User. I am overlooking these dependencies for the sake of
this example. In our hypothetical system, System.dll and the assembly
that contains our domain objects (e.g. User) are referenced
ubiquitously.

~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~


--Stuart



More information about the list mailing list