views:

599

answers:

2

Firstly, an apology for the length of this post. If brevity is the soul of wit, then this is a witless question.

I think my question boils down to:

What is the best way to override a constant array in Delphi child classes?

Background:

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

I have a constant array that is defined in a parent class, and also in lot of child classes. The type of the array elements is always the same, but the number of elements and the exact data differs from one child to the next (I am describing database tables, because of a specific grid control that requires that meta-data at compile time, but that's beside the point).

I have several functions that act on these arrays. As a trivial example, I might have a function to return the last element of the array.

If you define a "GetLastElement" in the parent, then call that inherited function from a child, it will still act on the parents version of the array. This is not what I expected. It seems like the children should call the inherited function on their own local version of the array.

Currently I have to duplicate those functions across each child class, which is maddening.

I want to be able to have an inherited function act on the local version of my constant array. What is the best way to do that? I have thought about defining a function in the base class that returns a static array, and then overriding that for each child. If I did that, then I would not be acting on an array, I'd be acting on a function result.

That would solve the inheritance problem, but it introduces a new problem in that I'd have to define a new type to encapsulate the array, and modify my (already convoluted) grid control to work with that type.

Any suggestions are welcome.

Below is a simplified application that demonstrates what I'm talking about:

In a Main form:

implementation

{$R *.dfm}

uses
  ParentClass, Descendant1, Descendant2;

procedure TfrmMain.btnTestClick(Sender: TObject);
var
  d1, d2: TParentClass;
begin
  d1 := TDescendant1.Create;
  d2 := TDescendant2.Create;

  //as it stands, this will return "E", then "A", which is good.
  //but if you move the LastElementOfArray function to the ParentClass,
  //then it will return "E", "E", ignoring the version of the array
  //defined inside TDescendant2.
  ShowMessage('d1=' + d1.LastElementOfArray);
  ShowMessage('d2=' + d2.LastElementOfArray);
end;


end.

In a file called ParentClass.pas:

unit ParentClass;

interface

type
  TParentClass = class
  public
    function LastElementOfArray: string; virtual; abstract;
  end;

const
  c_MyConstantArray: array[1..5] of string = ('A','B','C','D','E');

implementation

end.

In a unit called Descendant1.pas

//here, we will just take whatever array we got from the parent
unit Descendant1;

interface

uses
  ParentClass;

type
  TDescendant1 = class(TParentClass)
  public
    function LastElementOfArray: string; override;
  end;

implementation

{ TDescendant1 }

function TDescendant1.LastElementOfArray: string;
begin
  Result := c_MyConstantArray[High(c_MyConstantArray)];
end;

end.

In a file called Descendant2.pas

//override with a new version of the constant array (same length)
unit Descendant2;

interface

uses
  ParentClass;

type
  TDescendant2 = class(TParentClass)
  public
    function LastElementOfArray: string; override;
  end;

const
  c_MyConstantArray: array[1..5] of string = ('E','D','C','B','A');

implementation

{ TDescendant2 }

function TDescendant2.LastElementOfArray: string;
begin
  //I hate defining this locally, but if I move it to ParentClass,
  //then it will act on the ParentClass version of the array, which
  //is **NOT** what I want
  Result := c_MyConstantArray[High(c_MyConstantArray)];
end;

end.
+8  A: 

You could consider working with dynamic arrays instead. A dynamic array can be initialized in an expression like this:

type
  TStringArray = array of string;
// ...
var
  x: TStringArray;
begin
  x := TStringArray.Create('A', 'B', 'C')
end;

With this approach, you could put your array definition in e.g. a virtual class property getter, or a lazily-initialized (via a virtual call) class variable accessed via a class property getter. Then, the methods that need to work with "this" class's definition of the array can simply use the virtual getter via the property.

Using a named dynamic array also gets away from the issue of having to have separate types for arrays of different lengths, without losing the ability to initialize in an expression.

Barry Kelly
Now *that* is something *extremely* useful! If only I'd known that a year earlier ;-)Do you happen to know since which version the Delphi compiler supports this syntax?Thanks a lot for the great answer!
onnodb
+1  A: 

The easiest way to handle this is to have a protected property of your array (dynamic as Barry suggested), and assign this property (if necessary) in your constructor. Your parent can then implement against this internal array, and your children will all inherit the functionality.

The problem of your approach by referencing the constants directly is one of scope. Each time a reference is made, it will be going against what it sees, going up the local implementation section first (from the call upwards), followed by the interface, followed by the units in the implementation, followed by the units in the interface. By using a reference, it no longer matters what is in scope or not in scope... your object can always use the reference to work with what it currently is designed to operate against.

skamradt