Malcolm has been writing code for nearly 30 years, on everything from IBM mainframes to embedded devices. Someday he hopes to get really good at it.


Email Newsletter

genericfactory2

Storing code in a collection : TDictionary and Anonymous Methods

“Moulin pour écraser les cannes, en usage aux Antilles Espagnoles, Françaises et Anglaises” by El Bibliomata used under Creative Commons license.

In my earlier post, I mentioned you could use the new generic collection classes in Delphi 2009 to store anonymous methods instead of data. I wanted to try this out so I came up with the scenario of implementing factory classes as an excuse to experiment.

Factories can be a very useful way to centralise the creation logic for classes. Very often you see it when you have a base class and a whole bunch of different descendant classes and you want to do the creation in one place. Let’s say you have a reporting application. You may have a base report and a bunch of descendant reports, and you want to enforce some consistency over property values for the different reports that are set at the time of creation. Having these spread around every different place a report could be created may be error-prone. Alternatively, maybe you want to get away from the massive if…then..else blocks you often see in this situation. A Report Factory can give you one place to make sure all reports get created in the certain way, and also let the client code simply request a report by name and have the correct concrete report class created for them.

Problem is usually this means writing a new factory each time you want this, plus creating factory mapping classes to register each concrete report. This is not only a whole bunch of effort and unnecessary classes, it’s more new code to debug.

Instead, I thought I’d try creating a single factory class, that uses Generics to allow you to change the key used to request an instance, and that uses Anonymous Methods to do the creating so as to avoid the necessity for mapping classes. Sticking with my report scenario above, here’s how I wanted to instantiate it:

  ReportFactory := TFactory<string , TBaseReport>.Create;

Where the first generic parameter is the type of the key I will use to request a report, in this case a string representing the report name, and the second is the base type I want the Factory to return.

Here’s how I wanted to register new concrete implementations:

  ReportFactory.RegisterFactoryMethod('Malcolm''s Report',
                  function : TBaseReport
                  begin
                    Result := TReportFlexible.Create(rnReportMalcolm);
                    // other property setting/setup code
                  end);

You simply pass in the key value and the anonymous method that will be invoked to do the creation. Lastly, I want to retrieve a particular instance of my report by calling GetInstance on the factory passing in the key value.

  Report := ReportFactory.GetInstance('Malcolm''s Report');

Remember however, the type of the key value and the base type of the returned objects should be configurable via generic parameters.

Here’s the class definition I came up with:

  TFactoryMethod<tbasetype> = reference to function : TBaseType;
 
  TFactory<tkey , TBaseType> = class
  private
    FFactoryMethods : TDictionary<tkey , TFactoryMethod><tbasetype>&gt;;
    function GetCount: Integer;
  public
    constructor Create;
    destructor Destroy; override;
    property Count : Integer read GetCount;
    procedure RegisterFactoryMethod(Key : TKey;
                      FactoryMethod : TFactoryMethod<tbasetype>);
    procedure UnregisterFactoryMethod(Key : TKey);
    function IsRegistered (Key : TKey) : boolean;
    function GetInstance(Key : TKey) : TBaseType;
  end;

As you can see, TKey is the name of the generic parameter representing the type I want to use to request a concrete instance of the base type specified in TBaseType.  RegisterFactoryMethod lets you register an anonymous method of type TFactoryMethod which as we already discussed will do the creating, and there is a matching UnregisterFactoryMethod to remove a key\method pair. GetInstance is there, taking the generic parameter TKey and returning an instance of the base type. There are also a couple of methods to check if a particular key is already registered and another to return the number of registered keys.

Nothing there is terribly interesting if you’ve already played with generic types. However, for me the interesting part is I’m storing the key/method pairs in a TDictionary<TKey, TFactoryMethod<TBaseType>>.  Storing the anonymous method and the key in the dictionary is pretty straightforward:

procedure TFactory<tkey , TBaseType>.RegisterFactoryMethod(Key: TKey;
  FactoryMethod: TFactoryMethod<tbasetype>);
begin
  if IsRegistered(Key) then
    raise TFactoryMethodKeyAlreadyRegisteredException.Create('');
 
  FFactoryMethods.Add(Key, FactoryMethod);
end;

and retrieving it and executing it is equally so.

function TFactory<tkey , TBaseType>.GetInstance(Key: TKey): TBaseType;
var
  FactoryMethod : TFactoryMethod<tbasetype>;
begin
  if not IsRegistered(Key) then
    raise TFactoryMethodKeyNotRegisteredException.Create('');
 
  FactoryMethod := FFactoryMethods.Items[Key];
  if Assigned(FactoryMethod) then
    Result := FactoryMethod;
end;

With that, we’ve got a very reusable Factory class, so I should have no excuse for avoiding them in the future. Of course, this is a fairly simplistic implementation, it doesn’t handle pipelining during the creation process for example, but this would be relatively straight-forward to add with more anonymous methods stored in a TList<T> for example.

Anyway, hopefully this little experiment has sparked some thoughts in you. The source for both the class and a client implemented as unit tests is available from my delphi-experiments repository on github.

Meanwhile I’m off to do some more playing. Next post will probably be the Pooling example I mentioned earlier but somehow forgot to post.

Malcolm Groves

Malcolm has been writing code for nearly 30 years, on everything from IBM mainframes to embedded devices. Someday he hopes to get really good at it.

5 Comments » for Storing code in a collection : TDictionary and Anonymous Methods
  1. Tim Jarvis says:

    Hey, thats very cool. Nice post.

  2. Malcolm says:

    Hey Tim! Thanks. I find myself very excited by the language again. There have been a bunch of changes in recent versions: anonymous methods, generics, enumerators, helper classes, etc that I haven’t spent a whole bunch of time looking at, so now I feel like they’ve all come at once :-)

  3. Fred says:

    Hi, I tried your code and test to implement it to suit my needs but found that it leaked. I’m not an expert so I did not succeed finding if the leak where from the TDictionary or from your code. Could you please help me on that ?

    I’m running Delphi 2009 Update 3. I simply put ReportMemoryLeakOnShudown := True in the .dpr

    Thanks,

    Fred

  4. Malcolm says:

    Thanks Fred,

    Yes, it was an error in the TestGetInstance test, not in the Factory itself. I’ve fixed the download, so you should be good now.

    Cheers
    Malcolm

  5. Fred says:

    Thanks for your update Malcolm,
    It’s one of the clearest article i saw while searching information about generics and anonymous methods. Thanks to you, all is clear for me now!

    Regards,

    Fred

1 Pings/Trackbacks for "Storing code in a collection : TDictionary and Anonymous Methods"
  1. [...] replace convoluted if..then…else or case statements, this might spark some ideas. In fact in my next blog post I’ll show an example of a generic Factory implementation that uses a TDictionary internally [...]

Leave a Reply