more about it being a crutch in general, which has led to bad software architecture in other languages.
These bad architectures were bad in other languages, but are possibly even worse in JS, and entirely unnecessary.
And yet, because we *can* write code like that, many people will try to do so.
The bad which I’m referring to now is deep hierarchy structures, which lead to rigid code, and unmaintainable code.
You don’t have to take it from just Crockford (just a JS guy [not really just JS]), you can take it from the Gang of Four (prefer composition over inheritance, as they talk about SmallTalk and C), or you can take it from Robert “Uncle Bob” Martin, if you want more Java street-cred. You can take it from Bjarne Stroustrup, the creator of C++, as well.
Deep hierarchies are terrible for code-reuse, and equally awful for flexibility and extensibility.
…and yet, it’s one of the first things we attempt to do, when given the chance.
It’s not actually the class keyword that Crockford is afraid of; really, it’s the extends keyword which follows class, because people just can’t be trusted to do the right thing.
Pre-es6, people spent years developing fake traditional class-constructors, simulating privates, statics overrides with supers, et cetera…
They faithfully reconstructed systems specifically to get themselves into trouble (and then complain about it).
What kind of trouble?
Well, just look at the Penguin and the Duck.
class Penguin {
}
class Duck {
}
Done. …right?
But they’re both Birds!
class Bird {}
class Penguin extends Bird { }
class Duck extends Bird { }
And Birds fly, right?
I don’t want to have to reimplement something like that for every single Bird out there, so for the sake of reuse, Bird implements fly.
class Bird {
fly () { }
}
class Duck extends Bird { }
// erm... Penguins can't fly...
// but they're obviously Birds
class Penguin extends Bird {
// maybe I can give it a fly and make it just... do nothing?
// maybe it can throw an error?
fly () {
throw new UnflyingBirdException();
// ps: never, ever, EVER do this
}
}
So that’s a terrible, terrible idea. We don’t want Penguin to even have a fly, but we have to, because Bird does, and Penguins are clearly Birds…
Worse, there are are some other birds out there which can’t fly, and maybe we have to support them, too…
Okay, so what if we make a FlyingBird, and it extends Bird, and then most of the other birds can inherit from FlyingBird, and then Penguins and Kiwis and the like can still be Birds.
Great!
class Bird {}
class FlyingBird extends Bird {
fly () { }
}
class Penguin extends Bird {}
class Duck extends FlyingBird {}
// SUCCESS! ........?
But we’re not really getting everything we should be out of this…
…Penguins are birds who can swim.
So we’ll have a FlyingBird and a SwimmingBird!
class Bird {}
class FlyingBird extends Bird {
fly () { }
}
class SwimmingBird extends Bird {
swim () { }
}
class Penguin extends SwimmingBird { } // !WOO!
class Duck extends .............?!? { } // ?????
Ducks Fly, and Ducks Swim, and Ducks are definitely, definitely Birds…
So do they inherit from FlyingBird?
Do they inherit from SwimmingBird?
Do I have to make a FlyingSwimmingBird, because if I do, then I have to rewrite fly and swim specifically for that particular class, and that’s code-duplication, which isn’t DRY at all. Especially seeing as it’s at the same hierarchy level, and not an override somewhere further down the tree.
So what does Duck inherit from now? Maybe just Bird, but that means that Duck has to have its own fly and its own swim, and any other type of Bird that does both will have to do the same thing, rewriting its own methods, which is even less DRY…
Now, let’s say you’re 6 months into a Java project which looks like this:
This is why traits are the way to go
Обсуждают сегодня