Initialization Pattern?
type
TIdentification = (Foo, Bar, Fubar, Snark, Plugh, Plover);
TAssociation = (Ninja, Cowboy, Pirate);
TIdAssoArray = array[TIdentification] of TAssociation;
var // or const
IdentityToType : TIdAssoArray;
What is the best way of initializing IdentityToType so that it is robust against adding, removing or reordering the TIdentity members?
Actually the most robust way is not having an array at all but a list with records/classes that contain TIdentification and TAssociation and a check procedure that is called from the initialization section.
Not the best performance though.
I’m currently doing a loop for t := low(TIdentification) to high(TIdentification) and a case statement, but it’s getting harder to maintain, as the number of ids and associations goes up.
Declaring IdentityToType as const is only robust against adding/removing but not to reordering TIdentification. But for me that is usually good enough – you might put a comment there saying that if changing the order you must modify the const.
Is the association and mapping tightly coupled or is it actually somewhere else in the code?
If you persist the TIdAssoArray, you will need some RTTI verification coupled with the TIdentification item names.
Asbjørn Heid – The intent is to have it tightly coupled.
I wish it would have been possible to have a construct similar to
const
IdentityToType = TIdAssoArray(
(Foo: Ninja),
(Bar: Pirate),
(Fubar: Ninja),
(Snark: Pirate),
(Plugh: Ninja),
(Plover: Cowboy) );
which would complain on missing, duplicate or unknown elements.
Lars Fosdal Hm yes, I had some ideas on the tram but didn’t pan out.
To check the pattern at runtime, use:
function Check_TIdentification: Boolean;
var
tid: TIdentification;
const
tidName : array[TIdentification] of String = (
‘Foo’,
‘Bar’,
‘Fubar’,
‘Snark’,
‘Plugh’,
‘Plover’);
begin
for tid := Low(TIdentification) to High(TIdentification) do
begin
if (GetEnumName(TypeInfo(TIdentification),Ord(tid)) tidName[tid]) then
Exit(False);
end;
Result := true;
end;
This will catch adding,removing and reordering.
Leif Uneus And why does that require the enum name? 😉
function Check_TIdentification: Boolean;
const
IdValues: array[TIdentification] of TIdentification = (
Foo, Bar, Fubar, Snark, Plugh, Plover);
var
id: TIdentification;
begin
for id := Low(TIdentification) to High(TIdentification) do
if id IdValues[id] then
Exit(False);
Result := true;
end;
Leif Uneus – No, no and no. That’s introducing yet another order that needs to be maintained. Here is what I do now.
procedure InitIdentities;
var
t: TIdentification;
begin
for t := Low(IdentityToType) to (High(IdentityToType)
do Case t of
Foo,
Fubar,
Plugh: IdentityToType[t] := Ninja;
Bar,
Snark: IdentityToType[t] := Pirate;
Plover: IdentityToType[t] := Cowboy;
else raise EIdentificationConfigError.Create(‘Missing init for ‘ + GetEnumName(TypeInfo(TIdentification), Ord(t)));
end;
end;
Which will detect missing inits at first try – but – I’d rather have that check done at compile time, and have the Identity and Association relationships clearly defined in the source. The above is a bit messy when you start running into larger numbers of Identities and Associations.
Personally I hate wasting my time writing code to prevent stupid programmers from breaking anything.
A comment stating that you have to reorder the const array when you change the order of the enum should suffice. And for any mistakes: commit history/blame is your friend.
What if you have a multi-dimensional matrix?
Can’t they have comments?
Sure, but imagine manually changing the order of 50+ submatrixes… That’s a rather error prone task.
I guess I just want constant array declarations to be smarter.
Ok, here is another alternative:
procedure InitAsso(var asso: TIdAssoArray);
Type
TIdAssoRec = record
id : TIdentification;
asso: TAssociation;
end;
const
IdAssoRecArr : array[TIdentification] of TIdAssoRec =
((id:Foo; asso:Ninja),
(id:Bar; asso:Ninja),
(id:Fubar; asso:Cowboy),
(id:Snark; asso:Pirate),
(id:Plugh; asso:Pirate),
(id:Plover;asso:Cowboy));
var
tid: TIdentification;
begin
for tid := Low(TIdentification) to High(TIdentification) do
begin
if (tid IdAssoRecArr[tid].id) then
raise Hell;
asso[tid] := IdAssoRecArr[tid].asso;
end;
end;
Perhaps easier to maintain, but still ugly and still a runtime (mostly) check.
Stefan Glienke Yes, it could have been done without strings. My mind was set on next step where perhaps the array would have to be stored and must handle reordering. I guess using enumerations here is a bit of an antipattern for large enumeration items.
You could also do this:
Type
TIdAssoRec = record
private
id : TIdentification;
asso: TAssociation;
class function Init(i: TIdentification;a:TAssociation): TIdAssoRec; static;
public
class procedure InitAsso(var asso: TIdAssoArray); static;
end;
class function TIdAssoRec.Init(i: TIdentification; a: TAssociation): TIdAssoRec;
begin
Result.id := i;
Result.asso := a;
end;
class procedure TIdAssoRec.InitAsso(var asso: TIdAssoArray);
var
IdAssoRecArr: TArray;
tid: TIdentification;
begin
IdAssoRecArr :=
[Init(Foo, Ninja),
Init(Bar, Ninja),
Init(Fubar, Cowboy),
Init(Snark, Pirate),
Init(Plugh, Pirate),
Init(Plover, Cowboy)];
if (High(IdAssoRecArr) Ord(High(TIdentification))) then
raise Hell;
for tid := Low(TIdentification) to High(TIdentification) do
begin
if (tid IdAssoRecArr[Ord(tid)].id) then
raise Hell;
asso[tid] := IdAssoRecArr[Ord(tid)].asso;
end;
end;