VSoft Technologies Blogs

rss

VSoft Technologies Blogs - posts about our products and software development.


A Simple IoC Container for Delphi


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<TInterface: IInterface; TImplementation: class>(const name : string = '');overload;
  procedure RegisterType<TInterface: IInterface; TImplementation: class>(const singleton : boolean;const name : string = '');overload;
  {$ENDIF}

  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 = '');

  //Resolution
  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!
  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<IMyService>;
//or
  mySvc := TSimpleIoC.DefaultContainer.Resolve<IMyService>('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<IMyService,TMySmartService>();
  TSimpleIoC.DefaultContainer.RegisterType<IMyService,TMyDumbService>('dumb');
end;

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. 

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<IMyService>(
     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





Comments are closed.
Showing 5 Comments
Avatar  Vincent Parrett 3 years ago

Michele

You 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.

Avatar  Michele Tedesco 3 years ago

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?

Avatar  Vincent Parrett 3 years ago

Commenters please note, if you use these comments to advertise your products, your comments will not be approved.

Avatar  Vincent Parrett 3 years ago

Thanks Stefan, fixed.

Avatar  Stefan Glienke 3 years ago

There is a .DefaultContainer missing in the code snippet where you call Resolve.
Currently it looks as if Resolve was a class method.