This page would be a little more critical, but i feel that it can describe my residual misunderstanding of interfaces ...
Programming with interfaces is a not hard task: syntax is very clean, pointers are "smart", there are many base classes to inherit from and runtime type support is cool.
What i find harder is "design"... (the place where interfaces help more).
A problem here is that there are many alternative "styles" to choose:
Trying to model interfaces, i prefer avoid the "interface as contract" definition, that i find more appropriate in other environments like UML.
To me, an interface is a "view" on a "dark ball", that contains an hidden set of objects (again, aggregation ...)

But these "views" aren't "flat" but they support a dimension of (single) inheritance ...

where the "holes" (of different sizes) represent the contexts in which interfaces can be used ...
but this picture looks more correct,
after all, interfaces are single rooted ...

A strange thing is happening here
you cannot give the ball a name!
Aggregates are anonymous, and you cannot declare an interface type that "glues" together its parts.
but then, why stop at the ball ? also holes (in the last picture) could be overlapped ...
I'm not able to draw it, so image ...
If the ball has a name, there must be a hole all around ... so we can peel the ball a throw away its frontier, then some hole can be overlapped in an union one (a triangle plus a square gives a seven-pointed star) with some bifurcation inside the ball ...
uh, I think I've confused "names" with "shapes" ...
Delphi (and COM) remains the only language in the club (with C++, Java, C#, Corba) that does not support multiple interface inheritance, and a question raises spontaneously at this point:
Is multiple interface inheritance (MII) a "sound" type system ?
I really don't know...
Surely, you may run
into difficulties when there is some name clashing among methods ...
there are many "solutions" here, but how much satisfactory ?
(multiple) inheritance and method names (semantics) do not always mix well ...
//---------------------------------------------------------------------------
template < class Super >
class TOddPlayer : public Super
{
protected:
virtual void BeforePlay() {};
virtual void AfterPlay() {};
public:
void Play()
{
BeforePlay();
for (int i = 0; i < 4; i++)
Super::Play(); // required!
AfterPlay();
};
};
template < class Super >
class TOddTimer : public Super
{
private:
int Timer;
protected:
virtual void OnStart(int Value) {};
virtual void OnTick(int Value) {};
virtual void OnStop(int Value) {};
public:
void StartTimer(int Value)
{
Timer = Value;
OnStart(Timer);
};
void StopTimer()
{
OnStop(Timer);
Timer = 0;
};
void TickTimer()
{
Timer++;
OnTick(Timer);
}
};
//---------------------------------------------------------------------------
class TViolinist
{
protected:
virtual void Play()
{
FingerMove();
};
virtual void FingerMove()
{
cout << "Play IX Simphony ..." << endl;
};
};
class TChessMaster
{
protected:
virtual void Play()
{
ChessMove();
};
virtual void ChessMove()
{
char c = random(8) + 'A';
int r = random(8) + 1;
cout << "Queen in " << c << r << ", Check!" << endl;
};
};
//---------------------------------------------------------------------------
class TOddViolinist : public TOddTimer<TOddPlayer<TViolinist> >
{
protected:
virtual void BeforePlay()
{
StartTimer(80); //allegro?
};
virtual void AfterPlay()
{
StopTimer();
};
virtual void OnStart(int Value)
{
cout << "set metronome to " << Value
<< " tick per minute" << endl;
};
virtual void OnTick(int Value)
{
cout << "Tock" << endl;
};
virtual void OnStop(int Value)
{
cout << "stop metronome" << endl;
};
virtual void FingerMove()
{
TViolinist::FingerMove();
TickTimer();
};
};
//---------------------------------------------------------------------------
class TOddChessMaster : public TOddTimer<TOddPlayer<TChessMaster> >
{
private:
int ResidualTime;
protected:
virtual void BeforePlay()
{
randomize();
StartTimer(-ResidualTime);
};
virtual void AfterPlay()
{
StopTimer();
};
virtual void OnStart(int Value)
{
cout << "start chronometer, resudual time:"
<< ResidualTime << " sec"<< endl;
};
virtual void OnTick(int Value)
{
cout << ( Value % 2 ? "Tick" : "Tack" ) << endl;
};
virtual void OnStop(int Value)
{
ResidualTime = -Value;
cout << "stop chronometer, resudual time:"
<< ResidualTime << " sec"<< endl;
};
virtual void ChessMove()
{
TChessMaster::ChessMove();
TickTimer();
};
public:
TOddChessMaster() : ResidualTime(100) {};
};
//---------------------------------------------------------------------------
...
//---------------------------------------------------------------------------
void Test()
{
cout << " TOddViolinist " << endl;
TOddViolinist v;
v.Play();
cout << "TOddChessMaster" << endl;
TOddChessMaster c;
c.Play();
cout << "Press a key..." << endl;
getch();
}
//---------------------------------------------------------------------------
#pragma argsused
int main(int argc, char* argv[])
{
Test();
return 0;
}
//---------------------------------------------------------------------------
and its output
TOddViolinist set metronome to 80 tick per minute Play IX Simphony ... Tock Play IX Simphony ... Tock Play IX Simphony ... Tock Play IX Simphony ... Tock stop metronome TOddChessMaster start chronometer, resudual time:100 sec Queen in H7, Check! Tick Queen in C6, Check! Tack Queen in G5, Check! Tick Queen in B7, Check! Tack stop chronometer, resudual time:96 sec
Delphi approach to multiple inheritance is simple: "prohibit and rewrite
as SII"
This is different from a "no decision" rule, and it presents some advantages:
as a rewrite sample ...
type
IInterfaceBase = interface(IUnknown)
['{1BF42676-382B-11D5-B563-00AA00ACFD08}']
procedure BaseMethod;
end;
IInterfaceA1 = interface(IInterfaceBase)
['{1BF42671-382B-11D5-B563-00AA00ACFD08}']
procedure AMethod;
end;
IInterfaceA2 = interface(IInterfaceBase)
['{1BF42672-382B-11D5-B563-00AA00ACFD08}']
procedure AMethod;
end;
IInterfaceA3 = interface(IInterfaceBase)
['{14166251-51DE-11D5-B57C-00AA00ACFD08}']
procedure AMethod;
end;
IInterfaceSingle = interface(IUnknown)
['{1BF42675-382B-11D5-B563-00AA00ACFD08}']
function AsInterfaceA1: IInterfaceA1;
function AsInterfaceA2: IInterfaceA2;
function AsInterfaceA3: IInterfaceA3;
property InterfaceA1: IInterfaceA1 read AsInterfaceA1;
property InterfaceA2: IInterfaceA2 read AsInterfaceA2;
property InterfaceA3: IInterfaceA3 read AsInterfaceA3;
end;
{
// Illegal...
IInterfaceSingle = interface(IInterfaceA1,IInterfaceA2,IInterfaceA3)
end;
}
with a possible implementing class ...
type
TMySingle = class(TInterfacedObject,
IInterfaceSingle,
IInterfaceA1,IInterfaceA2,IInterfaceA3, //repeated inheritance
IUnknown)
protected
{TMySingle.IUnknown}
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
protected
procedure IInterfaceA1.BaseMethod = AlterBaseMethod;
procedure IInterfaceA1.AMethod = A1Method;
procedure IInterfaceA2.AMethod = A2Method;
procedure AlterBaseMethod;
procedure BaseMethod;
procedure AMethod;
procedure A1Method;
procedure A2Method;
function AsInterfaceA1: IInterfaceA1;
function AsInterfaceA2: IInterfaceA2;
function AsInterfaceA3: IInterfaceA3;
public
destructor Destroy; override;
end;
implementation of these methods is based on repeated interface inheritance in class declaration
function TMySingle.AsInterfaceA1: IInterfaceA1; begin result := Self as IInterfaceA1; end; function TMySingle.AsInterfaceA2: IInterfaceA2; begin result := Self as IInterfaceA2; end; function TMySingle.AsInterfaceA3: IInterfaceA3; begin result := Self as IInterfaceA3; end;
Visibroker for Delphi takes a different approach (keeping first line of inheritance and including remaining methods)...
type
IInterfaceBase = interface(IUnknown)
['{1BF42676-382B-11D5-B563-00AA00ACFD08}']
procedure BaseMethod;
end;
IInterfaceA1 = interface(IInterfaceBase)
['{1BF42671-382B-11D5-B563-00AA00ACFD08}']
procedure AMethod;
end;
IInterfaceA2 = interface(IInterfaceBase)
['{1BF42672-382B-11D5-B563-00AA00ACFD08}']
procedure AMethod;
end;
IInterfaceA3 = interface(IInterfaceBase)
['{14166251-51DE-11D5-B57C-00AA00ACFD08}']
procedure AMethod;
end;
IInterfaceSingle = interface(IInterfaceA1)
['{1BF42675-382B-11D5-B563-00AA00ACFD08}']
procedure IInterfaceA2BaseMethod;
procedure IInterfaceA2AMethod;
procedure IInterfaceA3BaseMethod;
procedure IInterfaceA3AMethod;
end;
another topic is "interface reflection" compared to object reflection
let's consider this code, adapted from Jim Coplien book
unit SignCoplien;
interface
uses
Math,SysUtils;
type
TNumber = class(TObject)
protected
function Promote(ANumber: TNumber): TNumber; virtual;
function OpPlus(ANumber: TNumber): TNumber; virtual;
public
procedure Dump; virtual;
function IsA(ANumber: TNumber): Boolean;
function Plus(ANumber: TNumber): TNumber;
function SquareRoot: TNumber; virtual; abstract;
end;
TComplex = class(TNumber)
private
FRe: double;
FIm: double;
function GetArg: double;
function GetModule: double;
protected
function OpPlus(ANumber: TNumber): TNumber; override;
function Promote(ANumber: TNumber): TNumber; override;
function GetIm: double; virtual;
public
procedure Dump; override;
property Re : double read FRe write FRe;
property Im : double read FIm write FIm;
property Module : double read GetModule;
property Arg : double read GetArg;
constructor Create; overload; virtual;
constructor Create(ARe,AIm: double); overload; virtual;
constructor CreateTrig(AMod,AArg: double); overload; virtual;
function SquareRoot: TNumber; override;
end;
TReal = class(TComplex)
protected
function GetIm: double; override;
function OpPlus(ANumber: TNumber): TNumber; override;
function Promote(ANumber: TNumber): TNumber; override;
public
procedure Dump; override;
constructor Create(ARe: double); overload;
constructor Create(ARe,AIm: double); overload; override;
property Im : double read GetIm;
function SquareRoot: TNumber; override;
end;
procedure Test;
implementation
uses
SignUti;
{ TNumber }
procedure TNumber.Dump;
begin
WriteLn('____________________________');
WriteLn('Class:',ClassName);
end;
function TNumber.IsA(ANumber: TNumber): Boolean;
begin
result := InheritsFrom(ANumber.ClassType);
end;
function TNumber.OpPlus(ANumber: TNumber): TNumber;
begin
raise Exception.Create('Abstract ...');
end;
function TNumber.Plus(ANumber: TNumber): TNumber;
var
TempNumber: TNumber;
begin
if Self.IsA(ANumber) then
begin
TempNumber := ANumber.Promote(Self);
result := ANumber.OpPlus(TempNumber);
end
else
if ANumber.IsA(Self) then
begin
TempNumber := Self.Promote(ANumber);
result := Self.OpPlus(TempNumber);
end
else
raise Exception.Create('Incompatibile ...');
end;
function TNumber.Promote(ANumber: TNumber): TNumber;
begin
raise Exception.Create('Abstract ...');
end;
{ TComplex }
procedure TComplex.Dump;
begin
inherited;
WriteLn('Re:',FRe);
WriteLn('Im:',FIm);
end;
function TComplex.OpPlus(ANumber: TNumber): TNumber;
var
Temp: TComplex;
begin
Temp := ANumber as TComplex;
Temp.FIm := Temp.FIm + Self.FIm;
Temp.FRe := Temp.FRe + Self.FRe;
result := Temp;
end;
function TComplex.Promote(ANumber: TNumber): TNumber;
var
Temp: TComplex;
begin
if (ANumber is TComplex) then
begin
Temp := TComplex.Create(
TComplex(ANumber).Re,TComplex(ANumber).Im);
end
else
if (ANumber is TReal) then
begin
Temp := TComplex.Create(TReal(ANumber).Re,0.0);
end
else
raise Exception.Create('UnPromotable ...');
result := Temp;
end;
constructor TComplex.Create;
begin
inherited Create;
end;
constructor TComplex.Create(ARe,AIm: double);
begin
inherited Create;
FRe := ARe;
FIm := AIm;
end;
function TComplex.GetIm: double;
begin
result := FIm;
end;
function TComplex.SquareRoot: TNumber;
begin
WriteLn('____________________________');
WriteLn('==> Complex Sqrt ...');
result := TComplex.CreateTrig(Sqrt(Module),Arg/2);
end;
function TComplex.GetArg: double;
begin
if Abs(FRe) <= 0.0 then
if FIm > 0.0 then
result := Pi
else
if FIm < 0.0 then
result := -Pi
else
result := 0.0
else
result := ArcTan2(FIm,FRe);
end;
function TComplex.GetModule: double;
begin
result := Sqrt(Sqr(FRe)+Sqr(FRe));
end;
constructor TComplex.CreateTrig(AMod, AArg: double);
begin
inherited Create;
FRe := AMod * Cos(AArg);
FIm := AMod * Sin(AArg);
end;
{ TReal }
procedure TReal.Dump;
begin
inherited;
end;
function TReal.OpPlus(ANumber: TNumber): TNumber;
var
Temp: TReal;
begin
Temp := ANumber as TReal;
Temp.FRe := Temp.FRe + Self.FRe;
result := Temp;
end;
function TReal.SquareRoot: TNumber;
begin
WriteLn('____________________________');
WriteLn('==> Real Sqrt ...');
if Re < 0.0 then
begin
WriteLn('??? Immaginary!');
result := TReal.Create(1/0-1/0);
end
else
result := TReal.Create(Sqrt(Re));
end;
function TReal.Promote(ANumber: TNumber): TNumber;
var
Temp: TReal;
begin
if (ANumber is TReal) then
begin
Temp := TReal.Create(TReal(ANumber).Re);
end
else
raise Exception.Create('UnPromotable ...');
result := Temp;
end;
constructor TReal.Create(ARe: double);
begin
inherited Create(ARe,0.0);
end;
constructor TReal.Create(ARe, AIm: double);
begin
raise Exception.Create('Illegal ...');
end;
function TReal.GetIm: double;
begin
raise Exception.Create('Illegal ...');
end;
///////////////////////////////////////////////////////////////////////////////
{ Test }
procedure TestCoplien;
var
c1,c2: TComplex;
r1,r2: TReal;
x,y: TNumber;
begin
r1 := TReal.Create(3.0);
r2 := TReal.Create(-5.0);
c1 := TComplex.Create(1.0,1.0);
c2 := TComplex.Create(-5.0,0);
x := r1.Plus(r1); x.Dump; x.Free;
x := r1.Plus(r2); x.Dump;
y := x.SquareRoot; x.Free; y.Dump; y.Free;
x := c1.Plus(c1); x.Dump; x.Free;
x := c1.Plus(c2); x.Dump;
y := x.SquareRoot; x.Free; y.Dump; y.Free;
x := r1.Plus(c2); x.Dump;
y := x.SquareRoot; x.Free; y.Dump; y.Free;
x := c1.Plus(r1); x.Dump;
y := x.SquareRoot; x.Free; y.Dump; y.Free;
end;
procedure Test;
begin
TestCoplien;
SignUti.Pause;
end;
end.
It looks a bit odd a first sight, a TReal class is derived from a
TComplex one, and plus operation works in this way:
so, what's wrong in consider a TReal as a specialization of a TComplex ?
nothing, but anyway something looks strange ...
The oddity here is that is natural to imagine a TComplex implementation based on a couple of TReal types
You can even let TComplex class derive from a TReal class and add a TReal field for imaginary part ...
but subtype relation ?
this should be in (higher) interfaces types, where IReal can be a subtype of IComplex (subtype of INumber).
we have a problem here, however
INumber.Plus method should require a INumber parameter, and (interface) inheritance let us pass IComplex and IReal values, but, if you look at TNumber.Plus implementation, you can find why it don't work.
Interfaces
don't support
an "intriguing" idea ...
how to mix aggregation, composition and layering
a big idea related to delegation is "visual" development, when you program by dragging components from a palette to a component container (a DataModule for example).
D6 introduces visual support for interfaces, letting bind components together via interfaces the component supports (great!)
If we consider aggregation, adding components to a DataModule could be seen as adding parts to an aggregate ("implements" with property)
but if we write a simple component with the same set of interfaces, we can make the component act as a "proxy" of the DataModule (the latter can be the delegated object of a delegating component)
but this component could be placed in a higher layer container and so on ...
a ball that when "closed" looks like a component but if "opened" is a DataModule ...
| Next | Up.. |