Jump to content
iiid354

Help to achieve multiple inheritance by class redesign

Recommended Posts

Hello,
I need some help with re-designing classes from a legacy program and thus I wanted to ask the experts here for help. 😁
Right now I have a base class THuman and derive different other classes like TManager, TWorker, TSales. This is done by a class function HumanAccepted at THuman that decides which class is created based on some external information.
So the example classes are looking like the following:

  THuman = class
  private
    FContractStart: Integer; // year of entry to company
  public
    constructor Create(const surname, name: String); virtual;
    function Work(const msg: String): boolean; virtual; // used for simple extra info
    function Work(p: TObject): boolean; virtual; // used for more complex stuff, contains a reference to another class where this class is a member of
    function AsText(): String; virtual; // show a nice text of all infos
    class function HumanAccepted(const name: String): boolean;
    // ...
    property StartOfContract: Integer read FContractStart;
  end;

  TManager = class(THuman)
  private
    FSubWorkers: Integer; // number of workers in his group
  public
    constructor Create(const surname, name: String); override;
    function Work(const msg: String): boolean; override; // used for simple extra info
    function Work(p: TObject): boolean; override; // used for more complex stuff, contains a reference to another class where this class is a member of
    function AsText(): String; override; // show a nice text of all infos, from TManager fields but also THuman
    // ...
    property AmountSubWorkers: Integer read FSubWorkers;
  end;

  TSales = class(THuman)
  private
    FSoldCars: Integer; // number of cars sold so far
    FSoldDrinks: Integer; // number of drinks sold so far
  public
    constructor Create(const surname, name: String); override;
    function Work(const msg: String): boolean; override; // used for simple extra info
    function Work(p: TObject): boolean; override; // used for more complex stuff, contains a reference to another class where this class is a member of
    function AsText(): String; override; // show a nice text of all infos, from TSales fields but also THuman
    // ...
    property SoldCars: Integer read FSoldCars;
    property SoldDrinks: Integer read FSoldDrinks;
  end;


Initially a loop iterates over all TCategoryHandlers and creates the found class (that's what class function HumanAccepted is for) as TCHuman which is then used and appended to a object which needs that info stored in my classes. The definitions as code:

  TCHuman = class of THuman;
  TCategoryHandlers = array[0..3] of TCRelease;
  Handlers: TCategoryHandlers = (THuman, TManager, TWorker, TSales);

 

However, now I have to redesign it as I need to have e.g. TSalesManager which provides me the properties of THuman, TManager and TSales so if Delphi would allow multiple inheritance that's what I'd choose...
One idea that came into my mind was to copy all stuff into THuman and use booleans to indicate if it's a manager, sales person, etc and do the actions/outputs based on that but I guess that's not a very good solution at all. :classic_blush:
So that's were I need you guys, do you have any recommendations what I should do to achieve something easily maintainable (avoid code duplications) and extendable for further classes? One thing I also need to do is in another unit that uses properties based on if base.human is TManager then or if base.human is TSales then to perform specific tasks with that info. And just for clarification but guess it's obviously, TSalesManager would need to call TSales.Work as well as TManager.Work.
We might also want to use mORMot to save all the information into a database at a later point in time, so this should be an option and be considered now.

Thanks in advance for any tips or ideas!

Share this post


Link to post

Since Delphi do not support multiple class inheritance, you should use interfaces. A class can support many interfaces. So TSalesManager can support IHuman, ISales and IManager interfaces. At the class level, you can delegate an interface implementation to another class.

  • Like 1

Share this post


Link to post

Thanks but it's still a bit unclear to me.

I need to declare following interfaces, right?

  IHuman = interface
  [GUID]
    constructor Create(const surname, name: String); virtual;
    function Work(const msg: String): boolean; virtual; // used for simple extra info
    function Work(p: TObject): boolean; virtual; // used for more complex stuff, contains a reference to another class where this class is a member of
    function AsText(): String; virtual; // show a nice text of all infos
    class function HumanAccepted(const name: String): boolean;
    // ...
    function GetContractStart(): Integer; // <-- needed because interfaces don't allow fields
    property StartOfContract: Integer read GetContractStart;
  end;

  IManager = interface
  [GUID]
    constructor Create(const surname, name: String); override;
    function Work(const msg: String): boolean; override; // used for simple extra info
    function Work(p: TObject): boolean; override; // used for more complex stuff, contains a reference to another class where this class is a member of
    function AsText(): String; override; // show a nice text of all infos, from TManager fields but also THuman
    // ...
    function GetAmountSubworkers(): Integer;
    property AmountSubWorkers: Integer read GetAmountSubworkers;
  end;
  
  ISales = interface
  [GUID]
    constructor Create(const surname, name: String); override;
    function Work(const msg: String): boolean; override; // used for simple extra info
    function Work(p: TObject): boolean; override; // used for more complex stuff, contains a reference to another class where this class is a member of
    function AsText(): String; override; // show a nice text of all infos, from TSales fields but also THuman
    // ...
    function GetSoldCars(): Integer;
    function GetSoldDrinks(): Integer;
    property SoldCars: Integer read GetSoldCars;
    property SoldDrinks: Integer read GetSoldDrinks;
  end;

But what then? Do I declare THuman = class(IHuman) and add the needed fields and implement all the functions? And then the same for TManager, TSales (with inheriting from THuman)?

But how to derive TSalesManager then? Inherit from one of the two classes which provides like a chain of inheritance?

Also how do I avoid code duplication as interfaces do not implement any functionality. :classic_unsure:

Would be nice if someone could give me a more detailed example (or also other approaches I could use).

Thanks!

Share this post


Link to post

No constructor in interface. The constructor stay in the class. Base methods such as Work() can be in a base interface and all other interfaces inherit from the base interface.

 

Share this post


Link to post

You mean like this? Or do IManager inherit from IHuman?

  ICommon = interface
  [GUID]
    function Work(const msg: String): boolean; virtual; // used for simple extra info
    function Work(p: TObject): boolean; virtual; // used for more complex stuff, contains a reference to another class where this class is a member of
    function AsText(): String; virtual; // show a nice text of all infos
    class function HumanAccepted(const name: String): boolean;
  end;

  IHuman = interface(ICommon)
  [GUID]
    function GetContractStart(): Integer; // <-- needed because interfaces don't allow fields
    property StartOfContract: Integer read GetContractStart;
  end;

  IManager = interface(ICommon)
  [GUID]
    function GetAmountSubworkers(): Integer;
    property AmountSubWorkers: Integer read GetAmountSubworkers;
  end;
  
  ISales = interface(ICommon)
  [GUID]
    function GetSoldCars(): Integer;
    function GetSoldDrinks(): Integer;
    property SoldCars: Integer read GetSoldCars;
    property SoldDrinks: Integer read GetSoldDrinks;
  end;

And then I implement a TCommon class based on ICommon? THuman based on IHuman, TManager based on THuman or only from IManager?

 

Share this post


Link to post

Interfaces can't have 'class' methods, either.

 

What is the actual GOAL that you are trying to accomplish?  Can you provide a use-case example of what you WANT that is not working for you?

Share this post


Link to post

I would divide this into f.x. Employee and Role, and perhaps have an extra Contract class connected to the role. 

I don't see a need for multiple inheritance, but having polymorphic roles and contracts looks natural. 

Share this post


Link to post
On 3/9/2021 at 9:46 AM, FPiette said:

At the class level, you can delegate an interface implementation to another class.

This is it.

Share this post


Link to post
19 hours ago, Remy Lebeau said:

Interfaces can't have 'class' methods, either.

Okay, so I need one base class for all derived classes to realize that?

 

My problem is that I don't find any real-world example. Only some stupid, simple examples which are so tiny and don't cover much and especially not how to derive the classes like I need with my example shown.

That's why I'm asking here, thought someone could show the base structures for the example I can work with to use it for my way more complex problem :classic_happy:

Share this post


Link to post

There is already a class for this:  TInterfacedObject

 

TEgon = class( TInterfacedObject, IHuman, IManager);

 

 

Share this post


Link to post

I've started to try to implement it but it doesn't really work even with what you guys wrote, thus attached my example code I've so far:

 

humas.pas

unit humans;

interface

type
  { common functions }
  ICommonFunc = interface
  ['{e1f156de-2e6e-4b93-a3b2-c723e73e75da}']
    function Work(const AMessage: String): boolean;
    function AsText(): String;
    function GetStillInCompany(): Boolean;
    property IsStillInCompany: Boolean read GetStillInCompany;
  end;

  { common Human }
  IHumanInfo = interface
  ['{6a4f763e-5365-4012-bd7b-520dbe8bfeb8}']
    function GetContractStart(): Integer;
    property StartOfContract: Integer read GetContractStart;
  end;

  THumanInfo = class(TInterfacedObject, IHumanInfo)
  private
    FFirstname: String;
    FLastname: String;
    FContractStart: Integer;
  public
    constructor Create(const AFirstname, ALastname: String);

    function GetContractStart(): Integer;
  end;

  { Manager }
  IManagerInfo = interface
  ['{c2ef4c56-60ac-4e5b-a3a6-674c4ff94b34}']
    function GetAmountSubworkers(): Integer;
    property AmountSubWorkers: Integer read GetAmountSubworkers;
  end;

  TManagerInfo = class(TInterfacedObject, IManagerInfo)
  private
    FSubworkers: Integer;
  public
    constructor Create(const ASubWorkers: Integer);

    function GetAmountSubworkers(): Integer;
  end;

  { Sales }
  ISalesInfo = interface
  ['{29ad75e3-6bae-4221-9e98-5979dcaf347f}']
    function GetSoldCars(): Integer;
    function GetSoldDrinks(): Integer;
    property SoldCars: Integer read GetSoldCars;
    property SoldDrinks: Integer read GetSoldDrinks;
  end;

  TSalesInfo = class(TInterfacedObject, ISalesInfo)
  private
    FSoldCars: Integer;
    FSoldDrinks: Integer;
  public
    constructor Create(const ASoldCars, ASoldDrinks: Integer);

    function GetSoldCars(): Integer;
    function GetSoldDrinks(): Integer;
  end;

  { real classes with all information needed }
  THuman = class(TInterfacedObject, ICommonFunc, IHumanInfo)
  private
    FHumanInfo: IHumanInfo;
    FStillInCompany: Boolean;
  public
    constructor Create(const AFirstname, ALastname: String);
    destructor Destroy; override;

    function Work(const AMessage: String): boolean;
    function AsText(): String;
    function GetStillInCompany(): Boolean;

    property HumanInfo: IHumanInfo read FHumanInfo implements IHumanInfo;
  end;

  TManager = class(TInterfacedObject, ICommonFunc, IHumanInfo, IManagerInfo)
  private
    FHumanInfo: IHumanInfo;
    FManagerInfo: IManagerInfo;
    FStillInCompany: Boolean;
  public
    constructor Create(const AFirstname, ALastname: String; const ASubWorkers: Integer);
    destructor Destroy; override;

    function Work(const AMessage: String): boolean;
    function AsText(): String;
    function GetStillInCompany(): Boolean;

    property HumanInfo: IHumanInfo read FHumanInfo implements IHumanInfo;
    property ManagerInfo: IManagerInfo read FManagerInfo implements IManagerInfo;
  end;

  TSales = class(TInterfacedObject, ICommonFunc, IHumanInfo, ISalesInfo)
  private
    FHumanInfo: IHumanInfo;
    FSalesInfo: ISalesInfo;
    FStillInCompany: Boolean;
  public
    constructor Create(const AFirstname, ALastname: String; const ASoldCars, ASoldDrinks: Integer);
    destructor Destroy; override;

    function Work(const AMessage: String): boolean;
    function AsText(): String;
    function GetStillInCompany(): Boolean;

    property HumanInfo: IHumanInfo read FHumanInfo implements IHumanInfo;
    property SalesInfo: ISalesInfo read FSalesInfo implements ISalesInfo;
  end;

  TSalesManager = class(TInterfacedObject, ICommonFunc, IHumanInfo, ISalesInfo, IManagerInfo)
  private
    FHumanInfo: IHumanInfo;
    FSalesInfo: ISalesInfo;
    FManagerInfo: IManagerInfo;
    FStillInCompany: Boolean;
  public
    constructor Create(const AFirstname, ALastname: String);
    destructor Destroy; override;

    function Work(const AMessage: String): boolean;
    function AsText(): String;
    function GetStillInCompany(): Boolean;

    property HumanInfo: IHumanInfo read FHumanInfo implements IHumanInfo;
    property SalesInfo: ISalesInfo read FSalesInfo implements ISalesInfo;
    property ManagerInfo: IManagerInfo read FManagerInfo implements IManagerInfo;
  end;

implementation

uses
  SysUtils;

constructor THumanInfo.Create(const AFirstname, ALastname: String);
begin
  writeln('THumanInfo.Create');
  FFirstname := AFirstname;
  FLastname := ALastname;
  FContractStart := 1990;
end;

function THumanInfo.GetContractStart: Integer;
begin
  writeln('GetContractStart');
  Result := FContractStart;
end;

constructor TManagerInfo.Create(const ASubWorkers: Integer);
begin
  writeln('TManagerInfo.Create');
  FSubworkers := ASubWorkers;
end;

function TManagerInfo.GetAmountSubworkers: Integer;
begin
  writeln('GetAmountSubworkers');
  Result := FSubworkers;
end;

constructor TSalesInfo.Create(const ASoldCars, ASoldDrinks: Integer);
begin
  writeln('TSalesInfo.Create');
  FSoldCars := ASoldCars;
  FSoldDrinks := ASoldDrinks;
end;

function TSalesInfo.GetSoldCars: Integer;
begin
  writeln('GetSoldCars');
  Result := FSoldCars;
end;

function TSalesInfo.GetSoldDrinks: Integer;
begin
  writeln('GetSoldDrinks');
  Result := FSoldDrinks;
end;

constructor THuman.Create(const AFirstname, ALastname: String);
begin
  writeln('THuman.Create');
  FHumanInfo := THumanInfo.Create(AFirstname, ALastname);
  FStillInCompany := True;
end;

destructor THuman.Destroy;
begin
  writeln('THuman.Destroy');
//  FHumanInfo.Free;
  inherited;
end;

function THuman.Work(const AMessage: String): boolean;
begin
  Result := False;
  writeln('THuman.Work');
  writeln('doing my work', AMessage);
  Result := True;
end;

function THuman.AsText: String;
begin
  writeln('THuman.AsText');
  Result := 'default';
end;

function THuman.GetStillInCompany(): Boolean;
begin
  writeln('THuman.GetStillInCompany');
  Result := FStillInCompany;
end;

constructor TManager.Create(const AFirstname, ALastname: String; const ASubWorkers: Integer);
begin
  writeln('TManager.Create');
  FHumanInfo := THumanInfo.Create(AFirstname, ALastname);
  FManagerInfo := TManagerInfo.Create(ASubWorkers);
  FStillInCompany := True;
end;

destructor TManager.Destroy;
begin
  writeln('TManager.Destroy');
//  FHumanInfo.Free;
//  FManagerInfo.Free;
  inherited;
end;

function TManager.Work(const AMessage: String): boolean;
begin
  Result := False;
  writeln('TManager.Work');
  writeln('just telling others what to do', AMessage);
  Result := True;
end;

function TManager.AsText: String;
begin
  writeln('TManager.AsText');
  Result := 'boss';
end;

function TManager.GetStillInCompany(): Boolean;
begin
  writeln('TManager.GetStillInCompany');
  Result := FStillInCompany;
end;

constructor TSales.Create(const AFirstname, ALastname: String; const ASoldCars, ASoldDrinks: Integer);
begin
  writeln('TSales.Create');
  FHumanInfo := THumanInfo.Create(AFirstname, ALastname);
  FSalesInfo := TSalesInfo.Create(ASoldCars, ASoldDrinks);
  FStillInCompany := True;
end;

destructor TSales.Destroy;
begin
  writeln('TSales.Destroy');
//  FHumanInfo.Free;
//  FSalesInfo.Free;
  inherited;
end;

function TSales.Work(const AMessage: String): boolean;
begin
  Result := False;
  writeln('TSales.Work');
  writeln('making money', AMessage);
  Result := True;
end;

function TSales.AsText: String;
begin
  writeln('TSales.AsText');
  Result := 'MONEY';
end;

function TSales.GetStillInCompany(): Boolean;
begin
  writeln('TSales.GetStillInCompany');
  Result := FStillInCompany;
end;

constructor TSalesManager.Create(const AFirstname, ALastname: String);
begin
  writeln('TSalesManager.Create');
  FHumanInfo := THumanInfo.Create(AFirstname, ALastname);
  FSalesInfo := TSalesInfo.Create(0, 5);
  FManagerInfo := TManagerInfo.Create(1);
  FStillInCompany := True;
end;

destructor TSalesManager.Destroy;
begin
  writeln('TSalesManager.Destroy');
//  FHumanInfo.Free;
//  FSalesInfo.Free;
//  FManagerInfo.Free;
  inherited;
end;

function TSalesManager.Work(const AMessage: String): boolean;
begin
  Result := False;
  writeln('TSalesManager.Work');
  writeln('making money', AMessage);
  Result := True;
end;

function TSalesManager.AsText: String;
begin
  writeln('TSalesManager.AsText');
  Result := 'MONEY and BOSS';
end;

function TSalesManager.GetStillInCompany(): Boolean;
begin
  writeln('TSalesManager.GetStillInCompany');
  Result := FStillInCompany;
end;

end.

show.pas

unit show;

interface

uses humans;

function ShowInfoText(r: IHumanInfo): String;
function GetSubWorkers(r: IHumanInfo): Integer;
function GetSoldDrinks(r: IHumanInfo): Integer;

implementation

uses
  SysUtils;

function ShowInfoText(r: IHumanInfo): String;
begin
  Result := 'this text should not appear';
  // function is always available
  //Result := IHumanInfo(r).AsText; // => Error: identifier idents no member "AsText"
end;

function GetSubWorkers(r: IHumanInfo): Integer;
begin
  Result := -1;

  if r is IManagerInfo then
  begin
    Result := IManagerInfo(r).GetAmountSubworkers;
  end;
end;

function GetSoldDrinks(r: IHumanInfo): Integer;
begin
  Result := -1;

  if r is ISalesInfo then
  begin
    Result := ISalesInfo(r).GetSoldDrinks;
  end;
end;

end.

test.lpr

program test;

{$APPTYPE CONSOLE}

{$IFDEF FPC}
  {$mode Delphi}
{$ENDIF}

uses SysUtils, Classes, humans, show;

var
  fHuman: IHumanInfo;
  fSales: IHumanInfo;
  fManager: IHumanInfo;
  fSalesManager: IHumanInfo;

procedure ShowInfo(res: IHumanInfo);
begin
  writeln('  ', ShowInfoText(res));
  writeln('  ', GetSubWorkers(res));
  writeln('  ', GetSoldDrinks(res));
end;

begin
  writeln('-1-');
  fHuman := THuman.Create('Alberto', 'Galva');
  ShowInfo(fHuman);

  writeln('-2-');
  fSales := TSales.Create('Emir', 'Naldo', 10, 27);
  ShowInfo(fSales);

  writeln('-3-');
  fManager := TManager.Create('Diana', 'Snicker', 3);
  ShowInfo(fManager);
  //fManager.Work('hello'); // => Error: identifier idents no member "Work"
  ShowInfo(fManager);

  writeln('-4-');
  fSalesManager := TSalesManager.Create('Roberto', 'Miaz');
  ShowInfo(fSalesManager);
  //fSalesManager.Work('The best moneymaker'); // => Error: identifier idents no member "Work"
  ShowInfo(fSalesManager);
end.

output:

-1-
THuman.Create
THumanInfo.Create
  this text should not appear
  -1
  -1
-2-
TSales.Create
THumanInfo.Create
TSalesInfo.Create
  this text should not appear
  -1
  -1
-3-
TManager.Create
THumanInfo.Create
TManagerInfo.Create
  this text should not appear
  -1
  -1
  this text should not appear
  -1
  -1
-4-
TSalesManager.Create
THumanInfo.Create
TSalesInfo.Create
TManagerInfo.Create
  this text should not appear
  -1
  -1
  this text should not appear
  -1
  -1

 

Edited by iiid354

Share this post


Link to post

You are conflating "Is-A" and "Has-A" attributes.

 

You're not talking about different kinds of humans. (Is-A)

 

You're talking about Employees that all "Have-A" different Role.

 

And each Role "Has-A" set of properties, like Responsibilities, Authorities, Permissions, etc.

 

Each management level "Has-A" set of people who report to them.

Also, just because you can look at something like an Org Chart that's hierarchical does not imply there's an "Is-A" relationship anywhere.

Job roles are not inherited from the top-down! Nor even from the bottom-up. Each layer "Has-A" different set of properties, some of which may be delegated from time to time and for different durations, but they're certainly not all inherited for ever and ever.

 

"Has-A" is called "composition"

"Is-A" is called "inheritance".

 

Since the larger context here is a "business", the base class identified as "human" is more of a distraction than anything else. Sure, everybody working in a business is a "human", but so what? Virtually every one of those people will, in reality, tell you that nobody at the company gives a crap about their life outside of the context of the business, which therefore excludes the vast majority of what their "human" attributes would include. If you dont have a much larger context in which all of those other attributes are being used, then it's "too abstract".

 

No, the common attribute everybody at a company shares is better described as an "Employee". That might be a subclass of "human" but the vast majority of the human parts are irrelevant as far as the company is concerned. Just ask anybody who's not being paid a living wage or decent benefits! The company asks, "Why are these MY problem?" Technically, they're not. At least, not until you look at a much larger context of the role the company has in society, for example -- which is a question far too many people here in America prefer to ignore because they have been brainwashed into thinking that's "socialism" or even "communism".  

 

But that's what happens when your base class brings along too much baggage. It also leads to confusion. 

 

Try starting with "Employee" as your base class and see what happens.

 

At that point, pretty much everything else can be expressed as collections of properties -- that is, "Has-A" attribute collections. Maybe there are some things where inheritance applies, but mostly you're talking about lots of mutually exclusive collections of rights, permissions, and authorities.

Edited by David Schwartz

Share this post


Link to post
19 hours ago, Fr0sT.Brutal said:

Side note: that's why we need auto-wrapping code blocks

please explain...

Edited by David Schwartz

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×