(IoC) is an old principle in object oriented programming, but it’s
gotten a lot of buzz the last couple of years because of the
proliferation of Dependency Injection (DI) tools and articles. Dependency Injection is a significant flavor of the broader Inversion of Control principle, but it’s not the whole enchilada. IoC is an important technique to make your code easier to test with TDD, but the other major benefit is extensibility.
Inversion of Control is also known as the “Hollywood Principle” – “don’t call me, I’ll call you.” In
short, this principle applies when you create classes that are passive,
i.e. they do not direct the executing flow of the code. It also applies to classes that are ignorant of the services that act upon them instead of directing the services themselves.
The original usage of the term Inversion of Control referred to the creation of reusable whitebox frameworks that could be extended by creating plugin classes. The
general idea being that you extend the existing behavior of a
framework by creating a class implementing an interface from the
framework. Your new class doesn’t call the framework; the framework code calls the new class.
The Plugin pattern as described by Martin Fowler in the PEAA book is a common pattern. I went looking for examples today and found several just by looking in my current project and at tools that we use every day. CruiseControl.Net is a very “pluggable” framework. CC.Net
has to work with multiple source control systems (Subversion, CVS,
Perforce, etc.), different build tools (NAnt or MSBuild), and different
strategies for publishing build results. A former colleague has been setting up a CC.Net server against a CC/Harvest (unusable pile of manure) source control repository. CC.Net doesn’t support Harvest out of the box, but it’s not a show stopper. Just create a class that implements CC.Net’s ISourceControl interface and configure CC.Net to call the new class in the CCNet.config file.
<intervalTrigger seconds=”60″ />
<!– Configure the connectivity to Subversion ISourceControl plugin and establish the working directory –>
<!– Configure the NAnt builder block –>
<!– Merge in test results from the NUnit tests –>
My StructureMap tool has a couple of plugin patterns to extend the basic framework with additional options. I wanted multiple options for project configuration. Simple projects would probably just use the basic XML configuration in the project path. Larger
multi-server applications might push a team to use a central database
or some sort of LDAP tree structure to store the configuration in a
common place. StructureMap uses classes that extend the MementoSource abstract class to fetch configuration data for instance graphs. To create and use a new means of configuration I first create a new class that inherits from MementoSource and override the virtual and abstract methods. StructureMap is directed to use the new MementoSource in the StructureMap.config file in the project directory.
<!—Configure this PluginFamily to use an XmlFileMementoSource that pulls configuration from an Xml file named “FullTesting.XML” —>
<PluginFamily Type=”StructureMap.Testing.Widget.Column” Assembly=”StructureMap.Testing.Widget”>
<Source Type=”XmlFile” FilePath=”FullTesting.XML” XPath=”Columns” NodeName=”Columns” />
Here are some other examples of the Plugin pattern:
- Log4Net provides extensible logging options. If
none of the two dozen or so built in logging storage mechanisms fit
your needs, you can happily create a custom implementation of the IAppender interface for customized logging.
- It’s very straightforward and often advantageous to create custom tasks to use in NAnt build files.
- The Provider model in .Net 2.0 is an example of the Plugin pattern (and the Service Locator variant of Dependency Injection).
The Open/Closed Principle
“Software entities (Classes, Modules, Functions, etc.) should be open for extension but closed for modification.”
This sounds like a contradiction at first glance, but it’s not. As explained by Robert Martin here,
the Open/Closed Principle (OCP) pushes you to design software in such a
way that new functionality can be added to system by creating brand new
code rather than modifying gobs of existing code. The thinking behind the OCP is to make a system easier to change by leaving currently working code alone, or at least minimizing the amount of code modification needed to add the new behavior. An abstracted Plugin pattern is one of the best ways to follow the OCP.
I used to think that
the OCP was only important for systems that really needed a lot of
extensibility, but I’ve found that following the Open/Closed Principle
helps make code that can evolve inside of an incremental design process. You
don’t necessarily have to use a Plugin pattern class that’s loaded via
reflection and configuration to get the benefits of the OCP. You do, however, have to follow the Dependency Inversion Principle to reap the benefits of the OCP.
A Word of Caution
Don’t use a Plugin pattern until it’s really necessary. Speculative
usage of the plugin pattern everywhere you think you’ll need
flexibility later is a quick path to a sclerotic, over-designed
application that nobody wants to work with. Not that I’ve ever
been guilty of that. The Plugin mechanism creates more
indirection in your code. That leads to additional intellectual overhead trying to understand what’s happening in your code base. I’m tangentially involved with a code base that extensively uses classes loaded by reflection. The end result in this case is simply an application that is harder to debug and trace (and impervious to ReSharper code navigation). If
you’re using the plugin pattern, I’d recommend using an existing tool
like StructureMap to take advantage of the diagnostics and
One of the most
wrong-headed sentenced in all of software development is “let’s build
the framework first, and then the application will be easy.” No, no, no. I’m never going to be a part of (or be the cause of) this exercise in stupidity ever again.
My next two posts
will talk about using Inversion of Control for improved testability and
using Dependency Injection as a crucial technique for TDD.