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
public
constructor Create;
destructor Destroy;override;
//Default Container
class function DefaultContainer : TSimpleIoC;
//Registration methods. Register the interface type, and an implementation.
{$IFDEF DELPHI_XE_UP}
//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(const name : string = '');overload;
procedure RegisterType(const singleton : boolean;const name : string = '');overload;
{$ENDIF}
procedure RegisterType(const delegate : TActivatorDelegate; const name : string = '' );overload;
procedure RegisterType(const singleton : boolean;const delegate : TActivatorDelegate; 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(const instance : TInterface; const name : string = '');
//Resolution
function Resolve(const name: string = ''): TInterface;
//Returns true if we have such a service.
function HasService : boolean;
//Empty the Container.. usefull for testing only!
procedure Clear;
//Raise an exception if Resolve would return nil.
property RaiseIfNotFound : boolean read FRaiseIfNotFound write FRaiseIfNotFound;
end;
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.
A quick refresher on IoC
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;
begin
if implename = 'dumb' then
result := TMyDumbService.Create
else
result := TMySmartService.Create;
end;
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 :
var
mySvc : IMyService;
begin
mySvc := TSimpleIoC.DefaultContainer.Resolve;
//or
mySvc := TSimpleIoC.DefaultContainer.Resolve('dumb');
...
end;
Of course we still need to tell the IoC container how to instanticiate something that implements IMyService.
Registering Implementations
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 :
type
TMyServiceTest = class
[SetupFixture]
procedure FixtureSetup;
end;
implementation
uses
MyServiceIntf,
MyServiceImpl;
...
procedure TMyServiceTest.FixtureSetup;
begin
TSimpleIoC.DefaultContainer.RegisterType();
TSimpleIoC.DefaultContainer.RegisterType('dumb');
end;
In the above example, if we called Resolve, 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.
Poor Man's DI
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.
TSimpleIoC.DefaultContainer.RegisterType(
function : IMyService
begin
result := TMyService.Create(TDependency.Create);
end);
I did say fudge!
Notes for DUnitX Users
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