How to encapsulate different classes within one class mantaining their unique methods? (multiple inheritance in delphi?)
Asked Answered
J

3

4

I'm currently rewriting a free educational digital circuit simulator to add inertiality to its features. My problem is how to dispatch events to original classes adding a pre-elaboration to them. I have something like this:

TC1 = class
  ID: integer;
  Connections : array [integer] of Pin;
  function Func1; virtual;
  function FuncN;
end;

TC2-1 = class (TC1)
  function Func1; override;
  function My1Func();
end;

TC2-n = class (TC1)
  function Func1; override;
  function MyNFunc();
end;


TContainer = class
  C1 : TC1;
  function ContFunc;
end;

function Container.ContFunc;
begin
    c1.Func1;
end;

Now this means that ContFunc call C2.Func1 as I wish, specializing behaviour of more than 300 components inheriting form TC1.

But now I have to add some special operations (equal for all component descendants from TC1 every time Func1 is called, and chosing during that operations if I have to call TC2-n.Func1 or not (after changing some property of ancestor TC1. Is there a way to do that cleanly, without changing all descendants of TC1? Can I use a helper class (deprecated?) like this:

TH = class helper of TC1
  function Func1 virtual; override;
end;

function TH.Func1;
begin
  if x then TC2.Func1 else SaveActionData; 
end

If I add TH, when TContainer call Func1, who is called? It call TC2.Func1 and not TH.Func1 as I wished?. Is there a way to override descenants method Func1 without writing an helper class for any single one (they will do all the same operations, meaning exactly equal code)? It is possible to call from TH the 300 descendant functions Func1 of TC2-n ?

In other words, I'm trying to find a way to obtain a call like this by Tcontainer call to c1.Func1;:

NewFunc1 (equal for all TC1 descendants) who call TC2.Func1 (different for any descendant of TC1).

Anyone can suggest a way to do that?

Jimmy answered 2/3, 2010 at 11:49 Comment(0)
S
4

You have some tasks that need to be performed whenever someone calls Func1, regardless of what descendants have chosen to do in their overridden methods. This is a job for the template method pattern.

Give the base class a public non-virtual method Func1 that performs the operations you need and then invokes a protected virtual method. The descendants can override that virtual method, but anyone using the class can only call the public non-virtual method.

type
  TC1 = class
  protected
    function InternalFunc1: Integer; virtual; // abstract?
  public
    function Func1: Integer;
  end;

function TC1.Func1;
begin
  if x then
    Result := InternalFunc1
  else
    Result := SaveActionData; 
end;

Now descendants can override InternalFunc1, and the base class will make sure it gets called only when appropriate.

type
  TC2 = class(TC1)
  protected
    function InternalFunc1: Integer; override;
  end;

You'll need to rename your current Func1 function in all your 300 descendant classes. The IDE's refactoring tools might be able to help with that.

Slosberg answered 2/3, 2010 at 11:49 Comment(2)
This one is a bright idea, Rob. I have to make some tests (never used refactoring tools, I'm completely new of this IDE feature), but at first look seems it could work. Thank you very much for your help.Morehead
I added a non-virtual Func2 in the TC1 class and replaced all (few) call to Func1 in the program to call Func2. Now virtual Func1 is called only by Func2 code. It works like a charm. Thank you again, Rob. I owe you (and all people of this nice forum) one.Morehead
S
0

You can make a wrapper class following the decorator pattern to describe the special tasks that need to occur when your program is operating in analogue mode. It can hold an instance of your digital component and call that component's methods after performing its own tasks.

type
  TAnalogueDecorator = class(TC1)
  private
    FComponent: TC1;
  public
    constructor Create(Wrapped: TC1);
    destructor Destroy; override;

    function Func1: Integer; override;
  end;

constructor TAnalogueDecorator.Create(Wrapped: TC1);
begin
  inherited Create;
  FComponent := Wrapped;
end;

destructor TAnalogueDecorator.Destroy;
begin
  FComponent.Free;
  inherited;
end;

function TAnalogueDecorator.Func1: Integer;
begin
  SaveActionData;
  Result := FComponent.Func1;
end;

Notice how there's no need to check your x condition beforehand. Instead of checking it each time you call any method, you can check it once before wrapping the digital component with the analogue one. Now all the places where you originally called Func1 directly on the digital class get detoured to the analogue class's methods first.

Slosberg answered 2/3, 2010 at 11:49 Comment(2)
This is a interesting option, thank you for suggesting. I have to think on it a little more, but if I understood well the big drawback of this solution is that I cannot anymore use special functions defined by TC1 descendants, ie: any method defined in TC2 classes that it is not contained in their ancestor TC1, correct? Looking at original question code, MyNFunc and My1Func will not be callable anymore from decorator class, correct? Any work-around about this problem?Morehead
Expose the wrapped component as a property of the wrapper so others can access its special methods. Or give the wrapper 300 other methods, each one calling the corresponding specialized method in the wrapped object. Or consolidate all those specialized functions into a few (or even just one) virtual methods that the descendants override specially.Slosberg
A
0

Class helpers are useful for modifying classes you can't get at the source to. If you're the author of class TC1, and you would be able to make the necessary changes by introducing a class helper for TC1, then why not just modify TC1.Func1, and then you're done? That should work.

Aromatic answered 2/3, 2010 at 11:56 Comment(3)
Problem is that Func1 is abstract member of TC1: work is done in descendants, and there are 300 different Func1 (it is Func1 that differentiate the behaviour of any digital components of the project). Moreover, the modification (that could be done 300 times by cut and paste in all descedant classes) structurally is unrelated with them, because add a pre-elaboration (inertial transition management) that normally only delay in time their operation (but when limits are reached, totally block them, introducing some new very complex behaviours totally unrelated with digital logic of components)Morehead
. I want not only to mantain original digital components, but continue to manage addition to them thinking only of digital behaviour, not the analogic (inertial one) that is a extreme case and often not shown to students (at least at beginning). It is the structure of the problem that suggest me to mantain separate the pre-elaboration and the Func1 operations. The former is a very complex one, but equal for all, the latter is special of any component, and it is better described and implemented when the former is not known. Surely I can rewrite the central algorithm of program (event-driver)Morehead
but it is another complex piece of software that has been througly tested in the past years, and I believe it should be better do not touch it. So the question is, there is a way to substitute the call to specialized Func1 when calling it and at the same time call the right Func1 from that call when needed?Morehead

© 2022 - 2024 — McMap. All rights reserved.