dl@g.g.oswego.edu (Doug Lea) (10/13/89)
Providing assistance in avoiding the confusion between using inheritence for `isa' relations (vaguely, `subtyping') versus `part-of' or `implemented-via' (i.e., `uses-code-from') relations (vaguely, `delegation') is one of the things I think C++ got right. A problem with some inheritence mechanisms is that there is only one flavor of connecting arrow between any two classes, that might be interpreted as `isa', `part-of', `uses-code-from', or anything else, for that matter. C++ at least gives two flavors: public inheritence and private inheritence. The former is nearly always best used when interpretable as `is a subtype of', and the latter as `is implemented via'. For example, class Vehicle { ... } class Engine { ... } class Chassis { ... } class Wheel { ... } Class Car: public Vehicle, private Engine, private Chassis { Wheel wheels[4]; //... } says that a Car is a subtype of Vehicle, implemented via (? -- see below) an Engine and Chassis. The exploited difference between public and private inheritence in C++ is that a Car may be used in any situation requiring a Vehicle, but not in situations requiring an Engine or Chassis, which is exactly right. A Car is also implemented via 4 wheels, but I didn't write class Car: ... private Wheel, private Wheel, Private Wheel, private Wheel partly because C++ wouldn't then let me then distinguish (name) the 4 wheels individually. But this also shows that I could have just as well written the whole thing as Class Car: public Vehicle { Engine engine; Chassis chassis; Wheel wheels[4]; //... } to make clear that the engine, etc., are used soley as components (i.e., part-of relations), and not for general code-sharing (i.e., implemented-via relations). For better and worse, C++ doesn't force you to adopt any particular strategy involving public inheritence (isa), private inheritence (implemented-via), and aggregation of components (part-of). In fact C++ is not thoroughly coherent in helping you to do The Right Thing when implementing such schemes. For example, it isn't at all sensible to redefine a non-virtual function in a public subclass (this is fine in a private subclass), but C++ doesn't outlaw this. Also, it is sometimes less than natural to create designs where the elements themselves are `abstract classes' so that, for example, if Engine were an abstract class you could create a subtype of Car (say, Lincoln) that contains a subtype of Engine (V8). (C++ forces such things to be components, not privately inherited, requiring pointers or references that must be properly initialized, etc.) However, the distinctions may be used to useful effect in may contexts. Consider designs like class Stack // a `pure virtual' abstract class for a stack of ints { public: virtual int empty() = 0; virtual int full() = 0; virtual void push(int x) = 0; virtual int& top() = 0; virtual void pop() = 0; }; class Array { //... int capacity() { ... } int reallocate(int newsize) { ... } //... }; class BoundedArrayStack: public virtual Stack { protected: Array s; int sp; public: BoundedArrayStack(int cap) { ... } int full() { return sp == s.capacity(); } void push(int x) { if (full()) error("push to full stack"); else ... } //... }; class ResizableArrayStack: public virtual Stack, private BoundedArrayStack { //... int full() { return 0; } void push(int x) { if (BoundedArrayStack::full()) s.reallocate(sp * 2);... } //... }; class LinkedList { ... } class LinkedStack: public virtual Stack { LinkedList l; //... }; class TraversableStack : public Stack { ... } // another abstract class class TraversableLinkedStack: public virtual TraversableStack, private LinkedStack { ... } //... -- Doug Lea, Computer Science Dept., SUNY Oswego, Oswego, NY, 13126 (315)341-2367 email: dl@oswego.edu or dl%oswego.edu@nisc.nyser.net UUCP :...cornell!devvax!oswego!dl or ...rutgers!sunybcs!oswego!dl