So far I’ve covered a much of the basics of Dependency Injection. There’s a lot more to it, and plenty more to discuss, but for this article, I want to stop and discuss a few items that I have kind of glossed over. So without further ado, here they are in my beloved bullet form:
- I have been, as you’ve likely noticed, encouraging you to use interfaces when coding, and registering classes as implementing those interfaces with the framework. What I think I failed to mention explicitly is that all interfaces registered with the Spring Framework have to have a GUID associated with them. You can add a GUID any time you want into your code by pressing CTRL+SHIFT+G. If you try to use an interface that doesn’t have a GUID, you’ll get this error: “Project <projectname>.exe raised exception class ERegistrationException with message 'Non-Guid Interface Services are not supported.”
- If you’ve looked closely at the code in the demos, you might have noticed that before you can do anything with the Spring Container, you need to call GlobalContainer.Build; This method needs to be called before you can get anything out of the ServiceLocator. The Build method is the code that gathers up all the registered classes and either creates them or enable them to be created on demand, depending on the lifetime type that you have selected. If you get the error 'LifetimeTypeManager was expected.' when you run your app, it likely means that you have faced to call Build on your container. And in fact, the Build method is really the main purpose of your DI Container. You should call Build once, and at the root of your application. For Delphi developers, this means that it probably ought to be called as one of the very first things in the DPR file. This also means that you need to register your classes as early as possible as well.
- You don’t have to use the Global Container and the Service Locator provided by the framework. You are more than welcome to create your own and expose the functionality as you want. What we have done here at Gateway Ticketing is to declare an interface that is a complete abstraction of the notion of a DI Container, and then implement the interface ourselves using a TContainer descendant from the Delphi Spring Framework. That way, if the framework changes (and it has since we started) or if we even decide to use a different container, our code is completely decoupled from any particular implementation. Just another great example of “Code against abstractions, not implementations.”
- I feel compelled to point out that the whole concept of a Service Locator is considered an “anti-pattern” by some. I haven’t come to a firm conclusion on this issue myself. Yes, a Service Locator is almost always a Singleton, but I personally don’t view read-only singletons to be bad as do some. (A read-write singleton is really a global variable, and I think we can all agree that global variables are the Spawn of Satan.) However, there appears to be some dispute as to whether the use of a container is indeed a Service Locator. Also, if all of your dependencies are defined before execution is available to the user, then is a Container really a variable at all? It remains a matter of debate. Ultimately, I guess I view a Service Locator as so valuable and useful so as to out-weigh any of the drawbacks they might have.
- There is a weakness here -- and which gets to the previous point – in that things are actually so decoupled and late-binding is so explicit that it is indeed possible to get a successful build and not know that you forgot to register a needed implementing class until runtime. And, in a complex system, it might be a long time before anyone notices that the implementing class for a seldom used interface is missing. If you follow the pattern that I have shown of registering an implementation in the initialization section of a unit, then you must use that unit somewhere in your app to actually have the registration take place. And as mentioned, if you forget to add it, and don’t actually include that unit in your app, the compiler won’t tell you and you will only find out at runtime. This is a weakness and you need to be very careful to ensure that you don’t fall into this trap. Strategies might include a single unit for doing all of your registration, or some type of static analysis that ensures that every call to GetService has a corresponding RegisterService call (or whatever your methods are called). Something to be aware of.
Those are just a few things that you might want to consider as you integrate Dependency Injection into your coding techniques. I’ve said it before, and I’ll say it again: If you aren’t doing Dependency Injection, you're doing it wrong.