Amazon.com Widgets Getting Giddy with Dependency Injection and Delphi Spring #9 – One Interface, Many Implementations

Getting Giddy with Dependency Injection and Delphi Spring #9 – One Interface, Many Implementations

By Nick at November 07, 2011 20:27
Filed Under: Delphi, Software Development

So far, we’ve been registering interfaces and implementations in a one-to-one relationship.  Each interface has one implementing class registered against it.  But what if you want to implement an interface many different ways, choosing which implementation to use depending on user input or other external factors?

As always, you can download the Delphi Spring Framework from GoogleCode.

Well, lucky for us, the Spring Container lets us do just that.  The Delphi Spring Framework container registration system allows you to specify a name for any giving implementation registration, thus distinguishing different registrations from on another, even if you register multiple implementers for the same interface. 

If you register multiple implementers for a given interface without specifying a name for each one, then the “last one in wins'”.

So, for instance, you might declare a simple credit card interface as follows:

type
  ICreditCard = interface
    ['{6490640C-0E2B-4F7D-908C-0E6A74DCC0A0}']
    function IsValid(aCreditCardNumber: string): boolean;
    function ChargeAmount(aCreditCardNumber: string; aAmount: Double): Boolean;
  end;

There are any number of credit cards that customers might want to use, so you’ll need to have credit card implementations for the various common vendors:

  GlobalContainer.RegisterType<TVisa>.Implements<ICreditCard>('VISA');
  GlobalContainer.RegisterType<TMasterCard>.Implements<ICreditCard>('MasterCard');
  GlobalContainer.RegisterType<TDiscover>.Implements<ICreditCard>('Discover');
  GlobalContainer.RegisterType<TAMEX>.Implements<ICreditCard>('AMEX');

This code registers four different classes (TVisa, TMasterCard, TDiscover, TAMEX) for the same interface (ICreditCard) via the string parameter on the GetService call.  Once these are registered, you can pick and choose whichever credit card processing class you want as the implementation of ICreditCard.  You can even change the selection at runtime based on, say, user input or different orders being processed, etc. 

For instance, if you have four radio buttons that allow the user to select one of four credit cards, you can do the following:

var
   CurrentCard: ICreditCard

...

procedure TMultipleImplementationsForm.RadioButton1Click(Sender: TObject);
begin
  CurrentCard := ServiceLocator.GetService<ICreditCard>('VISA');
end;

procedure TMultipleImplementationsForm.RadioButton2Click(Sender: TObject);
begin
  CurrentCard := ServiceLocator.GetService<ICreditCard>('MasterCard');
end;

procedure TMultipleImplementationsForm.RadioButton3Click(Sender: TObject);
begin
  CurrentCard := ServiceLocator.GetService<ICreditCard>('Discover');
end;

procedure TMultipleImplementationsForm.RadioButton4Click(Sender: TObject);
begin
  CurrentCard := ServiceLocator.GetService<ICreditCard>('AMEX');
end;

The above code will assign an instance of the appropriate implementing object to the single variable CurrentCard depending on which radio button the user selects.  The proper object is returned based upon the string parameter passed to the GetService call.  That string value, of course, corresponds to the object registered with that same string as shown above. 

Thus, you can register by name and then use as many implementing objects for a single interface as you want.   This is obviously very powerful, as you can choose from any number of implementations as well as add new implementations anytime you want.

A sample application showing this technique as well as some other interesting features can be found in the samples that come along with the Delphi Spring Framework

Comments (8) -

11/11/2011 2:39:13 AM #

Alexander Elagin

Hi Nick,

Firstly, thank you for this superb series of posts, sort of a tutorial. It takes time to adopt new concepts and have them settled in one's mind, and having a step-by-step guide is an incredible bonus.
Now, probably a dumb question. Suppose, I have a number of independent data handlers implementing the same interface, each in its own unit. The "main handler" has to build a list of available handlers and do something with it (execute them all, for example). It does not know how many of them exist, their names/properties/iids etc, only the interface it needs to call. How could this scenario be implemented the best way?

Alexander Elagin Russia |

11/11/2011 9:35:52 AM #

Nick Hodges

Alexander --

I guess I'd just use an external data structure to maintain a list of names for each implementation.  That structure would have to keep track of the registered implementations, and could then assign the implementations out as needed.

Or, put another way, Spring4D doesn't help you with that, you'd have to manage it yourself.

Nick Hodges United States |

11/11/2011 10:59:26 AM #

Doug Johnson

Patterns to the rescue.
Check out the Observer pattern...

Doug Johnson United States |

11/11/2011 12:24:48 PM #

Alexander Elagin

Ok, understood.

In my current implementation (written in 2000, Delphi 6) which I'm now refactoring under Delphi 2010 I used the following approach: each of the handlers registered its class (in the initialization section of the unit) in the singleton list which was globally available via a function call. The main handler operated on the said list of classes. Looks like the solution is pretty valid today.
I have played a little with the Spring's TServiceLocator.GetAllServices function, but as it keeps producing the infamous "Internal error URW1111", I've given up.

Alexander Elagin Russia |

11/11/2011 1:40:48 PM #

Nick Hodges

Alex --

If there is a bug in the Spring framework with GetAllServices, please report it and I'm sure that it will get fixed.

Nick Hodges United States |

11/12/2011 8:40:55 AM #

Alexander Elagin

Nick, I am sure the problem is not in the Spring FW, but in the buggy implementation of Generics in the D2010 compiler. This mysterious internal error URW1111 pops up in an absolutely innocent code (there are QC entries for it; even Spring.Reflection.pas has a workaround for another case), but it seems that it was somewhat fixed in XE. Again, thank you for your advices and sharing of knowledge!

Alexander Elagin Russia |

11/13/2011 11:02:07 AM #

Alexander Elagin

I got it! Smile

var H: IMyHandler;
...
for H in ServiceLocator.GetAllServices<IMyHandler> do begin
  WriteLn('Now executing: ', H.Description);
  H.Execute;
end;

Works in XE and does exactly what I need. Is it really that simple? Wink

Alexander Elagin Russia |

11/18/2011 10:08:37 PM #

Arioch

1) this seems completely opposite to XE2 class constructor feature and SmartLinker.
If some unit remains on uses list for some error, it would not be eliminated.
The mirror of "forgoten implementation" error

2) What i don't like afgain is string-based search over RTTI. ISn't it slow ?
IF compiler could geerate binary indexes/pointers, it seemingly must be better

Arioch Russia |

Comments are closed

A Little About Me

Hey, I'm Nick.  I'm interested in Software Development, Leadership, and Basketball.  I'm a big fan of Delphi, but love all cool programming languages.

A Pithy Quote for You

"The hardest thing in the world to understand is the income tax."    –  Albert Einstein

Little Buttons and Stuff

Nick Hodges

Create Your Badge
View Nick Hodges's profile on LinkedIn
profile for Nick Hodges on Stack Exchange, a network of free, community-driven Q&A sites
Powered by DiscountASP.net
Join Dropbox

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.

Book Stuff

Earn Free Stuff

Search & Win