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, you cannot create objects directly from it.

To use an abstract class, you must 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 I can reuse through inheritance but is not ready to be used directly in code.

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

As you might be a bit 🤔 confused by the definition, let’s clarify it with a real-life example.

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, or a Hatchback?

- 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 subtype of car.

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

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

Then it specializes into subclasses, such as Sedan, Coupe, Hatchback, which are specializations of Car, and you can instantiate them.

Methods and Abstract Classes

Abstract Methods

Abstract methods are declared methods, but they 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”).

Since they have no implementation, no class that has an abstract method can be instantiated. Therefore, it will immediately become an abstract class.

If you try to instantiate an abstract class, the compiler or the IDE will give you an error, stating 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 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

What we can do is inherit from other classes that will provide a concrete implementation of their 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();

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();

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;
}

JavaScript does not have native support for abstract classes, but 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();

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();

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()

Good 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 for 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 already “does things.” But by itself, it cannot fully define itself.

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

Here we have a possible example of detecting an abstract class

  • We have a class that already has logic
  • By itself, it will not work
  • It needs to specialize into different subtypes

So possibly DbConnection should be an abstract class.

So far, the “good” part. But abstract classes also have their problems and detractors. The biggest problem is that they can complicate your object model (and your life with them) and end up with a huge 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 DbObjectableBase, 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 aim for the coupling between classes and the levels of inheritance you are going to use.

And if you have doubts, prefer other solutions like composition or interfaces, which may not seem “as elegant” from the perspective of classic OOP, but will give you fewer problems in the future.