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

What are and how to use abstract classes

  • 8 min

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

To use an abstract class, it is necessary to inherit from it and provide a complete implementation for all 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 basis for other objects.

As you might be a bit like 🤔 with the definition, let’s look at it better with an everyday case.

Purpose of Abstract Classes

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

- Hello, I’d like to buy a car

+ Of course, what do you want to buy? A Sedan, a Coupe, a Saloon?

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

curso-poo-clase-abstracta

Depending on the salesperson’s mood that day, they will explain more or less kindly that you cannot buy a car “as a concept”. You can buy a particular subtype of car.

But a car “as a concept” is an abstraction. It’s 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, Car is an abstract class. It’s an idea that represents everything cars have in common. And it cannot be instantiated.

It is then specialized into subclasses, like Sedan, Coupe, Hatchback, which are specializations of Car, and you can instantiate.

Abstract Methods and Classes

Abstract Methods

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

Since they lack an implementation, any class that has an abstract method cannot be instantiated. Therefore, it will immediately become an abstract class.

If we try to instantiate an abstract class the compiler or 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 mentioned, if a class implements at least one abstract method (without implementation) the class must necessarily 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 that represents a car
abstract class Car
{
	// Abstract method, without implementation (no 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
Copied!

What we can do is inherit from it with other classes that will provide a concrete implementation of its abstract methods.

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

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

// Create instances of the derived classes
Car mySedan = new Sedan();
Car myCoupe = new Coupe();
Copied!

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 Shape
{
    // Abstract method
    public abstract double CalculateArea();

    // Concrete method
    public void Display()
    {
        Console.WriteLine("Displaying shape");
    }
}

public class Circle : Shape
{
    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.Display();
Copied!

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 Shape {
public:
    // Abstract method
    virtual double CalculateArea() const = 0;

    // Concrete method
    void Display() const {
        std::cout << "Displaying shape" << std::endl;
    }
};

class Circle : public Shape {
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.Display();
    return 0;
}
Copied!

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

class Shape {
    // Optional constructor to initialize common properties
    constructor() {
        if (this.constructor === Shape) {
            throw new Error("Cannot instantiate an abstract class.");
        }
    }

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

    // Concrete method
    Display() {
        console.log("Displaying shape");
    }
}

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

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

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

let circle = new Circle(5);
console.log(circle.CalculateArea());
circle.Display();
Copied!

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

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

    // Concrete method
    display(): void {
        console.log("Displaying shape");
    }
}

class Circle extends Shape {
    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.display();
Copied!

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 Shape(ABC):
    @abstractmethod
    def calculate_area(self):
        pass

    def display(self):
        print("Displaying shape")

class Circle(Shape):
    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.display()
Copied!

Best Practices Tips

As with almost everything in OOP, the doubt you will have is when and how far to use something. In the case of abstract classes, it’s to provide implementation of common methods that can be reused by subclasses.

Example, you have a DbConnection object that manages database connections. That object has some internal logic, it already “does things”. But by itself, it is not fully capable of defining itself.

To define itself, it needs to know more details, in this example about the database it will use. So we will have DbConnectionMySQL, DbConnectionPostgresSQL, DbConnectionMongo or things like that.

Here we have a possible example of abstract class detection

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

So DbConnection should probably 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 15th-century chicken 🐔.

When programming “classical” OOP you run the risk of going crazy using abstract classes. So you end up having DbConnectionBase, which uses DbRepositoryBase, which uses DbObjectableBase which uses… everything with “Base”.

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

And if in doubt, prefer other solutions like composition or interfaces, which may not look “as elegant” from the point of view of classical OOP, but will give you fewer problems in the future.