If you’re not familiar with the term “Inversion of Control”( IoC ) or “Dependency Injection” ( DI )you may wish to start with Martin Fowler’s post on the subject. If you’re looking for a way to do it with Perl, Bread::Board is the way to go. This post however is about the theory behind it, and a path to grokitude if you’re finding the concepts challenging. I should advise that I am not yet a buddha on implementation.
What is it?
Now that you’re familiar with an understanding of the terms that is not mine (or even if you didn’t bother), you may be wondering what I mean by “Inversion of Control Principle”, seeing as how we have the Dependency Injection Pattern and Inversion of Control Containers. I’m not sure if anyone actually uses the term “Inversion of Control Principle”, though google seems to suggest I am not the first.
The essence of the Inversion of Control Principle is do not attempt to control your code, let its callers control it. Give your caller as much power as you can.
This is of course not a very academic statement.
Dependency Inversion
An important piece of the Inversion of Control Principle is the common “Dependency Inversion”. It is defined as:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend upon details. Details should depend upon abstractions.
In more laymans terms depend on an interface and not a concrete implementation. For the Perler’s. Do not depend on LWP or DBI, but depend on their interface. This is covered more in my post on Interface Driven Design.
Lifecycle Inversion of Control
A second piece of Inversion of Control is do not attempt to control your objects lifecycle. This means, do not enforce a singleton pattern, do not enforce a flyweight pattern, do not enforce an instance. Your objects should be instances always, if you want a singleton put it at the top level of your application or use a Dependency Injection framework to manage the lifecycle. Also package variables that are not constants are evil, they will bite you because they are essentially singletons. Always make your objects instances and let their client code determine their lifecycle.
How do I do it?
Well you don’t have to use a fancy IoC/DI framework to do it. Those are simple tools to make life easier after you’ve designed your software to be consumed by them. To properly invert your control you first start by letting go. Huh? yeah I know.
You start by ensuring that your software has as few concrete dependencies as possible. Like I said you depend on interfaces. Some concrete dependencies are unavoidable, but try to make them uninteresting, or architecturally significant.
With Perl
Start by ensuring that concrete dependencies can be easily replaced at runtime. The easiest way to accomplish this is by allowing them to be passed to your constructor. when using Moose I tend to use lazy loading defaults a lot. Here’s an example from cybersource.
|
|
Although I don’t consider it likely that anyone will ever need to use another WSDL for Cybersource, one could easily inject a new Path::Class::File to one when creating the client at the constructor. Another perfect example is the Client itself. It’s designed to be useable as a singleton, because I never intend for someone to need to instantiate it twice, however I have not made it a singleton and internally I’m not aware of any code that it actually is treated as such (it appears to usually be instantiated at every request ).
Prefer Class, and Object interfaces to functional ones. Here’s some code that demonstrates why.
|
|
I have even better real examples, such as my one in interfaces where I replaced LWP with the AnyEvent::LWP… but unfortunately that code has not been released to the public. The examples given are basically the same principle.
Dependency Injection Frameworks
Dependency Injection Frameworks are not required to use the Inversion of Control Principle they simply make injecting your dependencies and managing lifecycles easier. It also allows you to do concrete class substitution from one place along with deciding that objects lifecyle, and have it’s dependencies inserted. When using a DI framework you should strive to call it only once per controller. Though a book that I’ve read suggests this is Domain Model controller and not an MVC controller, I was unable to make a full distinction.
This example runs particularly slow, probably due to reasons that people hate moose, however, if you remove the loop at the end you’ll notice executing one time is about the same as ten, which means that most of this is due to class compiling and loading, actual runtime is fast (or at least, fast enough).
The important thing to pick up is how the Order object requires a payment_gateway
that does submit
, but it doesn’t
know how to get that object, or what the implementation will be. Our Dependency Injection framework then simply
provides that dependency as needed. It would be trivial to replace our implementation of payment gateway with a
different one. The code is commented, to explain what it’s doing where.
|
|