6.001/Recitation 18
Rob Speer
Contents |
Class diagrams
Not too hard. Make a box for each class. Methods defined by the class go above the line. Instance variables go below the line. Arrows point back to superclasses.
An object example
The project proposes "dementors", trolls that are immune to suffering. Since your extension will be cooler than that, let's implement it now as an example.
Messing up
Suppose you absent-mindedly make the "troll-part" of a dementor be an instance of class TROLL, instead of a handler for it. What can go wrong?
Multiple inheritance
Multiple inheritance is discouraged in many languages' OO implementations, because it's hard to say which superclass "wins" if multiple superclasses define the same method.
The usual strategy -- and the one used by the 6.001 OO implementation -- is to search the superclasses' parents in depth-first order, from left to right. The "diamond problem" is a prominent example of where this goes wrong. If A and C both have INSTALL methods, C's INSTALL never gets run on a D object.
You can get subtle bugs from inconsistent resolution orders; a class C may resolve methods from A before B, while a subclass D resolves B before A, exposing methods of B in D that aren't exposed in the superclass C.
Here's a class hierarchy which cannot be resolved consistently:
A < root B < root X < A, B Y < B, A Z < X, Y
So how do you ensure consistent behavior when you have multiple inheritance?
Option 1: Don't
6.001, Perl, and other systems simply resolve methods depth-first, and tell you to watch out for diamonds and work around them yourself.
Option 2: Designate one "real" superclass
Java, C#, and Ruby don't have full multiple inheritance; they put limitations on all superclasses after the first to avoid conflicts in the method resolution order. Java and C# only allow the secondary superclasses to be abstract interfaces that don't define any methods. Ruby requires secondary superclasses to be "mixin modules" with their own namespaces.
Option 3: More syntax
This is C++'s solution to everything: give the programmer a way to specify any solution to a programming issue they want, using additional syntax that makes no freaking sense. Eiffel does something similar.
Option 4: Get a CS theorist to come up with something
Python did this, and as a result there have been three different resolution orders in Python 2:
- 2.1 and earlier: Depth-first
- 2.2: Depth-first, but only use a class's methods the last time it appears in the order
- 2.3 and later: The "merge" method, which either creates a consistent method resolution order or raises an error
2.2's method deals with the typical diamond problem, but fails in this situation:
A < root B < root C < root D < root E < root K1 < A, B, C K2 < D, B, E K3 < D, A Z < K1, K2, K3
When you linearize Z and K3:
Z < Z K1 K3 A K2 D B C E root K3 < K3 D A root
And apparently someone cared, which led to method 3, which can linearize Z consistently:
Z < Z K1 K2 K3 D A B C E

