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.. |