TypeScript - Abstract Classes

Hello there, aspiring programmers! Today, we're going to embark on an exciting journey into the world of TypeScript and explore one of its powerful features: Abstract Classes. Don't worry if you're new to programming; I'll guide you through this concept step by step, just as I've done for countless students over my years of teaching. So, let's dive in!

TypeScript - Abstract Classes

What are Abstract Classes?

Before we delve into the nitty-gritty of abstract classes, let's start with a simple analogy. Imagine you're at a car dealership, and you see a sign that says "Vehicle." Now, you can't actually buy a "Vehicle" because it's too general. You need to choose a specific type of vehicle, like a car, truck, or motorcycle. In programming, an abstract class is like that general "Vehicle" concept - it's a blueprint for other classes, but you can't create an object directly from it.

Abstract classes in TypeScript serve as base classes from which other classes can inherit. They may contain abstract methods (methods without a body) and concrete methods (methods with a body). The key thing to remember is that you cannot create an instance of an abstract class directly.

Creating Abstract Classes

Now, let's look at how we create an abstract class in TypeScript. We use the abstract keyword before the class keyword. Here's a basic structure:

abstract class ClassName {
    // Properties and methods go here
}

Abstract Methods

Abstract classes can have abstract methods. These are methods that are declared but don't have an implementation in the abstract class. Any class that extends this abstract class must provide an implementation for these methods.

Concrete Methods

Abstract classes can also have concrete methods, which are fully implemented methods that can be inherited by child classes.

Example 1: The Shape Abstract Class

Let's create a simple example to illustrate how abstract classes work. We'll create an abstract Shape class with an abstract method calculateArea().

abstract class Shape {
    color: string;

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

    abstract calculateArea(): number;

    displayColor(): void {
        console.log(`This shape is ${this.color}`);
    }
}

In this example:

  • Shape is our abstract class.
  • color is a property that all shapes will have.
  • calculateArea() is an abstract method. Notice it doesn't have a body, just a declaration.
  • displayColor() is a concrete method that all shapes can use as-is.

Now, let's create some specific shapes that extend our Shape class:

class Circle extends Shape {
    radius: number;

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

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

class Rectangle extends Shape {
    width: number;
    height: number;

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

    calculateArea(): number {
        return this.width * this.height;
    }
}

Now we can use these classes:

const circle = new Circle("red", 5);
console.log(circle.calculateArea()); // Output: 78.53981633974483
circle.displayColor(); // Output: This shape is red

const rectangle = new Rectangle("blue", 4, 6);
console.log(rectangle.calculateArea()); // Output: 24
rectangle.displayColor(); // Output: This shape is blue

In this example, both Circle and Rectangle extend the Shape class and provide their own implementation of the calculateArea() method. They also inherit the displayColor() method from the Shape class.

Example 2: The Animal Abstract Class

Let's create another example to reinforce our understanding. This time, we'll create an abstract Animal class:

abstract class Animal {
    name: string;

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

    abstract makeSound(): void;

    move(distance: number = 0): void {
        console.log(`${this.name} moved ${distance} meters.`);
    }
}

class Dog extends Animal {
    constructor(name: string) {
        super(name);
    }

    makeSound(): void {
        console.log("Woof! Woof!");
    }
}

class Cat extends Animal {
    constructor(name: string) {
        super(name);
    }

    makeSound(): void {
        console.log("Meow!");
    }
}

Let's use these classes:

const dog = new Dog("Buddy");
dog.makeSound(); // Output: Woof! Woof!
dog.move(10); // Output: Buddy moved 10 meters.

const cat = new Cat("Whiskers");
cat.makeSound(); // Output: Meow!
cat.move(5); // Output: Whiskers moved 5 meters.

In this example, Animal is our abstract class with an abstract makeSound() method and a concrete move() method. Dog and Cat extend Animal and provide their own implementation of makeSound().

Why Use Abstract Classes?

You might be wondering, "Why go through all this trouble? Why not just use regular classes?" Well, abstract classes are incredibly useful when you want to define a common interface for a set of related classes. They allow you to:

  1. Define a common structure for a group of related classes.
  2. Enforce certain methods to be implemented by child classes.
  3. Provide some common functionality that all child classes can use.

Think of it as creating a template or a contract that other classes must follow. It's a way of ensuring consistency across related classes while still allowing for customization where needed.

Methods in Abstract Classes

Here's a table summarizing the types of methods you can have in abstract classes:

Method Type Description Can be called on abstract class? Must be implemented by child class?
Abstract Method Declared without implementation No Yes
Concrete Method Fully implemented method Yes No (can be overridden)

Conclusion

And there you have it, folks! We've journeyed through the land of abstract classes in TypeScript. From understanding what they are, to creating them, to seeing them in action with real-world examples, you've now got a solid foundation in this powerful TypeScript feature.

Remember, abstract classes are like the blueprints for a building. They provide the structure and some of the details, but it's up to the classes that extend them to fill in the specifics and bring them to life.

As you continue your programming journey, you'll find abstract classes to be incredibly useful tools in your TypeScript toolbox. They help you write cleaner, more organized, and more maintainable code. So go forth and abstract to your heart's content!

Happy coding, and until next time, keep exploring and keep learning!

Credits: Image by storyset