Amazon.com Widgets Getting Giddy with Dependency Injection and Delphi Spring #5 – Delphi Spring Basics

Getting Giddy with Dependency Injection and Delphi Spring #5 – Delphi Spring Basics

By Nick at June 26, 2011 13:12
Filed Under: Delphi, Software Development

 

Remember, you can download the Delphi Spring Framework on Google Code.

Introduction

Okay, so we are finally at the point where we can actually start using a framework to do some of the things that we want to do.  So far we’ve seen how the Law of Demeter is important to good code design.  We’ve looked at how important it is to write testable code by reducing the dependencies between objects, and we’ve seen how it’s good to code against interfaces and to keep our constructors simple. I’m finally going to make good on my promise to show you how to write useful code in a unit with nothing in the interface section. 

I hope I’m doing a good job describing why and how you want to use a Dependency Injection framework.  I hope by the end of this, you can see how to use the Delphi Spring Framework.  However, I’m doing a very imperfect job of describing the motivations and reasons behind why you want to do dependency injection. However, all is not lost – I strongly recommend that you watch this outstanding episode of DotNetRocks TV, where a really smart guy (with a delightful Canadian accent – he sounds just like my brother-in-law) named James Kovacs explains the whole underpinnings of Dependency Injection and why it’s good.  He does the presentation in C#, but don’t let that put you Delphi guys off --  the ideas are all there. 

Problems With “DI via Constructor Parameters”

In the last episode, I talked about two rules that you should follow:

  1. Always code against interfaces
  2. Keep your constructors simple

We looked at how exposing only an interface will create clean, uncoupled, and testable code, and how keeping constructors simple will decrease one classes dependency on another.  We saw an example of what I called “Dependency Injection via Constructor Parameters” (DICP) where you don’t create internal classes yourself, but let them get passed in via parameters on a constructor. 

DICP allows you to create the services (classes) you need outside of the consuming class itself, but it still requires that you create specific classes you want in specific places in your code.  Despite having a certain level of inversion of control, you are still coupled to the specific class.  Consider the following code, based on our Pizza Oven code from the last installment:

var
  MyPizzaOven: TPizzaOven;
  MyPizza: TPizza;
begin
  MyPizza := TPizza.Create;
  MyPizzaOven := TPizzaOven.Create(MyPizza);
end;

This code is better than creating the TPizza class inside the constructor of TPizzaOven, but we still have the problem of the TPizza class and the TPizzaOven class being coupled together via the uses clause – TPizzaOven needs to know about the TPizza class and how it is declared. 

Even if we re-declare TPizza as an interface – IPizza – we still have to know how to create an instance of TPizza for the IPizza interface, and we aren’t all that much more decoupled than we were before – TPizzaOven still has to know about the specific declaration of TPizza. In fact, the code above really wouldn’t change at all if the MyPizza variable were an IPizza instead of a TPizza,right?

But what if there were a way to get an interface implementation without having to know anything about the specific implementation of that interface?   Now that would really decouple things and truly separate the interface from the functionality. If only there were a framework written in Delphi that would let you do this.  If only……!

The Spring Container

Okay, I was joshing – there is such a thing:  The Delphi Spring Container does exactly that.  A Dependency Injection container is a class that can hold references to interfaces and classes that implement them.  You can register specific implementations of interfaces in the container.  If you need an implementation of an interface, you ask the container, and it provides a reference to that implementation via the given interface.   When you need an implemented interface, you reference only the container, and thus there is no direct connection between the class needing the implementation and the implementation itself.  Pretty sweet, huh?

So how does this work?  The first thing you’ll likely want to do is to create a singleton implementation of the Spring Container.  I’ve created the following unit called uServiceLocator, which wraps up the Spring Container in a Singleton:

unit uServiceLocator;

interface

uses
        Spring.DI
      ;

  function ServiceLocator: TContainer;

implementation

var
  FContainer: TContainer;

function ServiceLocator: TContainer;
begin
  if FContainer = nil then
  begin
    FContainer := TContainer.Create;
  end;
  Result := FContainer;
end;


end.

The uServiceLocator uses the Spring.DI unit from the Delphi Spring Framework where the TContainer class is declared. I have named my container "ServiceLocator" because it, well, locates services. SmileYou can use this unit anywhere you need to register classes and interfaces, as well as anywhere you need to use those registered interfaces.

TContainer is a powerful class that leverages both Generics and anonymous methods to let you register classes that implement given interfaces.  In later articles, I’ll delve a little deeper into the mysterious inner workings of TContainer, but for now, we’ll just look at the practicalities of how it is used.

Registering With The Container

Now, if you want to use the container, either to register classes with interfaces, or to consume a given interface, all you need to is use the uServiceLocator unit and not any unit that actually declares classes.  In fact, you can declare your classes in the implementation section of the unit and leave only an interface in the interface section.  Remember the uNormalMathService unit from last time?   Now, we can declare it as follows: 

unit uNormalMathService;

interface

type
  IMathService = interface
    ['{BFC7867C-6098-4744-9774-35E0A8FE1A1D}']
    function Add(a, b: integer): integer;
    function Multiply(a, b: integer): integer;
  end;

implementation

uses
  uServiceLocator;
type

  TNormalMathServiceImplemenation = class(TInterfacedObject, IMathService)
    function Add(a, b: integer): integer;
    function Multiply(a, b: integer): integer;
  end;

{ TNormalMathServiceImplemenation }

function TNormalMathServiceImplemenation .Add(a, b: integer): integer;
begin
  Result := a + b;
end;

function TNormalMathServiceImplemenation .Multiply(a, b: integer): integer;
begin
  Result := a * b;
end;

procedure RegisterNormalMathService;
begin
  ServiceLocator.RegisterComponent<TNormalMathServiceImplemenation>.Implements<IMathService>('Normal');
  ServiceLocator.Build;
end;

initialization
  RegisterNormalMathService;

end.

First, note that the IMathService interface is the only thing available for use outside of the unit. Everything else is hidden in the implementation section.  Obviously, the “money code” here is the RegisterNormalMathService procedure. This is where the implementation of IMathService gets registered.  The procedure itself gets called in the unit’s initialization section, so the mere act of using the unit ensures that the TNormalAdditionService is registered and available for use by any other unit that uses the ServiceLocator.  The call to ServiceLocator.Build simply ensures that the TContainer is ready to answer requests for implementations of interfaces.

So what is happening in the RegisterNormalMathService call?  It’s almost self-explanatory.  The code performs pretty much as it reads: “Register the class called TNormalMathServiceImplementation, which implements the IMathService interface, under the name "Normal”.  (That’s what the last string parameter is – a named reference to the interface.  This allows you to register multiple implementations of a given interface and request them by name).

Nota Bene:  if you are going to provide multiple implementations of a given interface, you can declare that interface in a separate unit, use that new unit in the implementation section, and end up with no code at all in the interface section of your unit.  See, I told you it would work.  Winking smile

And once the service is registered, you can use it in a completely decoupled way:

unit uCalculator;

interface


implementation

uses
    uServiceLocator, uNormalMathService;

type
  TCalculator = class
  private
    FMathService: IMathService;
  public
    constructor Create;
    function Addition(a, b: integer): integer;
    function Multiplication(a, b: integer): integer;
  end;

constructor TCalculator.Create;
begin
  FMathService := ServiceLocator.Resolve<IMathService>('Normal');
end;

function TCalculator.Addition(a, b: integer): integer;
begin
  Result := FMathService.Add(a, b);
end;


function TCalculator.Multiplication(a, b: integer): integer;
begin
  Result := FMathService.Multiply(a, b);
end;

end.

In this case, the “money code” happens in the class’s constructor, where the ServiceLocator is asked to “resolve” (i.e., provide) a working instance of IMathService, using the implementation registered under the name 'Normal'. The Container then goes and looks into its list of registered items, finds the correct implementation, creates an instance of that class, and returns it as an interfaces.  All of that happens “auto-magically” inside the inner-workings of the TContainer class.

Later on we’ll see how you can control the lifetime of the created classes, pool them, or create instances as a Singleton.  You can even have complete control over how the resulting class is created via the power of anonymous methods. 

Note that the class knows nothing about anything other than IMathService.  It does use the uNormalMathService, but the implementation of that class is completely hidden.   The only reason that uNormalMathService is in the uses clause is because that is where the IMathService interface is declared.  If you really want, you can do as the quote block above says and declare the interface in a separate unit and not even require the use of the unit that implements IMathService at all.

Conclusion

Thus, we have a simple example of using the Delphi Spring DI Container that completely decouples the implementation of a class from the consumer of that class.  In the above example, the TCalculator class knows nothing of nor is connected in anyway to the implementation of the IMathService interface.  All of this happens inside of the Delphi Spring container. 

So far, this is just a simple, basic look at what the Delphi Spring Container can do.  The Delphi Spring Framework can actually automatically and seamlessly create implementations for interfaces without writing any code at all.  So stay tuned for that in coming installments. 

blog comments powered by Disqus

My Book

A Pithy Quote for You

"A man's got to know his limitations"    –  Dirty Harry Callahan

Amazon Gift Cards

General Disclaimer

The views I express here are entirely my own and not necessarily those of any other rational person or organization.  However, I strongly recommend that you agree with pretty much everything I say because, well, I'm right.  Most of the time. Except when I'm not, in which case, you shouldn't agree with me.