What an abstract class is
An abstract class is a half-finished template. It captures behavior shared by a family of classes without committing to one concrete version. Every Animal can speak(), for example, but how it speaks depends on whether it is a dog or a cat. The abstract class declares that every animal speaks, and the concrete subclasses supply the sound.
Languages with full support give an abstract class two properties:
- You can't create an instance of it directly. Writing
new Animal()is rejected, and you must pick a concrete subclass. - Every subclass must implement each abstract method, or the compiler refuses the program. The abstract class is a contract that is enforced.
An abstract class can also have ordinary methods with bodies. Subclasses inherit those like any other inherited method, and only the abstract methods have to be overridden.
JavaScript has no abstract keyword
C# and Java mark classes and methods with abstract. Python uses abc.ABC, and C++ uses pure virtual methods. JavaScript has nothing built in. The usual convention is a parent class whose method throws an error, so a subclass that forgets to override it fails as soon as the method is called:
class Animal {
speak() { throw new Error("Not implemented"); }
}
class Dog extends Animal {
speak() { console.log("Woof"); }
}
The error is a way to say "a subclass must override this". It is a runtime check, which means you find the mistake when the code runs, not when you write it. Both of these are valid JavaScript:
class Fish extends Animal {}
new Dog().speak(); // Woof
new Fish().speak(); // Error: Not implemented
Fish forgot to implement speak, and the program only fails at the call. Note also that new Animal() still works, because nothing prevents it; only calling its speak() throws.
Blocking direct instantiation
If you also want new Animal() to fail, check new.target in the constructor. Inside a constructor, new.target is the class that new was applied to:
class Shape {
constructor() {
if (new.target === Shape) {
throw new TypeError("Shape is abstract");
}
}
area() {
throw new Error(this.constructor.name + " must implement area()");
}
describe() {
return this.constructor.name + " with area " + this.area();
}
}
class Square extends Shape {
constructor(side) {
super();
this.side = side;
}
area() { return this.side * this.side; }
}
console.log(new Square(3).describe()); // Square with area 9
new Shape(); // TypeError: Shape is abstract
Here describe() is a concrete method shared by every subclass, and it calls area(), which each subclass must provide. A subclass that lacks area() fails with the message "Blob must implement area()" when describe() runs, naming the offending class through this.constructor.name.
The constructor can also catch a missing override earlier, at creation time instead of at the call. Comparing this.area with Shape.prototype.area shows whether the subclass replaced the method, and the constructor can throw if it didn't. That makes a forgotten method fail the moment the object is built.
The exercise, step by step
The starter code defines Animal with a throwing speak, and ends with let rex = new Dog(); rex.speak();. Dog doesn't exist yet.
- Create the concrete subclass. Below
Animal, writeclass Dog extends Animal { }. - Implement the method. Inside
Dog, writespeak()and make it log"Woof". This replaces the parent's version, so the throwing code never runs for dogs. Without the override,rex.speak()would end the program with the "Not implemented" error.
The solution in JavaScript
class Animal {
speak() { throw new Error("Not implemented"); }
}
class Dog extends Animal {
speak() {
console.log("Woof");
}
}
let rex = new Dog();
rex.speak();
The output is:
Woof
Common mistakes
- Expecting the language to complain early. Nothing is checked when the class is defined. A missing override shows up only when the method is called, so test each subclass.
- Misspelling the method in the subclass.
Speak()orspeek()is a new method, and callingspeak()still throws the parent's error. - Forgetting
extends. ADogthat doesn't extendAnimalhas its ownspeak, but it isn't anAnimal:rex instanceof Animalisfalse. - Using
new.targetwithoutsuper(). A subclass constructor that has to run the parent's check must callsuper()before usingthis. - Swallowing the error. Wrapping calls in a
try/catchthat ignores errors hides the very message that tells you a method is missing.
Key takeaways
- An abstract class is a template with methods that subclasses must supply.
- JavaScript has no
abstractkeyword; by convention the parent method throws an error. - A subclass makes the method concrete by overriding it.
- Checking
new.targetin the constructor can also prevent creating the abstract class itself.