Nashev
Nashev

Reputation: 635

How to make basic generic overridable owned collection with overridable owned items?

In Delphi 10 Seattle I want to make TProject class with structure like this:

  // TProject
  //   Frames[]:TFrame   -- all frames in project folder
  //   Sounds[]:TSound   -- all sounds in project folder
  //   WorkSets[]:TWorkSet -- frames subsets for some kind of works
  //     WorkSetFrames[]:TWorkSetFrame
  //   Clips[]:TClip       -- recorded clips
  //     ClipFrames[]:TClipFrame

and I want to have not only forward link from project to a collection and from collection to item. Also, I want to have backlinks from each final item to their owned collection and from each collection to their owner object.

Main request - all links must be correctly typed:

It must fill by constructors and obtain it from that basic generic classes.

And next - it must be overridable, to allow me to add property Name to generic collection item descendant and FindByName function to generic collection class and obtain a possibility to instantiate some of my collections from this two new generic classes. For example, to make TFrame and TFrameCollection with this feature.

It can be based on TCollection or TList, but it all don't have all the required features.

I have tried some declarations, but it led to class incompatibility errors (https://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29 problem noted in explanations).

UPD 1:

The last attempt to solve the problem of usage generic collection and generic collection item classes which use each other and allow override it led me to this code:

unit GenericCollection;

interface
uses Generics.Collections;

type
  TGenericCollectionItem<TCollectionOwner: class> = class
  public
    type
      TCollection = class(TList<TGenericCollectionItem<TCollectionOwner>>)
      private
        FOwner: TCollectionOwner;
      public
        property Owner: TCollectionOwner read FOwner;
        constructor Create(AOwner: TCollectionOwner);
      end;

  private
    FOwnerCollection: TCollection;
    function GetIndex: Integer;
    procedure SetIndex(const Value: Integer);
  public
    property Index: Integer read GetIndex write SetIndex;
    property OwnerCollection: TCollection read FOwnerCollection;
  end;

  TNamedGenericCollectionItem<TCollectionOwner: class> = class(TGenericCollectionItem<TCollectionOwner>)
  public
    type
      TNamedGenericCollection = class(TCollection)
      public
        function FindItemName(AName: string): TNamedGenericCollectionItem<TCollectionOwner>;
      end;
  private
    FName: string;
  public
    property Name: string read FName write FName;
  end;

implementation

{ TGenericCollectionItem<ACollectionOwnerType> }

function TGenericCollectionItem<TCollectionOwner>.GetIndex: Integer;
begin
  Result := OwnerCollection.IndexOf(Self);
end;

procedure TGenericCollectionItem<TCollectionOwner>.SetIndex(
  const Value: Integer);
var
  CurIndex: Integer;
begin
  CurIndex := GetIndex;
  if (CurIndex >= 0) and (CurIndex <> Value) then
    FOwnerCollection.Move(CurIndex, Value);
end;

{ TGenericCollectionItem<ACollectionOwnerType>.TCollection }

constructor TGenericCollectionItem<TCollectionOwner>.TCollection.Create(
  AOwner: TCollectionOwner);
begin
  inherited Create;
  FOwner := AOwner;
end;

{ TNamedGenericCollectionItem<TCollectionOwner>.TNamedGenericCollection }

function TNamedGenericCollectionItem<TCollectionOwner>.TNamedGenericCollection.FindItemName(
  AName: string): TNamedGenericCollectionItem<TCollectionOwner>;
var
  X: TGenericCollectionItem<TCollectionOwner>;
begin
  // TODO: Use hash-based index
  for X in Self do
    if TNamedGenericCollectionItem<TCollectionOwner>(X).Name = AName then
      Exit(TNamedGenericCollectionItem<TCollectionOwner>(X));
end;

end.

But when I use it by declaring

  TFrame = class(TNamedGenericCollectionItem<TProject>)
  end;

and adding to TProject

FFrames: TFrame.TNamedGenericCollection;

this call in TProject's constructor

FFrames := TFrame.TNamedGenericCollection.Create(Self);

still geave me annoying exception:

[dcc32 Error] ProjectInfo.pas(109): E2010 Incompatible types:
'TNamedGenericCollectionItem<TCollectionOwner>.TNamedGenericCollection' and
'GenericCollection.TNamedGenericCollectionItem<ProjectInfo.TProject>.TNamedGenericCollection'

What I can do to solve this?

Upvotes: 0

Views: 255

Answers (1)

Dsm
Dsm

Reputation: 6013

I really think that you are over-thinking this. Once I got my head round what you are trying to do, you just want the owner to know what sort its items are (which TObjectList< T > for example already knows) and for the item to know what its owner is, which is easily constructed.

unit Unitgenerics;

interface

uses
  System.Generics.Collections;

type
  TGItem<TOwner : class> = class
  private
    fOwner: TOwner;
  public
    constructor Create( const pOwner : TOwner );
    property Owner : TOwner
             read fOwner;
  end;

  TRealItem = class;

  TRealOwner = class( TObjectList<TRealItem> )
    // Items are already of type TRealItem
  end;

  TRealItem = class(TGItem< TRealOwner > )
    // Owner already defined and is of type TRealOwner
  end;

implementation

{ TGItem<TOwner> }

constructor TGItem<TOwner>.Create(const pOwner: TOwner);
begin
  inherited Create;
  fOwner := pOwner;
end;

end.

To extend this down to descendants, that is easy enough for the items if the owner didn't change. But it does. However all we need to do is use generics to reflect to the descendants how the owner changes - like this

unit Unitgenerics;

interface

uses
  System.Generics.Collections;

type
  TGItem<TOwner : class> = class
  private
    fOwner: TOwner;
  public
    constructor Create( const pOwner : TOwner );
    property Owner : TOwner
             read fOwner;
  end;

  TRealItem< TOwner : class > = class;

  TRealOwner<TOwner : class> = class( TObjectList<TRealItem< TOwner >> )
    // Items are already of type TRealItem<TOwner>
  end;

  TRealItem< TOwner : class > = class(TGItem< TRealOwner<TOwner> > )
    // Owner already defined and is of type TRealOwner<TOwner>
  end;

implementation

{ TGItem<TOwner> }

constructor TGItem<TOwner>.Create(const pOwner: TOwner);
begin
  inherited Create;
  fOwner := pOwner;
end;

end.

This is how to extend further without nesting generics too far...

unit Unitgenerics;

interface

uses
  System.Generics.Collections;

type
  TGItem<TOwner : class> = class
  private
    fOwner: TOwner;
  public
    constructor Create( const pOwner : TOwner );
    property Owner : TOwner
             read fOwner;
  end;

  TRealItem< TOwner : class > = class;

  TRealOwner<TOwner : class> = class( TObjectList<TRealItem< TOwner >> )
    // Items are already of type TRealItem
    // add some properties here
  end;

  TRealItem< TOwner : class > = class(TGItem< TRealOwner<TOwner> > )
    // Owner already defined and is of type TRealOwner
    // add some properties here
  end;

  T2ndLevelItem = class;
  T2ndLevelOwner = class;

  T2ndLevelOwner = class( TRealOwner< T2ndLevelOwner > )

  end;

  T2ndLevelItem = class( TRealItem< T2ndLevelOwner > )

  end;

  TInheritable2ndLevelItem< TOwner : class> = class;
  TInheritable2ndLevelOwner< TOwner : class> = class;

  TInheritable2ndLevelOwner< TOwner : class> = class( TRealOwner< TOwner > )

  end;

  TInheritable2ndLevelItem< TOwner : class> = class( TRealItem< TOwner > )

  end;

  T3rdLevelItem = class;
  T3rdLevelOwner = class;

  T3rdLevelOwner = class( TRealOwner< T3rdLevelOwner > )

  end;

  T3rdLevelItem = class( TRealItem< T3rdLevelOwner > )

  end;

  TInheritable3rdLevelItem< TOwner : class> = class;
  TInheritable3rdLevelOwner< TOwner : class> = class;

  TInheritable3rdLevelOwner< TOwner : class> = class( TInheritable2ndLevelOwner< TOwner > )

  end;

  TInheritable3rdLevelItem< TOwner : class> = class( TInheritable2ndLevelItem< TOwner > )

  end;



implementation

{ TGItem<TOwner> }

constructor TGItem<TOwner>.Create(const pOwner: TOwner);
begin
  inherited Create;
  fOwner := pOwner;
end;

end.

UPD1

I updated your example using my principles. In the process I realised that your main problem was dealing with the fact that the top level item is, in fact, a list. You are trying to explain this to the compiler with your convoluted constructs, but that is not the way to do it.

interface
uses
  System.Generics.Collections;

type
  TGenericCollectionItem< TOwnerCollection : class > = class
  private
    FOwnerCollection: TList<TOwnerCollection>;
    function GetIndex: Integer;
    procedure SetIndex(const Value: Integer);
  public
    constructor Create(pOwnerCollection: TList<TOwnerCollection>);
    property Index: Integer read GetIndex write SetIndex;
    property OwnerCollection: TList<TOwnerCollection> read FOwnerCollection;
  end;

  TNamedGenericCollectionItem<TOwnerCollection: class> = class(TGenericCollectionItem< TOwnerCollection>)
  public
  private
    FName: string;
  public
    property Name: string read FName write FName;
  end;

type
  TFrames = class;

  TProject = class( TList< TFrames > )
  private
    FFrames : TFrames;
  public
    constructor Create;

  end;

  TFrames = class(TNamedGenericCollectionItem< TFrames>)
  end;

implementation

{ TGenericCollectionItem<ACollectionOwnerType> }

constructor TGenericCollectionItem<TOwnerCollection>.Create(
  pOwnerCollection: TList<TOwnerCollection>);
begin
  inherited Create;
  pOwnerCollection := fOwnerCollection;
end;

function TGenericCollectionItem<TOwnerCollection>.GetIndex: Integer;
begin
  Result := OwnerCollection.IndexOf(Self);
end;

procedure TGenericCollectionItem<TOwnerCollection>.SetIndex(
  const Value: Integer);
var
  CurIndex: Integer;
begin
  CurIndex := GetIndex;
  if (CurIndex >= 0) and (CurIndex <> Value) then
    FOwnerCollection.Move(CurIndex, Value);
end;


{ TProject }

constructor TProject.Create;
begin
  inherited Create;
  FFrames:= TFrames.Create( self );
end;

I hope that this helps.

Upvotes: 1

Related Questions