Необходимо связать уникальное целочисленное значение с классами - PullRequest
5 голосов
/ 30 ноября 2010

Хорошо, у меня есть базовый класс, который мы назовем 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 (кто-нибудь знает почему?)

Так в принципе, я делаю это правильно или есть лучший способ сделать это? Необходимость создания функции класса и возврата значения оттуда кажется очень уродливой при сравнении (дополнительное объявление функции, реализация и код).

Ответы [ 6 ]

5 голосов
/ 30 ноября 2010

Более простым в обслуживании решением было бы создание класса отображения, в котором вы регистрируете все классы, которые хотите преобразовать в целое число.

Преимущества

  • Возможность обнаружения дубликатов регистраций.
  • Независимо от структуры вашего класса.
  • Включает преобразование обратно в имя класса.

Использование

  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 еще должны быть записаны

3 голосов
/ 30 ноября 2010

Есть много возможностей, например:

function TFruit.GetClassId(): Word;
begin
  Result := CRC16(ClassName);
end;
2 голосов
/ 30 ноября 2010

кто-нибудь знает почему?

Потому что вы объявляете поле класса? TOrange наследуется от TFruit, поэтому он также имеет поле ID = 0. Затем вы переопределяете это с другим полем ID = 2. Теперь у вас есть два из них. Если вы разыгрываете TOrange в TFruit, то вы получаете унаследованное поле, это как раз тот способ доступа к ним.

Если вы используете Delphi 2010+, используйте атрибуты:

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

Но зачем вам эти идентификаторы? Вам придется вручную пометить каждый тип вашего класса, это может привести к ошибкам. Просто используйте имя класса.

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

Если вы так озабочены пространством, сохраните таблицу идентификаторов классов в начале файла и динамически присваивайте идентификаторы:

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;

(Конечно, игнорируя ограничения памяти здесь для демонстрационных целей, но это легко сделать и без кеша памяти)

1 голос
/ 30 ноября 2010

Если вы используете Delphi 2010, вы можете использовать атрибуты , чтобы пометить ваши классы идентификатором.

1 голос
/ 30 ноября 2010

Во-первых, вам нужно

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;
0 голосов
/ 30 ноября 2010

С другой стороны: почему 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, потому что это защищенное поле).EDIT: изменяет видимость с public на опубликовано .Но то же самое может быть достигнуто с помощью директивы $ RTTI, чтобы позволить RTTI общедоступным членам.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...