0

I'm trying to use generics to 'genericize' a var that instantiates network transports of different types. I'm not sure if the "generic=no RTTI" rule would invalidate the approach or not, as I'm not yet up to speed with generics.

I've read this post:

What is the correct way to structure this generic object creation , which states the following in the question:

One other thing I would like to do if possible, is to change two creations:

LAdapter := TSQLiteNativeConnectionAdapter.Create(LFilename)

LAdapter := TFireDacConnectionAdapter.Create(FDatabaseLink.FConnection as TFDConnection, FDatabaseLink.OwnedComponent)

to use an abstract "GetAdapterClass" type function in the parent TModelDatabaseConnection and just declare the class of adapter in the child to do something like:

 LAdapter := GetAdapterClass.Create...

This is exactly what I would like to do as well. So if you can picture this:

type
  TTransport<T> = class(TComponent)
  private
    ...
    function GetTransport: TTransport;
    procedure SetTransport(AValue: TTransport);
    ...
  public
    ...
    property Transport: TTransport read GetTransport write SetTransport;
    ...
  end;

  TTCPIPTransport = class(TTransport<T>)
  private
    function GetSocket(Index: Integer): String;
    procedure SetSocket(Index: Integer; AValue: String);
  public
    property Socket[Index: Integer]: String read GetSocket write SetSocket;
  end;

  TServiceTransport = class(TTransport<T>)
  private
    function GetServiceName: String;
    procedure SetServiceName(AValue: String);
  public
    property ServiceName: String read GetServiceName write SetServiceName;
  end;

  TISAPITransport = class(TServiceTransport<T>);

  THTTPSysTransport = class(TServiceTransport<T>)
  private
    function GetURL(Index: Integer): String;
    procedure SetURL(Index: Integer; AValue: String);
  public
    property URL[Index: Integer]: read GetURL write SetURL;
  end;

  etc.

The idea is to create a base class that has all fields/properties/methods that are common to all transports, then have intermediate classes that contain fields/methods/properties that are common only to a certain subset of transports, then have the final version of each transport be specific to the type.

So when I call:

var
  trans: TTransport<T> // or TTransport<TTCPIPTransport> etc.
begin
  trans := TTransport<TTCPIPTransport>.Create(AOwner,....);
  trans.Transport.Socket[0] := '127.0.0.1:8523';
          OR
  trans := TTransport<TISAPITransport>.Create(AOwner,...);
  trans.Transport.ServiceName = 'Foo';
  ...
  etc.
end;

or perhaps even more generic then that, but have each instance of trans - without typecasting - have properties/fields/methods that are specific to the subclass automagically show up.

This way I can have a config screen that allows an administrator to select the type of transport say in a combo box, the have that variable value set the type inside the <> in code, and one set of code handles creation of the object by it's type.

Is this possible using generics?

DeCoder
  • 173
  • 1
  • 15
  • There is no case for generics here, at least from the example you've given. Your `TTCPIPTransport` _already is_ (a more specialized version of) `TTransport`, this is simple inheritance/polymorphism and there is no need for any `T`. The use for generics is when you want to impose a compiler-enforced type constraint between parameter types of methods, return types of a methods, property types or field types within the same generic class (class of ). – Boris B. Sep 05 '18 at 21:19
  • Generics are a compile time construct. You want to choose the type at runtime. – David Heffernan Sep 05 '18 at 21:35
  • Sorry, I'm just not that familiar. I want to instantiate a generic base class, and get a more specific type by specifying the type that I want without having to typecast the generic base class instance, and have the fields/properties/methods of the specific class exist in the base class variable instance. – DeCoder Sep 05 '18 at 21:44
  • @DeCoder simply declare your `trans` variable of the correct derived type to begin with, and then you don't have to type-cast it to access derived-specific properties. That has nothing to do with Generics. But you can't declare a generic variable based on what type is assigned to it later, and have the variable magically morph into that type. That is not how Delphi works. Variable types have to be known at compile-time and do not change at runtime. I think you need to rethink what you are attempting to do. – Remy Lebeau Sep 05 '18 at 22:22
  • @RemyLebeau, David Heffernan, Boris B, sigh, guess it was a nice pipe dream while it lasted. Thanks for the replies. – DeCoder Sep 05 '18 at 22:55
  • 4
    Generics isn't the solution. The search expression you're looking for is *class factory*. – Ken White Sep 06 '18 at 01:35
  • 1
    Your problem is perfectly tractable, and can readily be solved. It's just a mistake to try to force generics into run time type variation. Because generics is a compile time construct. – David Heffernan Sep 06 '18 at 04:21
  • @KenWhite thanks I'm reading through this now: http://forum.codecall.net/topic/60290-factory-design-pattern-with-delphi-image-viewer-demo-project/ . Any others that you would recommend? – DeCoder Sep 06 '18 at 16:44
  • @DavidHeffernan do you agree that the class factory concept is the best/right approach? Or just one of multiple ways to solve this. Which would you recommend? – DeCoder Sep 06 '18 at 17:21

1 Answers1

0

Here is my first (feeble) attempt at a class factory, never done this before. It works partially (generates the correct class) but isn't accessible as a distinct subclass of the base class without typecasting, which defeats the purpose. Please see inline comments

TWSTransport = class(TComponent)
  ...
public
  constructor Create(AOwner: TComponent); virtual; 
  ....   
end;

TWSTransportClass = Class of TWSTransport;

TWSTCPIPTransportClass = class of TWSTCPIPTransport;

TWSHTTPSysTransport = class(TWSServiceTransport);

TWSServiceTransport = class(TWSTransport);

TWSTransportStringConversion = class(TWSTransport);

TWSTransportStreamFormat = class(TWSTransportStringConversion);

TTransportFactory = class(TClassList)
private
  function GetTransport(Index: TWSTransportClass; AOwner: TkbmMWServer): TWSTransportClass;
public
  procedure RegisterTransportClass(ATransportClass: TWSTransportClass);
  property Transport[Index: TWSTransportClass; AOwner: TkbmMWServer]: TWSTransportClass read GetTransport;
end;    

function TTransportFactory.GetTransport(Index: TWSTransportClass; AOwner: TkbmMWServer): TWSTransportClass;
begin
if IndexOf(Index) > -1 then
  Result := TWSTransportClass(Items[IndexOf(Index)])
else
  Result := TWSTransportClass(Index.Create(AOwner));
end;

procedure TTransportFactory.RegisterTransportClass(ATransportClass: TWSTransportClass);
var
  index: Integer;
begin
  // is the transport registered?
  index := IndexOf(ATransportClass);
  if index < 0 then
    // the transport is not registered, add it to the list
    Add(ATransportClass);
end;



initialization
  factory := TTransportFactory.Create;
  factory.RegisterTransportClass(TWSHTTPSysTransport);
  factory.RegisterTransportClass(TWSISAPIRESTTransport);
  factory.RegisterTransportClass(TWSTCPIPTransport);

finalization
  FreeAndNil(factory);

end.

Here's how I tested it:

procedure TForm4.FormCreate(Sender: TObject);
var
  //trans: TWSTCPIPTransport; // this doesn't work
  trans: TWSTransport; // this works
begin
  trans := factory.Transport[TWSTCPIPTransport,Self];
  showmessage(trans.classname); // this shows the correct classname - TWSTCPIPTransport
  trans.AddSocket('127.0.0.1:80'); // the compiler gives an error here because this call is specific to a subclass of TWSTransport, TWSTCPIPTransport.
end;

So I'm still missing something... anyone see the mistake?

DeCoder
  • 173
  • 1
  • 15
  • 1
    You should explain why you found generics to be unsuitable, explain what you discovered instead, and explain why this code is a better alternative. Just dumping code into the answer space doesn't really work here. – Ken White Sep 07 '18 at 23:28
  • Actually, generics aren't unsuitable; I found a project that used generics to accomplish the task. However, in my research I also became aware of how D uses things like TComponentClass etc in it's own internal class factory mechanism, and think that is a better approach since it avoids the overhead of generics. However, I have yet to achieve my goal of having the factory return my subclass and just be able to assign it to a subclass instance without typecasting. :-( – DeCoder Sep 10 '18 at 21:00
  • Because someone managed to do it does not make it suitable. I'm quite certain that somewhere in history someone managed to drive a screw with a hammer and get it to hold something in place. That doesn't make a hammer suitable to replace a screwdriver. :-) – Ken White Sep 10 '18 at 22:16