Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to group constant strings together in Delphi

Tags:

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?

like image 477
Re0sless Avatar asked May 29 '09 16:05

Re0sless


3 Answers

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');
like image 85
Ondrej Kelle Avatar answered Sep 16 '22 11:09

Ondrej Kelle


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;
like image 24
Jim Avatar answered Sep 19 '22 11:09

Jim


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 ;)

like image 44
David Taylor Avatar answered Sep 18 '22 11:09

David Taylor