Someone asked for my thoughts on a bit from a book the other day, and this is what I derived from it. I'm curious what others think of my derivation and explanation. Here's the context I was asked about:
"
From a book I am reading (http://shop.oreilly.com/product/0636920024231.do)
Opinions? (this relates to things besides javascript in my opinion)
Classical Inheritance is Obsolete
"Those who are unaware they are walking in darkness will never seek the light."
—Bruce Lee
In "Design Patterns", the Gang of Four recommend two important principles of object
oriented design:
1. Program to an interface, not an implementation.
2. Favor object composition over class inheritance.
In a sense, the second principle could follow from the first, because inheritance exposes
the parent class to all child classes. The child classes are all programming to an implementation,
not an interface. Classical inheritance breaks the principle of encapsulation,
and tightly couples the child classes to its ancestors.
Why is the seminal work on Object Oriented design so distinctly anti-inheritance?
Because inheritance causes several problems:
1. Tight coupling. Inheritance is the tightest coupling available in OO design. Descendant
classes have an intimate knowledge of their ancestor classes.
2. Inflexible hierarchies. Single parent hierarchies are rarely capable of describing all
possible use cases. Eventually, all hierarchies are "wrong" for new uses -- a problem
that necessitates code duplication.
3. Multiple inheritance is complicated. It's often desirable to inherit from more than
one parent. That process is inordinately complex and its implementation is inconsistent
with the process for single inheritance, which makes it harder to read and
understand.
4. Brittle architecture. Because of tight coupling, it's often difficult to refactor a class
with the "wrong" design, because much existing functionality depends on the existing
design.
5. The Gorilla / Banana problem. Often there are parts of the parent that you don't
want to inherit. Subclassing allows you to override properties from the parent, but
it doesn't allow you to select which properties you want to inherit.
These problems are summed up nicely by Joe Armstrong in "Coders at Work", by Peter
Siebel:
“The problem with object-oriented languages is they've got all this implicit environment
that they carry around with them. You wanted a banana but what you got was a gorilla
holding the banana and the entire jungle.”
—Joe Armstrong
Inheritance works beautifully for a short time, but eventually the app architecture becomes
arthritic. When you've built up your entire app on a foundation of classical
inheritance, the dependencies on ancestors run so deep that even reusing or changing
trivial amounts of code can turn into a gigantic refactor. Deep inheritance trees are
brittle, inflexible, and difficult to extend.
More often than not, what you wind up with in a mature classical OO application is a
range of possible ancestors to inherit from, all with slightly different but often similar
configurations. Figuring out which to use is not straightforward, and you soon have a
haphazard collection of similar objects with unexpectedly divergent properties. Around
this time, people start throwing around the word "rewrite" as if it's an easier undertaking
than refactoring the current mess.
Many of the patterns in the GoF book were designed specifically to address these wellknown
problems. In many ways, the book itself can be read as a critique of the shortcomings
of most classical OO languages, along with the accompanying lengthy workarounds.
In short, patterns point out deficiencies in the language. You can reproduce
all of the GoF patterns in JavaScript, but before you start using them as blueprints for
your JavaScript code, you'll want to get a good handle on JavaScript's prototypal and
functional capabilities.
For a long time, many people were confused about whether JavaScript is truly object
oriented, because they felt that it lacked features from other OO languages. Setting
aside the fact that JavaScript handles classical OO with less code than most class-based
languages, coming to JavaScript and asking how to do classical inheritance is like picking
up a touch screen mobile phone and asking where the rotary dial is. Of course
people will be amused when the next thing out of your mouth is, "if it doesn't have a
rotary dial, it's not a telephone!"
JavaScript can do most of the OO things you're accustomed to in other languages, such
as inheritance, data privacy, polymorphism, and so on. However, JavaScript has many
native capabilities that make some classical OO features and patterns obsolete. It's
better to stop asking "how do I do inheritance in JavaScript?", and start asking, "what
cool new things does JavaScript enable me to do?"
"
I would lately agree with this. I think what is really being complained about here is something that should be complained about as modern OO has gone bananas with this concept: statefulness.
Think about it for a minute, what do you do in a base class?
· Construction/initialization code which sets up the state of the object where multiple objects are similar and therefore have common forms of state encapsulated in them. i.e:
public class BaseClass {
private Guid _thisInstancesToken;
public BaseClass() { _thisInstancesToken = Guid.NewGuid(); }
}
· Utility methods that are usable by your child classes.
protected void NewToken()
{
_thisInstancesToken = Guid.NewGuid();
}
The first obviously is state based, what initialization occurs in a base class that isn’t about the object’s state (or it’s environment; state in other objects)? There’s no reason to have base class initialization occur without it being side effectful (side effect n. Effect the system in a way other than returning something).
The second one, why would you ever call a utility method in your parent class rather than in a completely separate class? The only reason the parent class should have that method for you to call instead of another class having it, is because by being the objects parent it has access to the state, and can therefore alter that state.
Now that you see how inheritance really exists only to serve statefulness, let’s talk about why we should worry about statefulness.
Statefulness leads to side effects: The relationships inside of state. These are pieces of tight coupling which causes the ‘arthritic’ behavior he is referring to.
For example:
public class Car { public bool Running; }
public class Driver
{
Void StartCar(Car carToStart) { carToStart.Running = true; }
}
The above method StartCar causes a side effect in your current program’s running state, by simply executing someDriver.StartCar(aCar); every single part of the current running process which has any relationship to aCar is affected. This side effect creates a tightly coupled relationship between Driver and all parts of the system which have a reference to aCar . Though when you’re changing those other parts of the system which depend on Car, you wouldn’t immediately think you have to pay attention to
Driver when you’re changing them, after all they have no reference to Driver. If
Driver has this tight relationship to every part of the system that relies on a
Car without it being obvious, what other parts of the system are also maintaining similarly tight relationships by affecting the Car’s state?
Now look at the stateless way of doing the above and notice how it does not affect other parts of the system:
class Car { bool Running; }
class Driver
{
Car StartCar(Car carToStart)
{
Car mutableCar = carToStart.Clone();
mutableCar.Running = true;
return mutableCar;
}
}
Now you see, the
Driver's only relationship is to those who call
StartCar, because the method has no side effects. There could be 400 threads, or objects, or what have you in the system referencing carToStart, but calling
StartCar has 0 effect on the behavior of those parts of the system, therefore,
Driver has no relationship to those parts of the system. If the method that calls
StartCar takes the new car and replaces the old car with it however.. Then it would be said the method calling
StartCar has a side effect and that method’s class is then tightly relating itself to all who hold references to that instance of
Car.
So I believe the rant mentioned is really griping because inheritance only exists to create state and cause side effects in it, and through side effects disparate parts of a system become tightly coupled in unexpected ways.
“The problem with object-oriented languages is they've got all this implicit environment
that they carry around with them. You wanted a banana but what you got was a gorilla
holding the banana and the entire jungle.”
—Joe Armstrong
Joe sums up basically the problem above, you end up with a base class messing in your entire jungle, and when your base class causes the flapping of a butterflies wings in that jungle, you might get a hurricane in Jacksonville. J
Now someone out there is saying "Hey, wait a minute! You didn't say anything at all about the real purpose of inheritance! It abstracts your implementation from your contract! Polymorphism allows type substitution, what about that??". Well you're half right, yes polymorphism allows generalized coding methods against contracts rather than concrete implementations. However contract implementation really isn't the same thing as subclassing. This is why you can in C# and Java implement multiple contracts but not multiple base classes. So I derive from this that, though the syntax for implementing a contract and a base class are often the same, that is merely for syntactic consistency. What is actually happening when implementing a contract is quite different from what is happening underneath when your class implements a base class. For a broader explanation and detailing of subclassing/subtyping dangers refer to http://okmij.org/ftp/Computation/Subtyping/Trouble.html and http://okmij.org/ftp/Computation/Subtyping/Preventing-Trouble.html
This fellow explains a far more in depth understanding of the topic.
Please share your thoughts and point out where mine are lacking.
No comments:
Post a Comment