Recent

Author Topic: composition instead of inheritance  (Read 745 times)

k1attila1

  • Full Member
  • ***
  • Posts: 109
composition instead of inheritance
« on: March 05, 2026, 05:54:12 am »
If you want to use composition instead of inheritance, what would you use primarily?

Class
Object
Advanced record

Thanks

Khrys

  • Sr. Member
  • ****
  • Posts: 413
Re: composition instead of inheritance
« Reply #1 on: March 05, 2026, 06:40:13 am »
Since neither advanced records nor old-school objects can implement interfaces, I'd say classes are the way to go.

cdbc

  • Hero Member
  • *****
  • Posts: 2725
    • http://www.cdbc.dk
Re: composition instead of inheritance
« Reply #2 on: March 05, 2026, 07:24:06 am »
Hi
Yup, like @Khrys said: "Classes"  8)
As a side-note, /in a pinch, records _can_ support an interface/:
Code: Pascal  [Select][+][-]
  1. unit exp.dirinfo;
  2. {$mode ObjFPC}{$H+}   (* This <experimental> unit only works fully with unices & linuces, NO WINDERS! *)
  3.  
  4. interface
  5. uses classes, sysutils, istrlist, model.intf, cdbc.inodes;
  6. const
  7.   idiVersion = '3.23.12.2025'; /// experimental 'manual interface' :o)
  8. type
  9.   { IDirInfo is a service that can tell you information about a directory on unices, you
  10.     create it via a call to "GetDirInfo" and DON'T worry about freeing, it's automagical }
  11.   IDirInfo = interface['{F4E40DBC-4488-428C-B666-2B665F9FD4A7}']
  12.     { returns the unix-timestamp for a directory's last modification time, while
  13.       at the same time spitting out the timestamp in the form of a 'TDateTime' }
  14.     function DirAge(const aDirName: rawbytestring; out asDaTi: TDateTime): ptrint;
  15.     { returns the unix-timestamp for a file's last modification time, while
  16.       at the same time spitting out the timestamp in the form of a 'TDateTime' }
  17.     function FileAge(const aFileName: rawbytestring; out asDaTi: TDateTime): ptrint;
  18.     { returns an INodeList with the results from a recursive directory-scan }
  19.     function GetDirContent(aDirName: string;IncludeFullPath: boolean = false): INodeList;
  20.     { returns a stringlist with the directory-names (only) found in directory; NO files & NO recursion }
  21.     function GetDirs(aDirName: string;IncludeFullPath: boolean = false): IStringList;
  22.     { returns a stringlist with the filenames (only) found in directory; NO dirs & NO recursion }
  23.     function GetFiles(aDirName: string;IncludeFullPath: boolean = false): IStringList;
  24.   end;
  25. { service factory for IDirInfo :o) }
  26. function GetDirInfo: IDirInfo;
  27.  
  28. implementation
  29. {$ifdef unix} uses baseunix; {$endif}
  30.  
  31. type
  32.   TInterface = class
  33.   public
  34.     function QueryInterface(constref {%H-}IID: TGUID;{%H-} out {%H-}Obj): HResult; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; virtual;
  35.     function _AddRef: LongInt; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; virtual; abstract;
  36.     function _Release: LongInt; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};  virtual; abstract;
  37.   end;
  38.  
  39.   TRawInterface = class(TInterface)
  40.   public
  41.     function _AddRef: LongInt; override;
  42.     function _Release: LongInt; override;
  43.   end;
  44.   { muy importante memory layout for the interface }
  45.   PDirInfoVMT = {%H-}^TDirInfoVMT;
  46.   TDirInfoVMT = packed record
  47.     QueryInterface: CodePointer;
  48.     _AddRef: CodePointer;
  49.     _Release: CodePointer;
  50.     DirAge: CodePointer;
  51.     FileAge: CodePointer;
  52.     GetDirContent: CodePointer;
  53.     GetDirs: CodePointer;
  54.     GetFiles: CodePointer;
  55.   end;
  56.  
  57.   { TDirInfo is the actual implementor of our interface }
  58.   TDirInfo = class
  59.     { i figured it would work with a class-function }
  60.     class function DirAge(const aDirName: rawbytestring; out asDaTi: TDateTime): ptrint;
  61.     { but that it *also* works, without the class-part, that baffles me a bit %) }
  62.     function FileAge(const aFileName: rawbytestring; out asDaTi: TDateTime): ptrint;
  63.     { returns an INodeList with the results from a recursive directory-scan }
  64.     function GetDirContent(aDirName: string;IncludeFullPath: boolean = false): INodeList;
  65.     { returns a stringlist with the directory-names (only) found in directory; NO files & NO recursion }
  66.     function GetDirs(aDirName: string;IncludeFullPath: boolean = false): IStringList;
  67.     { returns a stringlist with the filenames (only) found in directory; NO dirs & NO recursion }
  68.     function GetFiles(aDirName: string;IncludeFullPath: boolean = false): IStringList;
  69.   end;
  70. //////////////// area 51 ////////////////
  71. const
  72.   { please note that while this uses instance methods of TRawInterface this does not necessarily
  73.     mean that a TRawInterface instance is passed in; e.g. the code in Generics.Defaults uses
  74.     a different type as Self that contains the reference count for those types where the
  75.     reference count is necessary /PD }
  76.   DirInfo_VMT: TDirInfoVMT = (QueryInterface: @TRawInterface.QueryInterface;
  77.                               _AddRef: @TRawInterface._AddRef;
  78.                               _Release: @TRawInterface._Release;
  79.                               DirAge: @TDirInfo.DirAge;
  80.                               FileAge: @TDirInfo.FileAge;
  81.                               GetDirContent: @TDirInfo.GetDirContent;
  82.                               GetDirs: @TDirInfo.GetDirs;
  83.                               GetFiles: @TDirInfo.GetFiles);
  84.   { ...and here we instantiate the interface as a const pointer }
  85.   DirInfo_Instance: pointer = @DirInfo_VMT;
  86. //////////////// area 51 ////////////////
  87. { TInterface }
  88. function TInterface.QueryInterface(constref IID: TGUID; out Obj): HResult; {$IfNDef WINDOWS}cdecl{$Else}stdcall{$EndIf};
  89. begin
  90.   Result:= E_NOINTERFACE;
  91. end;
  92.  
  93. { TRawInterface }
  94. function TRawInterface._AddRef: LongInt; {$IfNDef WINDOWS}cdecl{$Else}stdcall{$EndIf};
  95. begin
  96.  Result:= -1;        /// writeln('_AddRef');
  97. end;
  98.  
  99. function TRawInterface._Release: LongInt; {$IfNDef WINDOWS}cdecl{$Else}stdcall{$EndIf};
  100. begin
  101.  Result:= -1;        /// writeln('_Release');
  102. end;
  103.  
  104. { TDirInfo }
  105. class function TDirInfo.DirAge(const aDirName: rawbytestring; out asDaTi: TDateTime): ptrint;
  106. {$ifdef unix}
  107. var Info: baseunix.stat; SystemDirName: rawbytestring;
  108. begin
  109.   SystemDirName:= ToSingleByteFileSystemEncodedFileName(aDirName); /// compiler magic
  110.   if fpstat(pansichar(SystemDirName),Info{%H-}) < 0 then begin asDaTi:= MinDateTime; exit(-1); end
  111.   else begin
  112.     Result:= Info.st_mtime; /// we're mostly interested in 'modified'
  113.     asDaTi:= FileDateToDateTime(Result); /// just convenience for the user
  114.   end;
  115. {$else} begin asDaTi:= 0; Result:= -1; {$endif}
  116. end; { DirAge }
  117.  
  118. function TDirInfo.FileAge(const aFileName:rawbytestring;out asDaTi:TDateTime): ptrint;
  119. {$ifdef unix}
  120. var Info: baseunix.stat; SystemFileName: rawbytestring;
  121. begin
  122.   SystemFileName:= ToSingleByteFileSystemEncodedFileName(aFileName); /// compiler magic
  123.   if fpstat(pansichar(SystemFileName),Info{%H-}) < 0 then begin asDaTi:= MinDateTime; exit(-1); end
  124.   else begin
  125.     Result:= Info.st_mtime; /// we're mostly interested in 'modified'
  126.     asDaTi:= FileDateToDateTime(Result); /// just convenience for the user
  127.   end;
  128. {$else} begin asDaTi:= 0; Result:= -1; {$endif}
  129. end; { FileAge }
  130.  
  131. function TDirInfo.GetDirContent(aDirName: string; IncludeFullPath: boolean): INodeList;
  132. var lvl: integer = -1;
  133.   procedure ScanDir(aDir,anExt: string;aNodelist: INodeList;aRecursive: boolean);
  134.   var lnode: PFSNode; finished: longint; found: TSearchRec; dirs: TStrings; i: longint; ls: string;
  135.   begin
  136.     aDir:= IncludeTrailingPathDelimiter(aDir); { checks for (and) include(d) pathdelim }
  137.     inc(lvl); { new sublevel ~ first = 0 }
  138.     { first add our current dir }
  139.     lnode:= aNodelist.AddItem;
  140.     lnode^.fnType:= 1; // folder / dir
  141.     lnode^.fnDir:= aDir;
  142.     lnode^.fnInclude:= true;
  143.     lnode^.fnLevel:= lvl;
  144.     ls:= PickLastDir(aDir); { relative dir, not full path }
  145.     dirs:= TStringList.Create; { a list to hold subdirs, for use if aRecursive=true }
  146.     finished:= FindFirst(aDir + anExt,faAnyFile,found); { * and  faAnyFile works on linux }
  147.     while finished = 0 do begin { the findxxx functions return 0 on success }
  148.       if ((found.Name[1] <> '.') and (found.Name <> 'backup') and
  149.          (found.Name <> 'lib') and (found.Name <> 'published')) then begin { subdue the pestering . & .. intrinsics }
  150.         { whooops, subdir... add it to our list }
  151.         if (found.Attr and faDirectory = faDirectory) then
  152.           dirs.Add(aDir + found.Name)
  153.         else begin
  154.           lnode:= aNodelist.AddItem;
  155.           lnode^.fnType:= 0; // file
  156.           lnode^.fnFile:= found.Name; // in relative Dir
  157.           lnode^.fnDir:= ls; // let the files belong to this dir, which they do
  158.           lnode^.fnInclude:= true;
  159.           lnode^.fnLevel:= lvl+1;
  160.         end;
  161.       end;
  162.       finished:= FindNext(found); { fetch next item in dir }
  163.     end; { while finished }
  164.     FindClose(found); { release resources again }
  165.     { should we have a looksee in the subdirs?!? }
  166.     if aRecursive then for i:= 0 to dirs.Count - 1 do ScanDir(dirs[i],anExt,aNodelist,aRecursive);
  167.     dirs.Free; { thank you for your services :o) }
  168.     dec(lvl); { when dir is complete dec level, sub levels happen before here }
  169.   end;
  170.  
  171. begin { GetDirContent }
  172.   Result:= CreINodeList(); { we always return a valid inodelist, user checks for zero-count }
  173.   ScanDir(aDirName,'*',Result,true); { on unix, this will list everything }
  174. end; { GetDirContent }
  175.  
  176. function TDirInfo.GetDirs(aDirName: string; IncludeFullPath: boolean): IStringList;
  177. var finished: longint; found: TSearchRec;
  178. begin
  179.   Result:= CreStrings;
  180.   if aDirName = '' then exit(Result);
  181.   aDirName:= IncludeTrailingPathDelimiter(aDirName); { checks for (and) include(d) pathdelim }
  182.   finished:= FindFirst(aDirName + '*',faAnyFile,found); { * and  faAnyFile works on linux }
  183.   while finished = 0 do begin { the findxxx functions return 0 on success }
  184.     if (found.Name[1] <> '.') then begin { subdue the pestering . & .. intrinsics }
  185.       if (found.Attr and faDirectory = faDirectory) then
  186.         if IncludeFullPath then Result.Append(aDirName + found.Name)
  187.         else Result.Append(found.Name);
  188.     end;
  189.     finished:= FindNext(found); { fetch next item in dir }
  190.   end; { while finished }
  191.   FindClose(found); { release resources again }
  192. end;
  193.  
  194. function TDirInfo.GetFiles(aDirName: string;IncludeFullPath: boolean): IStringList;
  195. var finished: longint; found: TSearchRec;
  196. begin
  197.   Result:= CreStrings;
  198.   if aDirName = '' then exit(Result);
  199.   aDirName:= IncludeTrailingPathDelimiter(aDirName); { checks for (and) include(d) pathdelim }
  200.   finished:= FindFirst(aDirName + '*',faAnyFile,found); { * and  faAnyFile works on linux }
  201.   while finished = 0 do begin { the findxxx functions return 0 on success }
  202.     if (found.Name[1] <> '.') then begin { subdue the pestering . & .. intrinsics }
  203.       if (found.Attr and faDirectory <> faDirectory) then
  204.         if IncludeFullPath then Result.Append(aDirName + found.Name)
  205.         else Result.Append(found.Name);
  206.     end;
  207.     finished:= FindNext(found); { fetch next item in dir }
  208.   end; { while finished }
  209.   FindClose(found); { release resources again }
  210. end;
  211.  
  212. { service factory for IDirInfo :o) }
  213. function GetDirInfo: IDirInfo;
  214. begin { here we just typecast our const instance pointer to the right interface }
  215.   Result:= IDirInfo(@DirInfo_Instance);
  216. end; { ...and Bob's your uncle :o) }
  217.  
  218. end.
  219.  
Just for Kicks  :D
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE6/QT6 -> FPC Release -> Lazarus Release &  FPC Main -> Lazarus Main

JuhaManninen

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 4709
  • I like bugs.
Re: composition instead of inheritance
« Reply #3 on: March 05, 2026, 12:09:45 pm »
Code: Pascal  [Select][+][-]
  1.   { TDirInfo is the actual implementor of our interface }
  2.   TDirInfo = class
  3.   ...
  4.   end;
Hmmm... How does it implement the interface? It should mention IDirInfo somewhere.

@k1attila1, also consider abstract base classes.
Technically they implement inheritance but conceptually they behave like interfaces with one big advantage: You can have variables and can implement some functions there.
An advantage of an actual interface is that one class can implement many of them. (Comparable to multiple inheritance in some languages.)

In Object Pascal language memory management is messed with interfaces. It is confusing and leads to extra code like TRawInterface._AddRef etc.
That is one reason I prefer abstract base classes myself.
Mostly Lazarus trunk and FPC 3.2 on Manjaro Linux 64-bit.

cdbc

  • Hero Member
  • *****
  • Posts: 2725
    • http://www.cdbc.dk
Re: composition instead of inheritance
« Reply #4 on: March 05, 2026, 12:29:36 pm »
Hi Juha
Quote
Hmmm... How does it implement the interface?
Here:
Code: Pascal  [Select][+][-]
  1.   DirInfo_VMT: TDirInfoVMT = (QueryInterface: @TRawInterface.QueryInterface;
  2.                               _AddRef: @TRawInterface._AddRef;
  3.                               _Release: @TRawInterface._Release;
  4.                               DirAge: @TDirInfo.DirAge;
  5.                               FileAge: @TDirInfo.FileAge;
  6.                               GetDirContent: @TDirInfo.GetDirContent;
  7.                               GetDirs: @TDirInfo.GetDirs;
  8.  
Basically it requires knowledge about the internal memory-layout of the interface and the fact, that an interface is /merely/ a(n extra) VMT, albeit an elaborate one  :D
We just map the raw methodpointers into the right place...
It even works without the methods being 'class'-methods  %)
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE6/QT6 -> FPC Release -> Lazarus Release &  FPC Main -> Lazarus Main

BildatBoffin

  • New Member
  • *
  • Posts: 48
Re: composition instead of inheritance
« Reply #5 on: March 05, 2026, 01:16:31 pm »
I'd choose classes of course. The problem I see is that we cannot implement a little helper that is "overloading casts" (imagine being able to do at compile-time whatever you want depending on the target type). Also the language misses a bit the metaprog features allowing to build types in a thiner way (would be a benefit like the previous example for the record). Anyway this is just for the aesthetical aspects. Composition is already possible but the code required will be a lilltle bit more verbose.

 

TinyPortal © 2005-2018