SOL[I]D – The interface segregation principle
We can use the previous classes design to exemplify The Interface Segregation Principle as well. To demonstrate the importance of this principle, consider the scheme below.
In the diagram, we can see 3 classes (Fiat, Chevrolet and Hyundai) that implement the “Car” interface. We can see the correspondent code below:
class Car
{
public:
virtual void accelerate() = 0;
virtual void brake() = 0;
virtual void putGas() = 0;
virtual ~Car();
}
class Fiat : public Car
{
public:
virtual void accelerate() override
{ /* Increment the velocity */ }
virtual void brake() override
{ /* Reduce the velocity */ }
virtual void putGas() override
{ /* Insert some fuel in the tank */ }
}
class Chevrolet : public Car
{
public:
virtual void accelerate() override
{ /* Increment the velocity */ }
virtual void brake() override
{ /* Reduce the velocity */ }
virtual void putGas() override
{ /* Insert some fuel in the tank */ }
}
class Hyundai : public Car
{
public:
virtual void accelerate() override
{ /* Increment the velocity */ }
virtual void brake() override
{ /* Reduce the velocity */ }
virtual void putGas() override
{ /* Insert some fuel in the tank */ }
}
All cars must implement the 3 functions of the interface, these are: accelerate()
, brake()
and putGas()
. These functions do the exact thing that their name suggests, so it will be weird if we decided to implement a new car brand that doesn’t uses gas as its com fuel.
For example, all Tesla-branded cars will use only energy as their fuel. Imagine that this system we are managing/programming exists for some years and we are responsible just to make some maintenance in the code. Well, it’s will be a hard problem, because there’s no way to implement it in a good manner without refactoring some parts of the code.
Look at the suggestion below:
Here we are creating a new class called “Tesla” and implementing the same interface on it. The problem is we’ll need to create a method called plugToEnergy()
in Car interface because Teslas won’t use the putGas()
method at all.
The second problem is that we’ll be forced to implement inside all the other car brands the plugToEnergy()
method even though we don’t use it because the other cars aren’t electric. In the same way, the method putGas()
needs to be implemented inside Tesla, otherwise, it will break our compilation. Here’s the code for this situation:
class Car
{
public:
void accelerate() = 0;
void brake() = 0;
void putGas() = 0;
void plugToEnergy() = 0;
vitual ~Car();
}
class Tesla : public Car
{
public:
virtual void accelerate() override
{ /* Increment the velocity */ }
virtual void brake() override
{ /* Reduce the velocity */ }
virtual void putGas() override
{ /* Does nothing because there's no fuel tank */ }
virtual void plugToEnergy() override
{ /* Charge the car */ }
}
class Fiat : public Car
{
public:
virtual void accelerate() override
{ /* Increment the velocity */ }
virtual void brake() override
{ /* Reduce the velocity */ }
virtual void putGas() override
{ /* Insert some fuel in the tank */ }
virtual void plugToEnergy() override
{ /* Does nothing because it's not a electric car */ }
}
class Chevrolet : public Car
{
public:
virtual void accelerate() override
{ /* Increment the velocity */ }
virtual void brake() override
{ /* Reduce the velocity */ }
virtual void putGas() override
{ /* Insert some fuel in the tank */ }
virtual void plugToEnergy() override
{ /* Does nothing because it's not a electric car */ }
}
class Hyundai : public Car
{
public:
virtual void accelerate() override
{ /* Increment the velocity */ }
virtual void brake() override
{ /* Reduce the velocity */ }
virtual void putGas() override
{ /* Insert some fuel in the tank */ }
virtual void plugToEnergy() override
{ /* Does nothing because it's not a electric car */ }
}
The Interface Segregation Principle says that the situation above isn’t good because it will break our production and limit our program’s extensibility. The correct way to handle it is to segregate the car functionality from the engine fuel functionality. So, we’ll have a car interface for the methods accelerate()
and brake()
because all cars do that. The other interfaces called Combustion Engine and Electric Engine will be responsible to charge the car or put some gas on it depending on the engine. Take a look at the diagram and code below:
class Car
{
public:
virtual void accelerate() = 0;
virtual void brake() = 0;
virtual ~Car();
}
class CombustionEngine
{
public:
virtual void putGas() = 0;
virtual ~CombustionEngine();
}
class ElectricEngine
{
public:
virtual void plugToEnergy() = 0;
virtual ~ElectricEngine();
}
class Fiat : public Car, public CombustionEngine
{
public:
virtual void accelerate() override
{ /* Increment the velocity */ }
virtual void brake() override
{ /* Reduce the velocity */ }
virtual void putGas() override
{ /* Insert some fuel in the tank */ }
}
class Chevrolet : public Car, public CombustionEngine
{
public:
virtual void accelerate() override
{ /* Increment the velocity */ }
virtual void brake() override
{ /* Reduce the velocity */ }
virtual void putGas() override
{ /* Insert some fuel in the tank */ }
}
class Hyundai : public Car, public CombustionEngine
{
public:
virtual void accelerate() override
{ /* Increment the velocity */ }
virtual void brake() override
{ /* Reduce the velocity */ }
virtual void putGas() override
{ /* Insert some fuel in the tank */ }
}
class Tesla : public Car, public ElectricEngine
{
public:
virtual void accelerate() override
{ /* Increment the velocity */ }
virtual void brake() override
{ /* Reduce the velocity */ }
virtual void plugToEnergy() override
{ /* Charge the car */ }
}
Now, there are useless functions neither in combustion cars nor in electric cars. As I said before, the good solution above will need some code refactoring of the original problem, but it will be a good thing for your project because this way you are safe for expanding your system without making too big changes. Using the interface segregation will guarantee that on future changes you will need to compile and deploy just the part of the software that was really changed instead to recompile and deploy the classes/methods that already working well in the program.
[…] 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 […]