需要将唯一的整数值与类相关联

时间:2010-11-30 14:11:46

标签: delphi oop inheritance

好吧,我有一个基类,我们称之为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(任何人都知道原因?)

所以基本上,我这样做是对吗还是有更好的方法吗?通过比较(额外的函数声明,实现和代码),必须创建一个类函数并从那里返回一个值似乎非常难看。

6 个答案:

答案 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.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;

答案 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。