Welcome to Abstraction

Welcome back! Previously, you explored polymorphism and how it empowers you to create flexible code structures using inheritance. In this session, we will take a step further into a crucial aspect of object-oriented programming: Abstraction.

Understanding Abstraction in TypeScript

TypeScript provides native support for abstraction through the use of the abstract keyword. Abstract classes in TypeScript allow you to define base classes that cannot be instantiated directly. These classes can include abstract methods — methods without an implementation — which must be implemented by any derived (sub) class. This enforces a contract for subclasses, ensuring that certain methods are always present and implemented.

By leveraging abstract classes and methods, you can create clear and maintainable code structures, encouraging consistency and reusability across your codebase.

1. Declaring Abstract Classes and Methods

In TypeScript, you can declare an abstract class using the abstract keyword. Abstract methods are also marked with abstract and do not include an implementation in the base class. Here’s how you can define an abstract class with abstract methods:

// Define an abstract class Shape
abstract class Shape {
    protected color: string;

    constructor(color: string) {
        this.color = color;
    }

    // Abstract methods: must be implemented by subclasses
    abstract area(): number;
    abstract perimeter(): number;

    // Concrete method: can be used by all subclasses
    getColor(): string {
        return this.color;
    }
}

In this example, the Shape class is abstract and cannot be instantiated directly. The area and perimeter methods are declared as abstract, meaning any subclass of Shape must provide its own implementations. The getColor method is a concrete method that can be used by all subclasses.

2. Implementing the Methods in Derived Classes

Let’s create concrete classes that extend the abstract Shape class. Each subclass must implement the abstract methods defined in the base class.

Circle Class

// Define a Circle class that extends Shape
class Circle extends Shape {
    private radius: number;

    constructor(radius: number, color: string) {
        super(color);
        this.radius = radius;
    }

    // Implement the abstract methods
    area(): number {
        return Math.PI * this.radius * this.radius;
    }

    perimeter(): number {
        return 2 * Math.PI * this.radius;
    }
}

The Circle class extends Shape and provides concrete implementations for the area and perimeter methods. The constructor initializes the circle’s radius and color, passing the color to the base class constructor using super.

Rectangle Class
// Define a Rectangle class that extends Shape
class Rectangle extends Shape {
    private width: number;
    private height: number;

    constructor(width: number, height: number, color: string) {
        super(color);
        this.width = width;
        this.height = height;
    }

    // Implement the abstract methods
    area(): number {
        return this.width * this.height;
    }

    perimeter(): number {
        return 2 * (this.width + this.height);
    }
}

Similarly, the Rectangle class extends Shape, implementing the required area and perimeter methods and initializing its own attributes through the constructor.

3. Using the Base Class and Derived Classes
Abstract Classes vs. Interfaces in TypeScript
  • Abstract Classes

    • Can include both method implementations and abstract methods.
    • Can have fields, constructors, and visibility modifiers.
    • Use when you want to share code or state among related classes.
  • Interfaces

    • Only define method and property signatures—no implementation or state.
    • Use when you want to define a contract for any class to implement, regardless of its place in the hierarchy.

Summary:
Use an abstract class for shared code and enforced structure among related classes. Use an interface to define a contract that can be implemented by any class.

Example:

interface Drawable {
    draw(): void;
}

abstract class Shape {
    abstract area(): number;
    describe(): string {
        return "This is a shape.";
    }
}

class Circle extends Shape implements Drawable {
    draw(): void { /* ... */ }
    area(): number { return 0; }
}
Why It Matters

Abstraction in TypeScript allows you to define clear contracts for your code, ensuring that all derived classes implement essential functionality. This leads to:

  1. More organized and readable code: Abstract classes and methods enforce consistent structures and behaviors.
  2. Greater code reusability: Shared functionality can be placed in base classes, reducing duplication.
  3. Enhanced flexibility and maintainability: You can extend and modify your codebase with confidence, knowing that essential methods are always present in subclasses.

By mastering abstraction in TypeScript, you are building a strong foundation for creating robust, scalable, and maintainable software systems.

Intrigued? Let's move on to the practice section and solidify these concepts together. You're on your way to becoming proficient in building sophisticated and maintainable systems with TypeScript!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal