13 Oct 2013

Simplifying Inheritance

(Disclosure: I work for Google but on nothing related to Go or any other language.)


(Disclaimer: I haven’t used Go, so this is just a theoretical discussion, and I may be missing something.)


Go is an interesting programming language in many ways, one of which is that it’s the only object-oriented language I know of that doesn’t support implementation inheritance. That is, deriving a class from another class, as opposed having a class implement an interface (in the Java sense), which Go does support.


This gets rid of a lot of complexity in Java, which together is known as the fragile base class problem.


To begin with, a superclass can acquire a new method with the same name and parameter types as a subclass method, but with a different return type. This causes the subclass to no longer compile. At this point, the author of the subclass has little choice but to rename the conflicting method in the subclass, which further breaks clients of the subclass that may not care about the superclass.


If the return types are compatible, but the parameter types are not identical, we have a case of unintended overloading, which can be confusing. Imagine a derived class that has a method foo(ArrayList), deriving from a class that suddenly sprouts a method foo(List). Now client code randomly calls one of the two versions depending on what the declared type of the reference is, as opposed to the actual type of the object at runtime. This is confusing.


If the return type is compatible and parameter types are identical, then we have a case of unintended overriding, which can cause strange behavior at runtime. Or compiler errors, if the subclass version has less visibility than the superclass one.


More generally, changes to base classes can violate invariants of the derived class, like an immutable object becoming mutable. In the Go model, your class’s interface won’t change without your knowledge.


Another requirement for correct and safe overriding is documenting self-use patterns. Getting rid of implementation inheritance means getting rid of this gotcha, which few Java programmers know of.


There’s also no need for final methods or classes, since all method implementations and all classes are final in Go.


Finally, implementation inheritance forces programmers to deal with calls to overriden methods in superclass constructors. This is, in general, not safe in Java, since the subclass constructor hasn’t run yet, so the derived part of the object may not be in a valid or expected state for the derived method to run. C++ deals with this differently, by invoking the superclass version in such cases, but that’s equally or perhaps more confusing for the programmer to understand and remember — a call to a method sometimes binds to the superclass version, and sometimes to the subclass version, in the same object.


By getting rid of implementation inheritance, Go gets rid of many problems afflicting Java and C++.

No comments:

Post a Comment