How to group constant strings together in Delphi - delphi

I application that uses strings for different status an item can be during its life.
ie
OPEN,
ACTIVE,
CLOSED,
DELETE,
and so on, at the moment they are all hard coded into code like so
MyVar := 'OPEN';
I am working on changing this as it can be a maintenance problem, so I want to change them all to a constants, I was going to do it like so
MyVar := STATUS_OPEN;
but I would like to group them together into one data structure like so
MyVar := TStatus.Open;
What is the best way to do this in delphi 2007?
I know I can make a record for this, but how do I populate it with the values, so that it is available to all objects in the system without then having to create a variable and populating the values each time?
Ideal I would like to have one central place for the data structure and values, and have them easily accessible (like TStatus.Open) without having to assign it to a variable or creating an object each time I use it.
I am sure there is a simple solution that i am just missing. any ideas?

As Jim mentioned you could use class constants or an enumerated type:
type
TItemStatus = (isOpen, isActive, isClosed);
const
ItemStatusStrings: array[TItemStatus] of string = ('Open', 'Active', 'Closed');

See http://edn.embarcadero.com/article/34324 ("New Delphi language features since Delphi 7".
A class constant would do nicely. From that link above:
type
TClassWithConstant = class
public
const SomeConst = 'This is a class constant';
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ShowMessage(TClassWithConstant.SomeConst);
end;

I personally have used TOndrej's approach extensively in several large mission critical data processing platforms. The benefit of enumerations is that they can be easily passed around within an application, are very compact (an ordinal type), work perfectly in case statements and are completely type safe. The later point is important for maintenance since deleting or changing enumeration values will cause a compile error (a good think IMHO).
Some gotcha's to look out for with this approach:
Changing the declared order of enum values will foo bar the enum->string lookup array.
If you use the enum in case statements (a nice feature) be sure to take into account new values being added. I usually add an else to the case and throw an exception on unknown values. Much better than falling through the case.
If you are very concerned about the first gotcha, you can use a record in the lookup array and include the enum value in each record and validate the ordering from the unit initialization. This has saved my bacon many times in mission critical systems where maintenance is frequent. This approach can also be used to add additional meta data to each value (a very handy feature).
Best of luck!
unit Unit1;
interface
type
TItemStatusEnum = (isOpen, isActive, isClosed);
TItemStatusConst = class
class function EnumToString(EnumValue : TItemStatusEnum): string;
property OPEN: string index isOpen read EnumToString;
property CLOSED: string index isClosed read EnumToString;
property ACTIVE: string index isActive read EnumToString;
end;
var
ItemStatusConst : TItemStatusConst;
implementation
uses
SysUtils;
type
TItemStatusRec = record
Enum : TItemStatusEnum;
Value : string;
end;
const
ITEM_STATUS_LOOKUP : array[TItemStatusEnum] of TItemStatusRec =
((Enum: isOpen; Value: 'OPEN'),
(Enum: isActive; Value: 'ACTIVE'),
(Enum: isClosed; Value: 'CLOSED'));
procedure ValidateStatusLookupOrder;
var
Status : TItemStatusEnum;
begin
for Status := low(Status) to high(Status) do
if (ITEM_STATUS_LOOKUP[Status].Enum <> Status) then
raise Exception.Create('ITEM_STATUS_LOOKUP values out of order!');
end;
class function TItemStatusConst.EnumToString(EnumValue: TItemStatusEnum): string;
begin
Result := ITEM_STATUS_LOOKUP[EnumValue].Value;
end;
initialization
ValidateStatusLookupOrder;
end.
Update:
Thanks for the critique - you are absolutely correct in that I was solving what I perceived as the problem underlying the question. Below is a much simpler approach if you can live with the constraint that this must ONLY work in Delphi 2007 or later versions (might work in D2006?). Hard to shed those nagging thoughts of backward compatibility ;)
type
ItemStatusConst = record
const OPEN = 'OPEN';
const ACTIVE = 'ACTIVE';
const CLOSED = 'CLOSED';
end;
This approach is simple and has a similar feel to what one might do in a Java or .Net application. Usage semantics are as expected and will work with code completion. A big plus is that the constants are scoped at a record level so there is no risk of clashes with other definitions as happens with unit scoped types.
Sample usage:
ShowMessage(ItemStatusConst.ACTIVE);
ShowMessage(ItemStatusConst.CLOSED);
Just for fun, I have also revised my earlier approach to directly address the original question with a similar outcome. This time I made use of "simulated class properties" (see here) with a property indexer. This is clearly more complex than the record approach, but retains the ability to work with enum values, sets, strings AND can also be extended to implement additional meta data features where required. I believe this works for Delphi versions going as far back as Delphi 5 IIRC.
An example of where I used this technique to great effect was a parsing framework I created in Delphi to handle the full IBM AFPDS print data stream grammar. This flexibility is why I love working in Delphi - it is like a Swiss army knife ;)

Or you could combine #Jim and #TOndrej
TGlobalConsts = class
type
TItemStatus = (isOpen, isActive, isClosed);
const
ItemStatusStrings: array[TItemStatus] of string = ('Open', 'Active', 'Closed');
end;
Although you could make it a class or a record really. Although if it is a class you can add the class functions like #mghie suggested, and instead just get the values from the const array. Personally I prefer having all the strings in a const array in the interface instead of peppered in the function bodies in the implementation.
class function TGlobalConsts.Active: string;
begin
Result := ItemStatusStrings[itsActive];
end;
class function TGlobalConsts.Open: string;
begin
Result := ItemStatusStrings[itsOpen];
end;
There are a lot of ways to do it, that is for sure.

This is something that you can do in all versions of Delphi:
type
TStatus = class
class function Active: string;
class function Open: string;
...
end;
class function TStatus.Active: string;
begin
Result := 'ACTIVE';
end;
class function TStatus.Open: string;
begin
Result := 'OPEN';
end;
You can use this exactly as you want:
MyVar := TStatus.Open;
there is exactly one place to change the strings, and there is only code involved, no runtime instantiation. New features of recent Delphi versions aren't always necessary to do stuff like that...

In addition to what TOndrej said:
One can also declare a record constant:
type
TRecordName = record
Field1: Integer;
Field2: String
end;
const
itemstatus : TRecordName = (
Field1: 0;
Field2: 'valueoffield2'
);
An array of records is also possible, by combining the const record syntax above and the array syntax shown by TOndrej.
However, the way I usually prefer needs initialization:
populating a TStringDynArray with the names of an enum via RTTI
If it then turns out that the names should be configurable, the persistance (however it is arranged) can simply load/store the dynarray.

You could have a global variable of a record type. The record type must have a field for each status you have.
You can populate the record in the Initialize section of any unit, calling a procedure or function declared in that unit. You could have a single unit to specifically do this work, something like StatusTypes.pas.
In the interface section you could declare something like:
type
TStatus = record
OPEN: string;
end;
var
Status: TStatus;

Related

Assign an existing method by its name from one Unit to a generic TMethod in another unit

Basically, I have an application which will retrieve information out of an INI file and part of this process includes extracting the names -stored in the INI- of some procedures declared at global scope inside another unit.
I use the following to get the SQL method:
MyIni.ReadString('SQL', SubStr + '_Insert, '');
SubStr is the type of data I want, for example "titles", this is prefixed with the type of SQL procedure I want, in this case "_Insert" therefore, my request here could be seen as:
MyIni.ReadString('SQL', 'titles_Insert', '');
This would then retrieve the appropriate SQL procedure name "InsertTitlesSql" which is displayed inside the INI thus:
[SQL]
titles_Insert=InsertTitlesSql
I have a unit which lists the SQL procedures we use.
Like so:
unit uSqlLibrary;
interface
function InsertTitlesSql: string;
implementation
function InsertTitlesSql: string;
begin
{
INSERT INTO TITLES (ENGLISH, AFRIKAANS, KEY)
VALUES (:english,
:afrikaans,
:key)
}
Result := ''
+'INSERT INTO TITLES (ENGLISH, AFRIKAANS, KEY) '
+'VALUES (:english, '
+' :afrikaans, '
+' :key) ';
end;
end.
I've tried TGenericContainer without any success, I've also tried MethodAddress but I don't know how to tell MethodAddress to look at the other unit without an Object to reference (Form1.MethodAddress() for example) my best result so far is this:
type
TExec = procedure of object;
procedure TForm1.Button1Click(Sender: TObject);
var
M : TMethod ;
E : TExec ;
begin
M.Code := #Test; // Where "Test" is a procedure inside a secondary Unit.
E := TExec(M);
E;
end;
What I'm trying to do is get the SQL procedure by name that I want ( function InsertTitlesSql : string;) and assign it to a generic method that behaves in the same way.
My team lead has said that he doesn't want to edit the uSqlLibrary; so I can't do it that way and have thought to go by the method's name instead. Any other suggestions are welcome.
Hope this is clear. Sorry if it's not ( my use of terminology is not so good xD ). I'll try to elaborate on your queries to the best of my knowledge if I can.
You cannot use RTTI to enumerate procedures with global scope. You can enumerate methods, so you could convert your global procedures to be static class methods.
However, you also state that your team lead does not want you to change uSqlLibrary. This seems a little short sighted in my opinion. Feel free to tell him/her that I said so.
Anyway, if you cannot change uSqlLibrary then you cannot use RTTI. So you'll need to construct your own lookup table. Use a generic dictionary:
uses
System.Generics.Collections;
var
ProcTable: TDictionary<string, TProc>;
Instantiate it in the usual way. Add your functions at program startup:
....
ProcTable.Add('InsertTitlesSql', InsertTitlesSql);
....
When you need to look one up and call it do this:
var
Proc: TProc;
....
if not ProcTable.TryGetValue(ProcName, Proc) then
raise EProcNotFound.CreateFmt(...);
Proc();
The default equality comparer used for the key is case-sensitive by default. So you may elect to supply a custom equality comparer that compares keys without case sensitivity.

Object inheritance involving enumerated types

Delphi 2007, moving to Delphi XE over the next year.
Our product makes extensive use of a third-party component. We don't use the component directly, but instead use a custom descendant of it, to which we've added quite a lot of extra behavior (the custom descendant component was developed several years ago by developers who have since have retired).
In the source unit of the third-party Parent class, some enumerated types are declared, which control various operations of the component:
TSpecialKind = (skAlpha, skBeta, skGamma);
TSpecialKinds = set of TSpecialKind;
In our descendant class, we want to add new behavior, which would require expanding the selection of enumerated types. Essentially, we want this:
TSpecialKind = (skAlpha, skBeta, skGamma, skDelta, skEpsilon);
TSpecialKinds = set of TSpecialKind;
Obviously, we want to avoid editing the third-party code. Is it valid to simply redeclare the enumerated type, repeating the original values and adding our new ones, in our own descendent unit? Will it have any effect on existing code?
Edit:
Example scenario to (hopefully) clarify. Say you've got a (parent) component for ordering vehicle parts. The parent unit has an enumerated type Tvkind for vehicle kind, with values vkCar and vkCycle defined. These values are used, among other things, to indicate how many wheels the vehicle has, 4 or 2.
Now, in your descendent component, you want to be able to handle 3-wheeled vehicles as well. Extending the Tvkind enumerated type to include a new value vkTrike seems like the obvious approach. But what if you don't have access to or don't want to modify the parent component code?
Inheritance for enumeration types doesn't work the same way it works for Classes because code makes assumptions about enumerations that it would never make about a class. For example, given your original enumeration (the TSpecialKind), the third party component likely includes code like this:
var Something: TSpecialKind;
[...]
case Something of
skAlpha: ;
skBeta: ;
skGamma: ;
end;
Even if you could cast something that's not part of that enumeration to the TSpecialKind type, the result of that code above would be undefined (and definitively not good!)
Enumerations might be used in one other way, and if the third party component only uses it that way, then you might be able to do some "wizardry", but I don't recommend it. If the original TSpecialKind is only used through it's TSpecialKinds set type, and then it's only used like this:
if skBeta in VarOfTypeSpecialKinds then
begin
...
end;
(continued) then you could introduce a new type that enumerates all of the original values, in the same order, with the same value. If after you do that SizeOf(TSpecialKind) equals SizeOf(TNewType) then you can hard-cast the new set value to the old value and the old code would work the same. But frankly this is hacky, to many conditions for it to work properly, too fragile. The better solution would be to use a new enumeration type that's only used in your descendant component:
type TExtraSpecialKind = (skDelta, skEpsilon);
TExtraSpecialKinds = set of TExtraSpecialKind;
You'll probably have this set published in a different property; The solution is clean, will mix well with the descendant code and can be used cleanly too. Example:
if (skAlpha in SpecialKind) or (skDelta in ExtraSpecialKind) then
begin
// Do extra-sepcial mixed stuff here.
end;
I don't believe that you can reasonably expect to make the change that you want without modifying the original component.
Let's take your vehicle kind example and delve a bit deeper. I expect that the original component will have code like this:
case Kind of
vkCar:
CarMethod;
vkCycle:
CycleMethod;
end;
Now, suppose you introduce an enumerated type with an extra enumeration
TExtendedVehicleKind = (vkCar, vkCycle, vkTrike);
If the case statement above runs, with ExtendedKind equal to vkTrike, no method will be called.
Now, perhaps the behaviour that you want from the original control can be achieved by setting Kind to vkCar or vkCycle when ExtendedKind is vkTrike. But that seems unlikely to me. Only you can know for sure, because only you have the code, and know what your actual problem is.
Been in the "need to extended the enumerated type of a property".
Quick First Suggestion. Add your enumeration, as a new property wrapper to the existing property:
Potential Parent class code:
unit AcmeMachines;
interface
type
FoodCanEnum =
(
None,
Fish,
Bird,
Beef
);
AcmeCanAutoOpenMachineClass= class (object)
protected
{ protected declarations }
F_FoodCanEnum: FoodCanEnum;
function getFoodEnumProperty: FoodCanEnum;
procedure setFoodEnumProperty(const AValue: FoodCanEnum);
public
{ public declarations }
property FoodEnumProperty
read getFoodEnumProperty write setFoodEnumProperty;
end;
implementation
function AcmeCanAutoOpenMachineClass.getMyFoodEnumProperty: FoodCanEnum;
begin
Result := F_FoodCanEnum;
end;
procedure AcmeCanAutoOpenMachineClass.setMyFoodEnumProperty
(const AValue: CatFoodCanEnum);
begin
FoodEnumProperty:= AValue;
// do some specific business logic
end;
end;
Descendant Class code:
unit UmbrellaMachines;
interface
uses AcmeMachines;
type
CatFoodCanEnum =
(
None, <--- matches "AcmeMachines.None"
Fish, <--- matches "AcmeMachines.Fish"
Bird, <--- matches "AcmeMachines.Bird"
Beef, <--- matches "AcmeMachines.Beef"
Tuna,
Chicken
);
UmbrellaCanAutoOpenMachineClass = class (AcmeCanAutoOpenMachineClass)
protected
{ protected declarations }
F_CatFoodCanEnum: CatFoodCanEnum;
function getMyFoodEnumProperty: CatFoodCanEnum;
procedure setMyFoodEnumProperty(const AValue: CatFoodCanEnum);
public
{ public declarations }
// new property, "wraps" existing property
property MyFoodEnumProperty
read getMyFoodEnumProperty write setMyFoodEnumProperty;
end;
implementation
function UmbrellaCanAutoOpenMachineClass.getMyFoodEnumProperty: CatFoodCanEnum;
begin
// wrap existing "FoodEnumProperty" property, using an existing value as dummy
Result := F_CatFoodCanEnum;
end;
procedure UmbrellaCanAutoOpenMachineClass.setMyFoodEnumProperty
(const AValue: CatFoodCanEnum);
begin
// wrap existing property, using an existing value as dummy
// may be another value if necessary
AcmeCanAutoOpenMachineClass.ExistingFoodEnumProperty := AcmeMachines.None;
F_CatFoodCanEnum := AValue;
// add extended business logic for this class instances
end;
end;
Extra.
If possible, always add a "null" or "dummy" value to your own enumerations, usually, the first value:
type
CatFoodCanEnum =
(
None, // <--- these one
Tuna,
Chicken,
Beef
);
Cheers.

Get constant fields from a class using RTTI

Can I enumerate the constants(const) from a class?
I have tried
MyClass = class
const
c1 = 'c1';
c2 = 'c2';
c3 = 'c3';
end;
procedure GetConst();
var
ctx: TRttiContext;
objType: TRttiType;
field: trttifield;
s: string;
begin
ctx := TRttiContext.Create;
objType := ctx.GetType(MyClass.ClassInfo);
for field in objType.GetDeclaredFields do
s:= field.Name;
end;
I would like to get c1, c2, c2.
Is this possible?
edit:
what I want to do is define some keys for some external symbols(for a cad program)
symbol1=class
const
datafield1='datafield1';
datafield2='datafield2';
end;
symbol2=class
const
datafield21='datafield21abc';
datafield22='datafield22abc';
end
I don't like to use fields for this because I prefer not to seperate declareration and initialization.
I can't use an enum since I can't define the value as a string.
You can't get at those constants through RTTI. I suspect your best solution will be to use attributes instead. Not only will that have the benefit of actually working, I think it sounds like a cleaner and simpler solution to your problem.
If you use an enum, you can use TypInfo to translate strings to the enum values, and the enum values to strings in your code:
type
TDataFieldName = (datafield1, datafield2, datafield3);
uses TypInfo;
var df: TDataFieldName;
begin
df := TDataFieldName(GetEnumValue(TypeInfo(TDataFieldName), 'datafield1'));
ShowMessage(GetEnumName(TypeInfo(TDataFieldName), Ord(df)));
case df of
datafield1:;
datafield2:;
datafield3:;
end;
end;
(Typed from my head -- haven't tested this...)
This way the cad program can pass strings to your Delphi app, and you can translate them to the enum, or you can translate the enum to a string to pass to the cad program. It's also easy to do a case statement where the original value was a string, converted to an enum. This has come in very handy since Delphi doesn't support string case statements.
I've decided to use fields in my classes. Since I don't want to duplicate fields for declaration and initialization, i'm using rtti to initialize fields to the value of the field.
The benefits are: No rtti overhead on runtime. rtti is only performed during app startup. Also I get to use inheritance which is very useful for my project.

How to Start Creating My Own Classes with Delphi?

I posted a question a few days ago, and the answers told me to create my own classes.
I'm an old-school programmer from the pre-OOP days my programming is well structured, efficient and organized, but lacks in any custom OOPing other than using Delphi and 3rd party objects.
I had looked at how Delphi's object oriented classes worked back when I started using Delphi 2, but they seemed foreign to my programming background. I understand how they were and are excellent for developers designing components and for visual controls on the user interface. But I never found the need to use them in the coding of my program itself.
So now I look again, 15 years later, at Delphi's classes and OOPing. If I take, for example, a structure that I have such as:
type
TPeopleIncluded = record
IndiPtr: pointer;
Relationship: string;
end;
var
PeopleIncluded: TList<TPeopleIncluded>;
Then an OOP advocator will probably tell me to make this a class. Logically, I would think this would be a class inherited from the generic TList. I would guess this would be done like this:
TPeopleIncluded<T: class> = class(TList<T>)
But that's where I get stuck, and don't have good instructions on how ot do the rest.
When I look at some class that Delphi has as an example in the Generics.Collections unit, I see:
TObjectList<T: class> = class(TList<T>)
private
FOwnsObjects: Boolean;
protected
procedure Notify(const Value: T; Action: TCollectionNotification); override;
public
constructor Create(AOwnsObjects: Boolean = True); overload;
constructor Create(const AComparer: IComparer<T>; AOwnsObjects: Boolean = True); overload;
constructor Create(Collection: TEnumerable<T>; AOwnsObjects: Boolean = True); overload;
property OwnsObjects: Boolean read FOwnsObjects write FOwnsObjects;
end;
and then their definitions of the constructors and procedures are:
{ TObjectList<T> }
constructor TObjectList<T>.Create(AOwnsObjects: Boolean);
begin
inherited;
FOwnsObjects := AOwnsObjects;
end;
constructor TObjectList<T>.Create(const AComparer: IComparer<T>; AOwnsObjects: Boolean);
begin
inherited Create(AComparer);
FOwnsObjects := AOwnsObjects;
end;
constructor TObjectList<T>.Create(Collection: TEnumerable<T>; AOwnsObjects: Boolean);
begin
inherited Create(Collection);
FOwnsObjects := AOwnsObjects;
end;
procedure TObjectList<T>.Notify(const Value: T; Action: TCollectionNotification);
begin
inherited;
if OwnsObjects and (Action = cnRemoved) then
Value.Free;
end;
Let me tell you that this "simple" class definition may be obvious to those of you who have used OOP in Delphi for years, but to me it only provides me with hundreds of unanswered questions on what do I use and how do I use it.
To me, this does not appear to be a science. It appears to be an art of how to best structure your information into objects.
So this question, and I hope it doesn't get closed because I really need help with this, is where or how do I get the best instruction on using Delphi to create classes - and how to do it the proper Delphi way.
To me, this does not appear to be a science. It appears to be an art
of how to best structure your information into objects.
Well... Yeah. There really aren't a lot of formal requirements. It's really just a set of tools to help you organize your ideas, and eliminate a lot of duplication along the way.
Then an OOP advocator will probably tell me to make this a class. Logically, I would think this would be a class inherited from the generic TList.
Actually, the whole point of generic containers is that you don't have to make a new container class for each type of object. Instead, you'd make a new content class and then create a TList<TWhatever>.
Think of a class instance as a pointers to a record.
Now: why use a class when you could use a pointer to a record? A couple reasons:
encapsulation: You can hide some aspects of the implementation with the private keyword so that other developers (including your future self) know not to depend on implementation details that may change or that just aren't important to understanding the concept.
polymorphism: You can avoid a lot of special dispatch logic by giving each of your records a set of pointers to functions. Then, rather than having a large case statement where you do different things for each type of object, you loop through your list and send each object the same message, then it follows the function pointer to decide what to do.
inheritance: As you start making records with pointers to functions and procedures, you find that you often have cases where you need a new function-dispatch record that's very much like one you already have, except you need to change one or two of the procedures. Subclassing is just a handy way to make that happen.
So in your other post, you indicated that your overall program looks like this:
procedure PrintIndiEntry(JumpID: string);
var PeopleIncluded : TList<...>;
begin
PeopleIncluded := result_of_some_loop;
DoSomeProcess(PeopleIncluded);
end;
It's not clear to me what Indi or JumpID mean, so I'm going to pretend that your company does skydiving weddings, and that Indi means "individual" and JumpID is a primary key in a database, indicating a flight where all those individuals are in the wedding party and scheduled to jump out of the same plane... And it's vitally important to know their Relationship to the happy couple so that you can give them the right color parachute.
Obviously, that isn't going to match your domain exactly, but since you're asking a general question here, the details don't really matter.
What the people in the other post were trying to tell you (my guess anyway) wasn't to replace your list with a class, but to replace the JumpID with one.
In other words, rather than passing JumpID to a procedure and using that to fetch the list of people from a database, you create a Jump class.
And if your JumpID actually indicates a jump as in goto, then you'd probably actually a bunch of classes that all subclass the same thing, and override the same method in different ways.
In fact, let's assume that you do some parties that aren't weddings, and in that case, you don't need the Relationships, but only a simple list of people:
type TPassenger = record
FirstName, LastName: string;
end;
type TJump = class
private
JumpID : string;
manifest : TList< TPassenger >;
public
constructor Init( JumpID: string );
function GetManifest( ) : TList< TPassenger >;
procedure PrintManifest( ); virtual;
end;
So now PrintManifest() does the job of your PrintIndyEntry(), but instead of calculating the list inline, it calls Self.GetManifest().
Now maybe your database doesn't change much, and your TJump instance is always short lived, so you decide to just populate Self.manifest in the constructor. In that case, GetManifest() just returns that list.
Or maybe your database changes frequently, or the TJump sticks around long enough that the database may change underneath it. In that case, GetManifest() rebuilds the list each time it's called... Or perhaps you add another private value indicating the last time you queried, and only update after the information expires.
The point is that PrintManifest doesn't have to care how GetManifest works, because you've hidden that information away.
Of course, in Delphi, you could have done the same thing with a unit, hiding a list of cached passenger lists in your implementation section.
But clasess bring a little more to the table, when it comes time to implement the wedding-party-specific features:
type TWeddingGuest = record
public
passenger : TPassenger;
Relationship : string;
end;
type TWeddingJump = class ( TJump )
private
procedure GetWeddingManifest( ) : TList< TWeddingGuest >;
procedure PrintManifest( ); override;
end;
So here, the TWeddingJump inherits the Init and GetManifest from the TJump, but it also adds a GetWeddingManifest( );, and it's going to override the behavior of PrintManifest() with some custom implementation. (You know it's doing this because of the override marker here, which corresponds to the virtual marker in TJump.
But now, suppose that PrintManifest is actually a rather complicated procedure, and you don't want to duplicate all that code when all you want to do is add one column in the header, and another column in the body listing the relationship field. You can do that like so:
type TJump = class
// ... same as earlier, but add:
procedure PrintManfestHeader(); virtual;
procedure PrintManfiestRow(passenger:TPassenger); virtual;
end;
type TWeddingJump = class (TJump)
// ... same as earlier, but:
// * remove the PrintManifest override
// * add:
procedure PrintManfestHeader(); override;
procedure PrintManfiestRow(passenger:TPassenger); override;
end;
Now, you want to do this:
procedure TJump.PrintManifest( )
var passenger: TPassenger;
begin;
// ...
Self.PrintManifestHeader();
for guest in Self.GetManifest() do begin
Self.PrintManifestRow();
end;
// ...
end;
But you can't, yet, because GetManifest() returns TList< TPassenger >; and for TWeddingJump, you need it to return TList< TWeddingGuest >.
Well, how can you handle that?
In your original code, you have this:
IndiPtr: pointer
Pointer to what? My guess is that, just like this example, you have different types of individual, and you need them to do different things, so you just use a generic pointer, and let it point to different kinds of records, and hope you cast it to the right thing later. But classes give you several better ways to solve this problem:
You could make TPassenger a class and add a GetRelationship() method. This would eliminate the need for TWeddingGuest, but it means that GetRelationship method is always around, even when you're not talking about weddings.
You could add a GetRelationship(guest:TPassenger) in the TWeddingGuest class, and just call that inside TWeddingGuest.PrintManifestRow().
But suppose you have to query a database to populate that information. With the two methods above, you're issuing a new query for each passenger, and that might bog down your database. You really want to fetch everything in one pass, in GetManifest().
So, instead, you apply inheritance again:
type TPassenger = class
public
firstname, lastname: string;
end;
type TWeddingGuest = class (TPassenger)
public
relationship: string;
end;
Because GetManifest() returns a list of passengers, and all wedding guests are passengers, you can now do this:
type TWeddingJump = class (TJump)
// ... same as before, but:
// replace: procedure GetWeddingManfiest...
// with:
procedure GetManifest( ) : TList<TPassenger>; override;
// (remember to add the corresponding 'virtual' in TJump)
end;
And now, you fill in the details for TWeddingJump.PrintManifestRow, and the same version of PrintManifest works for both TJump and TWeddingJump.
There's still one problem: we declared PrintManifestRow(passenger:TPassenger) but we're actually passing in a TWeddingGuest. This is legal, because TWeddingGuest is a subclass of TPassenger... But we need to get at the .relationship field, and TPassenger doesn't have that field.
How can the compiler trust that inside a TWeddingJump, you're always going to pass in a TWeddingGuest rather than just an ordinary TPassenger? You have to assure it that the relationship field is actually there.
You can't just declare it as TWeddingJupmp.(passenger:TWeddingGuest) because by subclassing, you're basically promising to do all the things the parent class can do, and the parent class can handle any TPassenger.
So you could go back to checking the type by hand and casting it, just like an untyped pointer, but again, there are better ways to handle this:
Polymorphism approach: move the PrintManifestRow() method to the TPassenger class (removing the passenger:TPassenger parameter, as this is now the implicit parameter Self), override that method in TWeddingGuest, and then just have TJump.PrintManifest call passenger.PrintManifestRow().
Generic class approach: make TJump itself a generic class (type TJump<T:TPassenger> = class), and instead of having GetManifest() return a TList<TPassenger>, you have it return TList<T>. Likewise, PrintManifestRow(passenger:TPassenger) becomes PrintManifestRow(passenger:T);. Now you can say: TWeddingJump = class(TJump<TWeddingGuest>) and now you're free to declare the overridden version as PrintManifestRow(passenger:TWeddingGuest).
Anyway, that's way more than I expected to write about all this. I hope it helped. :)

Delphi - records with variant parts

I want to have a record (structure) with a 'polymorphic' comportment. It will have several fields used in all the cases, and I want to use other fields only when I need them. I know that I can accomplish this by variant parts declared in records. I don't know if it is possible that at design time I can access only the elements I need. To be more specific, look at the example bellow
program consapp;
{$APPTYPE CONSOLE}
uses
ExceptionLog,
SysUtils;
type
a = record
b : integer;
case isEnabled : boolean of
true : (c:Integer);
false : (d:String[50]);
end;
var test:a;
begin
test.b:=1;
test.isEnabled := False;
test.c := 3; //because isenabled is false, I want that the c element to be unavailable to the coder, and to access only the d element.
Writeln(test.c);
readln;
end.
Is this possible?
All variant fields in a variant record are accessible at all times, irrespective of the value of the tag.
In order to achieve the accessibility control you are looking for you would need to use properties and have runtime checks to control accessibility.
type
TMyRecord = record
strict private
FIsEnabled: Boolean;
FInt: Integer;
FStr: string;
// ... declare the property getters and settings here
public
property IsEnabled: Boolean read FIsEnabled write FIsEnabled;
property Int: Integer read GetInt write SetInt;
property Str: string read GetString write SetString;
end;
...
function TMyRecord.GetInt: Integer;
begin
if IsEnabled then
Result := FInt
else
raise EValueNotAvailable.Create('blah blah');
end;
Even if I heard that by original Niklaus Wirth's Pascal definition all should work as you expected, I saw no such behaviour in Delphi, starting from its ancestor, Turbo Pascal 2.0. Quick look at FreePascal showed that its behaviour is the same. As said in Delphi documentation:
You can read or write to any field of any variant at any time; but if you write to a field in one variant and then to a field in another variant, you may be overwriting your own data. The tag, if there is one, functions as an extra field (of type ordinalType) in the non-variant part of the record."
Regarding your intent, as far as I understood it, I would use two different classes, kind of
a = class
b : Integer
end;
aEnabled = class(a)
c: Integer
end;
aDisabled = class(a)
d: String //plus this way you can use long strings
end;
This way you can get some support from IDE's Code Editor even at designtime. More useful, though, is that it will be much more easier to modify and support later.
However, if you need quick switching of record variable values at runtime, #David Heffernan's variant , to use properties and have runtime checks, is more reasonable.
The example given is NOT a variant record, it includes all the fields all the time.
A true variant record has the variants sharing the same memory. You just use the "case discriminator: DiscType of ..... " syntax, no need for a separate field telling you what variant is active.

Resources