Lately I've been making use of dependency injection. I've been using it in the development of applications to switch between test and production services without impacting the calling code. I have been doing this so that I might test specific parts of the code through unit testing without having to worry about setting up a database.
I'm using StructureMap in my example, which is intended to illustrate how one might go about performing dependency injection. In the example below I've create an Invoice object. IRepository is an interface and TestRepository is a data access object which returns Invoices. Service is a class which makes uses Invoice Repositories. By using interfaces, I can at a later date with minimal change replace TestRepository with another class that performs data access against a database.
Invoice=public class
end;
IRepository=public interface
method FindAll:List<Invoice>;
end;
TestRepository=public class(IRepository)
public
method FindAll:List<Invoice>;
end;
Service=public class
public
constructor (repository:IRepository);
method FindAllInvoices:List<Invoice>;
end;
implementation
class method ConsoleApp.Main;
begin
StructureMapConfiguration.BuildInstancesOf<IRepository>().TheDefaultIsConcreteType<TestRepository>();
var service := ObjectFactory.GetInstance<Service>();
var invoices:=service.FindAllInvoices;
end;
constructor Service(repository:IRepository);
begin
self.repository:=repository;
end;
method Service.FindAllInvoices:List<Invoice>;
begin
result:=self.Repository.FindAll;
end;
method Repository.FindAll:List<Invoice>;
begin
var invoices:=new List<Invoice>;
invoices.Add(new Invoice);
invoices.Add(new Invoice);
invoices.Add(new Invoice);
result:=invoices;
end;
In the main method of ConsoleApp I'm doing a number of things.
1) Registering TestRepository as the type to return when an instance of IRepository is requested.
2) Requesting an instance of Service with dependencies injected.
3) Calling the FindAllInvoices on the service
Its important to note that I could of done step (1) using a configuration file, which would better illustrate the power of dependency injection. I could switch between
a test and production version of the Repository with no changes to the code.
The downside I see of dependency injection is that your tied to creating instances of objects using factory method. Your also to a certain extent have a dependency on the library your using, in this case StructureMap.
This being a continuation of my previous post, I'm going to demonstrate I think I quite an elegant solution to these two problems using AOP.
As in the previous post you can find the Aspect and an example at the Delphi Prism Aspects library.
If you open the solution, there are two projects to take a look at. Prism.StandardAspects.DependencyInjection which contains the PropertyDependencyInjector aspect and an example project showing how two use it which is called PropertyInjectorConsoleApplication.
Shown below is a sample of the code and its usage. In program.pas from the console application you can find the declaration of the InvoiceService. This time the aspect is intended for properties and I have highlighted the declaration below.
InvoiceService=public class
public
[aspect:PropertyDependencyInjector]
property ItemRepository:LineItemRepository;
[aspect:PropertyDependencyInjector]
property InvoiceRepository:InvoiceRepository;
method GetAllInvoices:List<Invoice>;
end;
Shown below is a code sample using the Service
StructureMapConfiguration.BuildInstancesOf<IRepository<Invoice>>().TheDefaultIsConcreteType<InvoiceRepository>();
StructureMapConfiguration.BuildInstancesOf<IRepository<LineItem>>().TheDefaultIsConcreteType<LineItemRepository>();
var service:=new InvoiceService;
if(assigned(service.InvoiceRepository))then
begin
var allInvoices:=service.InvoiceRepository.FindAll;
if(allInvoices.Count=1)then
begin
Console.WriteLine(String.Format('Invoice Id{0}', allInvoices[0].Id));
end
else
begin
Console.WriteLine('No invoices ?');
end;
end
else
begin
Console.WriteLine('No invoice repository');
end;
In the above code example, the only way you would know the dependency injection is taking place are the two lines calling BuildInstancesOf
Otherwise your creating InvoiceService objects in the normal manner and calling the properties without even knowing anything is going on in the background.
Behind the scenes the aspect is modifying the getters and setters to call StructureMap. If you use Reflector to look at the final code it should look like this
method InvoiceService.get_InvoiceRepository: InvoiceRepository;
begin
if not self.@p_InvoiceRepository_injected then begin
self.@p_InvoiceRepository_real := ObjectFactory.GetInstance<InvoiceRepository>;
self.@p_InvoiceRepository_injected := true
end;
begin
result := self.@p_InvoiceRepository_real;
exit
end
end;
Notice how the actual creation of property is delegated to StructureMap.
In conclusion, I think this is pretty cool.