VSoft Technologies BlogsVSoft Technologies Blogs - posts about our products and software development.https://www.finalbuilder.com/resources/blogsA Simple IoC Container for Delphihttps://www.finalbuilder.com/resources/blogs/postid/698/a-simple-ioc-container-for-delphiDelphiMon, 28 Oct 2013 11:27:00 GMT<p>When working on <a href="/resources/blogs/introducing-dunitx">DUnitX</a> 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 <strong>only</strong> does IoC, it does not (and nor will it ever) do dependency injection. If you need Dependency Injection, the <a href="http://www.spring4d.org/">Delphi Spring Framework</a> includes is a comprehensive IoC/DI container (along with some other useful stuff). </p> <p><a href="https://github.com/VSoftTechnologies/Simple-IoC">Simple IoC</a> is a copy of the IoC container in DUnitX, extracted into it's own <a href="https://github.com/VSoftTechnologies/Simple-IoC">GitHub</a> project. It's basically a single pas file, with a class TSimpleIoC. Lets take a look at TSimpleIoC :</p> <pre class="brush:delphi; toolbar:true;"> 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;</pre> <p> </p> <p>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. </p> <p> </p> <h3>A quick refresher on IoC</h3> <p>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 :</p> <pre class="brush:delphi; toolbar:true;"> function GetMyService(const implname : string) : IMyService; begin if implename = 'dumb' then result := TMyDumbService.Create else result := TMySmartService.Create; end;</pre> <p> </p> <p>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 :</p> <p> </p> <pre class="brush:delphi; toolbar:true;"> var mySvc : IMyService; begin mySvc := TSimpleIoC.DefaultContainer.Resolve<IMyService>; //or mySvc := TSimpleIoC.DefaultContainer.Resolve<IMyService>('dumb'); ... end;</pre> <p>Of course we still need to tell the IoC container how to instanticiate something that implements IMyService.  </p> <h3>Registering Implementations</h3> <p>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 :</p> <pre class="brush:delphi; toolbar:true;"> 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; </pre> <p> </p> <p>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. </p> <p> </p> <h3>Poor Man's DI</h3> <p>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. </p> <pre class="brush:delphi; toolbar:true;"> TSimpleIoC.DefaultContainer.RegisterType<IMyService>( function : IMyService begin result := TMyService.Create(TDependency.Create); end); </pre> <p>I did say fudge!</p> <h3>Notes for DUnitX Users</h3> <p>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. </p> <p>Simple IoC is open source, get it from <a href="https://github.com/VSoftTechnologies/Simple-IoC">GitHub</a></p> 698