extends Bird {
swim () {}
}
class Duck extends WaterFowl {}
class MallardDuck extends Duck {}
class MottledDuck extends Duck {}
class BlackDuck extends Duck {}
class PekinDuck extends Duck {}
class Swan extends WaterFowl {}
// ...
class Goose extends WaterFowl {}
// ...
class GameFowl extends Bird {}
class Pheasant extends GameFowl {}
class Quail extends GameFowl {}
class Partridge extends GameFowl {}
Your system is amazing, all of your pieces fit together perfectly, and all of your classes are super-tiny, because they’re all inheriting the functionality they should be, all the way up the tree.
6 months in, your manager asks you for two new birds:
Emu
Cormorants
What now?
Keeping in mind that practically all Cormorants fly, and feed by diving.
…practically all…
One sub-species of Cormorants, Flightless Cormorants can not fly (as the name might suggest).
You already have a taxonomy of hundreds of classes, in an intricately laid out system.
And your type-system in your code is working perfectly. Just by genus, or by family, or what have you, all of the branching subclasses of that root class (itself a branch of some other thing) can pass right through…
…now what’s going to happen to your taxonomy… the topology of your tree…
…how easy it will be to trust that once your tree gets shuffled and lopsided, that things flow through all of your typed method calls as easily as before? Will you eventually find yourself having to write methods like
public Boolean checkCollision (Bird a, Bird b) { /* ... */ }
What kind of bird? It might be really easy to check if one is flying and the other can’t fly… …but that requires crazy overrides, and basically everything can flow through because you’re just checking “Is it an Object?” in order to accomplish things in your type system, now.
This is frequently how game engines end up.
Everything is an instance of some descendant of GameObject so that worst case, your data can still be passed around.
So if this is the BAD way to look at things, what’s the better way?
Traits (Mix-Ins).
Languages like C++, Go, Scala (and of course, JS) allow you to define multiple sets of functionality to extend onto yourself.
What if, instead of this hierarchy, you had multiple objects with methods that you borrowed, instead?
// not valid JS
trait Flyer {
fly () { }
}
trait Swimmer {
swim () { }
}
class Bird {}
class Duck extends Bird, Swimmer, Flyer { }
class Penguin extends Bird, Swimmer { }
class Emu extends Bird { }
class GoldenRetriever extends Dog, Swimmer { }
Now it all takes care of itself.
We are composing our objects, rather than inheriting deeply.
Our type system now cares about things which we say our object can do, not what we say our object’s ancestry is.
If I had every skill of all of my ancestors, I’d be unstoppable.
More to the point, now my kid and I can have different strengths and weaknesses, and its okay.
When Crockford gives talks these days, talking about “Parasitic Inheritance” or something of the sort, he’s really talking about mix-ins/traits (without any keyword support for them in JS, unlike classes).
FunFunFunction’s mpjme has a great solution to my Duck+Penguin problem, or what he refers to as a MurderRobotDog.
I hope this helps you understand why people are wary around “class”.
It’s not that it can’t be used well, and can’t be used responsibly…
…it’s just that the vast majority won’t, and then they’ll complain about it, when their stuff breaks.
The last part there, about traits
Dude put this on theDevs blog
Обсуждают сегодня