Mike Warren 2 Posted September 7 I don't exactly need multiple inheritance (I think), but I'm trying to find a clean way of doing the following (This is simplified): This is in FireMonkey, if that makes a difference. I have 2 classes; TapRectangle, inherited from TRectangle, and TapCircle, inherited from TCircle. I want to retain all the properties and methods of the ancestor classes, but I want to add some new methods and properties, which are the same for both TapRectangle and TapCircle, so I can't inherit from TShape. I also want to be able to pass a TapRectangle or TapCircle to a function and access these new properties and methods. Is there an elegant way of doing all this? I can achieve what I want by copying the new properties and methods to each new class and the using the common ancestor TShape to pass my components, but it's not elegant. I've actually never used interfaces, but from what I understand that wouldn't work here. Please correct me if I'm wrong. Share this post Link to post
Mike Warren 2 Posted September 7 I tried it like this, which shows I don't understand interfaces properly: IapShape = interface procedure SetThing(AThing: Boolean); property Thing: Boolean read FThing write SetThing; // Error here: Field or method identifier expected end; TapRectangle = class(TRectangle, IapShape) private FThing: Boolean; procedure SetThing(AThing: Boolean); public property Thing: Boolean read FThing write SetThing; end; Share this post Link to post
Arnaud Bouchez 407 Posted September 7 (edited) This is not yet clean OOP for sure, since it would break the Liskov principle. IMHO there is no way to make it "elegant". My guess is that the "elegant" way is to use composition. That is, compose a "shared object", available to your function, which would have a circle and a rectangle properties, then additional properties. OOP should be as natural as possible. If you are fighting against your types, some refactoring may be needed. Multiple inheritance is IMHO never mandatory, for a clean OOP model. And always refer the the SOLID principles! Edited September 7 by Arnaud Bouchez 1 Share this post Link to post
PeterBelow 239 Posted September 7 2 hours ago, Mike Warren said: I tried it like this, which shows I don't understand interfaces properly: IapShape = interface procedure SetThing(AThing: Boolean); property Thing: Boolean read FThing write SetThing; // Error here: Field or method identifier expected end; TapRectangle = class(TRectangle, IapShape) private FThing: Boolean; procedure SetThing(AThing: Boolean); public property Thing: Boolean read FThing write SetThing; end; Interfaces can only have methods, not fields. Your Thing property needs a GetThing method as read accessor and the implemention can then refer to the FThing field of the class implementing the interface. Interfaces also imply lifetime management by reference counting (that comes from their original purpose in Delphi to work with COM). All classes derived from TInterfacedObject have the necessary infrastructure build in, but beware: classes derived from TComponent inherit an implementation of the relevant methods (_AddRef and _Release) that is not reference-counted, since the lifetime of components is controlled by their Owner. So do never store an interface reference obtained from a component in a field/variable that may outlive the component itself, that is a sure way to produce access violations when the compiler-generated code tries to finalize said reference when the field/variable goes out of scope! 1 Share this post Link to post
darnocian 87 Posted September 7 (edited) @Mike Warren All the feedback above is valid, but will add more comments... Just a comment on your interface where you flagged an error: IapShape = interface procedure SetThing(AThing: Boolean); function GetThing : boolean; // this is what was missing property Thing: Boolean read GetThing write SetThing; end; TapRectangle = class(TRectangle, IapShape) // If you want reference counting, TRectangle should inherit from TInterfacedObject, or else, you need to add the ref counting methods somewhere like TRectangle or here... private FThing: Boolean; function GetThing : boolean; // this can be as trivial as: result := FThing; procedure SetThing(AThing: Boolean); public property Thing: Boolean read FThing write SetThing; // this can actually be different to the interface, but ideally, to be consistent, it should be identical to ensure consistent behaviour end; Hope the comments help. It is possible to have a class implement multiple interfaces, and then in code, you can use methods like supports() to check if an interface supports other interfaces... Anyways, as Arnaud mentioned, composition is generally better, but depending on the scenario, having class/interface hierarchies have their uses.... Having a collection of shapes is a scenario which consist of a rectangles, circles, etc, benefits form the inheritance scenario you described, so if there are generic methods that take place on the shape, it may make sense... e.g. shape.draw, shape.area, shape.position ... but to get to the specific details of a shape, much as with classes, you can get an interface specific to the specialisation (TRectangle, IRectangle...) Anyways, the design topic is vast, and can be very opionated as well 😉 My suggestion in line with Arnaud is something like: Quote IapShape = interface [guid] // add a guid if you want to cast with 'as' keyword, or use 'supports' function which wraps 'queryinterface' end; IapCircle = interface(IapShape) [guid] // add circle specific methods / properties end; IapRectangle = interface(IapShape) [guid] // add rectangle specific methods / properties end; TapShape = class(TInterfacedObject, IapShape) end; TapShape<TShape:class> = class(TapShape) protected FShape: TShape; public constructor Create(const AShape: TShape); // set FShape destructor Destroy; override; // free FShape end; TapCircle = class(TapShape<TCircle>, IapCircle) // add circle specific methods end; TapRectangle = class(TapShape<TRectangle>, IapRectangle) // add rectangle specific methods end in the above, your TRectangle or TCircle would be contained in the FShape field Edited September 7 by darnocian 1 Share this post Link to post
Mike Warren 2 Posted September 8 Thank you all for your replies. I'll experiment with the example darnocian suppled. Share this post Link to post
Dave Novo 51 Posted September 9 The other thing I would suggest is to have a look at the "implements" keyword. Please consider the code below as pseudocode ICommonFuncs=Interface procedure CommonProc; end TCommonObj=class(TInterfacedObject,ICommonFunc) procedure CommonProc end TapRectangle=class(TRectangle, ICommonFunc) private FCommonObj:ICommonFunc function GetCommonFuncImp:TICommonFunc; public constructor Create; property CommonFuncImpl:ICommonFunc read GetCommonFuncImpl implements ICommonFunc; end constructor TapRectangle.Create; begin FCommonObj:=TCommonObj.Create as ICommonFunc; end The idea here is that the functionality needed for ICommonFunc is actually implemented by a separate object. When you query the ICommonFunc interface from TRectangle you get it from FCommobObj. You can then easily add the same functionality to TCircle in the same way. In general, FCommonObj may need some way to talk back to its owning object to get some specific information. This is helpful if the methods FCommonObj/ICommonFunc follow a template patter where most of the code is in TCommonObj but there is some specific information needed from TRectangle or TCircle. Share this post Link to post
Rollo62 539 Posted September 9 Not shure if this is what you need, but are you aware that you can use several interfaces, to define and separate several sub-structures in the same class? Maybe that is interesting for you too: IapShape = interface procedure SetThing(AThing: Boolean); property Thing: Boolean read FThing write SetThing; // Error here: Field or method identifier expected end; IapRectangle = interface ... end; TapRectangle = class(TRectangle, IapShape, IapRectangle) private FThing: Boolean; procedure SetThing(AThing: Boolean); public property Thing: Boolean read FThing write SetThing; end; IapTriangle = interface ... end; TapRectangle = class(TRectangle, IapShape, IapTriangle ) private FThing: Boolean; procedure SetThing(AThing: Boolean); public property Thing: Boolean read FThing write SetThing; end; Share this post Link to post