Recent

Author Topic: Refactoring code to support some kind of multiple inheritance  (Read 4398 times)

ASerge

  • Hero Member
  • *****
  • Posts: 2242
Re: Refactoring code to support some kind of multiple inheritance
« Reply #15 on: July 25, 2019, 05:06:07 am »
@ASerge: this example compiles and runs on target = Windows 32 bits: 
Compiled project still too lazy to do? OK, I do it. But first, let me clear my mind again. If you want to use interfaces instead of just classes, then you need to get away from the Create/Free model, and immediately use only interfaces that are released automatically. In your example, you still cannot use IDoItInterface2. So this is no different from declaring a simple class, which can contain a bunch of fields with interfaces. But the reasons for the use of interfaces will be lost, because you have to describe everything in it.
Code: Pascal  [Select][+][-]
  1. {$APPTYPE CONSOLE}
  2. {$MODE OBJFPC}
  3.  
  4. type
  5.   IDoItInterface1 = interface(IInterface)
  6.     ['{25C30F47-B693-4D01-A16D-9838BE169623}']
  7.     procedure DoIt1;
  8.   end;
  9.  
  10.   IDoItInterface2 = interface(IInterface)
  11.     ['{16ED0D5D-A85C-406C-8380-77D7B598823C}']
  12.     procedure DoIt2;
  13.   end;
  14.  
  15.   TDoItClass1 = class(TInterfacedObject, IDoItInterface1)
  16.   public
  17.     procedure DoIt1;
  18.   end;
  19.  
  20.   TDoItClass2 = class(TAggregatedObject, IDoItInterface1, IDoItInterface2)
  21.   private
  22.     function GetIDoItIf1: IDoItInterface1;
  23.   public
  24.     property IDoItItf1: IDoItInterface1 read GetIDoItIf1 implements IDoItInterface1;
  25.     procedure DoIt2;
  26.   end;
  27.  
  28.   TDoItClass3 = class(TObject)
  29.   private
  30.     FDoItItf1: IDoItInterface1;
  31.   public
  32.     constructor Create(const ADoItItf1: IInterface);
  33.     property IDoItInf1: IDoItInterface1 read FDoItItf1;
  34.     procedure DoIt2;
  35.   end;
  36.  
  37. constructor TDoItClass3.Create(const ADoItItf1: IInterface);
  38. begin
  39.   FDoItItf1 := ADoItItf1 as IDoItInterface1;
  40. end;
  41.  
  42. procedure TDoItClass3.DoIt2;
  43. begin
  44.   Writeln('TDoItClass3.DoIt2');
  45. end;
  46.  
  47. procedure TDoItClass2.DoIt2;
  48. begin
  49.   Writeln('TDoInClass2.DoIt2');
  50. end;
  51.  
  52. function TDoItClass2.GetIDoItIf1: IDoItInterface1;
  53. begin
  54.   Result := Controller as IDoItInterface1;
  55. end;
  56.  
  57. procedure TDoItClass1.DoIt1;
  58. begin
  59.   Writeln('TDoInClass1.DoIt1');
  60. end;
  61.  
  62. var
  63.   X2: TDoItClass2;
  64.   X3: TDoItClass3;
  65. begin
  66.   X2:= TDoItClass2.Create(TDoItClass1.Create);
  67.   X2.GetIDoItIf1.DoIt1;
  68.   X2.DoIt2; // Use as class, not as interface
  69.   //(X2 as IDoItInterface2).DoIt2; // Use as interface - get error on Free
  70.   X2.Free;
  71.   X3 := TDoItClass3.Create(TDoItClass1.Create);
  72.   X3.DoIt2;
  73.   X3.Free;
  74.   Readln;
  75. end.
First, TDoItClass1 used as interface, without Free. It's normal. TDoItClass2 no different from a simple class TDoItClass3, i.e. ignores the advantages of interfaces.

k1ng

  • New Member
  • *
  • Posts: 37
Re: Refactoring code to support some kind of multiple inheritance
« Reply #16 on: July 25, 2019, 08:46:21 am »
So many answers, need to read them again when I've more time and then probably test out some of code.
Just a few things:
  • needs to work in $mode Delphi
  • don't want to use these reference counting as it would need another bunch of changes
  • I don't really see how your proposals will take care of less code duplications as interfaces don't have functions coded?

I also updated the first example by function ShowValues to better indicate my idea.

Zoran

  • Hero Member
  • *****
  • Posts: 1830
    • http://wiki.lazarus.freepascal.org/User:Zoran
Re: Refactoring code to support some kind of multiple inheritance
« Reply #17 on: July 25, 2019, 09:16:10 am »
So many answers, need to read them again when I've more time and then probably test out some of code.
Just a few things:
  • needs to work in $mode Delphi
  • don't want to use these reference counting as it would need another bunch of changes
  • I don't really see how your proposals will take care of less code duplications as interfaces don't have functions coded?

I also updated the first example by function ShowValues to better indicate my idea.

The conversation diverged from original question. I suggest you focus on Leledumbo's post and use interface delegation.

Use corba interfaces to avoid reference counting.

PascalDragon

  • Hero Member
  • *****
  • Posts: 5481
  • Compiler Developer
Re: Refactoring code to support some kind of multiple inheritance
« Reply #18 on: July 25, 2019, 09:20:42 am »
  • needs to work in $mode Delphi
There is no difference in that functionality regarding modes.

  • I don't really see how your proposals will take care of less code duplications as interfaces don't have functions coded?
You implement a single TLangauge class implementing ILanguage and a single TGenre one implementing IGenre. Then in TLanguageGenre you require both ILanguage and IGenre and then implement these two interfaces using two properties that uses implements to implement the two interfaces using delegation with the help of TLanguage and TGenre.
Of course if you have only one class that uses both ILanguage and IGenre there isn't much advantage, however if you have other classes that implement one of the two interfaces or both the advantage becomes obvious.

devEric69

  • Hero Member
  • *****
  • Posts: 648
Re: Refactoring code to support some kind of multiple inheritance
« Reply #19 on: July 25, 2019, 10:09:41 am »
@ASerge and @king: a modified version that better shows the advantages of using T[Contained|Aggregated]Object:

Code: Pascal  [Select][+][-]
  1.  
  2.   type
  3.     IDoItInterface1 = interface(IInterface)
  4.       ['{25C30F47-B693-4D01-A16D-9838BE169623}']
  5.       procedure DoIt1;
  6.     end;
  7.  
  8.     IDoItInterface2 = interface(IInterface)
  9.     ['{16ED0D5D-A85C-406C-8380-77D7B598823C}']
  10.       procedure DoIt2;
  11.     end;
  12.  
  13.     { TDoInClass1 }
  14.  
  15.     TDoMultiIntfsImplementationsInThisClass = class(TInterfacedObject, IDoItInterface1, IDoItInterface2)
  16.     public
  17.       procedure DoIt1;
  18.       procedure DoIt2;
  19.     end;
  20.  
  21.     TDoIntfsCallsInThisClass = class(TAggregatedObject, IDoItInterface1, IDoItInterface2)
  22.     private
  23.       function GetIDoItIf1: IDoItInterface1;
  24.       function GetIDoItIf2: IDoItInterface2;
  25.     public
  26.       { get implemented interface 1 }
  27.       property IDoItFromNamedInterface1: IDoItInterface1 read GetIDoItIf1 implements IDoItInterface1;
  28.       { get implemented interface 1 }
  29.       property IDoItFromNamedInterface2: IDoItInterface2 read GetIDoItIf2 implements IDoItInterface2;
  30.     end;
  31.  
  32.  
  33.  
  34. var
  35.   frmMain: TfMain;
  36.  
  37. implementation
  38.  
  39. uses
  40.     Dialogs;
  41.  
  42. {$R *.lfm}
  43.  
  44. { TDoMultiIntfsImplementationsInThisClass }
  45.  
  46. procedure TDoMultiIntfsImplementationsInThisClass.DoIt1;
  47. begin
  48.   ShowMessage('DoIt1');
  49. end;
  50.  
  51. procedure TDoMultiIntfsImplementationsInThisClass.DoIt2;
  52. begin
  53.   ShowMessage('DoIt2');
  54. end;
  55.  
  56. { TDoIntfsCallsInThisClass }
  57.  
  58. function TDoIntfsCallsInThisClass.GetIDoItIf1: IDoItInterface1;
  59. begin
  60.   Result := Controller as IDoItInterface1;
  61. end;
  62.  
  63. function TDoIntfsCallsInThisClass.GetIDoItIf2: IDoItInterface2;
  64. begin
  65.   Result := Controller as IDoItInterface2;
  66. end;
  67.  
  68. procedure TfMain.FormCreate(Sender: TObject);
  69. var
  70.     oX: TDoIntfsCallsInThisClass;
  71. begin
  72.     oX:= TDoIntfsCallsInThisClass.Create(TDoMultiIntfsImplementationsInThisClass.Create);
  73.     oX.IDoItFromNamedInterface1.DoIt1;
  74.     oX.IDoItFromNamedInterface2.DoIt2;
  75.     oX.Free;    (* No error:
  76.     the Controller(s) of this Class always having a reference on the
  77.     implemented interfaces, the objects created from this Class are
  78.     managed in the [>>>Create..Free<<<] paradigm!
  79.     ➔ it's the reason for the existence,
  80.     of T[Contained|Aggregated]Object. *)
  81. end;
  82.  

Quote
don't want to use these reference counting as it would need another bunch of changes

T[Contained|Aggregated]Object hides the "painful aspect" of the reference counting: here, 1°) multi-heritages of capacities \ behaviours - gathered in separate interfaces - are implemented, via the implementation of multiple Interfaces, and 2°) the heap's memory management is controlled by the delegation to a T[Contained|Aggregated]Object, that publishes the multiple interfaces, and their implemented methods above.
Deja dit: T[Contained|Aggregated]Object is never released on its own, due to the interference counting: it remains POO "old style", as long as we declare variables T[Contained|Aggregated]Object: in this context, it's always the developer, who remains in control of when he wants to Free its created object.

• ps1: it's one method among others to make multiple inheritance ( but after that, it is everyone's choice and affinities. As I have already said, I prefer composition \ multiple encapsulations, and then to delegate method's publication to the encapsulating class ).
• ps2: in a context where we don't know, neither the OS, nor the database, ..., with which our objects must work (i.e. we have to create reusable objects "go anywhere"), programming by behaviour - capacity with COM interfaces, use of CORBA, ..., becomes the technology(ies) that shine: an overkill.

• Edit 1 (29 July 2019): just to clarify: when I say that it is an " over kill " in this situation, it's first degree. This means that it's precisely this type of technology(ies) that must be used to overcome a situation with much more complex constraints than the average.
• Edit 2: amho, in a more basic program, where we already code a lot with "normal" objects (Create..Free), T[Contained|Aggregated]Object can be seen as a simple decorator of TInterfaced objects, which decorates "in last ressort" their counting of class references, into an "old generation" Free method, testing "if Self<> nil...", allowing the "normal" use of (already or not) implemented Interfaced objects.
• Edit 3 (August 5th 2019): whether we refactor aprogram by extracting behavioral interfaces or behavioral abstract classes..., at the end it is the same thing: Interfaced Classes that implement one (or more behavioral interfaces), or concrete classes that implement one (or more) abstract behavioral classes, both end up embedded in the final "utility object" that will have to use delegation.
➔ And, at the end of the ends, both, interfaces and abstract classes are in fact used to allow polymorphism at run-time (ie to decouple classes as much as possible from each other - with a minimum of hard implementation couplings to compile time - to allow maximum polymophism at run-time).
A technical point: the Interfaces are very much used in Java, because Java has a real Java garbage collector.
Concerning Pascal, I have already said my point of view there (see Edit 2).
• Edit 4 (May 4th 2022): more semantics in the example (IDoItFromNamedInterface1 and IDoItFromNamedInterface2), to show that we can name via a named property, i.e. a named articulation, i.e. a named fluence before the the effective call of the interface implementation.
« Last Edit: May 04, 2022, 09:32:33 am by devEric69 »
use: Linux 64 bits (Ubuntu 20.04 LTS).
Lazarus version: 2.0.4 (svn revision: 62502M) compiled with fpc 3.0.4 - fpDebug \ Dwarf3.

ASerge

  • Hero Member
  • *****
  • Posts: 2242
Re: Refactoring code to support some kind of multiple inheritance
« Reply #20 on: July 25, 2019, 11:16:13 am »
Apparently I poorly explain. Imagine that there is a function that works with parameters of type IDoItInterface1. There is another that works with parameters of type IDoItInterface2. That is, they need to pass a link to the interface. This is where your class fail.
You more details of the shell above the TDoMultiIntfsImplementationsInThisClass type. Namely, this class is interesting. And you definitely don't use it in the Create/Free style. But shell, of course, can be a class or a set of functions.

devEric69

  • Hero Member
  • *****
  • Posts: 648
Re: Refactoring code to support some kind of multiple inheritance
« Reply #21 on: July 25, 2019, 11:57:45 am »
Quote
Apparently I poorly explain.
No: you explained it very well, and what you say is true.
But, you and I, both know that the combination of Interfaces variables and Classes variables is a friction point, in the way memory is managed. And that's the essential thing to understand.

My point of view is just that, aware of this, everyone does what he wants. Everyone can use only one type of variable ( only Interfaces variables, or only T[Contained|Aggregated]Object variables ), or mix the two, in full knowledge of the causes and consequences when using a mix of memory management (as your examples in mixed mode of memory management - leading to cases of Access Violations , if you do not understand the memory management that contextually prevails in the considered part of a mixed code - illustrate it very well).
use: Linux 64 bits (Ubuntu 20.04 LTS).
Lazarus version: 2.0.4 (svn revision: 62502M) compiled with fpc 3.0.4 - fpDebug \ Dwarf3.

Mr.Madguy

  • Hero Member
  • *****
  • Posts: 844
Re: Refactoring code to support some kind of multiple inheritance
« Reply #22 on: July 26, 2019, 02:50:14 pm »
Currently there are just two ways to implement multiple inheritance:
1) Interfaces. Limited way, suitable for abstract classes only.
2) Class composition. Clunky way. Multiple inheritance in C++ is actually just a syntax sugar around it.

Class composition is:
Code: Pascal  [Select][+][-]
  1. TBaseClass1 = class
  2.   function GetSomething:String;
  3. end;
  4.  
  5. TBaseClass2 = class
  6.   function GetSomething:String;
  7. end;
  8.  
  9. TMyClass = class
  10.   FClass1:TBaseClass1;
  11.   FClass2:TBaseClass2;
  12.   function GetSomething:String;
  13. end;
  14.  
  15. function TMyClass.GetSomething:String;
  16. begin
  17.   Result := FClass1.GetSomething + FClass2.GetSomething;
  18. end;
  19.  
I've been asking for implementation of multiple inheritance for a long time already, cuz I need code pattern like this:
Code: Pascal  [Select][+][-]
  1. TAbstractList<T> = class
  2. end;
  3.  
  4. TAbstractPairList<TKey, TValue> = class(TAbstractList<TPair<TKey, TValue>>:virtual)
  5. end;
  6.  
  7. TList<T> = class(TAbstractList<T>:virtual)
  8. end;
  9.  
  10. TPairList<TKey, TValue> = class(TAbstractPairList<TKey, TValue>:virtual)
  11. end;
  12.  
  13. TFinalList<TKey, TValue> = class(TAbstractPairList<TKey, TValue>:virtual, TList<TPair<TKey, TValue>>:override, TPairList<TKey, TValue>:override)
  14. end;
  15.  
« Last Edit: July 26, 2019, 03:06:28 pm by Mr.Madguy »
Is it healthy for project not to have regular stable releases?
Just for fun: Code::Blocks, GCC 13 and DOS - is it possible?

Zoran

  • Hero Member
  • *****
  • Posts: 1830
    • http://wiki.lazarus.freepascal.org/User:Zoran
Re: Refactoring code to support some kind of multiple inheritance
« Reply #23 on: July 26, 2019, 05:27:56 pm »
1) Interfaces. Limited way, suitable for abstract classes only.

Interface delegation (as pointed by Leledumbo, and later furter explained by PascalDragon) is much less limited, actually gets you pretty close to real multiple inheritance.

Mr.Madguy

  • Hero Member
  • *****
  • Posts: 844
Re: Refactoring code to support some kind of multiple inheritance
« Reply #24 on: July 26, 2019, 08:23:24 pm »
Interface delegation (as pointed by Leledumbo, and later furter explained by PascalDragon) is much less limited, actually gets you pretty close to real multiple inheritance.
I know everything about interfaces, including interface delegation, and class composition. I actively use them in my project. Problem is - I just feel, that this solution is clunky. I still have to make wrapper classes and some other clunky structures, that implies unnecessary waste of my time and effort. And I just don't like, how my code looks like. I.e. what I want to say, is that multiple inheritance is natural way of describing class structures, I need.

Interface delegation is nothing more, than just virtual multiple inheritance, I've shown above. Virtual multiple inheritance is just more natural. You don't need to look under hood of this process. You don't need to do it manually. Compiler does everything for you.

And at the end everything boils down to quality of code and effectiveness of programming. My project is very large. I don't like both quality of my code and it's performance due to some penalties, caused by extra wrapper classes. I just want to make it better.

Dunno. Just look at my code pattern. Now try to describe it in terms of interface delegation. Do you see any difference?
« Last Edit: July 26, 2019, 08:41:46 pm by Mr.Madguy »
Is it healthy for project not to have regular stable releases?
Just for fun: Code::Blocks, GCC 13 and DOS - is it possible?

k1ng

  • New Member
  • *
  • Posts: 37
Re: Refactoring code to support some kind of multiple inheritance
« Reply #25 on: June 20, 2020, 05:39:14 pm »
Hey, thanks for all your input.
Only thing which is not clear:

Code: Pascal  [Select][+][-]
  1.   if x.base is TLanguage then
  2.     Result := TLanguage(x.base).languages.Text;

How do I manage such things? Can't test for interfaces either because they don't support property (which is what languages is).

As I'd need to check at some places if x is TLanguage or TGenre to be able to cast it properly because it is not always only TLanguageGenre.
« Last Edit: June 20, 2020, 05:43:54 pm by k1ng »

asdf1337

  • Jr. Member
  • **
  • Posts: 56
Re: Refactoring code to support some kind of multiple inheritance
« Reply #26 on: March 05, 2021, 10:39:29 pm »
I've found this post by searching and I've a similar problem but I use properties, so based on the previous given examples my code is:
Code: Pascal  [Select][+][-]
  1.   TBase = class
  2.   private
  3.     Fauthor: String;
  4.     Fname: String;
  5.     Fyear: integer;
  6.   published
  7.     property Author: Boolean read Fauthor;
  8.     property Name: Boolean read Fname;
  9.     property Year: Boolean read Fyear;
  10.   end;
  11.  
  12.   TGenre = class(TBase)
  13.   private
  14.     Fgenre: String;
  15.   published
  16.     property Genre: Boolean read Fgenre;
  17.   end;
  18.  

Is it possible to have this with interface delegation (as recommended here) as well?

Of course if you have only one class that uses both ILanguage and IGenre there isn't much advantage, however if you have other classes that implement one of the two interfaces or both the advantage becomes obvious.
What would you recommend if I only have both once but still need to have them together in this one class?

 

TinyPortal © 2005-2018