abstract (typescript)

ref –

  • https://www.tutorialsteacher.com/typescript/abstract-class
  • https://stackoverflow.com/questions/260666/can-an-abstract-class-have-a-constructor?rq=1
  • https://www.typescripttutorial.net/typescript-tutorial/typescript-abstract-classes/
  • https://khalilstemmler.com/blogs/typescript/abstract-class/
  • https://khalilstemmler.com/blogs/typescript/abstract-class/

TypeScript – Abstract Class

Abstraction removes the need to implement all the low-level details upfront; instead, we can focus on the high-level and figure out the specifics later.

Abstract classes are mainly for inheritance where other children classes derive from parent classes.

An abstract class is typically used to:

  • define common behaviors for derived classes to extend
  • cannot be instantiated directly (although regularly parent class can be instantiated)
  • An abstract method does not contain implementation. It only defines the signature of the method without including the method body, which the child class MUST define

We deal primarily with two types of classes in object-oriented programming languages: concretions and abstractions.

Concrete classes are real, and we can construct objects from them to use at runtime using the new keyword. They’re what most of us end up using when we first start learning object-oriented programming.

On the other hand, abstractions are blueprints or contracts that specify the properties and methods that a concretion should have. We can use them to contractualize the valid structure of an object or a class.

another example:

In the previous examples, we used the interface keyword to create abstractions. However, when we speak of abstractions in object-oriented programming, we’re generally referring to one of two tools:

  • interfaces (i.e.: the interface keyword) or
  • abstract classes (i.e.: the abstract keyword in front of a class)

We want to focus on defining a contract containing all the necessary things that a subtype needs to provide if they want it to work with the base type — and leave it to developers to implement them?

This satisifes:

Liskov Substitution Principle — since we define valid subtypes, each implementation should work and be interchangeable as long as it implements the contract.

Dependency Inversion — we do not directly depend on the concretions; instead, we rely on the abstraction that the concretions depend on; this keeps our core code unit testable.

Defining common properties

Within this Book abstraction, we can then decide on the contract for a Book. Let us say that all Book subclasses must have author and title properties. We can define them as instance variables and accept them as input using the abstract class constructor.

Defining common logic

We can then can place some of the common logic within the Book abstract class using regular methods.

Remember that an abstract class is an abstraction comparable to an interface. We can’t instantiate it directly. Trying anyway for demonstration purposes, we’ll notice that we get an error that looks like this:

We’ll introduce one of our specific types of Book concretions — the PDF class. We extend/inherit the abstract Book class as a new subclass to hook it up.

Since PDF is a concrete class, we can instantiate it. The PDF object has all of the properties and methods of the Book abstraction, so we can call getBookTitle and getBookAuthor as if it were originally declared on the PDF class.

Defining mandatory methods

Abstract classes have one last important feature: the notion of the abstract method. Abstract methods are methods that we must define on any implementing subclass.

In the abstract class, we define them like so:

Notice that there is no implementation for the abstract method? That’s because we implement it on the subclass.

Demonstrating with the PDF and an additional EPUB subclass, we add the required abstract method to both of them.

Failing to implement the required abstract methods will fail to make the class complete and concrete — this means we’ll run into errors when trying to compile our code or create instances.

Use cases (when to use abstract classes)

Now that we know the mechanics behind how abstract classes work, let’s talk about real-life use cases for it — scenarios you are likely to encounter.

There are two primary use cases for needing to use abstract classes:

  • Sharing common behavior
  • Template method pattern (framework hook methods)

Sharing Common Behavior

Let’s say that we definitely need to fetch data from:

a Users API located at example.com/users
a Reviews API located at example.com/reviews

What’s the common logic?

Setting up an HTTP library (like Axios)
Setting up interceptors to set up common refetching logic (say, like when an access token expires)
Performing HTTP methods

Here, we’ve placed the low-level behavior within a BaseAPI abstraction. Now we can define the high-level behavior — the rich stuff — in the subclasses.

So now we can get all sort of data whether it us Users, Reviewers…etc.

Template Method Pattern ( framework hooking )

So then, a simplistic version of the abstract class for a component could look like the following:

And then to use it, we’d need to extend the Component abstraction and implement the render method.

We need to implement the render method because it is a critical part of the work involved with deciding what gets created on screen.

An abstract class is a suitable tool for this problem is because there is an algorithm running behind the scenes — an algorithm in which the render step is but a single step amongst many.

We can see that there are three distinct phases to React: mounting, updating, and unmounting. React’s abstract class further gives us (the client developer) the ability to customize our components by connecting the ability to hook into various lifecycle events. For example, you can perform some behavior as soon as your component mounts (componentDidMount), when it updates (componentDidUpdate), and when it’s about to unmount (componentWillUnmount).

so the algorithm may look like this:

And now, a customized component subclass can plug in behavior within those key lifecycle hook methods. This is the concept of Inversion of Control.

So as a framework designer, the Template Method design pattern is attractive when:

  • You need the client to implement a step of the algorithm → so you make that step an abstract method
    You want to provide the ability for clients to customize behavior at various steps of the algorithm → so you expose optional lifecycle methods to the client

Supplementals

The following abstract class declares one abstract method find and also includes a normal method display.

In the above example, Person is an abstract class which includes one property and two methods, one of which is declared as abstract.

The find() method is an abstract method and so must be defined in the derived class. In other words, the Employee class derives from the Person class and so it must define the find() method as abstract.

The Employee class must implement all the abstract methods of the Person class, otherwise the compiler will show an error.

Note:
The class which implements an abstract class must call super() in the constructor.