2010-11-30 50 views
5

好的关联,所以我有一个基类我们称之为TFruit。从这个有喜欢TAppleTOrange等各种后裔。我需要将后代类的属性保存到一个文件中。需要唯一的整数值与类

为了能够在加载数据时创建正确的类,每个类都需要有一个我写入文件的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(任何人都知道为什么吗?)

所以基本上,我是这样做的权利或是否有更好的方式来做到这一点?不必创建一个类的函数,并返回一个值从那里似乎被比较(额外的函数声明,执行和代码),非常难看。

回答

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; 

请注意,有更好的结构映射一个字符串为整数。在没有编译器的情况下编写它,并且不知道Delphi5之外的许多基本结构,我选择了一个明显的实现。

注意,IsAlreadyRegistered重载函数还是要被写入

+0

为了方便起见,可以将一个类方法添加到返回该类的ID的注册对象的基类中。而且由于类方法(不像C#样式的静态方法)接收类型为`self`参数,所以它按预期工作。 – CodesInChaos 2010-11-30 19:33:33

+0

@CodeInChaos,true,但是这会要求每个类“知道”它是可以避免的ID,并且在试图找出下一个可用ID是什么时可能会创建新的后代。 – 2010-11-30 19:38:03

1

首先,你需要

type 
    TFruit = class 
    end; 

    TApple = class(TFruit) 
    end; 

    TOrange = class(TFruit) 
    end; 

,然后你可以使用Fruit.ClassNameFruit.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; 
+0

不需要我需要将数十万个“水果”保存到文件中,出于空间原因,我不想使用类名。我需要一个数字,我将在文件中写入一个Word(16位)字段。 – David 2010-11-30 14:19:05

+0

@David:你可以写一个`ClassToID`函数。 – 2010-11-30 14:23:06

1

如果您使用德尔福2010,您可以使用attributes与ID标记你的类。

+0

不幸的是,我为这个项目使用了Delphi 2005。如果是D2010,我可能会跟你的建议。 – David 2010-11-30 15:27:57

3

有很多可能性,例如:

function TFruit.GetClassId(): Word; 
begin 
    Result := CRC16(ClassName); 
end; 
2

人知道为什么吗?

因为你声明了一个类字段? TOrange继承自TFruit,所以它也有ID = 0字段。然后用另一个ID = 2字段覆盖它。现在你有两个。如果您将Torange投射到TFruit,那么您将获得继承领域,这正是访问它们的途径。

如果你在德尔福2010+,使用属性:

[ClassId(4)] TOrange = class(TFruit) 

但是,为什么你需要摆在首位这些ID?你必须手动标记你的每个类的类型,这很容易出错。只需使用类名。

var t: TOrange; 
begin 
    writeFile(t.Classname, t.Data); 

如果你这么关心空间,保持一个类名,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; 

(当然忽略内存的限制这里用于演示的目的,但很容易做到这一点没有内存缓存)

0

寻找其他角度:为什么ID不是只读对象属性(而不是类常量)?

所以:

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公共成员。