好吧,我有一个基类,我们称之为TFruit
。从这里可以看到TApple
,TOrange
等各种后代。我需要将后代类的属性保存到文件中。
为了能够在加载数据时创建正确的类,每个类需要有一个ID
,我在写入实际数据之前会写入该文件。目前,我已经提出了以下方法:
type
TFruit = class
const ID = 0;
end;
TApple = class(TFruit)
const ID = 1;
end;
TOrange = class(TFruit)
const ID = 2;
end;
测试这个,我发现我需要非常小心我宣布哪个类。如果我用这个:
var Fruit: TFruit;
Fruit := TOrange.Create;
...然后Fruit.ID
将返回零。但是,将Fruit
声明为TOrange
会产生预期结果Fruit.ID = 2
(任何人都知道原因?)
所以基本上,我这样做是对吗还是有更好的方法吗?通过比较(额外的函数声明,实现和代码),必须创建一个类函数并从那里返回一个值似乎非常难看。
答案 0 :(得分:5)
更容易维护的解决方案是创建一个映射类,您可以在其中注册要转换为整数的所有类。
<强>优点强>
<强>用法强>
RegisterClass.Register(0, TFruit);
RegisterClass.Register(1, TApple);
RegisterClass.Register(2, TOrange);
<强>实施强>
TRegisterClass = class
private
FList: TStringList;
public
function FindID(AClass: TClass): Integer;
function FindClassName(const ID: Integer): string;
procedure Register(const ID: Integer; AClass: TClass);
end;
...
function TRegisterClass.FindID(AClass: TClass): Integer;
begin
Assert(Assigned(AClass));
Result := -1;
if FList.IndexOf(AClass.ClassName) <> -1 then
Result := Integer(FList.Objects[FList.IndexOf(AClass.ClassName)]);
end;
function TRegisterClass.FindClassName(const ID: Integer): string;
var
I: Integer;
begin
Result := EmptyStr;
for I := 0 to Pred(FList.Count) do
if Integer(FList.Objects[I]) = ID then
begin
Result := FList[I];
Exit;
end;
end;
procedure TRegisterClass.Register(const ID: Integer; AClass: TClass);
begin
if IsAlreadyRegistered(ID) then
raise Exception.Create('Duplicate ID Registration')
else if IsAlreadyRegistered(AClass) then
raise Exception.Create('Duplicate Class Registration');
FList.AddObject(AClass.ClassName, Pointer(ID));
end;
请注意,有一个更好的结构可以将String映射到整数。在没有编译器的情况下编写这个并且不了解Delphi5之外的许多基本结构,我选择了一个明显的实现。
请注意,仍然必须编写IsAlreadyRegistered重载函数
答案 1 :(得分:3)
有很多种可能性,例如:
function TFruit.GetClassId(): Word;
begin
Result := CRC16(ClassName);
end;
答案 2 :(得分:2)
谁知道为什么?
因为你宣布了一个课堂领域? TOrange继承自TFruit,所以它也有ID = 0字段。然后用另一个ID = 2字段覆盖它。现在你有两个。如果您将TOrange转换为TFruit,那么您将获得继承字段,这正是访问它们的方式。
如果您使用的是Delphi 2010+,请使用属性:
[ClassId(4)] TOrange = class(TFruit)
但为什么你首先需要这些ID?您必须手动标记您的每个类类型,这很容易出错。只需使用类名。
var t: TOrange;
begin
writeFile(t.Classname, t.Data);
如果您对空间如此关注,请在文件开头保留一个classname-id表,并在动态时动态分配ID:
procedure WriteObject(c: TObject);
var id: integer;
begin
if not GetAlreadyRegisteredClassnameId(c.Classname, id) then
id := AddClassnameToTable(c.Classname);
writeToCache(id, c.Data)
end;
procedure WriteFile()
var i: integer;
begin
for i := 0 to ObjectCount-1 do
WriteObject(objects[i]);
OutputClassnameTableToFile;
OutputObjectCacheToFile;
end;
(当然为了演示目的忽略了内存限制,但是没有内存缓存也很容易做到这一点)
答案 3 :(得分:1)
首先,你需要
type
TFruit = class
end;
TApple = class(TFruit)
end;
TOrange = class(TFruit)
end;
然后你可以使用Fruit.ClassName
和Fruit.ClassType
,不是吗?
function ClassToID(const Fruit: TFruit): word;
begin
if Fruit is TApple then
result := 1
else if Fruit is TOrange then
result := 2;
end;
或
TFruitClass = class of TFruit;
type
TFruitAndID = record
FruitClass: TFruitClass;
ID: word;
end;
const FruitIDs: array[0..1] of TFruitAndID =
((FruitClass: TApple; ID: 1), (FruitClass: TOrange; ID: 2));
function ClassToID(Fruit: TFruit): word;
var
i: Integer;
begin
for i := 0 to high(FruitIDs) do
if FruitIDs[i].FruitClass = Fruit.ClassType then
Exit(FruitIDs[i].ID);
end;
答案 4 :(得分:1)
如果您使用的是Delphi 2010,则可以使用attributes使用ID标记您的课程。
答案 5 :(得分:0)
从另一个角度来看:为什么ID不是只读对象属性(而不是类const)?
所以:
type
TFruit = class
protected
FId: Integer;
published
property ID:Integer read FId;
end;
TApple = class(TFruit)
constructor Create;
end;
TOrange = class(TFruit)
constructor Create;
end;
<...>
constructor TApple.Create;
begin
FId := 1;
end;
constructor TOrange.Create;
begin
FId := 2;
end;
因此,您的示例代码现在可以使用了。 (后代可以看到FId,因为它是受保护的字段)。 编辑:将可见性从 公共 更改为 已发布 。但是使用$ RTTI指令可以实现同样的目的,即允许公共成员使用RTTI。