[fpc-pascal] "is"

Thomas Schatzl tom_at_work at gmx.at
Thu Apr 7 18:46:45 CEST 2005


Thomas Schatzl schrieb:
> Michael Van Canneyt schrieb:
>> For example;
>> MI for interfaces for me is strange (to say the least) because
>> interfaces were introduced to avoid the mess of MI in the first
>> place. So why on earth would you want to introduce it ??
> Since interfaces do not have/inherit implementations by definition this
> is not an issue, they simply aggregate function *specifications* (no 
> code). This is multiple _interface_ inheritance. Commonly considered 
> good =)

Maybe an elaboration of the Rat-example (well, grew to a mouse-example 
:) helps to clarify some problems you have with interface-MI:

I marked the comments which talk about advantages this feature gives the 
programmer with "(+)"

     TWasAcceptable = Boolean;

     IMammal = interface
        procedure sleep(); // a mammal can sleep
     IMeatEater = interface
         // a meat eater can eat
        function eat(m : TMeal) : TWasAcceptable;
     IPlantEater = interface
         // a plant eater can eat too
     	function eat(m : TMeal) : TWasAcceptable;
     // something that can eat both plants and meat
     IEatsEverything = interface(IMeatEater, IPlantEater);

     IJumping = interface
        procedure jumparound();

     // a mouse can sleep, eat something and wiggle with its
     // tail (and certainly much more =)
     IMouse = interface(IMammal, IEatsEverything)
        procedure wiggleWithTail();

Now I have several types of mice:

     // a mouse can do everything what a mouse is supposed to do
     // this class _implements_ the abilities
     TMouse = class(TAnimal, IMouse)
        procedure sleep();
        procedure eat(m : TMeal);
        procedure wiggleWithTail();

(+) Don't need to specify that TMouse implements IMammal, IPlantEater, 
IMeatEater and 10 other interfaces as well. I hope you don't forget one.
Can be "remedied" by a hierarchy of singly inherited interfaces, e.g. 
IPlantEatingMammal, IMeatEatingMammal, IAllEatingMammal, IMouseMammal, 
IJumpingMouseMammal, ...

     // for example's sake although Micky Mouse looks like a mouse
     // and can do the same things as the animal with the same
     // name it is not derived from TMouse (it is not an animal
     // after all)
     TMickeyMouse = class(TCartoonCharacter, IMouse, IJumping)
        procedure sleep();
        procedure eat(m : TMeal);
        procedure wiggleWithTail();
        procedure jumparound();

Implement methods of classes. Certainly in completely different ways - 
while the mouse animal accepts maybe rotten meat, Mickey Mouse may not, 
but it eats edible meals. Maybe due to the ancestors (single 
inheritance!) the methods are already partially implemented as well.

(+) You may notice that although there's a name clash in the interface 
specification. Does not matter at _all_, because it's only a 
specification, e.g. "has that method, don't care how it's implemented" 
(+) There's no problem with instantiation either because you simply do 
_not_ instantiate interfaces at all. It's only a specification of what 
this type can do.

In the code using these classes I can now use instances of both classes 
(no news here, you could that with single inherited interfaces too).

// gives *any* plant eater a meal to eat. Consider we have some cow
// class which implements IPlantEater as well (and hence can eat too!)
procedure eat_a_plant(obj : IPlantEater; plant : TPlantMeal);

// gives the given object a meal. Since this is a generic meal, only
// objects which can eat everything can eat here. No need to check
// further in the code of the procedure whether the meal is of meat or
// plant type, we can be sure it accepts it.
procedure eat_just_something(obj : IEatsEverything; meal : TMeal);

(+)Can't do that with singly inherited interfaces... (in this case it 
might be reasonable to check whether the meal is edible for 
planteaters/meateaters and maybe throw an exception) but actually it'd 
be natural.
Consider the case when there's not only two types of things animal can 
eat... remember that case'ing doesn't work with interfaces.
(+) Again: The fact that IEatseverything is multiply inherited (and the 
method signatures overlap) does not even matter. Interfaces are just 
specifications which are always bound on runtime....

// gives the mouse a meal and forces tail wiggling if it was
// acceptable
procedure eat_some_meat_and_wiggle_with_tail_if_ok(obj : IMouse; meat : 

// lets the given mammal walk around
procedure walkaround(obj : IMammal);

What happens if you add another completely different animal?

IFish = interface
	procedure swim();

IShark = interface(IFish, IEatsEverything);

TShark = class(TFish, IShark)
	procedure swim();
         procedure eat(m : TMeal);

.... any method (which were originally designed for the hierachy of 
mammals) still work. You can feed the shark independent of being a shark 
(only interested that it can eat everything you throw at it). You can 
easily check whether the given object is a fish, or whether it has _all_ 
properties of a shark, or something else.

This can be more or less done with single interface inheritance too.

Now I am going to add some property to all sharks, say that it has a 
natural killer instinct (don't let that property's name bother you, 
inventing something reasonably okay is hard ;-).

   INaturalKiller = interface;

and change IShark to

   IShark = interface(IFish, IEatsEverything, INaturalKiller);

(+) Recompile. Now every instance can be checked whether it has that 
INaturalKiller property easily, without further code modifications (with 
singly inheritance you'd now be starting to invent names for more 
combinations... or design everything with a set in a common base class 
to be able to use it reasonably well in an OO sense (which requires you 
to find some common base class, maybe TAnimal. But that doesn't work 
very often)


Summing MI with interfaces up:
- interfaces are only specifications, so there is no clash of 
- they neither have some initialization, so there's no problem about 
constructing the instance either
- they provide means to model your world naturally


(***) there are different possibilities resolving this "conflict". Some 
languages treat it as conflict (where logically there isn't), e.g. C# 
(solved via the "implements" directive in Delphi8+ I assume), others see 
no conflict, e.g. Java.
Implementation is straightforward in the latter case (already done by 
ml), maybe some more thoughts needed for the former.

More information about the fpc-pascal mailing list