TITLE: Using inheritance: subtyping and subclassing PROBLEM: krc@wam.umd.edu (Kevin R. Coombes) I've been looking at a couple of existing class libraries recently, and I'm unhappy with some of the uses to which inheritance has been put. I'd like to collect some other opinions. class Set : public Bag { ... class Queue : public Deque { ... is it reasonable to use public inheritance in this way? RESPONSE: rmartin@thor.Rational.COM (Bob Martin), 22 Mar 93 Not IHMO. Neither of these represent ISA relationships. A set is not a kind of bag. A queue is not a kind of deque. At least not in the OO sense. A related issue is the following: class Rectangle; class Square : public Rectangle; This is an incorrect model for the same reason. A Square does not have an ISA relationship to a rectangle!! Why? Becuase if A ISA B then B can do everything A can do. And this is certainly NOT the case with squares and rectangles. A Square cannot do everying a rectangle can do. For example, a square cannot change its width in proportion to its height like a rectangle can. So, lets define when inheritance CAN be used. A can inherit from B if A will be able to do EVERYTHING B can do. A cannot publicly inherit from B if A will be not be able to do everything B can do. Thus a Set cannot publicly inherit from a Bag since a Set cannot do everything a Bag can do... --------- To many people this is confusing. It seems silly to say that a square is not a rectangle. Of course it is. A square is a special case of a rectangle. Right! But only based on its constraints. A square has all the same constraints that a rectangle has, plus a few more. But in OO we don't necessarily inherit constraints. Derived classes are never more constrained than base classes. Either their constraints are equivalent, or there are fewer of them. Thus, if D derives from B, D can be used wherever B is (has no more constraints than B) and may be used in places where B cannot (May have fewer constraints than B). --------- So, to restate. When you publicly inherit, then you must not subtract functionality, nor add more constraints. You may only enhance functionality and decrease constraits. RESPONSE: marc@mercutio.ultra.com (Marc Kwiatkowski), 23 Mar 93 Well, I beg to differ. A square certainly is a rectangle. You more or less say [...] [...] There is no confusion. I think you describe the situation quite accurately here; namely, SQUARE has more constraints that RECTANGLE. Your conlusion, however, seems unjustified. Constraints certainly can be inherited. Eiffel takes great care to make sure the constraints (class invariants and post conditions) are inherited. Indeed, postulating that class invariants and post conditions be inherited and only strengthened, makes the "is-a" relationship between SQUARE and RECTANGLE is all the more obvious. RECTANGLE class invariant: ((theta0 == theta1 == theta3 == theta4 == pi/2) && (l0 == l2) && (l1 == l3)) SQUARE class invariant: ((theta0 == theta1 == theta3 == theta4 == pi/2) && (l0 == l2) && (l1 == l3) && (l0 == l1)) Thus, SQUARE is-a RECTANGLE with one more invariant. Eiffel takes pain to enforce this notion in order to guarantee type consistency. Consider the following to see why this is necessary. Class B inherits from class A, but weakens the invariance rules of class A. A feature "b" unique to class B is applied to object X of class B, such that X is now in a state that meets the invariance requirements of class B but not those of class A. A feature "a" inherited from class A without modification is then applied to object X. The result of this is undefined, since X fails to meet the invariance requirements specified by A and "a". If the language does not specify the inheritance of constraints, then the compiler must generate code for the above scenario. This leaves the programmer of class A in the unenviable position of being unable to assume anything about the invariants of his class when it is manifested in a child class. > So, to restate. When you publicly inherit, then you must not > subtract > functionality, nor add more constraints. You may only > enhance > functionality and decrease constraits. I won't go so far as to say that exactly the converse of this is true, but almost. I think Eiffel handles this matter very well. There, preconditions are allowed to be relaxed from parent class to child class, while post-conditions and class invariants can only be strengthened.