Topic 6.4
Abstract Classes
In one line
An abstract class is a half-finished blueprint: it fixes the steps and shares the finished parts, but leaves some members abstract for each subclass to fill in. You cannot build an object from it directly, and the compiler makes sure every subclass completes it.
Think of it like this
A form with blanks, like a school report-card template. The school prints the layout: the logo, the table, the signature line. Some boxes are already filled (the school name) and some are blank (the child's name, the marks). You cannot hand in the blank template as a report card. Each teacher fills in the blanks for one student. An abstract class is that template: finished parts plus blanks that every subclass must fill.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Abstract class
- A class that cannot be built directly and may contain members that subclasses must supply.
- Abstract member
- A method, property or accessor with no body, marked
abstract. Subclasses must implement it. - Concrete
- Fully implemented. A concrete class can be built with
new. - Template method
- A method in the base class that fixes the order of steps and calls pieces that subclasses provide.
- Abstract constructor type
- The type of an abstract class itself:
newis not allowed on it. - Lifecycle
- The fixed series of steps an object goes through, which the base class controls.
- Contract
- What a type promises: the members that must exist.
Step by step
01Fixed steps, blanks to fill
Report is abstract. title() and format are blanks. render() is finished and calls them, and body() has a default that a subclass may replace.
Sales fills format and title() and gets render() and body() for free.
abstract class Report {
abstract title(): string;
abstract readonly format: string;
render() { // template method: fixed steps
return "[" + this.format + "] " + this.title() + "\n" + this.body();
}
protected body() { // default a subclass may replace
return "(empty)";
}
}
class Sales extends Report {
format = "pdf";
title() {
return "Sales";
}
}02You cannot build the template itself
new Report() is rejected: an abstract class exists to be extended. This also protects you from calling a method that has no body.
03An incomplete subclass is caught
If Circle extends Shape forgets area(), the error is on the class name and names both the missing member and the class it came from. Add the method or make Circle abstract too.
abstract class Shape {
abstract area(): number;
abstract name: string;
}
class Circle extends Shape { // error TS2515: does not implement inherited abstract member area
name = "circle";
}04Abstract constructors in function arguments
A function that does new ctor() needs a concrete class. typeof Shape is abstract, so passing Shape is rejected; passing Sq (a concrete subclass) works. If your function only needs the class for instanceof or static members, type it as abstract new () => Shape.
05Abstract class or interface
Reach for an interface when many unrelated classes just need the same shape. Reach for an abstract class when subclasses share real code and the base class should control the order of steps.
Try it yourself
- 1
Complete the circle
In the second example, add
area() { return 3 * this.r * this.r; }toCircle. Predict the Types panel, then addconsole.log(new Circle(2).describe())and predict the output.Clean, and it prints
circle area 12.0. - 2
Override the default body
In the first example add
protected body() { return "42 sales"; }insideSalesand predict the second console line. Then addoverridein front of it (it works because the parent has abody). - 3
Make Circle abstract instead
Put
abstractin front ofclass Circlein the second example. Is the error gone? Trynew Circle(1).The class error disappears (an abstract class can leave blanks), and
new Circle(1)isTS2511.
Code & diagrams
Sales fills the two blanks and inherits render(). The last line is the compile error TS2511. The program still runs (it prints the report) because abstract only exists in the checker; the emitted class is an ordinary class.
Console
(nothing was printed)Expected console output
[pdf] Sales
(empty)Circle never wrote area(), so the error sits on its class line. Rect is complete and prints. Add area() { return 3 * this.r * this.r; } to Circle to fix.
Console
(nothing was printed)Expected console output
rect area 6.0make needs to run new, so only concrete classes qualify. describe only reads the class name, so abstract new () => Shape accepts abstract and concrete classes.
Console
(nothing was printed)Expected console output
4
Shape SqBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Instantiate the template
In the first example, keep the line const bad = new Report(); at the end.
Myth vs fact
Myth
abstract means the class is empty.
Fact
An abstract class often has plenty of real code. It is abstract because it leaves some members for subclasses, and so cannot be built alone.
Myth
Abstract classes and interfaces are the same thing.
Fact
An interface is erased and holds no code. An abstract class exists at runtime, can hold code and state, and a class can extend only one.
Myth
abstract is enforced when the program runs.
Fact
It is a TypeScript-only check. The emitted JavaScript is a normal class, and untyped code can still call new on it.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
abstractis erased: nothing in the emitted JavaScript stopsnew Report(), and abstract members produce no code. If you need a runtime guard (for code that may be called from plain JavaScript), addif (new.target === Report) throw new Error(...)in the constructor. - ▸
A subclass can implement an abstract property with a plain field (
format = "pdf", the usual case) or with a getter, as long as the types agree. - ▸
Calling an abstract method from the abstract class's constructor compiles but is a bug waiting to happen, because subclass fields are not set yet (Topic 6.3). TypeScript reports
TS2715: Abstract property 'x' in class 'C' cannot be accessed in the constructorfor abstract properties; abstract methods called there are your responsibility. - ▸
abstract new () => T(an abstract construct signature) was added in TypeScript 4.2. It lets mixins and factories accept abstract classes safely.
Remember this
- 1
The problem this solves: Sometimes a parent class has shared steps but can't finish the job itself, and a subclass could forget to supply the missing part. An abstract class makes the compiler demand it. You write
abstract class Report { ... }. An abstract member has no body and the wordabstractin front (abstract title(): string;). It is a promise: "every real subclass will provide this". A normal (concrete) member in the same class has a body and is inherited as is. An abstract class may mix both. - 2
Two rules make this useful. First, you can't create an object from the abstract class itself:
new Report()is errorTS2511: Cannot create an instance of an abstract class. Second, a concrete subclass must implement every abstract member it inherits, or TypeScript reportsTS2515: Non-abstract class 'Circle' does not implement inherited abstract member area from class 'Shape'. A subclass that stays incomplete must itself be declaredabstract. - 3
The most useful pattern is the template method: the abstract class has a normal method (
render) that defines the steps of an algorithm and calls abstract or overridable pieces (title,body) in a fixed order. Subclasses fill in the pieces; nobody can change the order. Frameworks use this a lot: the base class owns the lifecycle, you supply the details. - 4
Abstract members can be methods, properties (
abstract readonly format: string;) and accessors (abstract get id(): string). They can bepublicorprotectedbut notprivate, because a private member can't be seen by the subclass that must implement it. Do not use abstract members in the abstract class's own constructor: the subclass has not run yet, the same order issue you saw in Topic 6.3 (the checker rejects reading an abstract property there, see the notes below). - 5
Abstract class or interface? Both describe what an object must have. An interface (Topic 3.2) is erased at runtime and holds no code; a class can implement many of them (Topic 6.5). An abstract class survives to runtime (so
instanceof Reportworks), can hold shared code and state, and can have constructors and non-public members, but a class can extend only one parent. Rule of thumb: use an interface for "can do X", and an abstract class when subclasses really share code and a lifecycle. - 6
Types for classes themselves matter here:
typeof Shapefor an abstract class is an abstract constructor type. You can't pass it where a function typenew () => Shapeis needed, because that type promisesnewworks. TypeScript reportsCannot assign an abstract constructor type to a non-abstract constructor type. To accept both concrete and abstract classes writeabstract new () => Shape(an abstract construct signature).
Explain it without notes
What is an abstract class and what does the compiler enforce about it?
What is the template method pattern and how does an abstract class support it?
When would you choose an abstract class over an interface?
Why can't you pass an abstract class to a parameter typed new () => Shape?
Practice
Write an abstract class Notifier with an abstract channel: string, an abstract send(message: string): void and a concrete notify(message: string) that logs the channel and then calls send. Add EmailNotifier.
Add a second subclass SmsNotifier that forgets to implement send. Write down the error.
Trade-offs
- ↔
An abstract class gives you shared code and a controlled lifecycle but uses up the single
extendsslot. An interface keeps the slot free but shares nothing. - ↔
Template methods make the order of steps hard to change by accident, but they hide control flow: readers must jump between base and child to follow one operation.
Done when you can
I can write an abstract class with abstract and concrete members.
I can explain
TS2511andTS2515.I can write a template method and explain why it is useful.
I can choose between an abstract class and an interface.
I can explain why
abstract new () => Texists.