2010-11-30 2 views
5

申し訳ありませんので、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ゼロを返します。しかし、TOrangeとしてFruitを宣言することは(誰もがなぜ知っている?)期待される結果が得られます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

+0

@CodeInChaos、しかし、それは、各クラスがそれを避けることができるIDを「知っている」ことを必要とし、次の利用可能なIDが何であるかを突き止める際に新しい子孫を作成する苦痛になる可能性があります。 –

+0

いいえ、私はこのメソッドがあなたの 'FindID'関数を' self'をparamとして呼び出すことを意味します。しかし、シリアライザの実装の詳細と考えることができるので、クラス自体がIDを知らないと構造的に優れている可能性があります。 – CodesInChaos

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

+0

@David: 'ClassToID'関数を書くことができます。 –

1

あなたは、Delphi 2010を使用している場合は、IDを使用してクラスをタグ付けするためにattributesを使用することができます。

+0

悲しいことに私はこのプロジェクトでDelphi 2005を使用しています。もしそれがD2010なら私はおそらくあなたの提案に行くだろう。 – David

3

を書かなければならないこと注たとえば、多くの可能性が、あります

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

これは一意のIDにつながりません。 – CodesInChaos

+0

そのような短い文字列の衝突?近く不可能。 –

2

誰もがなぜ知っていますか?

クラスフィールドを宣言しているので、 TOrangeはTFruitから継承しているので、ID = 0フィールドも持っています。次に、別のID = 2フィールドで上書きします。今、あなたはこれらの2つを持っています。 TFrangeにTOrangeをキャストして継承したフィールドを取得している場合、これは正確にアクセスする方法です。

あなたがデルファイ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は読み取り専用のオブジェクトプロパティ(クラス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:を公開し公共からへの可視性を変更します。しかし、$ RTTIディレクティブを使ってRTTIを公開することもできます。

+0

RTTIを動作させるには、iircプロパティに 'published'可視性が必要です(質問のサンプルコードは暗黙的に公開されています) – mjn

+0

@mjustin - これはDelphiのバージョン2010+に依存しますRTTI –

+0

@Gerry:コードは必須です私がコメントで見たようにDelphi 2005で作業するには、あなたは正しいです:) – mjn

関連する問題