Skip to content


 Zabbix
 Grafana
 Prometheus
 Threejs and TypeScript
 SocketIO and TypeScript
 Blender Topological Earth
 Sweet Home 3D
 Design Patterns Python
 Design Patterns TypeScript
   
 Course Coupon Codes
Three.js and TypeScript
Kindle Edition
$9.99 $14.99 Paperback 
$29.99 $34.99




Design Patterns in TypeScript
Kindle Edition
$9.99 $14.99 Paperback
$19.99 $24.99




Design Patterns in Python
Kindle Edition
$9.99 $14.99 Paperback
$19.99 $24.99




Abstract Classes

Video Lecture

Section Video Links
Abstract Classes Abstract Classes Abstract Classes 

Overview

You can extend classes with Abstract classes. Consider an abstract class as a base class. It is a class that may have methods and properties that are common, but another class can be created which extends from this base class and overrides any existing methods or can add additional methods and properties specific for itself.

It is different than an interface in the way that is not indicating rules that the class must follow, but the class that is extending will already have its own copies of the base classes properties and methods once any new object is instantiated using it.

Abstract Class Example 1

class Animal {
    name: string
    age: number

    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }

    feed(food: string, amount: number): void {
        console.log(
            'Feeding ' +
                this.name +
                ' the ' +
                this.constructor.name +
                ' ' +
                amount +
                ' kg of ' +
                food
        )
    }
}

class Cat extends Animal {}

class Dog extends Animal {}

const CAT = new Cat('Cosmo', 8)
const DOG = new Dog('Rusty', 12)
CAT.feed('Fish', 0.1)
DOG.feed('Beef', 0.25)

Replace ./src/test.ts with this code above, compile and execute it.

tsc -p ./src
node ./dist/test.js

Outputs

Feeding Cosmo the Cat, 0.1kg of Fish
Feeding Rusty the Dog, 0.25kg of Beef

Note that the Cat and Dog classes don't actually contain any properties, constructor or methods. They extend the Animal class so they have the properties and methods already.

If you look at the JavaScript compiled code, the extends keywords does exist. The JavaScript extends keyword was introduced to the JavaScript language in the ES6/ES2015 updates.

As an exercise, change the target parameter in your tsconfig.json to ES3, eg,

{
    "compilerOptions": {
        "strict": true,
        "target": "ES3",
        "module": "CommonJS",
        "outDir": "../dist",
        "rootDir": "./",
        "moduleResolution": "node"
    },
    "include": ["**/*.ts"]
}

and then look at the compiled JavaScript in ./dist/test.js and you will see that it is significantly different code and much harder to read.

Once finished change the target back to ES2015. Note that you can also set it to ES6, since ES6 and ES2015 are the same.

Also note in the above example, in the feed method, the difference in the output of this.name and this.constructor.name. Since the CAT and DOG objects used their own Cat and Dog classes, the constructor's name of the base class (Animal) was also updated for those specific instances.

Abstract Class Example 2

In example 1, the Cat and Dog properties, methods and constructors were created automatically behind the scenes. It was unnecessary to create any different versions in the extending classes. If you want a custom constructor and/or method, then you can override them. Look at the Cat class below.

class Animal {
    name: string
    age: number

    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }

    feed(food: string, amount: number): void {
        console.log(
            'Feeding ' +
                this.name +
                ' the ' +
                this.constructor.name +
                ' ' +
                amount +
                ' kg of ' +
                food
        )
    }
}

class Cat extends Animal {
    isHungry: boolean
    constructor(name: string, age: number, isHungry: boolean) {
        super(name, age)
        this.isHungry = isHungry
    }

    feed(food: string, amount: number): void {
        if (this.isHungry) {
            super.feed(food, amount)
        } else {
            console.log(
                this.name +
                    ' the ' +
                    this.constructor.name +
                    ' is not hungry'
            )
        }
    }
}

class Dog extends Animal {}

const CAT = new Cat('Cosmo', 8, false)
const DOG = new Dog('Rusty', 12)
CAT.feed('Fish', 0.1)
DOG.feed('Beef', 0.25)

Replace ./src/test.ts with this code above, compile and execute it.

tsc -p ./src
node ./dist/test.js

Outputs

Cosmo the Cat is not hungry
Feeding Rusty the Dog 0.25 kg of Beef

See how the Cat class has it's own overridden constructor, and created a new variable that it can use called isHungry.

Also note how the constructor also calls the super() method along with any parameters. The super() method refers to the constructor of the base class.

In extending classes, it is compulsory to call the base classes super() method in the constructor otherwise you get an error,

Constructors for derived classes must contain a 'super' call

In the overridden feed method, I have called the super.feed(food, amount) method. This is calling the base classes feed method. It is not compulsory to implement any super calls in the overridden methods if you don't need to. If the cat is not hungry, then the base classes (Animal) feed method wasn't called.

Abstract Class Example 3

Be aware if overriding any properties in the extended classes. They will have preference over the equivalent properties in the base class. In the below example I have declared any instance of Cat to default with the name Emmy. Despite then initializing a new the Cat with the name of Cosmo, the name when printed still remained overridden as Emmy.

class Animal {
    name: string
    age: number

    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }

    feed(food: string, amount: number): void {
        console.log(
            'Feeding ' +
                this.name +
                ' the ' +
                this.constructor.name +
                ' ' +
                amount +
                ' kg of ' +
                food
        )
    }
}

class Cat extends Animal {
    isHungry: boolean
    name = 'Emmy'
    constructor(name: string, age: number, isHungry: boolean) {
        super(name, age)
        this.isHungry = isHungry
    }

    feed(food: string, amount: number): void {
        if (this.isHungry) {
            super.feed(food, amount)
        } else {
            console.log(
                this.name +
                    ' the ' +
                    this.constructor.name +
                    ' is not hungry'
            )
        }
    }
}

class Dog extends Animal {}

const CAT = new Cat('Cosmo', 8, false)
const DOG = new Dog('Rusty', 12)
CAT.feed('Fish', 0.1)
DOG.feed('Beef', 0.25)

Outputs

Emmy the Cat is not hungry
Feeding Rusty the Dog 0.25 kg of Beef

If I wanted to use the new name Cosmo to override the predefined Emmy in the extending class, I would need to set it again in the Cats constructor.

class Animal {
    name: string
    age: number

    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }

    feed(food: string, amount: number): void {
        console.log(
            'Feeding ' +
                this.name +
                ' the ' +
                this.constructor.name +
                ' ' +
                amount +
                ' kg of ' +
                food
        )
    }
}

class Cat extends Animal {
    isHungry: boolean
    name = 'Emmy'
    constructor(name: string, age: number, isHungry: boolean) {
        super(name, age)
        this.isHungry = isHungry
        this.name = name
    }

    feed(food: string, amount: number): void {
        if (this.isHungry) {
            super.feed(food, amount)
        } else {
            console.log(
                this.name +
                    ' the ' +
                    this.constructor.name +
                    ' is not hungry'
            )
        }
    }
}

class Dog extends Animal {}

const CAT = new Cat('Cosmo', 8, false)
const DOG = new Dog('Rusty', 12)
CAT.feed('Fish', 0.1)
DOG.feed('Beef', 0.25)

Outputs

Cosmo the Cat is not hungry
Feeding Rusty the Dog 0.25 kg of Beef