Das Pizza-Beispiel ist schlecht, denn niemand würde einen Pizza-Service schreiben, wo die Toppings als Enums deklariert sind.
Kommen wir mal zu diesem Beispiel, eine Ampelschaltung mit der
StateMachine (auch die kann
erweitert werden):
Delphi-Quellcode:
program Project3;
{$APPTYPE CONSOLE}
{$R *.res}
{$DEFINE USE_ENUMS}
uses
System.SysUtils,
Stateless,
Stateless.Utils;
type
{$IFDEF USE_ENUMS}
{$SCOPEDENUMS ON}
TState = (
{} Red,
{} RedYellow,
{} Yellow,
{} Green );
TTrigger = ( Timer );
{$ELSE}
TState =
type string;
TStateHelper =
record helper
for TState
const
Red = '
Red';
RedYellow = '
RedYellow';
Yellow = '
Yellow';
Green = '
Green';
end;
TTrigger =
type string;
TTriggerHelper =
record helper
for TTrigger
const
Timer = '
Timer';
end;
{$ENDIF}
TTrafficLight = TStateMachine<TState, TTrigger>;
TTrafficLightData =
class
private
FState: TState;
public
property State: TState
read FState
write FState;
end;
procedure Test( AData: TTrafficLightData );
var
LLight: TTrafficLight;
LIdx : Integer;
begin
LLight := TTrafficLight.Create(
function: TState
begin
Result := AData.State;
end,
procedure(
const s: TState )
begin
AData.State := s;
end );
try
LLight.Configure( TState.Red )
{} .Permit( TTrigger.Timer, TState.RedYellow );
LLight.Configure( TState.RedYellow )
{} .Permit( TTrigger.Timer, TState.Green );
LLight.Configure( TState.Green )
{} .Permit( TTrigger.Timer, TState.Yellow );
LLight.Configure( TState.Yellow )
{} .Permit( TTrigger.Timer, TState.Red );
Writeln( LLight.ToString );
for LIdx := 1
to 10
do
begin
LLight.Fire( TTrigger.Timer );
// ohne Enum und 'Blue' kommt erst hier die Exception
Writeln( LLight.ToString );
end;
finally
LLight.Free;
end;
end;
procedure TestStart;
const
InitialStateString = '
Blue';
// Wert wird aus der Datenbank gelesen
var
LData: TTrafficLightData;
begin
LData := TTrafficLightData.Create;
try
{$IFDEF USE_ENUMS}
LData.State := TEnum.ToEnum<TState>( InitialStateString );
// Wirft eine Exception bei 'Blue'
{$ELSE}
LData.State := InitialStateString;
// 'Blue' wird anstandslos akzeptiert
{$ENDIF}
Test( LData );
finally
LData.Free;
end;
end;
begin
try
TestStart;
except
on E:
Exception do
Writeln( E.ClassName, '
: ', E.
Message );
end;
Readln;
end.
In diesem (speziellen) Fall bringt mir der Enum den Vorteil, dass
fehlerhafte Daten
'Blue'
sehr früh als solche mir um die Ohren fliegen und auch
sehr nah an der Quelle des Übels.
Bei der Definition der Konstanten kann man so einen
Fehler machen
Delphi-Quellcode:
TStateHelper = record helper for TState
const
Red = 'Red';
RedYellow = 'Red'; // CopyPaste-Fehler durch den Programmierer
Yellow = 'Yellow';
Green = 'Green';
end;
der sich aber anstandslos
kompilieren lässt. Wenn ich Glück habe, fällt dies zur Laufzeit auf, wenn ich Pech habe läuft einfach alles nur Grütze und keiner weiß warum - beten wir, dass wir einen
Unittest haben, der diesen Fehler aufdeckt.
Bei einem Enum geht das nicht
Delphi-Quellcode:
TState = (
{} Red,
{} Red, // CopyPaste-Fehler durch den Programmierer
{} Yellow,
{} Green );
denn
da schreit einen
der Compiler direkt beim Kompilieren an.
Zitat von
Meine Meinung:
Es geht nicht darum, dass Enums besser als Konstanten sind.
Es gibt aber Fälle (s.o.), wo ein Enum besser ist als Konstanten.
Genau wie es Fälle gibt, wo Konstanten besser als Enums sind.
Und es gibt Fälle, da sind weder Enums noch Konstanten angebracht (s. Pizza-Toppings).
Man muss wissen, welche Vor- und Nachteile das eine oder das andere mit sich bringt und dann eine Entscheidung treffen und mit dieser und den daraus resultierenden Nachteilen leben - Refactoring geht ja auch immer.
Ich würde es gerne noch größer und fetter und bunter und noch auffälliger schreiben, aber ich bin hier in den Möglichkeiten durch das Forum beschränkt