abstract and implements
An architecture firm has a standard house design. Everything in it is drawn except one room: in its place it says "design it yourself". You cannot build from such a design: the inspector will not accept a house with an empty spot. First the architect draws the room in, and only then is a house built from the finished blueprint.
The inspector has another document too, a list of requirements: "the house must have a way out onto the roof". He checks a finished blueprint against this list, but the list itself draws nothing. The architect draws every item himself.
In TypeScript the standard design is an abstract class, and the list of requirements is an interface in implements.
An abstract class
An abstract class is a class with the keyword abstract from which no object can be created. It may leave methods without a body, and then every subclass must write them.
abstract class Shape {
abstract area(): number;
describe(): string {
return `area ${this.area().toFixed(1)}`;
}
}
class Square extends Shape {
side = 2;
override area(): number {
return this.side ** 2;
}
}
console.log(new Square().describe()); // area 4.0Let's go through the example line by line:
abstract class Shapeis the standard design: noShapeobject can be created.abstract area(): number;is a method without a body, that very empty room. Its result type is already set.describeis a finished method that every subclass gets. It callsarea, even thoughareahas no body yet.Squarewritesarea, and an object can now be created from it.- The program prints
area 4.0.
There are two errors tsc catches. The call new Shape():
error TS2511: Cannot create an instance of an abstract class.A subclass that did not write area, for example class Circle extends Shape {}:
error TS2515: Non-abstract class 'Circle' does not implement inherited abstract member area from class 'Shape'.implements
The keyword implements after a class's name checks the class against an interface: everything the interface requires must be in the class.
interface Printable {
print(): string;
}
class Invoice implements Printable {
total = 100;
print(): string {
return `Total: ${this.total}`;
}
}
const doc: Printable = new Invoice();
console.log(doc.print()); // Total: 100Printablerequires a methodprintthat returns a string.class Invoice implements Printablepromises that the class has such a method, andtscchecks it.- The interface does not require the field
total, and the field does not get in its way. - The program prints
Total: 100.
If you remove the method print from Invoice, tsc reports an error, and the second line of the message names what is missing:
error TS2420: Class 'Invoice' incorrectly implements interface 'Printable'.
Property 'print' is missing in type 'Invoice' but required in type 'Printable'.A class can have only one base class through extends, but any number of interfaces in implements, separated by commas: class Invoice implements Printable, Payable.
implements gives nothing
implements only checks. It adds not a single field or method to the class, and in the JavaScript not a word of implements Printable is left.
Here the inspector picture stops working. An inspector checks a blueprint only against the list attached to it, while TypeScript accepts an Invoice wherever a Printable is expected even without implements: it compares the shape. implements is there so that an error shows up in the class itself, not where its object is passed on.
Nothing of the word abstract is left in the JavaScript either: Shape is an ordinary class there, and it has no method area at all. Only tsc keeps you from creating a Shape.
Interviewers often ask how an abstract class differs from an interface. The answer:
- an abstract class can carry finished code and fields, like the method
describe, and a subclass gets them throughextends; - an interface only describes a shape and gives the class nothing;
- a class has one base class, while it can implement any number of interfaces.
Common mistake
Expecting implements to pass parameter types to the class:
interface Printable {
print(copies: number): string;
}
class Invoice implements Printable {
print(copies) { // ✗
return `${copies} copies`;
}
}error TS7006: Parameter 'copies' implicitly has an 'any' type.The interface knows that copies is a number, but implements passes nothing into the class, not even types. A parameter without a type gets the type any, and strict mode does not allow that. Write the type in the method itself: print(copies: number): string.
Key points
- No object can be created from an abstract class (
TS2511), and every subclass must write itsabstractmethods (TS2515). implementschecks that the class has everything the interface requires, otherwise you get errorTS2420.implementsadds nothing to the class, not even parameter types, and nothing of it is left in the JavaScript.- A class has one base class, but any number of interfaces in
implements.