![]() |
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:
-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?
case
Im folgenden ein
Delphi-Quellcode:
der prüft, ob ein
TStadtMusikantenChecker
Delphi-Quellcode:
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
TAnimal
Delphi-Quellcode:
gebildet:
TStadtMusikantenCheckerSub = class(TStadtMusikantenChecker)
Delphi-Quellcode:
War so etwas gemeint? Oder habe ich etwas falsch verstanden?
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. |
AW: Case-Anweisung auf Enumerationstyp. Eines Tages wird der Enumerationstyp erweiter
Wenn, dann
Delphi-Quellcode:
. Sonst weiss ja
WriteLn( TStadtMusikantenCheckerSub.checkIsInBremerStadtMusikanten(animal) );
Delphi-Quellcode:
nichts von der Existenz von
TStadtMusikantenChecker
Delphi-Quellcode:
und die neue Funktion wird nicht aufgerufen. Oder hab ich da was falsch verstanden? :stupid:
TStadtMusikantenCheckerSub
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? |
AW: Case-Anweisung auf Enumerationstyp. Eines Tages wird der Enumerationstyp erweiter
Zitat:
Zitat:
Habe ich bislang jedenfalls noch nie so gemacht :| |
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 ![]() |
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:
die bessere Wahl.
set of TAnimal
|
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:
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.
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. Merke: Mit sauberem Code kaschiert man auch das eigene Chaos (mach ich jedenfalls so):-D |
AW: Case-Anweisung auf Enumerationstyp. Eines Tages wird der Enumerationstyp erweiter
Zitat:
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. |
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).
|
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:
|
AW: Case-Anweisung auf Enumerationstyp. Eines Tages wird der Enumerationstyp erweiter
Zitat:
|
AW: Case-Anweisung auf Enumerationstyp. Eines Tages wird der Enumerationstyp erweiter
Zitat:
Zitat:
Aber lasst uns ruhig noch nen bisschen auf dem Beispiel rumreiten, bis wir den eigentlichen Sinn davon wieder vergessen haben :twisted: |
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. |
AW: Case-Anweisung auf Enumerationstyp. Eines Tages wird der Enumerationstyp erweiter
Zitat:
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). |
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. |
AW: Case-Anweisung auf Enumerationstyp. Eines Tages wird der Enumerationstyp erweiter
Zitat:
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 22:04 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