Delphi chamando um construtor virtual baseado no tipo TObject
Eu tenho um objeto que é derivado do objeto TStringList que chamo de "TAutoString". Ele permite que você especifique um tipo de objeto quando a lista é criada. Então, cada vez que uma nova entrada é adicionada à lista de strings, também cria uma cópia do objeto associado a essa entrada de string. Isso facilita o armazenamento de todos os tipos de informações adicionais junto com cada string. Por exemplo:
type TMyObject = class(TObject)
public
Cats: integer;
Dogs: integer;
Mice: integer;
end;
MO := TAutoString.Create(TMyObject);
Dentro do objeto, as informações da classe são armazenadas em uma variável de classe:
private
ObjectClass: TClass;
constructor TAutoString.Create(ObjClass: TClass);
begin
inherited Create;
ObjectClass:=ObjClass;
end;
Agora, sempre que um novo item é adicionado, ele cria um novo objeto do tipo especificado:
function TAutoString.Add(const S: string): Integer;
begin
Result:=inherited Add(S);
Objects[Result]:=ObjectClass.Create;
end;
Agora posso adicionar ou ler informações associadas a cada entrada de string.
TMyObject(MO.Objects[25]).Cats := 17;
D:=TMyObject(MO.Objects[25]).Dogs;
Isso funciona muito bem, desde que o objeto não tenha um construtor. Se o objeto tiver um construtor, seu construtor não será chamado quando o objeto for criado porque o construtor de TObject não é virtual.
Alguém pode pensar em uma maneira de contornar esse problema. Já vi soluções que usam as bibliotecas RTTI, mas isso está no Delphi-7, que não tem uma biblioteca RTTI.
À parte, parece um pouco estranho que o construtor de TObject não seja virtual. Se fosse, permitiria todos os tipos de recursos úteis, como o que estou tentando implementar.
EDIT: A sugestão de Remy abaixo foi apenas o empurrãozinho que eu precisava. Eu havia tentado originalmente uma estratégia semelhante, mas não consegui fazer funcionar. Quando não pareceu funcionar da maneira que pensei que deveria, presumi que devia haver algo que eu não entendia sobre métodos virtuais. Sua postagem me empurrou para olhar para ele novamente. Descobri que eu havia deixado de fora a diretiva "Override" para o construtor do objeto que eu queria anexar. Agora funciona exatamente como deveria.
A outra questão que me preocupava era que eu já tinha usado as Auto Strings em um monte de outras aplicações onde o objeto era baseado em "TObject" e eu não queria voltar e mudar todo aquele código. Resolvi esse problema sobrecarregando os construtores e tendo um para objetos baseados em TObject e outro para meus objetos TAutoClass:
constructor Create(ObjClass: TAutoClass); overload; virtual;
constructor Create(ObjClass: TClass); overload; virtual;
Dependendo de qual construtor é chamado, a classe de objeto armazenada em uma variável diferente.
private
AutoClass: TAutoClass;
ObjectClass: TClass;
Então, quando o objeto é construído, eu verifico qual foi atribuído e uso aquele:
procedure TAutoString.CreateClassInstance(Index: integer);
begin
if AutoClass<>nil then Objects[Index]:=AutoClass.Create
else Objects[Index]:=ObjectClass.Create
end;
A nova versão funciona perfeitamente com qualquer tipo de objeto.
Respostas
Para fazer o que você deseja, você terá que definir uma classe base da qual seus objetos de lista derivem, e então você pode adicionar um construtor virtual a essa classe. Seu ObjectClassmembro terá que usar esse tipo de classe em vez de usar TClass.
Por exemplo:
type
TAutoStringObject = class(TObject)
public
constructor Create; virtual;
end;
TAutoStringObjectClass = class of TAutoStringObject;
TAutoString = class(TStringList)
private
ObjectClass: TAutoStringObjectClass;
public
constructor Create(ObjClass: TAutoStringObjectClass);
function Add(const S: string): Integer; override;
...
end;
...
constructor TAutoStringObject.Create;
begin
inherited Create;
end;
constructor TAutoString.Create(ObjClass: TAutoStringObjectClass);
begin
inherited Create;
ObjectClass := ObjClass;
end;
function TAutoString.Add(const S: string): Integer;
var
Obj: TAutoStringObject;
begin
Obj := ObjectClass.Create;
try
Result := inherited AddObject(S, Obj);
except
Obj.Free;
raise;
end;
end;
...
Então, você simplesmente ajusta suas classes de objetos derivados para usar em TAutoStringObjectvez de TObject, por exemplo:
type
TMyObject = class(TAutoStringObject)
public
...
constructor Create; override;
end;
MO := TAutoString.Create(TMyObject);
...
E seu construtor será chamado, conforme o esperado.