Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Die Arbeit mit Fake-Objekten erspart die Prüfung auf Nil (https://www.delphipraxis.net/213246-die-arbeit-mit-fake-objekten-erspart-die-pruefung-auf-nil.html)

mytbo 23. Jun 2023 23:56


Die Arbeit mit Fake-Objekten erspart die Prüfung auf Nil
 
Liste der Anhänge anzeigen (Anzahl: 1)
Zugegeben, die Überschrift ist etwas zugespitzt formuliert, es soll zur Diskussion anregen. Starten wir mit Bekanntem. Gegeben ist die Situation: Arbeit mit einem Geschäftsobjekt (Business Object).

1) Die Definition sieht wie folgt aus:
Delphi-Quellcode:
type
  TPerson = class(TObject)
  private
    FID: TID;
    FVorname: String;
    FNachname: String;
    function GetCaption: String;
  public
    constructor Create(pmID: TID); reintroduce;
    property Caption: String // Format: Nachname, Vorname
      read GetCaption;
    property ID: TID
      read FID;
    property Vorname: String
      read FVorname write FVorname;
    property Nachname: String
      read FNachname write FNachname;
  end;

type
  TdmDaten = class(TDataModule)
  public
    function GetPerson(pmPersonID: TID): TPerson;
  end;

function TdmDaten.GetPerson(pmPersonID: TID): TPerson;
begin
  if IsValidID(pmPersonID) then
    Result := FPersonList.Find(pmPersonID)
  else
    Result := Nil;
end;
Angewendet wie folgt:
Delphi-Quellcode:
var person: TPerson := dmDaten.GetPerson(idPerson);
if person <> Nil
  name := person.Caption
else
  name := 'John Doe';
2) In der nächsten Überarbeitung erweitern wir das Datenmodul:
Delphi-Quellcode:
type
  TdmDaten = class(TDataModule)
  public
    ...
    function GetPersonCaption(pmPersonID: TID): String;
  end;

function TdmDaten.GetPersonCaption(pmPersonID: TID): String;
begin
  var person: TPerson := GetPerson(pmID);
  if person <> Nil then
    Result := person.Caption
  else
    Result := 'John Doe';
end;
Jetzt ist es deutlich komfortabler in der Anwendung:
Delphi-Quellcode:
name := dmDaten.GetPersonCaption(idPerson);
In der realen Welt kann ein Geschäftsobjekt viele Eigenschaften haben. Nicht alle kann/will man über einen Direktzugriff zugänglich machen. Damit wären wir wieder am Anfang und beim ersten Beispiel.

3) Was wäre, wenn es immer ein Objekt gibt. Wenn kein reales vorhanden ist, dann auf Wunsch ein Fake-Objekt. Es könnte wie folgt aussehen:
Delphi-Quellcode:
type
  TPerson = class(TObject)
  public
    function IsFake: Boolean;
    ...
  end;

  TdmDaten = class(TDataModule)
  public
    function GetPerson(pmPersonID: TID; pmFakeReturn: Boolean = False): TPerson;
    ...
  end;

function TPerson.IsFake: Boolean;
begin
  Result := (Self = __FakePerson);
end;

function TdmDaten.GetPerson(pmPersonID: TID; pmFakeReturn: Boolean): TPerson;
begin
  Result := Nil;
  if IsValidID(pmPersonID) then
    Result := FPersonList.Find(pmPersonID);

  if (Result = Nil) and pmFakeReturn then
    Result := __FakePerson;
end;

initialization
  __FakePerson := TPerson.Create(0);
  __FakePerson.Vorname := 'John';
  __FakePerson.Nachname := 'Doe';

finalization
  __FakePerson.Free;
Der Zugriff wie folgt:
Delphi-Quellcode:
name := dmDaten.GetPerson(idPerson, True).Caption;
Diese Schreibweise sieht elegant aus, beinhaltet aber auch Gefahren. Allerdings nichts, was bei disziplinierter Programmierung ein Problem sein sollte. Zur Zeit arbeite ich mit Fall 1), 2) und anderen Techniken. Gebe aber zu, der Ansatz mit dem Fake-Objekt hat einen gewissen Charme und schwirrt im Kopf herum. Im Anhang der Sourcecode zum Spielen.

Nachtrag: Die Bezeichnung Fake-Objekt scheint zu unpräzise. Dahinter kann sich auch ein Default-Objekt verbergen.
Delphi-Quellcode:
type
  TDefaultPerson = class(TPerson)
  protected
    function GetCaption: String; override;
  end;

function TDefaultPerson.GetCaption: String;
begin
  if DayOfWeek(Date) = 2 then // I don't like monday
    Result := Format('%s, %s', ['Lecter', 'Hannibal'])
  else
    Result := inherited GetCaption;
end;
Ich habe das Beispiel angepasst.

Bis bald...
Thomas

KodeZwerg 24. Jun 2023 10:14

AW: Die Arbeit mit Fake-Objekten erspart die Prüfung auf Nil
 
Nur mal auf die schnelle zusammengebasteln wie ich es meist klassisch mache ...
Code:
type
  TPerson = packed record
    FirstName,
    SurName: string;
    Age: Integer;
  end;
  TPersons = array of TPerson;

type

  { TPersonalClass }

  TPersonalClass = class(TObject)
    strict private
      FPersons: TPersons;
      FCount: Integer;
      FIndex: Integer;
    private
      procedure SetIndex(const AIndex: Integer);
    public
      constructor Create;
      destructor Destroy; Override;
      procedure Add(const AFirstName, ASurName: string; const AAge: Integer); overload;
      procedure Add(const APerson: TPerson); overload;
      function GetPerson: TPerson;
      procedure SetPerson(AValue: TPerson);
    published
      property Index: Integer read FIndex write SetIndex;
      property Count: Integer read FCount;
  end;

{ TPersonalClass }

constructor TPersonalClass.Create;
begin
  FCount := 0;
  FIndex := -1;
  SetLength(FPersons, 0);
end;

destructor TPersonalClass.Destroy;
begin
  SetLength(FPersons, 0);
  inherited Destroy;
end;

procedure TPersonalClass.SetIndex(const AIndex: Integer);
begin
  if (AIndex < FCount) then
    FIndex := AIndex;
end;

procedure TPersonalClass.Add(const AFirstName , ASurName: string;
  const AAge: Integer);
var
  i: Integer;
begin
  if ((AFirstName <> '') and (ASurName <> '')) then
    begin
      i := Length(FPersons);
      SetLength(FPersons, Succ(i));
      FPersons[i].FirstName := AFirstName;
      FPersons[i].SurName := ASurName;
      FPersons[i].Age := AAge;
      FCount := Length(FPersons);
      if ((FIndex = -1) and (FCount > 0)) then
        FIndex := Pred(FCount);
    end;
end;

procedure TPersonalClass.Add(const APerson: TPerson);
var
  i: Integer;
begin
  if ((APerson.FirstName <> '') and (APerson.SurName <> '')) then
    begin
      i := Length(FPersons);
      SetLength(FPersons, Succ(i));
      FPersons[i].FirstName := APerson.FirstName;
      FPersons[i].SurName := APerson.SurName;
      FPersons[i].Age := APerson.Age;
      FCount := Length(FPersons);
      if ((FIndex = -1) and (FCount > 0)) then
        FIndex := Pred(FCount);
    end;
end;

function TPersonalClass.GetPerson: TPerson;
begin
  if (FIndex >= 0) then
    begin
      Result.FirstName := FPersons[FIndex].FirstName;
      Result.SurName := FPersons[FIndex].SurName;
      Result.Age := FPersons[FIndex].Age;
    end;
end;

procedure TPersonalClass.SetPerson(AValue: TPerson);
begin
  if (FIndex >= 0) then
    begin
      FPersons[FIndex].FirstName := AValue.FirstName;
      FPersons[FIndex].SurName := AValue.SurName;
      FPersons[FIndex].Age := AValue.Age;
    end;
end;

peterbelow 24. Jun 2023 13:02

AW: Die Arbeit mit Fake-Objekten erspart die Prüfung auf Nil
 
Zitat:

Zitat von mytbo (Beitrag 1523698)
Allerdings nichts, was bei disziplinierter Programmierung ein Problem sein sollte. Zur Zeit arbeite ich mit Fall 1), 2) und anderen Techniken. Gebe aber zu, der Ansatz mit dem Fake-Objekt hat einen gewissen Charme und schwirrt im Kopf herum. Im Anhang der Sourcecode zum Spielen.

Bis bald...
Thomas

Das nennt sich "NULL object pattern" und hat durchaus seinen Nutzen. Wenn konsequent verwendet spart es an vielen Stellen Test von Rückgabewerten auf nil und dergleichen. Allerdings versteckt man dadurch auch Fehlerfälle, die dem Benutzer besser zur Kenntnis gebracht werden sollten...

mytbo 24. Jun 2023 16:40

AW: Die Arbeit mit Fake-Objekten erspart die Prüfung auf Nil
 
Zitat:

Zitat von peterbelow (Beitrag 1523711)
Das nennt sich "NULL object pattern" und hat durchaus seinen Nutzen. Wenn konsequent verwendet spart es an vielen Stellen Test von Rückgabewerten auf nil und dergleichen. Allerdings versteckt man dadurch auch Fehlerfälle, die dem Benutzer besser zur Kenntnis gebracht werden sollten...

Danke für den Hinweis. Vielleicht ist es nur Semantik, aber ich hatte das Entwurfsmuster Nullobjekt etwas anders verstanden:
Zitat:

Instead of using a null reference to convey absence of an object (for instance, a non-existent customer), one uses an object which implements the expected interface, but whose method body is empty. The advantage of this approach over a working default implementation is that a null object is very predictable and has no side effects: it does nothing.
Mein Verständnis der Umsetzung ist etwas allgemeiner. Es kann eben mehr als nichts sein, z.B. ein Default-Objekt. Die Verwendung des Wortes "Fake-Objekt" sollte dem Rechnung tragen. Vielleicht lege ich die Definition das Pattern auch nur zu eng aus. Begebe mich auf Recherche.

Bis bald...
Thomas

mytbo 24. Jun 2023 16:52

AW: Die Arbeit mit Fake-Objekten erspart die Prüfung auf Nil
 
Zitat:

Zitat von KodeZwerg (Beitrag 1523704)
Nur mal auf die schnelle zusammengebasteln wie ich es meist klassisch mache ...

Danke für deinen Beitrag. Da du mORMot verwendest, kannst du dein Datenobjekt auch von der Klasse TObjectWithID ableiten und/oder dein dynamisches Array mit TDynArray verwalten. Dadurch bekommst du viel Funktionalität geschenkt.

Bis bald...
Thomas

Blup 24. Jun 2023 18:57

AW: Die Arbeit mit Fake-Objekten erspart die Prüfung auf Nil
 
In unsererem Framework ist in der Basisklasse aller Datenobjekte die Klassenmethode NullObject vorgesehen, die eine spezielle Instance der Klasse zurückgibt.
Dabei werden einmal erzeugte NullObjekte in einem Dictionary registriert, wenn benötigt wiederverwendet und bei Programmende freigegeben.
Bei einem NullObjekt werden Änderungen an Properties im Setter ignoriert.

jaenicke 24. Jun 2023 18:59

AW: Die Arbeit mit Fake-Objekten erspart die Prüfung auf Nil
 
Das sind im Grunde zwei vollkommen unterschiedliche Aspekte. Der eine Aspekt ist, dass man statt nil immer einen neutralen Wert zurückliefert. Das geht aber nur, wenn der Anwendungsfall dafür geeignet ist, sprich dieses neutrale Objekt auch wirklich keine Auswirkungen hat. Eine Prüfung, ob das Objekt gültig ist, ist daher unnötig, was den großen Vorteil ausmacht.

Fakedaten sind eine komplett andere Schiene, bei der man statt eines neutralen Objekts ein ungültiges Fakeobjekt zurückliefert. Dann muss man aber lediglich statt der Prüfung auf nil eine andere Validitätsprüfung ausführen, was den Aufwand nicht verringert, aber fehleranfälliger ist. Denn auf das Fakeobjekt kann man ja zugreifen, ohne dass ein Fehler passiert (der auffallen würde und geloggt werden würde). Trotzdem kommt am Ende ggf. etwas Falsches heraus, was man dann ggf. aufwendig sucht.

Ein Anwendungsfall wäre die Qualitätssicherung, bei der man dann die Fakedaten anzeigt, um Tests ohne Dateneingabe zu ermöglichen. Das ist aber hier ja nicht gemeint gewesen.

freimatz 26. Jun 2023 08:51

AW: Die Arbeit mit Fake-Objekten erspart die Prüfung auf Nil
 
Stimme dem zu außer:
Zitat:

Zitat von jaenicke (Beitrag 1523721)
Denn auf das Fakeobjekt kann man ja zugreifen, ohne dass ein Fehler passiert

Man könnte ja es so machen, dass das "Fakeobjekt" den Fehler erzeugt z.B. eine Exceptionen.
Dann muss das nicht jeder Aufrufer machen. Bei einem Nil und einer vergessenen Abfrage würde man dann eine schönere exception bekommen und nicht eine AV.
Auf der anderen Seite könnte ja dann auch schon der, der das Objekt liefern soll eine Exceoption raisen wenn es nicht geht. :cyclops:

Wir machen Nullobjekte in der Regel (nur) dann, wenn das Nichtvorhandensein ein Anwendungsfall ist.

jaenicke 26. Jun 2023 09:46

AW: Die Arbeit mit Fake-Objekten erspart die Prüfung auf Nil
 
Zitat:

Zitat von freimatz (Beitrag 1523736)
Man könnte ja es so machen, dass das "Fakeobjekt" den Fehler erzeugt z.B. eine Exceptionen.
Dann muss das nicht jeder Aufrufer machen. Bei einem Nil und einer vergessenen Abfrage würde man dann eine schönere exception bekommen und nicht eine AV.

Das ist sicherlich richtig, aber dafür lohnt sich meiner Meinung der Aufwand nicht, da das ja nicht oft passieren sollte. Der Vorteil eines Fakeobjekts wäre dann ja weg.


Alle Zeitangaben in WEZ +1. Es ist jetzt 01:09 Uhr.

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz