Delphi-PRAXiS

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.

Stevie 8. Sep 2014 18:55

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

Zitat von Dejan Vu (Beitrag 1271672)
Na, ich würde auch dann keinen Kunden ableiten, sondern die Strategy verwenden (mit der Factory, fertig).

Zitat:

Zitat von sahimba (Beitrag 1271678)
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.

Wie schon gesagt, das Beispiel hinkt, ein einziger numerischer Wert, der sich unterscheidet, rechtfertigt kaum verschiedene Klassen.
Aber lasst uns ruhig noch nen bisschen auf dem Beispiel rumreiten, bis wir den eigentlichen Sinn davon wieder vergessen haben :twisted:

Dejan Vu 8. Sep 2014 19:20

AW: Case-Anweisung auf Enumerationstyp. Eines Tages wird der Enumerationstyp erweiter
 
Da hat mal einmal im Leben die Möglichkeit, etwas an Deinen Ausführungen zu kritisieren, und ja ok, dreht sich dabei im Kreis, u aber man muss doch die ganzen Jahre nachholen, und dann wird einem das auch noch madig gemacht.

Das ist echt nicht fair. :(

Ich schmolle jetzt übrigens, falls es hier Irgendjemanden interessiert.

Stevie 8. Sep 2014 20:03

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

Zitat von Dejan Vu (Beitrag 1271694)
Da hat mal einmal im Leben die Möglichkeit, etwas an Deinen Ausführungen zu kritisieren, und ja ok, dreht sich dabei im Kreis, u aber man muss doch die ganzen Jahre nachholen, und dann wird einem das auch noch madig gemacht.

Das ist echt nicht fair. :(

Ich schmolle jetzt übrigens, falls es hier Irgendjemanden interessiert.

Ach, da gibts oft genug Gelegenheiten, du musst sie nur erkennen :stupid:

Aber mal Quatsch beiseite, das ist natürlich das Problem bei Beispielen, um bestimmte Aspekte zu verdeutlichen, dass es immer Haken gibt und man im Rahmen dieses limitierten Beispielcodes etwas komplett anders lösen würde/könnte. Nur meist ist der Code ja da, um eine bestimmte Sache zu verdeutlichen.
Im übrigen ist das auch ein Kritikpunkt, der von manchen gebracht wird - "ihr mit euren Beispielen, das klappt in der Praxis doch eh alles nicht" (erst Samstag wieder in der Pause von irgendwo aufgeschnappt).

Dejan Vu 8. Sep 2014 20:19

AW: Case-Anweisung auf Enumerationstyp. Eines Tages wird der Enumerationstyp erweiter
 
Das ist ein schönes Thema für eine K&T-Thread (obwohl es fachbezogen und sehr interessant ist): "Wie finde ich einfache Beispiele, anhand derer ich einen Sachverhalt erklären kann, ohne das irgendein Nörgler die in der Luft zerreißt".

Und Nörgler gibt es immer. Irgendwie.

Manchmal bekomme ich es hin, aber manchmal auch nicht, und da bügele ich im Vortrag den Nörgler schon mal mit einem 'Maneuverkritik können wir nachher üben, jetzt bleiben wir beim Thema' und beim 2. Einwand "Das stört jetzt". Meistens reicht es. Der Typ ist zwar beleidigt, aber der Vortrag ist gerettet :lol:

Aber wir kommen echt vom Thema ab. Wobei dazu mit 'Factory, Strategy, aber nicht übertreiben' eigentlich alles gesagt ist.

Stevie 8. Sep 2014 20:37

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

Zitat von Dejan Vu (Beitrag 1271698)
Wobei dazu mit 'Factory, Strategy, aber nicht übertreiben' eigentlich alles gesagt ist.

Exakt. Das erwähne ich auch fast immer, wenn ich über Prinzipien etc rede: das sind Werkzeuge und grobe Richtlinien in unserem riesengroßen Werkzeugkasten.
Das heißt nicht, dass sie immer und überall Anwendung finden müssen - manchmal kann man auch fünfe grade sein lassen und muss das nicht bis ins letzte Bit verdesignpatternifizieren :mrgreen:


Alle Zeitangaben in WEZ +1. Es ist jetzt 11:50 Uhr.

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