Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Case-Anweisung auf Enumerationstyp. Eines Tages wird der Enumerationstyp erweitert. (https://www.delphipraxis.net/181772-case-anweisung-auf-enumerationstyp-eines-tages-wird-der-enumerationstyp-erweitert.html)

Der schöne Günther 8. Sep 2014 16:33

Case-Anweisung auf Enumerationstyp. Eines Tages wird der Enumerationstyp erweitert.
 
Es geht mir um einen Punkt von Bernd Uas Vortrag "Besseren Code mit Delphi" von den Delphi-Tagen 2014:

Einen seiner letzten Punkte habe ich nicht verstanden:

Wenn man, beispielsweise mittels
Delphi-Quellcode:
case
-Anweisung, einen Enumerationstypen überprüft sollte man sich die Möglichkeit offen lassen, in einer Unterklasse auch auf neu hinzugekommene Typen zu reagieren. Wie soll das gehen?

Im folgenden ein
Delphi-Quellcode:
TStadtMusikantenChecker
der prüft, ob ein
Delphi-Quellcode:
TAnimal
zu den Bremer Stadtmusikanten gehört: Er prüft nur auf Hund und Maus. Später wurde TAnimal um eine Katze erweitert. Dafür wurde dann die Ableitung
Delphi-Quellcode:
TStadtMusikantenCheckerSub = class(TStadtMusikantenChecker)
gebildet:

Delphi-Quellcode:
program Project1;

{$APPTYPE CONSOLE}
{$R *.res}

uses System.SysUtils;

type
   TAnimal = (hund, katze, maus);

   TStadtMusikantenChecker = class
      public class function checkIsInBremerStadtMusikanten(const animal: TAnimal): Boolean; virtual;
      protected class function checkIsInBremerStadtMusikantenHook(const animal: TAnimal): Boolean; virtual;
   end;

   TStadtMusikantenCheckerSub = class(TStadtMusikantenChecker)
      protected class function checkIsInBremerStadtMusikantenHook(const animal: TAnimal): Boolean; override;
    end;



class function TStadtMusikantenChecker.checkIsInBremerStadtMusikanten(const animal: TAnimal): Boolean;
begin
   case animal of
      TAnimal.hund: Result := True;
      TAnimal.maus: Result := False
   else
      //raise EArgumentOutOfRangeException.Create(EmptyStr);
      Result := checkIsInBremerStadtMusikantenHook(animal);
   end;
end;

class function TStadtMusikantenChecker.checkIsInBremerStadtMusikantenHook(const animal: TAnimal): Boolean;
begin
   raise EArgumentOutOfRangeException.Create(EmptyStr);
end;

class function TStadtMusikantenCheckerSub.checkIsInBremerStadtMusikantenHook(const animal: TAnimal): Boolean;
begin
   case animal of
      TAnimal.katze: Result := True;
   else
      Result := inherited;
   end;
end;


const
   animal: TAnimal = TAnimal.hund;

begin
   try
      WriteLn( TStadtMusikantenChecker.checkIsInBremerStadtMusikanten(animal) );
   except
      on E: Exception do
         Writeln(E.ClassName, ': ', E.Message);
   end;

   ReadLn;
end.
War so etwas gemeint? Oder habe ich etwas falsch verstanden?

nuclearping 8. Sep 2014 16:49

AW: Case-Anweisung auf Enumerationstyp. Eines Tages wird der Enumerationstyp erweiter
 
Wenn, dann
Delphi-Quellcode:
WriteLn( TStadtMusikantenCheckerSub.checkIsInBremerStadtMusikanten(animal) );
. Sonst weiss ja
Delphi-Quellcode:
TStadtMusikantenChecker
nichts von der Existenz von
Delphi-Quellcode:
TStadtMusikantenCheckerSub
und die neue Funktion wird nicht aufgerufen. Oder hab ich da was falsch verstanden? :stupid:

Obgleich sich mir nicht der Sinn erschließt, warum man dafür eine extra Unterklasse implementieren soll, wenn man die Enum erweitert? Soll das gegeben dem Fall sein, dass man keinen Zugriff auf den Originalcode hat? Bzw. nicht darin "rumwurschteln" braucht?

Der schöne Günther 8. Sep 2014 17:06

AW: Case-Anweisung auf Enumerationstyp. Eines Tages wird der Enumerationstyp erweiter
 
Zitat:

Zitat von nuclearping (Beitrag 1271658)
Wenn, dann TStadtMusikantenCheckerSub

Ja, natürlich. Im geposteten Code noch vollkommen ok, denn es ist ja ein Hund 8-)

Zitat:

Obgleich sich mir nicht der Sinn erschließt, warum man dafür eine extra Unterklasse implementieren soll [...]nicht darin "rumwurschteln"?
Deshalb frage ich ja. Ist aber, finde ich, schon ein Argument dafür. Die Unterklasse ist dann wie ein kleiner Helfer der die Oberklasse um eine detailliertere Behandlung "erweitert".

Habe ich bislang jedenfalls noch nie so gemacht :|

Stevie 8. Sep 2014 17:14

AW: Case-Anweisung auf Enumerationstyp. Eines Tages wird der Enumerationstyp erweiter
 
Bei sowas sind enums zu vermeiden und Vererbung/Polymorphie zu nutzen (z.B. Strategie oder Kommando Pattern)

Wenn ich das richtig in Erinnerung habe, dann ging es um das TCustomer Beispiel und den jeweiligen Rabatt. In solch einem Fall hab ich verschiedene TCustomer Nachfahren, die jeweils die GetRabatt Methode überschreiben.

In diesem Zusammenhang finde ich diesen Clean Code Talk sehr erhellend.

Uwe Raabe 8. Sep 2014 17:22

AW: Case-Anweisung auf Enumerationstyp. Eines Tages wird der Enumerationstyp erweiter
 
Das hast du noch nicht ganz richtig verstanden. Da deine Methode eine Eigenschaft des übergebenen Parameters bzw. die Zugehörigkeit zu einer Gruppe prüft, ist erstens die case-Anweisung an sich fraglich und zweitens eine Ableitung wegen einer vergrößerten Testmenge auch nicht gerade pfiffig. Ohne jetzt weiter auf die adäquate Lösung des Musikantenproblems einzugehen, lieber mal ein Beispiel für das was Bernd eigentlich meint.

Wenn das case-Konstrukt allein dafür da ist, unterschiedliche Implementationen aufzurufen, ist es eigentlich ratsamer für jede Implementation eine abgeleitete Klasse zu schreiben und dann je nach gewünschter Implementation die passende Klasse zu instantiieren. Dabei verlagert man dann den case-Konstrukt aus der Implementationsauswahl an die Stelle, wo die richtige Klasse ausgewählt werden muss. Die Idee dahinter ist eigentlich die, daß eine Klasse nur für eine bestimmte Aufgabe bestimmt sein und nicht mehrere Aufgaben zusammenfassen soll. Bei einer neuen Aufgabe erweitert man dann nicht die Funktionalität der einen Klasse sondern schreibt eine eigene.

Würde man das Musikantenproblem so implementieren, dann würde man eine Basis-Klasse schreiben, die eine

Delphi-Quellcode:
function checkIsInBremerStadtMusikanten: Boolean; virtual; abstract;


deklariert, und davon für jedes mögliche Tier eine Ableitung bereitstellt, die diese Funktion entsprechend implementiert. Je nach abzufragendem Tier verwendest du dann die entsprechende Klasse. Das ist natürlich blanker Unsinn und man sieht dabei schön, daß man ein Clean Code Prinzip nicht blind auf alle möglichen Fälle anwenden sollte.

Für diesen konkreten Fall wäre ein
Delphi-Quellcode:
set of TAnimal
die bessere Wahl.

Dejan Vu 8. Sep 2014 17:23

AW: Case-Anweisung auf Enumerationstyp. Eines Tages wird der Enumerationstyp erweiter
 
Na in dem Beispiel mit den Tieren aus Bremen wäre das Case-Switch imho noch annehmbar. Deswegen gleich eine Klassenfamilie aufzumachen, wäre dann doch etwas Overkill. Lesbar soll das ja noch bleiben. Aber der Hinweis auf die Pattern ist sicherlich hilfreich (Sobald eine zweites Case ins Spiel kommt oder etwas mehr passiert, als einen Return-Wert zu setzen, sollte man aber ansetzen).

Abschließend vielleicht noch das Stichwort: Factory. Das ist das, was Uwe und Stevie meinen. In der (Klassen)Fabrik wird anhand einer Eigenschaft der Spezialist (also die Klasse) ausgewählt, der die Verarbeitung übernimmt.

Einfaches Beispiel: Pizza.
Delphi-Quellcode:
Type
  TPizza = (Margeritha, Funghi, QuatroStattione, MitWienerWurstUndSenf);

// Anstatt
Case Pizza of
  Margeritha: begin PackdieTomatesoßerauf; UndKäse; end;
  Funghi : begin PackdieTomatesoßerauf; Pilze; UndKäse; end;
  ...
...
  end;
//schreibt man
  PizzaFactory.CreateRecipe(Pizza).BelegDiePizza();
// Und dieser Code ändert sich dann niemals wieder.
Man stelle sich vor, aus der kleinen Klitsche, die 4 Pizzas macht, wird mal eine, die 30 Sorten kann. Dann würde das Case-Statement ziemlich schrottig aussehen, wohingegen bei der Factory alles immer noch so aufgeräumt ist, wie am ersten Tag.

Merke: Mit sauberem Code kaschiert man auch das eigene Chaos (mach ich jedenfalls so):-D

Stevie 8. Sep 2014 17:36

AW: Case-Anweisung auf Enumerationstyp. Eines Tages wird der Enumerationstyp erweiter
 
Zitat:

Zitat von Dejan Vu (Beitrag 1271669)
Bei den Kunden mit den Rabatten würde ich mir auch 2x überlegen, ob ich dafür Ableitungen von Kunden machen würde. Erstens sind das immer noch Kunden und nur weil der heute mal 20% Rabatt bekommt (Geburtstag!) ist das doch kein anderer Kunde, und außerdem würde für den Rabatt wieder eine Strategy passen, unter der Annahme, das es sich bei Rabatt um etwas komplexeres handelt, als nur eine einzelne Zahl.

Ich stelle mir auch lustig vor, dem Kunden zur Laufzeit einen anderen Rabatt zu verpassen: Da müsste ich den Kunden komplett zerstören (also seine Instanz, nicht ihn), nur weil heute sein Geburtstag ist.. Nun ja.

In deinem Beispiel besteht die Rabattberechnung aus mehreren Teilen, der erste Teil, der sich über den Typ des Kunden ergibt (normalo, silber, gold...), also der Rabatt, den er immer bekommt und zusätzlich ich nenns mal temporären Rabatt (weil er Geburtstag hat oder Deutschland 7 Tore geschossen hat, oder...).

Natürlich hinkt das Beispiel wie es Beispiele nunmal tun, denn der flexibelste Weg wäre eine Rabatt Property. Die ermöglicht nämlich auch individuell ausgehandelten Rabatt, den man z.b. in seiner Datenbank pflegt. Aber darum gehts ja in dem Fall nicht, sondern darum, Conditions über Polymorphie zu lösen.

Dejan Vu 8. Sep 2014 17:42

AW: Case-Anweisung auf Enumerationstyp. Eines Tages wird der Enumerationstyp erweiter
 
Na, ich würde auch dann keinen Kunden ableiten, sondern die Strategy verwenden (mit der Factory, fertig).

nuclearping 8. Sep 2014 18:06

AW: Case-Anweisung auf Enumerationstyp. Eines Tages wird der Enumerationstyp erweiter
 
Danke für die Erklärungen. :thumb: Werde mir das Video mal in Ruhe anschauen. Bei sowas lernt man ja (wie man sieht) nie aus. :stupid:

sahimba 8. Sep 2014 18:07

AW: Case-Anweisung auf Enumerationstyp. Eines Tages wird der Enumerationstyp erweiter
 
Zitat:

Zitat von Stevie (Beitrag 1271664)
In solch einem Fall hab ich verschiedene TCustomer Nachfahren, die jeweils die GetRabatt Methode überschreiben.

Da fiele mir allerdings als erstes das Strategy Pattern ein.


Alle Zeitangaben in WEZ +1. Es ist jetzt 18:00 Uhr.
Seite 1 von 2  1 2      

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