I have created a couple of interfaces to describe a collection and its items: IetCollection and IetCollectionItem. And of course I have two classes implementing these two interfaces: TetCollection and TetCollectionItem (both inheriting from TInterfacedObject.)
Then I have a series of interfaces where the top level interfaces inherits from IetCollectionItem and the rest from it (lets call them ISomeBasicType and ISomeSpecificType1 and ISomeSpecificType2.)
The class TSomeBasicType inherits from class TetCollectionItem and also implemented ISomeBasicType. The other classes in the hierarchy inherit from TSomeBasicType and implement their respective interfaces (i.e. ISomeSpecificType1 and ISomeSpecificType2.)
When I populate a collection I use a factory method to get a reference to ISomeBasicType. Everything works just fine up to that point.
But when I try to traverse the collection and ask if a collection item supports either ISomeSpecificType1 or ISomeSpecificType2 the answer I get is no.
I have been trying to solve this problem and I have achieved nothing, so any help will be greatly appreciated.
Here is some code:
// This is the basic type
IetCollectionItem = interface
end;
// Implementation of the basic type
TetCollectionItem = class(TInterfacedObject, IetCollectionItem)
end;
ISomeBasicType = interface(IetCollectionItem)
end;
ISomeSpecificType1 = interface(ISomeBasicType)
end;
// Implements ISomeBasicType, should inherit implementation of IetCollectionItem
// from TetCollectionItem
TSomeBasicType = class(TetCollectionItem, ISomeBasicType)
end;
// Implements ISomeSpecificType1, should inherit implementation of ISomeBasicType
// from TSomeBasicType and implementation of IetCollectionItem from
// TetCollectionItem
TSomeSpecificType1 = class(TSomeBasicType, ISomeSpecificType1)
end;
This is the code I user to populate the collection:
var
aBaseType: ISomeBasicType;
aSpecificType: ISomeSpecificType1;
begin
aBaseType:= TheFactory(anID, aType); // Returns a reference to ISomeBasicType
if Supports(aBaseType, ISomeSpecificType1, aSpecificType) then
begin
// Do something to the specific type
aTypeCollection.Add(aSpecificType);
end
else
aTypeCollection.Add(aBaseType);
And here is the code which fails: I loop through the collection and I check to see if any of the items in it support one of the child interfaces.
var
iCount: Integer;
aBaseType: ISomeBasicType;
aSpecificType: ISomeSpecificType1;
begin
for iCount:= 0 to Pred(aTypeCollection.Count) do
begin
aBaseType:= aTypeCollection[iCount];
// This is where Supports fails
if Supports(aBaseType, ISomeSpecificType1, aSpecificType) then
begin
end;
end;
end;
And here is the code for TheFactory:
function TheFactory(const anID: Integer; const aType: TetTypes): ISomeBasicType;
begin
Result:= nil;
case aType of
ptType1 : Result:= TSomeSpecificType1.Create(anID, aType);
ptType2 : Result:= TSomeSpecificType2.Create(anID, aType);
end;
Assert(Assigned(Result), rcUnknonwPhenomenonType);
end; {TheFactory}
Although your code makes me quite dizzy, just from your question title I have a feeling I know where your problem is. Delphi's interface polymorphism unfortunately doesn't behave like Delphi's class polymorphism (I somewhere read that this back in the days had to do with some COM interface compatibility). The point is, that if you are querying a class instance for a specific interface Delphi only finds those interfaces that are directly listed in the class declaration, although another interface in a class declaration might have been inherited from the one you are querying for. See this simple example to understand what I mean.
And sorry, if my answer completly missed your problem.
type
TForm61 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
IBase = interface
['{AE81FB3C-9159-45B0-A863-70FD1365C113}']
end;
IChild = interface(IBase)
['{515771E7-44F6-4819-9B3A-F2A2AFF74543}']
end;
TBase = class(TInterfacedObject, IBase)
end;
TChild = class(TInterfacedObject, IChild)
end;
TChildThatSupportsIbase = class(TChild, IBase)
end;
var
Form61: TForm61;
implementation
{$R *.dfm}
procedure TForm61.Button1Click(Sender: TObject);
var
Child: IChild;
ChildThatSupportsIbase: IChild;
begin
Child := TChild.Create;
ChildThatSupportsIbase:= TChildThatSupportsIbase.Create;
if Supports(Child, IBase) then
ShowMessage('TChild supports IBase')
else
ShowMessage('TChild doesn''t supports IBase');
if Supports(ChildThatSupportsIbase, IBase) then
ShowMessage('TChildThatSupportsIbase supports IBase')
else
ShowMessage('TChildThatSupportsIbase doesn''t supports IBase');
end;
Sample code edited to use your class hierarchy. Both Supports calls return True. I only added GUID's to your interfaces.
If my crystal ball is in working order, you forgot to give your interfaces GUID's.
Here's a proof that what I think you're asking works. If this is not what you're asking, take the hint and replace the code block with a short but complete console application that clearly displays the problem:
program Project29;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
// This is the basic type
IetCollectionItem = interface
end;
// Implementation of the basic type
TetCollectionItem = class(TInterfacedObject, IetCollectionItem)
end;
ISomeBasicType = interface(IetCollectionItem)
['{F082CD83-5030-42EE-A1A8-FF91769F986F}']
end;
ISomeSpecificType1 = interface(ISomeBasicType)
['{8789FD5A-FC94-4F19-B28B-8ABA67D66DAE}']
end;
// Implements ISomeBasicType, should inherit implementation of IetCollectionItem
// from TetCollectionItem
TSomeBasicType = class(TetCollectionItem, ISomeBasicType)
end;
// Implements ISomeSpecificType1, should inherit implementation of ISomeBasicType
// from TSomeBasicType and implementation of IetCollectionItem from
// TetCollectionItem
TSomeSpecificType1 = class(TSomeBasicType, ISomeSpecificType1)
end;
var iBase: IetCollectionItem;
begin
iBase := TSomeSpecificType1.Create;
if Supports(iBase, iSomeBasicType) then
WriteLn('iBase supports iSomeBasicType')
else
WriteLn('iBase does not support iSomeBasicType');
if Supports(iBase, ISomeSpecificType1) then
WriteLn('iBase supports ISomeSpecificType1')
else
WriteLn('iBase does not support ISomeSpecificType1');
WriteLn('Press ENTER'); Readln;
end.
First you place something which clearly does NOT support ISomeSpecificType1 in the list:
if Supports(aBaseType, ISomeSpecificType1, aSpecificType) then
begin
// Do something to the specific type
aTypeCollection.Add(aSpecificType);
end
else
aTypeCollection.Add(aBaseType); //<------- this
Then you wonder why it does not support ISomeSpecificType1.
What's the problem exactly? Why do you think all or even ANY of the items from the collection should support ISomeSpecificType1?
It could have been that every single item you have added did not support it.
Related
I am trying to figure out how to write a generic factory in XE2. Lets say I have this:
type
TObjectTypes = (otLogger, otEmail);
type
TLoggerTypes = (lFile, lConsole, lDatabase);
type
TEmailTypes = (etPOP3, etSMTP);
Classes:
TSMTPEmail = class(TInterfacedObject, IEmail); // Supports emailing only
TPOP3Email = class(TInterfacedObject, IEmail); // Supports emailing only
TFileLogger = class(TInterfacedObject, ILogger); // Supports logging only
etc.
Now I do this to loop thru all TObjectTypes:
procedure TForm1.FormCreate(Sender: TObject);
var
_Interf: IInterface;
_Configuration: TDictionary<string, TValue>;
_ObjectType: TObjectTypes;
begin
_Configuration := nil;
_Configuration := TDictionary<string, TValue>.Create;
try
_Configuration.Add('FileLogFileName', '20160320.Log');
_Configuration.Add('SMTPEmailHost', 'mail.server.lt');
_Configuration.Add('POP3Server', 'some_server');
for _ObjectType := Low(TObjectTypes) to High(TObjectTypes) do
begin
_Interf := TTheFactory.Make(_ObjectType, _Configuration);
if Assigned(_Interf) then
begin
OutputDebugString(PWideChar((_Interf as TObject).ClassName));
if Supports(_Interf, IEmail) then
(_Interf as IEmail).Send('X');
if Supports(_Interf, ILogger) then
(_Interf as ILogger).GetLastErrorMsg;
end;
end;
finally
FreeAndNil(_Configuration);
end;
end;
So, I need a generic factory and be able to loop not thru all TObjectTypes, but thru all TLoggerTypes or thru all TEmailTypes and skip creating some e.g. lDatabase from TLoggerTypes or etPOP3 from TEmailTypes.
Factory should produce all kind of classes.
In Delphi making factories is pretty simple, thanks to metaclasses (class references), simple example of which is TClass:
TClass = class of TObject
In most cases, you should define your own abstract class for all factory members and metaclass for it:
TMyFactoryObject = class (TObject)
public
constructor FactoryCreate(aConfiguration: TConfiguration); virtual; abstract;
end;
TMyFactoryClass = class of TMyFactoryObject;
In this abstract class you can add some methods common for all descendants, in my example we have constructor which takes configuration as argument. How to react to it will be determined in descendants.
Then you declare descendant classes:
TMyLogger = class (TMyFactoryObject, ILogger)
private
...
public
constructor FactoryCreate(aConfiguration: TConfiguration); override;
... //implementation of ILogger interface etc
end;
TMyEmail = class (TMyFactoryObject, IEmail)
private
...
public
constructor FactoryCreate(aConfiguration: TConfiguration); override;
... //implementation of IEmail interface etc
end;
now you declare array of possible descendant classes:
var
MyFactory: array [otLogger..otEmail] of TMyFactoryClass;
and in initialization section or in other places you populate this array:
MyFactory[otLogger]:=TMyLogger;
MyFactory[orEmail]:=TMyEmail;
At last, TTheFactory.Make(_ObjectType, _Configuration); from your question can be replaced with:
MyFactory[_ObjectType].FactoryCreate(_Configuration);
and you'll get needed object as instance of type MyFactoryObject.
See http://docwiki.embarcadero.com/RADStudio/Seattle/en/Class_References for more information.
I'm doing a full rewrite of an old library, and I'm not sure how to handle this situation (for the sake of being understood, all hail the bike analogy):
I have the following classes:
TBike - the bike itself
TBikeWheel - one of the bike's wheel
TBikeWheelFront and TBikeWheelBack, both inherits from TBikeWheel and then implements the specific stuff they need on top of it
This is pretty straightforward, but now I decide to create multiple kind of bikes, each bikes having it's own kinds of wheel - they do the same stuff as a regular front/back wheels, plus the specific for that bike.
TBikeXYZ - inherits from TBike
TBikeWheelXYZ - inherits from TBikeWheel
And here is my problem: TBikeWheelFrontXYZ should inherit from TBikeWheelXYZ (to get the specific methods of an XYZ wheel), but it should also inherit from TBikeWheelFront (to get the specific methods of a front wheel).
My question here is, how can I implement that in a way that doesn't:
feel like a hack
force me to rewrite the same code several time
Delphi does not support Multiple Inheritance. But classes can support / implement multiple interfaces and you can delegate interface implementation, so you can kinda simulate multiple inheritence.
Use interfaces. Something like this (Off the top of my head, based on your description.....)
type
IBikeWheel = interface
...
end;
IXYZ = interface
...
end;
IFrontWheel = interface(IBikeWheel)
...
end;
TBike = class
...
end;
TBikeWheel = class(TObject, IBikeWheel);
TBikeWheelXYZ = class(TBikeWheel, IXYZ);
TBikeFrontWheelXYZ = class(TBikeWheelXYZ, IFrontWheel);
Then implement classes for the interfaces that do what the corresponding classes in your old (presumably C/C++) library does and instantiate them in the corresponding class's constructor.
Use polymorhism to implment each 'thing' as an object hierarchy in its own right and then add object properties to that object in turn. So, create a hierarchy of wheels, and a hierarchy of bikes. Then add wheels to bikes as fields in the ancestor bike object. See below.
TBikeWheel = class
TBikeWheelXYZ = class( TBikeWheel )
TBike = class
FFrontWheel : TBikeWheel;
property FrontWheel : TBikeWheel
read FrontWhell
TBikeABC = class( TBike)
constructor Create;
end;
constructor TBikeABC.Create;
begin
inherited;
FFrontWheel := TBikeWheel.Create;
end;
TBikeXYZ = class( TBike)
constructor Create;
end;
constructor TBikeXYZ.Create;
begin
inherited;
FFrontWheel := TBikeWheelXYZ.Create;
end;
A variation of Brian Frost's suggestion:
TBikeWheel = class
TBikeWheelXYZ = class( TBikeWheel )
TBike = class
FFrontWheel : TBikeWheel;
protected
function CreateWheel: TBikeWheel; virtual;
public
property FrontWheel : TBikeWheel
read FrontWheel
end;
TBikeABC = class( TBike)
protected
function CreateWheel: TBikeWheel; override;
end;
function TBikeABC.CreateWheel: TBikeWheel;
begin
result := TBikeWheel.Create;
end;
TBikeXYZ = class( TBike)
protected
function CreateWheel: TBikeWheel; override;
end;
function TBikeXYZ.CreateWheel: TBikeWheel;
begin
result := TBikeWheelXYZ.Create;
end;
Basically - you CAN'T. Delphi does not support multiple inheritance.
So left with that dilemma, the question is: could you possibly refactor that library in such a way that you can get away with using interface? Is the multiple inheritance mostly about functions and methods? If so - use interfaces. Delphi can support multiple interfaces on a class.
If the multi-inheritance is more about inheriting actual functionality in the classes, then you're probably looking at a bigger scale refactoring, I'm afraid. You'll need to find a way to break up those functional dependencies in such a way you can make it inherit from a single base class, possibly with some additional interfaces thrown in.
Sorry I can't provide an easy answer - that's just the reality of it.
Marc
You can try to extract an interface, say IFrontWheel, out of TBikeWheelFront, so that it is a subclass of TBikeWheel but implements IFrontWheel. Then TBikeWheelXYZ inherits from TBikeWheel and TBikeWheelFrontXYZ inherits from TBikeWheelXYZ and implements IFrontWheel.
Then you can define a class TFrontwheel and give it the same methods as the interface, but now you implement them. Then TBikeWheelFront and TBikeWheelXYZ get a private member of type TFrontwheel and the IFrontWheel implementations of them simply delegate to the private member methods.
This way you don't have double implementations.
Another alternative with newer versions of Delphi is to leverage generics in a compositional model. This is particularly useful in the case where the multiple base classes (TBarA and TBarB in this example) are not accessible for modification (ie: framework or library classes). For example (note, the necessary destructor in TFoo<T> is omitted here for brevity) :
program Project1;
uses SysUtils;
{$APPTYPE CONSOLE}
type
TFooAncestor = class
procedure HiThere; virtual; abstract;
end;
TBarA = class(TFooAncestor)
procedure HiThere; override;
end;
TBarB = class(TFooAncestor)
procedure HiThere; override;
end;
TFoo<T: TFooAncestor, constructor> = class
private
FFooAncestor: T;
public
constructor Create;
property SomeBar : T read FFooAncestor write FFooAncestor;
end;
procedure TBarA.HiThere;
begin
WriteLn('Hi from A');
end;
procedure TBarB.HiThere;
begin
WriteLn('Hi from B');
end;
constructor TFoo<T>.Create;
begin
inherited;
FFooAncestor := T.Create;
end;
var
FooA : TFoo<TBarA>;
FooB : TFoo<TBarB>;
begin
FooA := TFoo<TBarA>.Create;
FooB := TFoo<TBarB>.Create;
FooA.SomeBar.HiThere;
FooB.SomeBar.HiThere;
ReadLn;
end.
you can try this way, if you do not want to repeat the code several times and want a decoupled code.
type
TForm1 = class(TForm)
btnTest: TButton;
procedure btnTestClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TBike = class
end;
IBikeWheel = interface
procedure DoBikeWheel;
end;
TBikeWheel = class(TInterfacedObject, IBikeWheel)
public
procedure DoBikeWheel;
end;
IBikeWheelFront = interface
procedure DoBikeWheelFront;
end;
TBikeWheelFront = class(TInterfacedObject, IBikeWheelFront)
public
procedure DoBikeWheelFront;
end;
IBikeWheelBack = interface
end;
TBikeWheelBack = class(TInterfacedObject, IBikeWheelBack)
end;
TBikeWheelFrontXYZ = class(TInterfacedObject, IBikeWheel, IBikeWheelFront)
private
FIBikeWheel: IBikeWheel;
FBikeWheelFront: IBikeWheelFront;
public
constructor Create();
property BikeWheel: IBikeWheel read FIBikeWheel implements IBikeWheel;
property BikeWheelFront: IBikeWheelFront read FBikeWheelFront implements IBikeWheelFront;
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
{ TBikeWheel }
procedure TBikeWheel.DoBikeWheel;
begin
ShowMessage('TBikeWheel.DoBikeWheel');
end;
{ TBikeWheelFrontXYZ }
constructor TBikeWheelFrontXYZ.Create;
begin
inherited Create;
Self.FIBikeWheel := TBikeWheel.Create;
Self.FBikeWheelFront := TBikeWheelFront.Create;
end;
{ TBikeWheelFront }
procedure TBikeWheelFront.DoBikeWheelFront;
begin
ShowMessage('TBikeWheelFront.DoBikeWheelFront');
end;
procedure TForm1.btnTestClick(Sender: TObject);
var
bikeWhell: TBikeWheelFrontXYZ;
begin
bikeWhell := nil;
try
try
bikeWhell := TBikeWheelFrontXYZ.Create;
IBikeWheelFront(bikeWhell).DoBikeWheelFront;
IBikeWheel(bikeWhell).DoBikeWheel;
except
on E: Exception do
begin
raise;
end;
end;
finally
if Assigned(bikeWhell) then FreeAndNil(bikeWhell);
end;
end;
Sorry, Delphi does not support Multiple Inheritance.
I would like to suggest the following steps:
Inherit the TBikeWheelFrontXYZ class from either TBikeWheelXYZ or TBikeWheelFront (since in Delphi multiple inheritance is impossible as mentioned in the answers above).
Convert one of the parent classes TBikeWheelXYZ or TBikeWheelFront to class helper for the TBikeWheel class.
Add the class helper unit to the unit, where the TBikeWheelFrontXYZ class is declared.
This question already has answers here:
Two classes with two circular references
(2 answers)
Closed 9 years ago.
I would like to pass "self" as parameter to a method of another class (in a different unit). However the type of the first class is unknown in the second one, because I can't put the first unit into the uses section of the second unit. So I define the parameters type as pointer but when I try to call a method from the first class the Delphi 7 parser tells me that the classtyp is required.
So how should I solve this problem?
By making the class known in the implementaion part you can cast the given reference.
unit UnitY;
interface
uses Classes;
type
TTest=Class
Constructor Create(AUnKnowOne:TObject);
End;
implementation
uses UnitX;
{ TTest }
constructor TTest.Create(AUnKnowOne: TObject);
begin
if AUnKnowOne is TClassFromUnitX then
begin
TClassFromUnitX(AUnKnowOne).DoSomeThing;
end
else
begin
// ....
end;
end;
end.
I like the interface approach for this type of problem. Unless your units are very tightly coupled, in which case they should probably share a unit, interfaces are tidy ways of exchanging relevant parts of classes without having to have full knowledge of each type.
Consider :
unit UnitI;
interface
type
IDoSomething = Interface(IInterface)
function GetIsFoo : Boolean;
property isFoo : Boolean read GetIsFoo;
end;
implementation
end.
and
unit UnitA;
interface
uses UnitI;
type
TClassA = class(TInterfacedObject, IDoSomething)
private
Ffoo : boolean;
function GetIsFoo() : boolean;
public
property isFoo : boolean read GetIsFoo;
procedure DoBar;
constructor Create;
end;
implementation
uses UnitB;
constructor TClassA.Create;
begin
Ffoo := true;
end;
function TClassA.GetIsFoo() : boolean;
begin
result := Ffoo;
end;
procedure TClassA.DoBar;
var SomeClassB : TClassB;
begin
SomeClassB := TClassB.Create;
SomeClassB.DoIfFoo(self);
end;
end.
and notice that TClassB does not have to know anything about TClassA or the unit that contains it - it simply accepts any object that abides by the IDoSomething interface contract.
unit UnitB;
interface
uses UnitI;
type
TClassB = class(TObject)
private
Ffoobar : integer;
public
procedure DoIfFoo(bar : IDoSomething);
constructor Create;
end;
implementation
constructor TClassB.Create;
begin
Ffoobar := 3;
end;
procedure TClassB.DoIfFoo(bar : IDoSomething);
begin
if bar.isFoo then Ffoobar := 777;
end;
end.
I'm making a framework (for internal use only) that has common code among 3 o 4 delphi database CRUD applications..
A common object of mi framework is a TContext,
TContext = class (IContext)
function DB: IDatabase;
function CurrentSettings: ISettings;
..
end;
that is passed to the initialization method of lots of other objects.. example (this will be application code):
TCustomer.Initialize(Context: IContext)
TProjectList.Initialize(Context: IContext)
..
Every application has some specific context functions (that only will be called from application code):
IApp1Context = interface (IContext)
procedure DoSomethingSpecificToApp1;
procedure DoOtherThing;
..
end;
So when I create a Context, Im creating a IApp1Context, and sending it to the initialization methods.. from the framework code everything is fine, the problem is that from the application code I'm constantly casting from IContext to IApp1Context to access the specific
App1 functions.. so all my application code looks like (and its a lot of code like this):
(FContext as IApp1Context).DoSomethingSpecificToApp1
(FContext as IApp1Context).DoOtherThing;
..
The thing is clearly usable, but doesnt reads well in my opinion. Maybe I'm exaggerating; is there is a clever design technique for this situation that I'm not aware of?
Use a temporary variable. Assign it once at the start of the method, and then use it where you need it.
var
AppContext: IApp1Context;
begin
AppContext := FContext as IApp1Context;
AppContext.DoSomethingSpecificToApp1;
AppContext.DoOtherThing;
end;
Or, since it looks like your IContext object is a field of an object, make your IApp1Context variable be a field of the same object. It could even replace the IContext field since IApp1Context already exposes everything the IContext does.
procedure TCustomer.Initialize(const Context: IContext);
begin
FContext := Context;
FAppContext := FContext as IApp1Context;
// ...
end;
What do you think of this possible solution using generics?
pro: no casting necesary
down: the generic invades almost every interface and class of the framework, making it more complicated..
// framework //
type
IContext = interface
function DB;
..
end;
TContext = class (TInterfaedObject, IContext)
function DB;
..
end;
IBusinessObj<T: IContext> = interface
procedure Initialize(AContext: T);
end;
TBusinessObj<T: IContext> = class (TInterfacedObject, IBusinessObj<T>)
protected
FContext: T;
public
procedure Initialize(Context: T); virtual;
end;
procedure TBusnessObj<T>.Initialize(Context: T);
begin
FContext := Context;
FContext.DB.Connect;
end;
// application //
type
IApp1Context = interface (IContext)
procedure DoSomethingElse;
..
end;
TApp1Context = class(TContext, IContext, IApp1Context)
function DB;
procedure DoSomethingElse;
end;
TCustomer = class(TBusinessObj<IApp1Context>)
public
procedure Initialize(AContext: IApp1Context); override;
end;
procedure Start;
var
C: IBusinessObj<IApp1Context>;
begin
C := TCustomer.Create;
C.Initializate(TApp1Context.Create);
..
end;
procedure TCustomer.Initialize(AContext: IApp1Context);
begin
inherited;
FContext.DoSomethingElse // here I can use FContext as a IApp1Context..
end;
Comment please, Thanks!
You could also give your class a private function AppContext defined like this:
function AppContext : IApp1Context;
begin
Result := FContext as IApp1Context;
end;
This avoids the additional variable declaration and keeps the cast local. From client code you can just write:
AppContext.DoSomethingSpecificToApp1;
AppContext.DoOtherThing
I'm doing a full rewrite of an old library, and I'm not sure how to handle this situation (for the sake of being understood, all hail the bike analogy):
I have the following classes:
TBike - the bike itself
TBikeWheel - one of the bike's wheel
TBikeWheelFront and TBikeWheelBack, both inherits from TBikeWheel and then implements the specific stuff they need on top of it
This is pretty straightforward, but now I decide to create multiple kind of bikes, each bikes having it's own kinds of wheel - they do the same stuff as a regular front/back wheels, plus the specific for that bike.
TBikeXYZ - inherits from TBike
TBikeWheelXYZ - inherits from TBikeWheel
And here is my problem: TBikeWheelFrontXYZ should inherit from TBikeWheelXYZ (to get the specific methods of an XYZ wheel), but it should also inherit from TBikeWheelFront (to get the specific methods of a front wheel).
My question here is, how can I implement that in a way that doesn't:
feel like a hack
force me to rewrite the same code several time
Delphi does not support Multiple Inheritance. But classes can support / implement multiple interfaces and you can delegate interface implementation, so you can kinda simulate multiple inheritence.
Use interfaces. Something like this (Off the top of my head, based on your description.....)
type
IBikeWheel = interface
...
end;
IXYZ = interface
...
end;
IFrontWheel = interface(IBikeWheel)
...
end;
TBike = class
...
end;
TBikeWheel = class(TObject, IBikeWheel);
TBikeWheelXYZ = class(TBikeWheel, IXYZ);
TBikeFrontWheelXYZ = class(TBikeWheelXYZ, IFrontWheel);
Then implement classes for the interfaces that do what the corresponding classes in your old (presumably C/C++) library does and instantiate them in the corresponding class's constructor.
Use polymorhism to implment each 'thing' as an object hierarchy in its own right and then add object properties to that object in turn. So, create a hierarchy of wheels, and a hierarchy of bikes. Then add wheels to bikes as fields in the ancestor bike object. See below.
TBikeWheel = class
TBikeWheelXYZ = class( TBikeWheel )
TBike = class
FFrontWheel : TBikeWheel;
property FrontWheel : TBikeWheel
read FrontWhell
TBikeABC = class( TBike)
constructor Create;
end;
constructor TBikeABC.Create;
begin
inherited;
FFrontWheel := TBikeWheel.Create;
end;
TBikeXYZ = class( TBike)
constructor Create;
end;
constructor TBikeXYZ.Create;
begin
inherited;
FFrontWheel := TBikeWheelXYZ.Create;
end;
A variation of Brian Frost's suggestion:
TBikeWheel = class
TBikeWheelXYZ = class( TBikeWheel )
TBike = class
FFrontWheel : TBikeWheel;
protected
function CreateWheel: TBikeWheel; virtual;
public
property FrontWheel : TBikeWheel
read FrontWheel
end;
TBikeABC = class( TBike)
protected
function CreateWheel: TBikeWheel; override;
end;
function TBikeABC.CreateWheel: TBikeWheel;
begin
result := TBikeWheel.Create;
end;
TBikeXYZ = class( TBike)
protected
function CreateWheel: TBikeWheel; override;
end;
function TBikeXYZ.CreateWheel: TBikeWheel;
begin
result := TBikeWheelXYZ.Create;
end;
Basically - you CAN'T. Delphi does not support multiple inheritance.
So left with that dilemma, the question is: could you possibly refactor that library in such a way that you can get away with using interface? Is the multiple inheritance mostly about functions and methods? If so - use interfaces. Delphi can support multiple interfaces on a class.
If the multi-inheritance is more about inheriting actual functionality in the classes, then you're probably looking at a bigger scale refactoring, I'm afraid. You'll need to find a way to break up those functional dependencies in such a way you can make it inherit from a single base class, possibly with some additional interfaces thrown in.
Sorry I can't provide an easy answer - that's just the reality of it.
Marc
You can try to extract an interface, say IFrontWheel, out of TBikeWheelFront, so that it is a subclass of TBikeWheel but implements IFrontWheel. Then TBikeWheelXYZ inherits from TBikeWheel and TBikeWheelFrontXYZ inherits from TBikeWheelXYZ and implements IFrontWheel.
Then you can define a class TFrontwheel and give it the same methods as the interface, but now you implement them. Then TBikeWheelFront and TBikeWheelXYZ get a private member of type TFrontwheel and the IFrontWheel implementations of them simply delegate to the private member methods.
This way you don't have double implementations.
Another alternative with newer versions of Delphi is to leverage generics in a compositional model. This is particularly useful in the case where the multiple base classes (TBarA and TBarB in this example) are not accessible for modification (ie: framework or library classes). For example (note, the necessary destructor in TFoo<T> is omitted here for brevity) :
program Project1;
uses SysUtils;
{$APPTYPE CONSOLE}
type
TFooAncestor = class
procedure HiThere; virtual; abstract;
end;
TBarA = class(TFooAncestor)
procedure HiThere; override;
end;
TBarB = class(TFooAncestor)
procedure HiThere; override;
end;
TFoo<T: TFooAncestor, constructor> = class
private
FFooAncestor: T;
public
constructor Create;
property SomeBar : T read FFooAncestor write FFooAncestor;
end;
procedure TBarA.HiThere;
begin
WriteLn('Hi from A');
end;
procedure TBarB.HiThere;
begin
WriteLn('Hi from B');
end;
constructor TFoo<T>.Create;
begin
inherited;
FFooAncestor := T.Create;
end;
var
FooA : TFoo<TBarA>;
FooB : TFoo<TBarB>;
begin
FooA := TFoo<TBarA>.Create;
FooB := TFoo<TBarB>.Create;
FooA.SomeBar.HiThere;
FooB.SomeBar.HiThere;
ReadLn;
end.
you can try this way, if you do not want to repeat the code several times and want a decoupled code.
type
TForm1 = class(TForm)
btnTest: TButton;
procedure btnTestClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TBike = class
end;
IBikeWheel = interface
procedure DoBikeWheel;
end;
TBikeWheel = class(TInterfacedObject, IBikeWheel)
public
procedure DoBikeWheel;
end;
IBikeWheelFront = interface
procedure DoBikeWheelFront;
end;
TBikeWheelFront = class(TInterfacedObject, IBikeWheelFront)
public
procedure DoBikeWheelFront;
end;
IBikeWheelBack = interface
end;
TBikeWheelBack = class(TInterfacedObject, IBikeWheelBack)
end;
TBikeWheelFrontXYZ = class(TInterfacedObject, IBikeWheel, IBikeWheelFront)
private
FIBikeWheel: IBikeWheel;
FBikeWheelFront: IBikeWheelFront;
public
constructor Create();
property BikeWheel: IBikeWheel read FIBikeWheel implements IBikeWheel;
property BikeWheelFront: IBikeWheelFront read FBikeWheelFront implements IBikeWheelFront;
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
{ TBikeWheel }
procedure TBikeWheel.DoBikeWheel;
begin
ShowMessage('TBikeWheel.DoBikeWheel');
end;
{ TBikeWheelFrontXYZ }
constructor TBikeWheelFrontXYZ.Create;
begin
inherited Create;
Self.FIBikeWheel := TBikeWheel.Create;
Self.FBikeWheelFront := TBikeWheelFront.Create;
end;
{ TBikeWheelFront }
procedure TBikeWheelFront.DoBikeWheelFront;
begin
ShowMessage('TBikeWheelFront.DoBikeWheelFront');
end;
procedure TForm1.btnTestClick(Sender: TObject);
var
bikeWhell: TBikeWheelFrontXYZ;
begin
bikeWhell := nil;
try
try
bikeWhell := TBikeWheelFrontXYZ.Create;
IBikeWheelFront(bikeWhell).DoBikeWheelFront;
IBikeWheel(bikeWhell).DoBikeWheel;
except
on E: Exception do
begin
raise;
end;
end;
finally
if Assigned(bikeWhell) then FreeAndNil(bikeWhell);
end;
end;
Sorry, Delphi does not support Multiple Inheritance.
I would like to suggest the following steps:
Inherit the TBikeWheelFrontXYZ class from either TBikeWheelXYZ or TBikeWheelFront (since in Delphi multiple inheritance is impossible as mentioned in the answers above).
Convert one of the parent classes TBikeWheelXYZ or TBikeWheelFront to class helper for the TBikeWheel class.
Add the class helper unit to the unit, where the TBikeWheelFrontXYZ class is declared.