The Design of Software (CLOSED)

A public forum for discussing the design of software, from the user interface to the code architecture. Now closed.

The "Design of Software" discussion group has been merged with the main Joel on Software discussion group.

The archives will remain online indefinitely.

IS-A vs Interface (class hierarchy design)

I'm no fan of inheritance to extend functionality in C++. I find that it inevitably leads to class spaghetti.

But I do find inheritance to be useful for implementing interfaces. Then I can use the interface to define a set of messages that my class agrees to receive.

There is a lot of propaganda out there about how you have to make sure all inheritance implements is-a and not has-a. But what about 'can-do', as with interfaces? Is can-do another acceptable form of inheritance.

For example, a bumblebee is-a-insect and it can-fly. A bird also can-fly but it is not an insect. A bird can-walk and is-an animal. A robot can-walk but is not an animal.

Notice also that I am leading to a situation here where multiple-inheritance is kicking in! And that is another supposed big no-no and yet here we are with can-walk and can-fly and is-a bird and it really seems to me to be the right approach.

I know there must be articles out there outlining that inheriting a set of verbs or functionality is OK. Anyone have links?

It really seems to me that interface inheritance is always OK. And not just of abstract interface classes either - it's useful to throw in some utility functions and a field or two (ie max-flight-range, stall-speed) in those interface classes as well.

But many contemporaries are radically opposed to this, whcih makes me wonder if its all a mistake.
Scott
Friday, October 15, 2004
 
 
Can you explain what you see as the essential difference between is-a and can-do?  They seems essentialy identical to me, though I may be holding a different meaning than you are.

As far as I understand it, the only correct way to view inheritance is through the lens of interface compatibility, in terms of the complete semantics of the interface.  Bertrand Meyer discusses this at length in his Object Oriented Software Construction book in terms of constraints on the contracts implemented by super/sub classes.
fit
Friday, October 15, 2004
 
 
The essential difference between an is-a and can-do is imaginary. You can always rewrite "can-walk" as "is-a walker". But it's not important.

The point here being, that pure single-path inheritance is often not the best way to arrange things. You end up with certain behaviors (can-walk) crossing between the separate inheritance tree branches. So interfaces provide an elegant way to do that, without the need to duplicate behaviors or reshuffle the inheritance tree.

In which case, "is-a" is always the primary set of behaviors of an object, the things that it can't essentially live without. All other, usually more specific, things become its "can-do" behavior interfaces. Usually, the two are implemented a bit differently in the compiler, but that should not concern the language's end user. They are very close conceptually.
.
Saturday, October 16, 2004
 
 
I think that inheriting interfaces (where, in C++, by "interface" I mean a class that contains nothing but "pure", abstract, virtual functions) is a good thing.

I see little wrong with inheriting (i.e. implementing) more than one interface (though it doesn't happen as often as implementing a single interface): certainly it's useful, and I believe harmless, to allow it.

In the http://discuss.joelonsoftware.com/default.asp?pg=pgDiscussThread&ixDiscussTopicParent=14660&ixDiscussGroup=3&cReplies=38 thread (search that thread for "without inheriting from concrete") I ranted about the difficulty of using C++ without also inheriting implementation ... a problem that might not exist in another language with a more graceful support for delegating methods.

C# (and Java, perhaps) let you inherit any number of abstract interfaces, and inherit at most one implementation.

A problem with inheriting more than one implementation is especially troublesome when every class already inherits from some base "System.Object" class: see http://www.google.com/search?hl=en&q=inheritance+diamond

C++ overcomes this by allowing "virtual" inheritance; and C# overcomes it by not allowing inheritance of more than one implementation.
Christopher Wells Send private email
Saturday, October 16, 2004
 
 
I had to think about this one a bit, Scott.

We know, of course, that multiple inheritance is bad if the two "parents" share a method name, for instance.  If we inherit from both Class A and Class B, and each class has its own way of doing X, which way does our child object do X?  It's a logical contradiction, which is why Java, C#, VB .NET, etc. don't allow it.

But with multiple interface implementation, if Interface A and Interface B have identically-named methods X, they won't clash because you don't inherit the behavior; you provide the behavior in your implementing object.

*However*, if you forget that both interfaces have this same-named method, then you might implement X for Interface A, but forget about X in Interface B.  Then later you use your object as an Interface B and use method X, and get weird results because your X does things in an interface A way.  (A convoluted explanation, I know, but I hope it makes sense if read slowly.)  In this case you don't get a logical contradiction, but you can definitely end up with bizarre results if your interfaces clash.

So I think this is why people would be against cluttering your classes with too many interfaces.
Kyralessa Send private email
Saturday, October 16, 2004
 
 
By the way, the "is a" and "has a" sound like they come out of Design Patterns.  "Is a" refers to inheritance, while "has a" refers to composition (which Design Patterns finds more useful than inheritance, IIRC).  I don't know if they have a term for interfaces, but perhaps you could say "acts like a" or "can pass as a"?
Kyralessa Send private email
Saturday, October 16, 2004
 
 
In OO Design, problems can be solved either through inheritence and overloading or by aggregation and delegating. Both solutions are correct and each has it specific advantages and drawbacks. Aggregation, however, is easy modifiable at runtime, that's why a lot of design patterns rely on it.

Back to the original question. As far as I get it, Scott is proposing a naming convention to distinguish between interface inheritance and concrete inheritance (can-do vs is-a).

Though distingushing this is imaginary on a theoretical level (as was stated above), such a distinction can be usefull to communicate the underlying design concept. If it helps people to answer themselves the question if to make a base class or an interface, it sure is a good thing.
Gerd Riesselmann
Sunday, October 17, 2004
 
 
I believe that long ago Grady Booch called the "can do" interface a "mix-in" class or interface. I found an old web reference here:

http://www.cise.ufl.edu/~jnw/CIS4930/Lectures/l8.html

Of course, it is always fun to rediscover old ideas and give them new name :)
Roger Jack Send private email
Sunday, October 17, 2004
 
 
"then you might implement X for Interface A, but forget about X in Interface B"

If your concrete class allegedly implements interface A and B yet method X has to have 2 versions in the same concrete class then there's a really incredibly serious problem far beyond "forgetting" to implement the second version.

If you need that second version, then each interface needs its own separate implementation - you shouldn't have the two interfaces joined together like that in the first place.

If both interfaces have an identical method that has the same name and the same purpose then the concrete class should only implement it once.

Sunday, October 17, 2004
 
 
Specifically for C++:

Interfaces can be exported. Class specifications no (Interface = class containing only public pure methods). For example, changes in the private part of a class specification (the header) can change what gets exported out of a .dll or .so to the point where the binary compatibility is broken. Interfaces are a lot better since they are almost equivalent to exporting C functions. The down side is that there is more code required to handle the interface abstraction - for example class factories, registries, etc.

From a programming perspective, multiple inheritance from classes require virtual inheritance and scope resolution. This is poorly understood and misused by most programmers. Multiple interface inheritance has none of the problems above.

From an architectural perspective, a component-based architecture requires use of interfaces.

From a maintenance perspective, it is easier to implement version management at the interface level (version can be negotiated when requesting an interface). Interface based system have better dependency graphs (their Martin metric is better) than class-spaghetti ones which makes the first kind cheaper to maintain.
Dino
Monday, October 18, 2004
 
 
> You can always rewrite "can-walk" as "is-a walker".

But "can-wank" cannot be rewritten as "is-a wanker".

Aside from the joke, there's an important point in there, which is that 'optional' behaviour in the "can" case does not translate to "is-a". Just because someone can masturbate does not mean that they do (and therefore they cannot legitimately be called a name associating them with that action), despite the "99 percent of men masturbate and the other one percent are liars" quote.

Tuesday, October 19, 2004
 
 
By OOAD principles, class inheritance is towards two different purposes: interface inheritance and implementation inheritance.

Interface inheritance gives "substitutability" or the flexibility to use one object in the place of the other. If you are someone who is only interested in making things fly, you could use both a bee and an eagle thru the IFlyer interface. Of course the bee could have other interfaces as well since an interface is only a list of messages an object understands.

Implementation inheritance gives code reuse, default behaviors etc. Here, you cannot have multiple implementations for the same message as that would be conceptually wrong as well. This type of inheritance is useful when you want to build a layer of abstraction with the objects specifying only the behavior at their level. For example, suckle() is neither suitable for Animal not for Man, it is definitely at the level of Mammal.

Pure interface inheritance is when the base class is abstract and has only pure virtual functions. Pure implementation inheritance is when the inheritance is private (C++). A mix of these is more common.

All that said, there is no right and wrong of using either inheritance. Suitability is of course the criterion.
subhash Send private email
Wednesday, October 20, 2004
 
 
> Pure interface inheritance is when the base class is abstract and has only pure virtual functions

This has always seemed a bit sick to me. If my Bird must implement the fly method, there is nothing to say that when you call the method it will not walk instead (and what would penguins do?). It relies on you, me, and everyone else having the same definition of fly, and every opther possible method name, and sticking to it.

Is VogonSpaceship.fly equivalent to Brick.fly?

Wednesday, October 20, 2004
 
 
> This has always seemed a bit sick to me.

That's because the 'real world' isn't always neatly and unambiguously categorizable. Software artefacts often are, though: for example, do you see any ambiguity in having open, close, reset, send, and receive methods on an abstract IDevice interface?
Christopher Wells Send private email
Wednesday, October 20, 2004
 
 
Firstly, you are confusing natural language with method specifications. A method specification need (should) not end with an English word. It will have detailed descriptions of what is expected as response for a method.

Secondly, what is the class from which VogonSpaceship and Brick derive from? I cannot imagine such an abstraction and therefore their definitions of fly need not coincide. The objects are of unrelated types and hence the way they react to messages is unrelated
subhash Send private email
Wednesday, October 20, 2004
 
 
> It will have detailed descriptions of what is expected as response for a method.

Whoopee. A description.

> Secondly, what is the class from which VogonSpaceship and Brick derive from?

From DouglasAdamsQuote. But it isn't a class they derive from, it is some interface which they both have. Completely differently.

> I cannot imagine such an abstraction and therefore their definitions of fly need not coincide.

YHNRTHHGTTG.

(To be pedantic the verb would by "float".)

Christopher Wells - my point is that if you inherit only an interface you could easily implement open as close and the only thing you have to tell you that that is wrong is a spec someone knocked up in five minutes and that is full of holes anyway. I like the idea of interface inheritance alright, but unless/until pre and post conditions can be attached to that interface it remains a promise based on nothing very much.

Wednesday, October 20, 2004
 
 
"A method specification need (should) not end with an English word. "

Greek maybe? :-)


Seriously, can you please explain in more detail?
Dino
Wednesday, October 20, 2004
 
 
interface Bird {
  void fly();
  void walk();
  void jump();
  void float();
  void swim();
  void dive();
  ...
}


class Penguin implements Bird {
  void fly() throws CannotFlyException {
    throw CannotFlyException();
  }

  void walk() {
    ...
  }

  void jump() {
    ...
  } 
  ...
}

or, in a real OO manner

interface Traveler {
  void travel(TravelMethod method);
  TravelMethod getTravelMethod(Class methodClass);
}

interface TravelMethod{
  void travel();   
}

interface Fly extends TravelMethod { ... }
interface Walk extends TravelMethod { ... }
interface Swim extends TravelMethod { ... }
interface Drive extends TravelMethod { ... }

interface PenguinTravelMethod extends TravelMethod {}


class Penguing implements Traveler {
  class PenguinWalkMethod implements Walk, PenguinTravelMethod {
    void travel() {
      ...
    }
  }

  class PenguinSwimMethod implements Swim, PenguinTravelMethod {
    void travel() {
      ...
    }
  }


  void travel(TravelMethod method) throws InvalidTravelMethodException {
    getTravelMethod(method).travel();
  }

  TravelMethod getTravelMethod(Class methodClass) throws InvalidTravelMethodException {
    if(methodClass.equals(Walk.class)) {
    return new PenguinWalkMethod();
  } else if (methodclass.equals(Swim.class)) {
    return new PenguinSwimMethod();
  } else {
    throw new InvalidTravelMethodException(this);
  }   
}
Dino
Wednesday, October 20, 2004
 
 
And a bit better, instead of using the PenguinTravelMethod interface:

interface TravelMethod{
  void travel();
  Class whoseMethod();
}

...

public class Penguin ... {
  public class PenguinWalkMethod() {
    void travel() { ... }
    Class whoseMethod() { return Penguin.class; }
  }

  ...

}
Dino
Wednesday, October 20, 2004
 
 
This discussion is getting very interesting.  What are the ramifications of a malicious interface implementer?  Does it matter if the interface is "fly" but the implementer chooses to walk?  Can an interface thwart this sort of thing through its other code; by, say, having an "altitude" property for which zero (or ground level, yes, I know it's not always zero) is not an acceptable return value?
Kyralessa Send private email
Wednesday, October 20, 2004
 
 
Dino:

Natural language is flexible and allows more ambiguity than a structured language like C which is less ambiguous due to language specifications.

There are no two ways to interpret printf("foo"); There can be multiple interpretations of 'fly' in 'I made it fly'. If it was a brick that I "made fly", it would mean I threw it. If it was a bird, I could have induced it to spread its wings and take off by itself.

Now, the definition of 'fly' in the natural language is varying according to the object and context. In order to use this 'word' as a method name, I have to resolve the ambiguities and state clearly what I mean by the method 'fly()' in the interface IFlyer. Am I modelling self-propelled flying or just suspension in the air.

Object-oriented programming is all about modelling. And if a class hierarchy is ambiguous, it is a fault of the modelling and not the technique. Also, fixing the right abstractions is the tricky part which requires a good amount of forethought and experience.
subhash Send private email
Thursday, October 21, 2004
 
 
Thanks for the discussion everyone. I now feel that multiple iterface inheritance is not a problem at all.

Where it is very useful is in modularizing sets of things that have to connect to more than one interface.

For example, a plugin that receives a stream of messages from a driver and can also draw itself and can also transmit messages to a different driver. Each driver doesn't need to know whether or not the object it is talking to can draw itself or talk to anyone. And something that is drawable doesn't need to know anything about where the data being displayed is coming from. So, you can have three interfaces inherited right there, each basically being a protocol, or a set of things that the object can do, separate from what the object is.

Thanks again.
Scott
Thursday, October 21, 2004
 
 
> What are the ramifications of a malicious interface implementer?

I did not necessarily mean malicious. It could as easily be a mistake, particularly if someone copies and pastes.

> Can an interface thwart this sort of thing through its other code

That is partly my point, an interface doesn't have any code. That's why it seemed sort of "sick" to me.

> by, say, having an "altitude" property for which zero (or ground level, yes, I know it's not always zero) is not an acceptable return value?

That is the kind of thing I was thinking of, though it also is subject to the malicious or careless programmer. Do you _always_ check return values? No, nor me. That's why I think an interface should have some kind of code attached to it, even if it were not available to client or child classes and which just automatically ran, say, before and after a method. That way the interface designer describes something of the meaning of the methods in something stronger, more checkable, than a textual specification. Of course, then it is no longer an interface in the purest sense of the word.

Thursday, October 21, 2004
 
 
subhash,

I posted eariler today some java like code solving the penguins can fly problem. However, the posts were filtered out.

Yes, you are right, it comes down to proper modelling and experience. Using the right names, which eliminate most amibguity is important like you write.

Anyway, here's my example:

interface Traveler {
  void travel(Class methodClass);
  TravelMethod getTravelMethod(Class methodClass);
}

interface TravelMethod {
  void travel();
  Class travelerClass();
}

interface Fly extends TravelMethod {}
interface Swim extends TravelMethod {}
interface Walk extends TravelMethod {}
interface Jump extends TravelMethod {}
interface Drive extends TravelMethod {}


class Penguing implements Traveler {
  class PenguinSwim implements Swim {
      void travel() { ... }
      Class travelerClass() { return Penguin.class; }
  }

  class PenguinWalk implements Walk {
      void travel() { ... }
      Class travelerClass() { return Penguin.class; }
  }

  void travel(Class methodClass) throws InvalidTravelMethodException {
      getTravelMethod(methodClass).travel();
  }

  void getTravelMethod(Class methodClass) {
      if(methodClass.equals(Walk.class))
          return new PenguinWalkMethod();
      else if (methodClass.equals(Swim.class))
          return new PenguinSwimMethod();
      else
          throw new InvalidTravelMethodException();
  }
}
Dino
Thursday, October 21, 2004
 
 
Dino:

Agree mostly .. One minor comment on your model is that your abstraction Traveller is a little too high that you lose quite a bit of type-safety. When I use a Penguin, I do not know (from its methods) what it is capable of. And asking penguin.getTravelMethod(Skate.class) will be ignored by the compiler and will be caught only by the runtime.
subhash Send private email
Thursday, October 21, 2004
 
 
subhash,

You have very good comments.

"When I use a Penguin, I do not know (from its methods) what it is capable of".

That's not entirely true. The PenguinSwim, PenguinWalk inner classes define the Penguin travel methods. You kind of know what a Penguin can do, but indeed the type safety is not enforced at compile time.

For typesafety one can go ahead and specialize the travel for the Penguin and create new methods like swim, walk which delegate into the travel method.

Of course, when calling through the interfaces one has to rely on exceptions to figure out if a traveler can walk or swim  or drive or whatever.

Thx.
Dino
Thursday, October 21, 2004
 
 
I can't imagine programming C++ without interface classes. :-)

Preferably you only inherit in exactly _one_ step. Your new class implements zero or one base class and optionally a bunch of interfaces.

The base class should not inherit from any class. Interface classes should not be inherited from other classes either.

The base class represents the life-time of instances of your object, interfaces normally don't.

We have this naming convention at our company:


class CShape {
    public: CShape();
    public: virtual ~CShape();
};

class IDrawable {
    public: virtual void IDrawable_Draw(CCanvas& canvas)=0;
    public: virtual void IDrawable_Flush()=0;
};

class IClickable {
    public: virtual void IClickable_Click(int x, int y)=0;
};

class CBitmap : public CShape, public IDrawable, public IClickable {
    public: CBitmap();
    public: virtual ~CBitmap();
    public: void Load();

    //...
    public: virtual void IDrawable_Draw(CCanvas& canvas);
    public: virtual void IDrawable_Flush();

    public: virtual void IClickable_Click(int x, int y);
};


This is an imaginary example, of coarse.

These rules taken together makes the interfaces extremely explicit and avoid colliding function names in CBitmap.

The design becomes very "flat" and has the fewest possible moving parts. You can see directly at the definition of CBitmap every single class involved in the inheritance.


I think about interface classes as SCART-sockets on a VCR. My TV, xbox, GameCube and VCR:s all supports the SCART-standard but they don't share the same base class - they might not have base classes at at all.


By supplying a reference to the _caller_ of the interface classes' functions, an object can support _several_ "SCART sockets" simultaneously.


Do I make any sense?


/Marcus
Marcus Zetterquist Send private email
Tuesday, November 09, 2004
 
 

This topic is archived. No further replies will be accepted.

Other recent topics Other recent topics
 
Powered by FogBugz