By Vincent ParrettOn October 28, 2013
When working on DUnitX recently, I wanted a simple IoC container for internal use, and didn't want to add any external dependencies to the project. So I wrote my own. My container implementation only does IoC, it does not (and nor will it ever) do dependency injection. If you need Dependency Injection, the Delphi Spring Framework includes is a comprehensive IoC/DI container (along with some other useful stuff).
Simple IoC is a copy of the IoC container in DUnitX, extracted into it's own GitHub project. It's basically a single pas file, with a class TSimpleIoC. Lets take a look at TSimpleIoC :
TSimpleIoC = class
class function DefaultContainer : TSimpleIoC;
//Registration methods. Register the interface type, and an implementation.
//Exe's compiled with D2010 will crash when these are used.
//NOTES: The issue is due to the two generics included in the functions. The constaints also seem to be an issue.
procedure RegisterType<TInterface: IInterface; TImplementation: class>(const name : string = '');overload;
procedure RegisterType<TInterface: IInterface; TImplementation: class>(const singleton : boolean;const name : string = '');overload;
procedure RegisterType<TInterface: IInterface>(const delegate : TActivatorDelegate<TInterface>; const name : string = '' );overload;
procedure RegisterType<TInterface: IInterface>(const singleton : boolean;const delegate : TActivatorDelegate<TInterface>; const name : string = '');overload;
//Register an instance as a signleton. If there is more than one instance that implements the interface
//then use the name parameter
procedure RegisterSingleton<TInterface :IInterface>(const instance : TInterface; const name : string = '');
function Resolve<TInterface: IInterface>(const name: string = ''): TInterface;
//Returns true if we have such a service.
function HasService<T: IInterface> : boolean;
//Empty the Container.. usefull for testing only!
//Raise an exception if Resolve would return nil.
property RaiseIfNotFound : boolean read FRaiseIfNotFound write FRaiseIfNotFound;
There are two ways to use TSimpleIoC, either by just using the DefaultContainer class function (I wanted to call it Default, but that's a reserved word in Delphi!), or by creating an instance of TSimpleIoC and using the instance.
The main purpose of IoC is to decouple the interface from the implementation. Without IoC, I need to know (or rather the compiler does) that TMySmartService implements IMyService, and I need to instanciate TMySmartService to get an instance that implements IMyService. But what if I want to use multiple implementations? I guess I could do this :
function GetMyService(const implname : string) : IMyService;
if implename = 'dumb' then
result := TMyDumbService.Create
result := TMySmartService.Create;
But that's not very nice. What If I need to provide another implementation? This is where the use of an IoC Container simplify's things. The above code becomes this :
mySvc : IMyService;
mySvc := TSimpleIoC.DefaultContainer.Resolve<IMyService>;
mySvc := TSimpleIoC.DefaultContainer.Resolve<IMyService>('dumb');
Of course we still need to tell the IoC container how to instanticiate something that implements IMyService.
TSimpleIoC has a bunch of RegisterType overloads, the best one's of which only work on Delphi XE and up, the Delphi 2010 compiler falls over quite badly. I could have removed the D2010 support altogether, but we're using it here with DUnitX and FinalBuilder 7 (which is written in D2010). So, lets register our IMyService implementation with the container :
TMyServiceTest = class
In the above example, if we called Resolve<IMyService>, a new instance of TMySmartService would be created each time. Registering multiple implementations is as simple as giving the implementations names. You can of course also register Singletons, even already instantiated implemetations as a singleton.
Ok, so I said Simple IoC would never do DI, but you can fudge it using an activator delegate. An activator delegate is an anonymous function that will create an instance of your implementation. You can use this as a means to pass in constructor dependencies.
function : IMyService
result := TMyService.Create(TDependency.Create);
I did say fudge!
DUnitX has this IoC container, but it's called TDUnitXIoC. Note that DUnitX makes use of the DefaultContainer internally, so you should not call the Clear method on it. You would be better off creating your own container instance.
Simple IoC is open source, get it from GitHub
MicheleYou don't have to do dependency injection to do inversion of control. In this example, it's really just an implementation of the service locator pattern. It's not always practical to do dependency injection, but you can still achieve some form of decoupling.
Just for my understanding, what is a IoC good for, if no "Dependy injection" is made with it?As far as I've understood, it is good practice to decouple software functionalities by using interfaces instead of concrete implementation. To do so, I use DI with a "container".What do I need an IoC for, if not to decouple software?
Commenters please note, if you use these comments to advertise your products, your comments will not be approved.
Thanks Stefan, fixed.
There is a .DefaultContainer missing in the code snippet where you call Resolve.Currently it looks as if Resolve was a class method.