[fpc-pascal] "is"

Michael Van Canneyt michael at freepascal.org
Thu Apr 7 21:39:18 CEST 2005



On Thu, 7 Apr 2005, Thomas Schatzl wrote:

> Hello,
> 
> 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 "(+)"
> 
> type
> TWasAcceptable = Boolean;
> 
> IMammal = interface
>   procedure sleep(); // a mammal can sleep
> end;
> IMeatEater = interface
> // a meat eater can eat
>   function eat(m : TMeal) : TWasAcceptable;
> end;
> IPlantEater = interface
>    // a plant eater can eat too
> 	function eat(m : TMeal) : TWasAcceptable;
> end;
> // something that can eat both plants and meat
> IEatsEverything = interface(IMeatEater, IPlantEater);
> 
> IJumping = interface
>   procedure jumparound();
> end;
> 
> // a mouse can sleep, eat something and wiggle with its
> // tail (and certainly much more =)
> IMouse = interface(IMammal, IEatsEverything)
>   procedure wiggleWithTail();
> end;
> 
> 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();
> end;
> 
> (+) 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();
> end;
> 
> [...]
> 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).
> 
> E.g.
> // 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 :
> TMeal);
> ...
> 
> // lets the given mammal walk around
> procedure walkaround(obj : IMammal);
> ...
> 
> -------
> What happens if you add another completely different animal?
> 
> IFish = interface
> 	procedure swim();
> end;
> 
> IShark = interface(IFish, IEatsEverything);
> 
> TShark = class(TFish, IShark)
> procedure swim();
>         procedure eat(m : TMeal);
> end;
> 
> .... 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 ;-).
> 
> e.g.
> 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 implementations
> - they neither have some initialization, so there's no problem about
> constructing the instance either
> - they provide means to model your world naturally

All this is nice, but it boils down to: MI for interfaces is shorthand notation. 
No more, no less. It introduces no added value (other than notational) or functionality.

I find it more clear to have
if  (A is IA) and (A is IB) then
  ..
Instead of 

If (A is IC) then
  ..

Where IC would simply be IA and IB conmbined. In IC, you hide what actually is.
(the names are not always as descriptive as in your example.)

If you need to check for IA and IB regularly in your code, then it is easy to write a 
general-purpose function

Function SupportsInterfaces(A : TInterfacedObject; Interfaces : Array of Interface) : Boolean;

And then write

if SupportsInterfaces(A,[IA,IB]) then

Which is much more explicit about the properties you are actually querying.

Also, if your component needs more than, say, 5 interfaces, then I think you need to do 
some serious rethinking of your design. Even in OpenOffice (one of the more elaborate 
collections of interfaces I've encountered so far) there are few objects with SO many 
interfaces.

So, KIS (Keep it Simple) is the principle. 
Which is the computer variant of science's "Ockham's razor":

''Pluralitas non est ponenda sine neccesitate'', 
which translates as 
``entities should not be multiplied unnecessarily''. 

The latin is rather fitting for computer language IMHO :-)

Michael.




More information about the fpc-pascal mailing list