Jump to content
Mike Warren

Simulate Multiple Inheritance

Recommended Posts

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

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

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 by Arnaud Bouchez
  • Thanks 1

Share this post


Link to post
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!

 

  • Thanks 1

Share this post


Link to post

@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 by darnocian
  • Thanks 1

Share this post


Link to post

Thank you all for your replies. I'll experiment with the example darnocian suppled.

 

Share this post


Link to post

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

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

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

×