November 30, 2022

SO[L]ID – The Liskov substitution principle

By leonardo

The Liskov substitution principle is one of the most important principles of the SOLID principles because it describes how the object orientation must work. Basically, it says that an object of class B and another one of class C, both inherited from a class A, when instanced within a base class variable and some method is called, both the subclasses method should return/work properly without breaking the program.

As we can see above, all the classes “Fiat”, “Chevrolet”, “Hyundai” and “Tesla” inherits the class “Car”. The problem here is, despite it’s violating The Interface Segregation Principle, the method called putGas will be useless for the class “Tesla” and its return will break the program depending on how it’ll be coded. Take a look at the code below:

class Car
{
public:

	virtual void accelerate()
	{ /* Increment the velocity */ }
	virtual void brake()
	{ /* Reduce the velocity */ }
	virtual void putGas()
	{ /* Insert some fuel in the tank */ }
	virtual ~Car();
}

class Fiat : public Car
{
public:

	Fiat() = default;
	~Fiat();
}

class Chevrolet : public Car
{
public:

	Chevrolet() = default;
	~Chevrolet();
}

class Hyundai : public Car
{
public:

	Hyundai() = default;
	~Hyundai();
}

class Tesla : public Car
{
public:
	Tesla() = default;
	virtual void putGas() override 
	{ throw "There's no fuel tank on this car" }
	~Tesla();
}

As “Tesla” is an electric car, the method putGas() will throw an exception that will break the program execution if it is called. Let’s do some tests:

int main()
{
	std::vector<Car> Cars;
	Cars.push_back(Fiat());	
	Cars.push_back(Chevrolet());	
	Cars.push_back(Hyundai());	
	Cars.push_back(Tesla());	
	
	/* Here we are accelerating all the cars */
	for(Car& _Car : Cars)
	{
		_Car.accelerate();
	}
	
	/* Next, we will brake all the cars, this way: */
	for(Car& _Car : Cars)
	{
		_Car.brake();
	}
	
	/* Now the gas is over, so, we will take some gas. */
	for(Car& _Car : Cars)
	{
		_Car.putGas();
	}
	
	return 1;
}

The line _Car.putGas(); will run fine for the first 3 elements of the vector, in the last element which is of the class “Tesla” it will throw an exception and block the program.

The Liskov substitution principle says: If you can invoke a method q() of a base class T, so you must be able to invoke a method q() of a subclass (derivated class) of T.

It’s a really basic design problem. When you look at our classes construction above, it’s obvious that something is wrong and we could solve this problem using The Interface Segregation Principle that you can find in the link.