2

Can I declare a field as static on every descendant class instead of sharing its value for all of them ?

This is the base class:

type
  TKpModelViewControllerEntity = class(TInterfacedObject, IKpEntity)
  protected
    class var ResourceName: string; 
    procedure Manage;  
  public
    class function Create: IKpEntity;
  end;

This is a descendant

unit Customers;

type
  TCustomers = class(TKpModelViewControllerEntity)
  end;

initialization
  TCustomers.ResourceName := 'customers/';

And another one:

unit Articles;

type
  TArticles = class(TKpModelViewControllerEntity)
  end;

initialization
  TArticles.ResourceName := 'articles/';

When I try to create a Customers screen (TCustomers.Create.Manage) its Resourcename has the "articles/" value instead of "customers/".

Is there a way to indicate for the static fields to hold separate values for every descendant class ?.

Thank you.

Marc Guillot
  • 6,090
  • 1
  • 15
  • 42

3 Answers3

5

This can easily be achieved with attributes:

type
  ResourceNameAttribute = class(TCustomAttribute)
  private
    FValue: string;
  public
    constructor Create(const AValue: string);
    property Value: string read FValue;
  end;

type
  TKpModelViewControllerEntity = class(TInterfacedObject, IKpEntity)
  protected
    class function ResourceName: string;
    procedure Manage;
  public
    class function Create: IKpEntity;
  end;

class function TKpModelViewControllerEntity.ResourceName: string;
begin
  Result := '';
  var ctx := TRttiContext.Create;
  var cls := ctx.GetType(Self);
  var attr := cls.GetAttribute<ResourceNameAttribute>;
  if attr <> nil then
    Result := attr.Value;
end;

constructor ResourceNameAttribute.Create(const AValue: string);
begin
  inherited Create;
  FValue := AValue;
end;

...

type
  [ResourceName('customers/')]
  TCustomers = class(TKpModelViewControllerEntity)
  end;

type
  [ResourceName('articles/')]
  TArticles = class(TKpModelViewControllerEntity)
  end;
Uwe Raabe
  • 45,288
  • 3
  • 82
  • 130
5

I would use a class virtual method instead of a class var, eg:

type
  TKpModelViewControllerEntity = class(TInterfacedObject, IKpEntity)
  protected
    class function ResourceName: string; virtual;
    ...
  end;

class function TKpModelViewControllerEntity.ResourceName: string;
begin
  Result := ''; // or whatever default you want...
end;
type
  TCustomers = class(TKpModelViewControllerEntity)
  protected
    class function ResourceName: string; override;
    ...
  end;

class function TCustomers.ResourceName: string;
begin
  Result := 'customers/';
end;
type
  TArticles = class(TKpModelViewControllerEntity)
  protected
    class function ResourceName: string; override;
    ...
  end;

class function TArticles.ResourceName: string;
begin
  Result := 'articles/';
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks Remy, it would definitely work, but I find the solution with attributes very neat on the descendants side, so I've gone with it. – Marc Guillot Jul 12 '23 at 14:12
  • 2
    @MarcGuillot sure, using attributes will work. Just be aware that they add more RTTI to your compiled executable. Whereas this solution doesn't use any RTTI at all. – Remy Lebeau Jul 12 '23 at 18:55
1

Class fields/methods are static fields, not stored into class instance data, so it reference the first in inheritance chain.

A good pratice should be to:

  1. Define an interface, for example IResource, which declares a method function GetResourceName: String.
  2. Implement that interface on TCustomers and TArticles.
  3. Query the resource name by the above method.
Antonio Petricca
  • 8,891
  • 5
  • 36
  • 74