Class-based inheritance and the Weak Base Class problem
The introduction of the class
keyword in ES6 marked a significant shift in JavaScript, allowing developers to more naturally implement an object-oriented approach. With the extends
keyword, JavaScript introduced a streamlined way to inherit properties and methods between classes. However, this approach is not without its challenges, one of which is the Weak Base Class
problem.
Consider a scenario with three classes: Animal
, Bird
, and Penguin
. In this hierarchy, Animal
is the base class, Bird
extends Animal
, and Penguin
extends Bird
. This structure might initially seem straightforward - Penguin
inherits from Bird
, which in turn inherits from Animal
. However, this chain of inheritance can introduce complexity and unexpected behavior, as Penguin
implicitly inherits from Animal
through Bird
.
To better understand this, let's delve into a concrete example:
1class Animal {
2 constructor(name) {
3 this.name = name;
4 }
5
6 eat() {
7 console.log(`${this.name} is eating.`);
8 }
9}
10
11class Bird extends Animal {
12 constructor(name, kind) {
13 super(name);
14 this.kind = kind;
15 }
16
17 fly() {
18 console.log(`${this.name}, a ${this.kind}, is flying.`);
19 }
20}
21
22class Penguin extends Bird { // Here lies the Weak Base Class problem
23 constructor(name) {
24 super(name, "penguin");
25 }
26
27 swim() {
28 console.log(`${this.name} is swimming, not flying.`);
29 }
30}
In this example, Penguin
inherits from Bird
, which in turn inherits from Animal
. While this inheritance chain seems logical, it can lead to issues if the methods in Animal
are not compatible with the behaviors of Penguin
. For instance, if Bird
has a method fly()
, it's not relevant for a Penguin
, which can't fly.
To tackle this issue, developers might need to introduce an intermediate class or refactor the existing classes. For example, they could introduce a FlightlessBird
class between Bird
and Penguin
to better represent the behaviors of flightless birds.
xxxxxxxxxx
console.log(penguin.fly());
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name} is eating.`);
}
}
class Bird extends Animal {
constructor(name, kind) {
super(name);
this.kind = kind;
}
fly() {
console.log(`${this.name}, a ${this.kind}, is flying.`);
}
}
class Penguin extends Bird { // Here lies the Weak Base Class problem
constructor(name) {
super(name, "penguin");
}
swim() {
console.log(`${this.name} is swimming, not flying.`);
}