Language: EN

que-es-una-clase-abstracta-en-programacion

What are and how to use abstract classes

An abstract class is a class that cannot be instantiated directly. That is, objects cannot be created directly from it.

To use an abstract class, it is necessary to inherit from it and provide a complete implementation for all the abstract methods defined in the abstract class.

Why would you want a class that cannot be instantiated? An abstract class is used to define a common structure that can be reused through inheritance, but is not ready to be used directly in the code.

Ultimately, abstract classes are used to model abstract or generic concepts that do not have a concrete implementation, but are the common base of other objects.

As you may have been a little like this 🤔 with the definition, let’s see it better with an everyday case.

Purpose of abstract classes

Imagine you walk into a car dealership. You approach one of the salespeople, and you have the following conversation:

- Hello, I would like to buy a car

+ Of course, what would you like to buy, a Sedan, a Coupe, a Berline?

- No no, I would like to buy a car “as a concept”

curso-poo-clase-abstracta

Depending on how the salesperson is feeling that day, they will explain more or less kindly that you cannot buy a car “as a concept”. You can buy a particular type of car.

But a car “as a concept” is an abstraction. It is an idea that captures the common characteristics of all types of cars. And ideas cannot be bought 🤷 (and then they would kick you out of the dealership).

In this case, the Car is an abstract class. It is an idea that represents everything that cars have in common. And it cannot be instantiated.

Then it is specialized in subclasses, such as Berline, Coupe, Sedan, which are specializations of Car, and can be instantiated.

Abstract methods and classes

Abstract methods

Abstract methods are declared methods, but do not have an implementation in the class where they are declared. That is, they are basically methods that have no “body”, they are “empty”.

By not having an implementation, no class that has an abstract method can be instantiated. Therefore, it immediately becomes an abstract class.

If we try to instantiate an abstract class, the compiler or the IDE will give us an error, saying that an abstract class cannot be instantiated.

Abstract classes

Abstract classes are those that cannot be instantiated directly, but are designed to be subclassed.

An abstract class can contain both abstract methods (without implementation) and concrete methods (with implementation).

But, as we have commented, if a class implements at least one abstract method (without implementation) the class must be abstract.

Practical example

Let’s see it with our little example of Car, which is an abstract class. Therefore, it cannot be instantiated directly.

// Abstract class representing a car
abstract class Car
{
	// Abstract method, without implementation (without body)
	void Drive();
}

// Trying to create an instance of the abstract class (this is not possible)
 Car abstractCar = new Car(); // This will give a compilation error

What we can do, however, is inherit from other classes that will provide a concrete implementation of their abstract methods.

// Class representing a Berline, derived from Car
class Berline extends Car
{
	void Drive()
	{
		Console.Log("Driving a Berline");
	}
}

// Class representing a Coupe, derived from Car
class Coupe extends Car
{
	void Drive()
	{
		Console.Log("Driving a Coupe");
	}
}

// Create instances of the derived classes
Car myBerline = new Berline();
Car myCoupe = new Coupe();

Examples in different languages

Let’s see examples of declaring abstract classes in different programming languages

Abstract classes in C# are defined using the abstract keyword. These classes can contain abstract methods, which must be implemented by derived classes.

public abstract class Figure
{
    // Abstract method
    public abstract double CalculateArea();

    // Concrete method
    public void Show()
    {
        Console.WriteLine("Showing figure");
    }
}

public class Circle : Figure
{
    public double Radius { get; set; }

    public override double CalculateArea()
    {
        return Math.PI * Radius * Radius;
    }
}

// Usage
Circle circle = new Circle { Radius = 5 };
Console.WriteLine(circle.CalculateArea());
circle.Show();

In C++, abstract classes are defined using at least one pure virtual function. A pure virtual function is defined with = 0 at the end of the function declaration.

#include <iostream>
#include <cmath>

class Figure {
public:
    // Abstract method
    virtual double CalculateArea() const = 0;

    // Concrete method
    void Show() const {
        std::cout << "Showing figure" << std::endl;
    }
};

class Circle : public Figure {
public:
    double Radius;

    double CalculateArea() const override {
        return M_PI * Radius * Radius;
    }
};

// Usage
int main() {
    Circle circle;
    circle.Radius = 5;
    std::cout << circle.CalculateArea() << std::endl;
    circle.Show();
    return 0;
}

JavaScript does not have native support for abstract classes, we can simulate them “more or less” with different techniques.

class Figure {
    // Optional constructor to initialize common properties
    constructor() {
        if (this.constructor === Figure) {
            throw new Error("An abstract class cannot be instantiated.");
        }
    }

    // Abstract method
    CalculateArea() {
        throw new Error("You must implement the abstract method CalculateArea");
    }

    // Concrete method
    Show() {
        console.log("Showing figure");
    }
}

class Circle extends Figure {
    constructor(r) {
        super();  // Calls the base class constructor
        this.r = r;
    }

    CalculateArea() {
        return Math.PI * this.r * this.r;
    }
}

// Usage
try {
    let figure = new Figure();  // This will throw an error
} catch (error) {
    console.error(error.message);
}

let circle = new Circle(5);
console.log(circle.CalculateArea());
circle.Show();

In TypeScript, abstract classes are defined using the abstract keyword.

abstract class Figure {
    // Abstract method
    abstract calculateArea(): number;

    // Concrete method
    show(): void {
        console.log("Showing figure");
    }
}

class Circle extends Figure {
    radius: number;

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

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

// Usage
const circle = new Circle(5);
console.log(circle.calculateArea());
circle.show();

In Python, abstract classes are defined using the abc module and the ABC class. Abstract methods are defined using the @abstractmethod decorator.

from abc import ABC, abstractmethod
import math

class Figure(ABC):
    @abstractmethod
    def calculate_area(self):
        pass

    def show(self):
        print("Showing figure")

class Circle(Figure):
    def __init__(self, radius):
        self.radius = radius

    def calculate_area(self):
        return math.pi * self.radius ** 2

# Usage
circle = Circle(5)
print(circle.calculate_area())
circle.show()

Best practices Tips

Like almost everything in OOP, the question you will have is when and to what extent to use something. In the case of abstract classes, it is to provide implementation of common methods that can be reused by subclasses.

For example, you have a DbConnection object that manages database connections. That object has a certain internal logic, it “does things” already. But by itself, it cannot be fully defined.

To be defined it needs to know more details, in this example of the database it will use. So we will have DbConnectionMySQL, DbConnectionPostgresSQL, DbConnectionMongo or something like that.

Here we have a possible example of detecting an abstract class

  • We have a class that already has logic
  • It doesn’t work by itself
  • It needs to be specialized into different subtypes

So possibly DbConnection should be an abstract class.

So far the “good” part. But abstract classes also have their problems, and their detractors. The biggest problem is that they can complicate your object model (and your life with them), and end up with a mess 🐔.

When you program “classic” OOP, you run the risk of going crazy using abstract classes. So you end up having DbConnectionBase, which uses DbRepositoryBase, which uses DbObjectBase which uses… all with “base”.

In summary, if you have to use an abstract class, use it. That’s what they are for, they are useful. But use them wisely, and have as your goal the coupling between classes and the levels of inheritance you are going to use.

And if you have doubts, prefer other solutions such as composition or interfaces, which are not “as elegant” from the classic OOP point of view, but will give you fewer problems in the future.