Problem: without Inheritance
Let's build an example based on the given code in the question. Different vehicles are serviced in a different manner. So, we have different classes for Bike
and Car
because the strategy to service a Bike
is different from the strategy to service a Car
.
The Garage
class accepts various kinds of vehicles for servicing. Observe the code and see the how the Garage
class violates the open-closed principle:
class Bike {
public void service() {
System.out.println("Bike servicing strategy performed.");
}
}
class Car {
public void service() {
System.out.println("Car servicing strategy performed.");
}
}
class Garage {
public void serviceBike(Bike bike) {
bike.service();
}
public void serviceCar(Car car) {
car.service();
}
}
As you might have noticed, whenever some new vehicle like Truck
or Bus
is to be serviced, the Garage
will need to be modified to define new methods serviceTruck()
and serviceBus()
. That means the Garage
class must know every possible vehicle like Bike
, Car
, Bus
, Truck
and so on. So, it violates the open-closed principle by being open for modification. Also it's not open for extension because to extend the new functionality, we need to change it.
Solution: with Inheritance
Abstraction
To solve the problem in the code above and satisfy the open-closed principle, we need to abstract the implementation details of the servicing strategy for each type of vehicle. That means we need abstraction of Bike
and Car
classes.
Polymorphism
We also want the Garage
class to accept many forms of the vehicle, like Bus
, Truck
and so on, not just Bike
and Car
. That means we need polymorphism (many forms).
Inheritance
So, for satisfying the open-closed principle, the most important mechanisms are abstraction and polymorphism. In statically typed languages such as Java, C# etc. the important tool that provides abstraction and polymorphism is inheritance.
To abstract the implementation details of the servicing strategies for various types of vehicles we use an interface
called Vehicle
and have an abstract method service()
.
And for the Garage
class to accept many forms of the Vehicle
, we change the signature of its method to service(Vehicle vehicle) { }
to accept the interface Vehicle
instead of the actual implementation like Bike
, Car
etc. We also remove the multiple methods from the class as just one method will accept many forms.
interface Vehicle {
void service();
}
class Bike implements Vehicle {
@Override
public void service() {
System.out.println("Bike servicing strategy performed.");
}
}
class Car implements Vehicle {
@Override
public void service() {
System.out.println("Car servicing strategy performed.");
}
}
class Garage {
public void service(Vehicle vehicle) {
vehicle.service();
}
}
Closed for modification
As you can see in the code above, now the Garage
class has become closed for modification because now it doesn't know about the implementation details of servicing strategies for various types of vehicles and can accept any type of new Vehicle
. We just have to extend the new vehicle from Vehicle
interface and send it to the Garage
. We don't need to change any code in the Garage
class.
Another entity that's closed for modification is our Vehicle
interface.
We don't have to change the interface to extend the functionality of our software.
Open for extension
The Garage
class now becomes open for extension in the context that it will support the new types of Vehicle
, without modifying.
Our Vehicle
interface is open for extension because to introduce any new vehicle, we can extend from the Vehicle
interface and provide a new implementation with a strategy for servicing that particular vehicle.
So, as you can see, the inheritance is a just tool provided by the programming languages that we use for abiding by the open-closed principle rules.
That's it! Hope that helps.