I C the IOC Pattern
In my recent travels, I came across something about the "IOC Pattern". I like learning from design patterns and I hadn't heard of this one before so I decided to investigate. As with many of the design patterns out there, the IOC pattern simply puts a name to something I've done and seen in the past in a slightly different context.
Anyhow, as it turns out "IOC" refers to "Inversion of Control" and is a popular pattern in the Java community. Inversion of Control itself is actually a very generic term used to describe a situation where some kind of control is moved or delegated elsewhere.
For example, it can be used to describe the difference in flow control between a console app that takes input and therefore has control over when its methods are called (Console.WriteLine() followed by Console.ReadLine()) and a Windowed application where the Windowing system controls when that application's methods are called (via callbacks).
Martin Fowler insightfully points out that Inversion of Control differentiates a library from a framework and also refers to it as:
The Hollywood Principle - "Don't call us, we'll call you"
In this particular context, the term "IOC pattern" is referring to how dependencies are resolved and it solves a similar problem as the Abstract Factory pattern except in a far more complete manner by not only instantiating a particular concrete implementation of an interface but also by hooking up all of its dependencies.
Here's the situation: you want to have decoupled components which interact through interfaces. These interfaces can be defined in a separate assembly from the users of those interfaces as well as the implementers of those interfaces. Hence the decoupling. There is the problem of how to hook up the interfaces with concrete implementations while maintaining that decoupling.
This is where the inversion of control occurs, instead of having the user of the interface take a dependency to the implementer, it is inverted to an "assembler" or "container" that is a part of a framework that handles the plumbing of the dependencies.
There is a good article with clear diagrams and code samples that describes this in detail here.
From the article, I find that its pretty neat how there are frameworks like PicoContainer, NanoContainer and Spring. It certainly eliminates having to write that plumbing code yourself.
Most notably in scenarios that involve complex chains of dependent classes, the framework will take care of the plumbing and it makes it easier to support multiple clients that have subtle differences in those chains. This mechanism allows you to have a lot of flexibility in terms of the aggregation of interface implementations even after deployment.
That said, for a good majority of Windows Forms application, I would be wary of using frameworks such as these. Mainly due to the performance impact of instantiating and plumbing all of your dependencies together via reflection. There is a cost of reflection. Yes, the cost varies with the complexity of the dependency chains, however, so does the value of the pattern. Its about finding the right balance.
I'm not saying I wouldn't maintain the decoupling between components. In particular, I would make the container as performant as I can, most likely through hard coding and using "new" instead of CreateInstance, even if that means reducing the flexibility.
Like most things, it really depends on the situation and what you are trying to accomplish. This is a very powerful and appropriate mechanism for plug-in scenarios however, if all this plumbing occurs at the startup of a Windows Forms application, it may be more important to optimize for that performance even if it breaks the reusable generic IOC "framework" solution. The Java scenarios that use this don't have to deal with the desktop application startup performance issue.
Which reminds me of this quote Rico Mariani had posted a while back:
“In the last ten years we have encountered quite a few C++ projects that fell far short of their required performance. At the heart of those performance failures was a design and an implementation geared for extreme flexibility and reusability. The obsession with reusable code has produced software that, due to the lack of efficiency, was not even usable, not to mention reusable. ... The problem is that performance and flexibility are often enemies. It is not an artifact of poor design or of implementation. We are talking about a fundamental tension between two forces that are pushing in opposite directions.”
—Dov Bulka, David Mayhew, Efficient C++: Performance Programming Techniques, p. 223.