iiid354 0 Posted March 8, 2021 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. 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
FPiette 383 Posted March 9, 2021 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. 1 Share this post Link to post
iiid354 0 Posted March 9, 2021 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. 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
FPiette 383 Posted March 9, 2021 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
iiid354 0 Posted March 10, 2021 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
Remy Lebeau 1394 Posted March 10, 2021 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
Lars Fosdal 1792 Posted March 10, 2021 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
Attila Kovacs 629 Posted March 10, 2021 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
iiid354 0 Posted March 11, 2021 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 Share this post Link to post
Markus Kinzler 174 Posted March 11, 2021 There is already a class for this: TInterfacedObject TEgon = class( TInterfacedObject, IHuman, IManager); Share this post Link to post
iiid354 0 Posted March 15, 2021 (edited) 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 March 15, 2021 by iiid354 Share this post Link to post
David Schwartz 426 Posted March 15, 2021 (edited) 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 March 15, 2021 by David Schwartz Share this post Link to post
Fr0sT.Brutal 900 Posted March 16, 2021 Side note: that's why we need auto-wrapping code blocks 1 Share this post Link to post
David Schwartz 426 Posted March 17, 2021 (edited) 19 hours ago, Fr0sT.Brutal said: Side note: that's why we need auto-wrapping code blocks please explain... Edited March 17, 2021 by David Schwartz Share this post Link to post
Fr0sT.Brutal 900 Posted March 17, 2021 2 hours ago, David Schwartz said: please explain... I mean, in the forum. 1 Share this post Link to post