Generic factory looping
Asked Answered
A

1

0

I am trying to figure out how to write a generic factory in XE2. Lets say I have this:

type
  TObjectTypes = (otLogger, otEmail);

type
  TLoggerTypes = (lFile, lConsole, lDatabase);

type
  TEmailTypes = (etPOP3, etSMTP);

Classes:

TSMTPEmail = class(TInterfacedObject, IEmail); // Supports emailing only
TPOP3Email = class(TInterfacedObject, IEmail); // Supports emailing only
TFileLogger = class(TInterfacedObject, ILogger); // Supports logging only

etc.

Now I do this to loop thru all TObjectTypes:

procedure TForm1.FormCreate(Sender: TObject);
var
  _Interf: IInterface;
  _Configuration: TDictionary<string, TValue>;
  _ObjectType: TObjectTypes;
begin
  _Configuration := nil;
  _Configuration := TDictionary<string, TValue>.Create;
  try
    _Configuration.Add('FileLogFileName', '20160320.Log');
    _Configuration.Add('SMTPEmailHost', 'mail.server.lt');
    _Configuration.Add('POP3Server', 'some_server');

    for _ObjectType := Low(TObjectTypes) to High(TObjectTypes) do
    begin
      _Interf := TTheFactory.Make(_ObjectType, _Configuration);
      if Assigned(_Interf) then
      begin
        OutputDebugString(PWideChar((_Interf as TObject).ClassName));
        if Supports(_Interf, IEmail) then
          (_Interf as IEmail).Send('X');

        if Supports(_Interf, ILogger) then
          (_Interf as ILogger).GetLastErrorMsg;
      end;
    end;
  finally
    FreeAndNil(_Configuration);
  end;
end;

So, I need a generic factory and be able to loop not thru all TObjectTypes, but thru all TLoggerTypes or thru all TEmailTypes and skip creating some e.g. lDatabase from TLoggerTypes or etPOP3 from TEmailTypes.

Factory should produce all kind of classes.

Adp answered 20/3, 2016 at 7:16 Comment(1)
I don't understand the question at allArms
H
4

In Delphi making factories is pretty simple, thanks to metaclasses (class references), simple example of which is TClass:

TClass = class of TObject

In most cases, you should define your own abstract class for all factory members and metaclass for it:

TMyFactoryObject = class (TObject)
  public
    constructor FactoryCreate(aConfiguration: TConfiguration); virtual; abstract;
end;

TMyFactoryClass = class of TMyFactoryObject;

In this abstract class you can add some methods common for all descendants, in my example we have constructor which takes configuration as argument. How to react to it will be determined in descendants.

Then you declare descendant classes:

TMyLogger = class (TMyFactoryObject, ILogger)
  private
    ...
  public
    constructor FactoryCreate(aConfiguration: TConfiguration); override;
    ... //implementation of ILogger interface etc
  end;

TMyEmail = class (TMyFactoryObject, IEmail)
  private
    ...
  public
    constructor FactoryCreate(aConfiguration: TConfiguration); override;
    ... //implementation of IEmail interface etc
  end;

now you declare array of possible descendant classes:

var 
  MyFactory: array [otLogger..otEmail] of TMyFactoryClass;

and in initialization section or in other places you populate this array:

MyFactory[otLogger]:=TMyLogger;
MyFactory[orEmail]:=TMyEmail;

At last, TTheFactory.Make(_ObjectType, _Configuration); from your question can be replaced with:

MyFactory[_ObjectType].FactoryCreate(_Configuration);

and you'll get needed object as instance of type MyFactoryObject.

See http://docwiki.embarcadero.com/RADStudio/Seattle/en/Class_References for more information.

Hackle answered 20/3, 2016 at 11:42 Comment(6)
Why name the constructors FactoryCreate instead of plain old Create?Satisfy
@RobKennedy I was afraid it will conflict with usual TComponent.Create(aOwner) if OP inherits it from TComponent which is quite possible. Maybe hard to find, because it compiles all right and not everybody pays attention to warning 'constructor Create hides virtual method of base type TComponent'Hackle
I will end up with few different factories. I want one for everything.Dollydolman
@EdijsKolesnikovičs Is logger and email different objects, or one object can support both logging and emailing? Add to your question, what are classes your factory should produce? How so different objects which you treat differently ended up in one loop at all?Hackle
With the technique demonstrated here, Edijs, all the class-specific code from your hypothetical monolithic factory gets transferred to the individual classes, which is where it belongs. The only remaining action of your "generic factory" is to decide which specific class to instantiate, and Yuriy shows how to achieve that with a simple array lookup. Delphi classes essentially are class factories.Satisfy
@EdijsKolesnikovičs I've used static one-dimensional array in example, but in fact you can store class references in any data structure, it can be 2D array, smth. like MyFactory[otLogger][lFile], MyFactory[otLogger][lConsole], .... MyFactory[otEmail][etPOP3], then you can use some way to iterate through data structure and do whatever you need with each element. But again, I'm not quite sure why loggers and mailers ended up begin in one loop, they seem so different for me (different settings, different interfaces) that you don't gain much from factory. I just don't know your motives for it.Hackle

© 2022 - 2024 — McMap. All rights reserved.