TypeScript abstract class

In Mobile App Development


Abstract classes are classes defined with the abstract keyword. They are much like regular classes except that we cannot directly create an object of an abstract class using its constructor.
alt
Editorial Commitee Qualified.One,
Management
alt

While all novice developers when thinking about interfaces ask "when and why use them", when thinking about abstract classes they are joined by "how they differ from interfaces and when a particular design is preferable". The answers to these questions can be found in this chapter, but it's worth looking at the general characteristics first.


        abstract class Figure {
}
 
// let someFigure = new Figure()    // Error!

As a rule, abstract classes describe entities that in reality have no concrete embodiment. For example, a geometric figure may represent a circle, a square, a triangle, but there is no geometric figure as such. There are specific shapes that we work with. At the same time, all shapes can have some common functionality. In that case, we can define an abstract shape class, put the common functionality into it and inherit classes of concrete geometric shapes:


abstract class Figure {
    getArea(): void{
        console.log("Not Implemented")
    }
}
class Rectangle extends Figure{
     
    constructor(public width: number, public height: number){ 
        super();
    }
     
    getArea(): void{
        let square = this.width * this.height;
        console.log("area =", square);
    }
}
 
let someFigure: Figure = new Rectangle(20, 30)
someFigure.getArea();   // area = 600

In this case the abstract class defines a getArea() method that calculates the area of the shape. The rectangle class defines its own implementation for this method.

TypeScript abstract class: abstracy methods

However in this case the getArea method in the base class does not perform any useful work, because an abstract shape cannot have an area. And in this case it is better to define such method as abstract:


abstract class Figure {
    abstract getArea(): void;
}
class Rectangle extends Figure{
     
    constructor(public width: number, public height: number){ 
        super();
    }
     
    getArea(): void{
        let square = this.width * this.height;
        console.log("area =", square);
    }
}
 
let someFigure: Figure = new Rectangle(20, 30)
someFigure.getArea();

An abstract method does not define any implementation. If a class contains abstract methods, such a class must be abstract. In addition, when inheriting, derived classes must implement all abstract methods.

Abstract fields in TypeScript abstract class

An abstract class may also have abstract fields, i.e. fields defined with the abstract modifier. When inheriting, the derived class also must provide an implementation for them:


abstract class Figure {
    abstract x: number;
    abstract y: number;
    abstract getArea(): void;
}
class Rectangle extends Figure{
    //x: number;
    //y: number;
     
    constructor(public x: number, public y: number, public width: number, public height: number){ 
        super();
    }
     
    getArea(): void{
        let square = this.width * this.height;
        console.log("area =", square);
    }
}

In this case, the Figure class defines two abstract fields x and y, which conventionally represent the start point of the figure:


abstract x: number;
abstract y: number;

The Rectangle class provides an implementation for them by defining the fields via constructor parameters:


constructor(public x: number, public y: number, public width: number, public height: number)

By and large the abstract fields don't make much sense here, but TypeScript still allows their use.

TypeScript abstract class - theory

Now it's time to understand the theory of abstract classes, and specifically to answer questions that may arise when developing programmes.

Interface or TypeScript abstract class is a frequent question, the answer to which is not always obvious. In reality, they are completely different constructs, both in terms of implementation and ideology. Interfaces are designed to describe a public api, which serves to interface to a program. Furthermore, they should not, and in TypeScript cannot, implement the business logic of the part they represent. They are ideal candidates for implementing low coupling. The emphasis of program design should be precisely on interfaces.

TypeScript abstract classes, if needed, should implement interfaces to the same degree and for the same purpose as normal classes. They should definitely be used as a base type when a set of logically related classes share a common logic, the use of which in its pure form makes no sense. In other words, if the logic placed in a class cannot or must not be executed separately from its descendants, then the creation of instances of such classes must be prohibited.

For example, a TypeScript abstract class Animal that implements the IAnimal interface with two members: the isAlive property and the voice method, can and should implement the isAlive property, since this property has a predefined number of states (alive or dead) and cannot be different depending on the descendant. While the voice method will have a different implementation, depending on the offspring, as cats meow and crows caw.

Nevertheless, one may reasonably wonder why this functionality could not be implemented in a regular base class.

An abstract class is able to not only tell the architect that the entity in question is abstract to the subject area, i.e. not a separate part, but also to prevent the creation of an instance of the class whose operation could break the application.

Once again, the same thing, but in different words. Since a base class will implement the logic implied by the interfaces partitioned by the principle of interface separation, which will be used to interface with the rest of the program, it is possible for its instance to fall into places that imply logic not present in it. That is, high-level logic, unique to descendants, may be hidden behind a less specific interface, implemented by the base class itself. In order to avoid such run-time errors scenarios, instances of such classes must be prevented. (The principle of interface separation is covered in "Types - Interfaces").

Furthermore, an abstract class with abstract members will prevent the developer from forgetting to implement the necessary logic in the descendants.

But that's not all. The IAnimal interface will actually be a composite type. That is, it will belong to ILiveable type, describing isAlive property and IVoiceable type, describing voice method. You can't do this with a TypeScript abstract class, because a class can only extend one other class, whereas interfaces can extend many other interfaces and therefore belong to many data types at once. This is exactly what IAnimal interface demonstrates by extending ILiveable and IVoiceable interfaces.

Another frequent question is about replacing interfaces with TypeScript abstract classes. Technically, an abstract class consisting only of abstract members can fulfill the role ideologically given to an interface. But it's better to forget about it, because the interface is designed to describe the public part of the object.