Single Ancestor

Source: comp.object
Date: 11-Jul-97

Related Sites


------------------------------

o-< Problem: Is it bad to derive all classes from a single ancestor class?


---------------

o-< Stephen asked:

I remember reading something about "OO designs requiring a single ancestor on all objects are broken" (don't remember where). But I've since seen some class libraries that uses such design (e.g. MFC) and languages where objects all has a single ancestor (e.g. eiffel?, Java).

I do not understand why such a statement is made. Can somebody explain why having a single ancestor is think of as bad and whether it is justified? And what kind of function would/should this common ancestor provide?


---------------

o-< Roger Browne said:

How could it be bad? A universal ancestor is the natural place for behaviour that applies to every object. For example, in Eiffel every object can be cloned, and has a default STRING representation. Therefore, features 'clone' and 'out' (amongst others) are found in class GENERAL.


---------------

o-< Eric Nicholas Rizzo added:

Anyone who feels that a single ancestor structure is not useful, necessary, etc., might be enlightened by a look at the class Object in any Smalltalk implementation. It typically has quite a bit more than a handful of methods. How would you do things like a universal dependency mechanism or exceptions without a common ancestor for "all" (read "nearly, 99.9% all") objects?


---------------

o-< Tyson Jensen gave other examples:

As a regular user of MFC (everything inherited from CObject), and OLE (everything inherited from IUnknown) I have a "users" perspective on why.

The good: OLE benefits from it's IUnknown concept in that the IUnknown interface provides a way to get at other interfaces, meaning that objects that are unknown at compile time can be used at run time. That is, I can plug in a new spell checker into MS Word without the annoyance of patches, recompiles or even talking to a single person at MS! Similar benefits are realized in CORBA and JAVA from using a standardized ancestor.

The bad: MFC can be very slow for certain applications. The inheritance tree is quite deep for several commonly used objects. As can be expected, each layer of inheritance slows the overall performance by some small margin. With applications where speed is critical, MFC is often avoided in favor of direct API calls (not OO at all!).
[The depth of the inheritance tree does not cause delays in C++. It is MFC's message maps mechanism that causes delays that depend on the depth of the inheritance hierarchy. -YS]

The ugly: I've had to spend a fair amount of time trying to figure out how a given class works, because it is inherited from some class which is only marginally related, which inherits from a class that is only marginally related, and so on. By the top of the inheritance tree, I end up looking at a completely unrelated class, with virtual functions implemented at the bottom of the tree! In my own work, I prefer to inherit only classes that are directly related in function to the chosen ancestor.


---------------

o-< Robert C. Martin provided the cons:

The problems with single ancestor models (called "Tree" models) are:

  1. Every object in the system has all the attributes and methods of the common ancestor, whether they need it or not. This can be very wasteful of space and cpu time.
  2. If the common ancestor needs to be changed for any reason, every program in the system must be rebuilt. (in some languages)
If your language/application can tolerate this, then having a common ancestor is not a problem. However, there are a fair number of application domains and language that cannot tolerate this in general.


---------------

o-< Royce Howland gave counter-examples:

It is an unwarranted assumption that there is behavior that applies to every object. Enforcement of a single root is a constraint that can prove to be obnoxious in the (admittedly rare) cases where universal behavior is undesirable.

As another poster pointed out, Eiffel permitting every object to be cloned might be a problem if you have objects that you don't want to be clonable (e.g. implementations of the Singleton pattern). [see More Info section -YS]

As another example, NeXT's implementation of distributed objects with Objective C introduces a new root class NXProxy that does not inherit from Object, the normal root class. There are many behaviors of Object that are not appropriate for NXProxy.

Most people don't miss the ability to have multiple inheritance roots, but it is occasionally useful.


---------------

o-< Dave Harris added other considerations:

It can be a minor problem when you are trying to use classes from different vendors, that insist on different root classes. This doesn't matter in languages (like Eiffel and Java) where you can't choose a different root. It is also less important in languages which support multiple inheritance.

There are obvious efficiency costs in requiring you to inherit some root class that's full of unnecessary virtual functions. There can also be design costs if the root class doesn't work the way you want. For example, serialization sometimes gets put there, despite being rather heavy and sensitive. Eiffel includes clone, even though some objects may not want to be clonable.

I suspect the commentator was thinking of C++, which traditionally puts a lot of emphasis on efficiency. It also supports MI, but it didn't always and some library designers are not comfortable using it. The theory would be that it is better to put things like serialization into a mixin class [see More Info section -YS], to be inherited from when needed using MI, then into the root class.


------------------------------

o-< More Info:

Singleton Pattern

Mixins:
C++ Mixins Tip 1
C++ Mixins Tip 2
C++ Mixins Tip 3
C++ Mixins Tip 4


------------------------------