What if you want to employ the Dependency Injection pattern in your application, but don’t want to use a third party framework for it?
By way of exploring this question, I’ll tell you an anecdote. I am currently developing a new FOSS project. I won’t tell you what it is. At this early stage, it’s a secret. Given that the project uses a lot of service oriented interfaces, I thought using Dependency Injection throughout would reduce over-all complexity. I didn’t want to use the Delphi Spring Framework or something similar – I won’t go into the reasons. I ended up rolling my own DI code.
Most of rolling your own DI is pretty much straight forward. Use an Abstract Factory for your service container and Attributes & RTTI to identify injection targets.
Then we come to the bug-bear: How to inject an instance into the interface data members of a object being constructed by the DI service?
The problem defined.
What we have:
- We have an object, whose type is unknown at compile-time. This is the object which is being constructed with the aid of dependency injection. We’ll call it the
Product
. - One of the interface data members of the
Product
needs to be set. This is dependency injection. We’ll call it theDependency Member
. Thedependency member
is unknown at compile-time. At run-time we can identify theDependency member
with attributes and so we have an instance of type TRttiField describing thedependency member
. - The
dependency member
is an interface pointer of some type. We don’t know what type it is at run-time, but from RTTI, we can get the GUID of the interface type that thedependency member
will implement. - We have the
Injection
. This is an interface to an object (read “service”) of the same interface type as thedependency member
.
What we need:
- We need to set the
Dependency member
to theInjection
.
Sean’s Solution:
One might expect that there would be some simple RTTI function to set the dependency member
, like so..
procedure Inject( var Rtti: TRttiContext; Product: TObject; DepMember: TRttiField; const ServiceGUID: TGUID; const Injection: IInterface; {Injection is hard-cast from the unknown service interface type.}); begin DepMember.SetInterfaceValue( Product, ServiceGUID, Injection) end;
However, I don’t believe there is. Here is what I came up with:
procedure Inject( var Rtti: TRttiContext; Product: TObject; DepMember: TRttiField; const ServiceGUID: TGUID; const Injection: IInterface; {Injection is hard-cast from the unknown service interface type.}); var InjectionAsRTTIValue: TValue; begin Assert( DepMember.FieldType.TypeKind = tkInterface); Assert( DepMember.FieldType is TRttiInterfaceType); Assert( IsEqualGUID( ServiceGUID, TRttiInterfaceType( DepMember.FieldType).GUID)); Assert( Supports( Injection, ServiceGUID)); TValue.Make( @Injection, DepMember.FieldType.Handle, InjectionAsRTTIValue); DepMember.SetValue( Product, InjectionAsRTTIValue) end;
First off, great post – good food for thought.
Just a quick question though, why do you have a TRttiContext parameter on the Inject procedure?
Would be great to see a worked example of how you use the Inject method.
About the TRttiContext parameter. You need to keep at least one of these records alive, even if you dont directly use it, otherwise all your other Rtti objects cease to be valid. Refer to Barry Kelly’s blog entry (http://bit.ly/tUqueJ) for details.
I’ll post a worked example shortly – a simple bare-bones DI framework.
> I didn’t want to use the Delphi Spring Framework or something similar – I won’t go into the reasons.
Pity Thart could be the most insightful and practical part of the article 🙂