2010-11-30 10 views
5

In Ordnung, also habe ich eine Basisklasse, die wir TFruit nennen. Von diesem gibt es verschiedene Nachkommen wie TApple, TOrange und so weiter. Ich muss die Eigenschaften der Nachkommen Klassen in einer Datei speichern.Sie müssen einen eindeutigen ganzzahligen Wert mit Klassen verknüpfen

Um beim Laden der Daten die richtige Klasse erstellen zu können, muss jede Klasse eine ID haben, die ich in die Datei schreibe, bevor ich die eigentlichen Daten schreibe. Zur Zeit habe ich mit der folgenden Art und Weise tun kommen:

type 
    TFruit = class 
    const ID = 0; 
    end; 

    TApple = class(TFruit) 
    const ID = 1; 
    end; 

    TOrange = class(TFruit) 
    const ID = 2; 
    end; 

Testing dieses, fand ich heraus, dass ich super vorsichtig sein müssen, welche Klasse ich erklären. Wenn ich diese verwenden:

var Fruit: TFruit; 

    Fruit := TOrange.Create; 

... dann wird Fruit.IDNull zurück. Allerdings Fruit als TOrange erklärt wird das erwartete Ergebnis Fruit.ID = 2 ergeben (Wer weiß, warum?)

Also im Grunde bin ich dieses Recht zu tun, oder gibt es einen besseren Weg, es zu tun? Eine Klassenfunktion zu erstellen und einen Wert von dort zurückzugeben, erscheint im Vergleich sehr hässlich (zusätzliche Funktionsdeklaration, Implementierung und Code).

Antwort

5

Eine einfacher zu wartende Lösung wäre das Erstellen einer Zuordnungsklasse, in der Sie alle Klassen registrieren, die Sie in eine Ganzzahl konvertieren möchten.

Vorteile

  • Fähigkeit doppelten Registrierungen zu erkennen.
  • Unabhängig von Ihrer Klassenstruktur.
  • Enthält die Umwandlung zurück in einen Klassennamen.

Nutzungs

RegisterClass.Register(0, TFruit); 
    RegisterClass.Register(1, TApple); 
    RegisterClass.Register(2, TOrange); 

Implementierung

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; 

Bitte beachten Sie, dass es bessere Strukturen einen String in eine Integer abzuzubilden. Ich schreibe dies ohne einen Compiler und kenne nicht viele grundlegende Strukturen jenseits von Delphi5. Ich habe eine offensichtliche Implementierung gewählt.

Beachten Sie, dass die IsAlreadyRegistered ladenen Funktionen müssen noch geschrieben werden

+0

Der Bequemlichkeit halber könnte man der Basisklasse von registrierten Objekten eine Klassenmethode hinzufügen, die die ID dieser Klasse zurückgibt. Und da Klassenmethoden (im Gegensatz zu statischen C# style-Methoden) den Klassentyp als "self" -Parameter erhalten, funktioniert dies wie erwartet. – CodesInChaos

+0

@CodeInChaos, wahr, aber das würde erfordern, dass jede Klasse "weiß", dass es eine ID ist, die vermieden werden kann, und es könnte ein Schmerz werden, neue Nachkommen zu schaffen, herauszufinden, was die nächste verfügbare ID ist. –

+0

Nein, ich meine, dass diese Methode Ihre 'FindID' Funktion mit' self' als Parameter aufruft. Es kann jedoch architektonisch besser sein, wenn Klassen selbst keine IDs kennen, da sie als Implementierungsdetail des Serialisierers betrachtet werden können. – CodesInChaos

1

Zuerst müssen Sie

type 
    TFruit = class 
    end; 

    TApple = class(TFruit) 
    end; 

    TOrange = class(TFruit) 
    end; 

und dann können Sie Fruit.ClassName und Fruit.ClassType, können Sie nicht?

function ClassToID(const Fruit: TFruit): word; 
begin 
    if Fruit is TApple then 
    result := 1 
    else if Fruit is TOrange then 
    result := 2; 
end; 

oder

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

Nein. Ich muss viele hunderttausend "Früchte" in einer Datei speichern und aus Platzgründen möchte ich den Klassennamen nicht verwenden. Ich brauche eine Nummer, die ich als Word-Feld (16 Bit) in die Datei schreiben werde. – David

+0

@David: Sie können eine 'ClassToID' Funktion schreiben. –

1

Wenn Sie mit Delphi 2010 Sie attributes können Ihre Klassen mit der ID zu markieren.

+0

Leider verwende ich Delphi 2005 für dieses Projekt. Wenn es D2010 wäre, würde ich wahrscheinlich mit Ihrem Vorschlag gehen. – David

3

gibt es viele Möglichkeiten, zum Beispiel:

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

Dies führt nicht zu eindeutigen IDs. – CodesInChaos

+0

Kollision auf solchen kurzen Strings? Nahezu unmöglich. –

2

jemand wissen, warum?

Weil Sie ein Klassenfeld deklarieren? TOrange erbt von TFruit, also hat es auch das Feld ID = 0. Dann überschreiben Sie es mit einem anderen ID = 2 Feld. Jetzt haben Sie zwei davon. Wenn Sie TOrange in TFruit umwandeln, erhalten Sie ein geerbtes Feld. Dies ist genau der richtige Weg, um darauf zuzugreifen.

Wenn Sie auf Delphi 2010+, Verwendung Attribute:

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

Aber warum müssen Sie diese IDs in erster Linie? Sie müssen jeden einzelnen Klassentyp manuell markieren, dies ist fehleranfällig. Verwenden Sie einfach den Klassennamen.

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

Wenn Sie mit Platz so besorgt sind, halten Sie eine Klassennamen-ID-Tabelle am Anfang der Datei und IDs dynamisch zuweisen, wie Sie gehen:

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; 

(Natürlich Speicher zu ignorieren Einschränkungen hier zu demonstrativen Zwecken, aber es ist leicht, dies auch ohne Memory-Cache zu tun)

0

Blick auf andere Winkel: warum ID ist keine schreibgeschützte Objekteigenschaft (anstelle einer Klasse const)?

So:

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; 

So, Ihr Beispielcode wird jetzt arbeiten. (Die Nachkommen können Fld sehen, weil es ein geschütztes Feld ist). EDIT: ändert die Sichtbarkeit von öffentlichen zu veröffentlicht. Das Gleiche kann jedoch mit der $ RTTI-Direktive erreicht werden, um RTTI öffentlichen Mitgliedern zu ermöglichen.

+0

Damit RTTI funktionieren kann, muss die Eigenschaft published Sichtbarkeit haben (der Beispielcode in der Frage verwendet implizit veröffentlicht) – mjn

+0

@mjustin - dies hängt von der Delphi-Version ab 2010 hat RTTI erweitert –

+0

@Gerry: der Code ist erforderlich mit Delphi 2005 arbeiten (wie ich in einem Kommentar gesehen habe), aber du hast recht :) – mjn

Verwandte Themen