The Open-Closed principle explained in Python.

The Open-Closed principle explained in Python.
Photo by Daniel McCullough / Unsplash

Towards better programming with SOLID principle 2/5.

This post is part 2 of a series on the SOLID principles.
You can find the first post here.

The nature of software is that it is ever changing. Software is never "complete". There is almost always something to fix or something to improve. This is because software is built to meet business needs and business needs are never stagnant. It is only a closed business that has stagnant or non-existent needs.

So naturally the question arises, how do we build software that accommodates change and is still robust?

The open-closed principle is a way to answer this question.

The open-closed principle is in some ways quite related to the single responsibility principle that was explained in the first post.
Essentially this principle dictates the following:

Software components (classes and functions) should be open for extension but closed for modification.

The idea is quite simple. The business logic of a specific class in your code should not be changed. Rather if an additional feature has to be implemented the code should be extended instead of being modified. This makes debugging easier and the code easier to maintain as it grows and changes with business needs. Imagine having to rewrite unit tests every time your code changes due to a new business need!

As always we look at an example to illustrate the point and drive it home ๐Ÿ˜ƒ

Code

Imagine we have the following piece of code:

We use the same main theme from the first post namely that of a car dealership.
A new car needs to be instantiated before it can be bought. It is possible to buy a car with the buy function and also possible to buy with a 20% discount with the buy_with_discount function.

The provided cash to the buy_with_discount function needs to be exactly 80% of the price of the car for it to be bought.

We can run the code and see the BMW is bought for 80000 with a 20% discount.

Now, let's assume the car dealer wishes to offer several discounts for loyal members of the dealership, for VIP people or for close friends and relatives. Or we can assume the dealer wishes to run a campaign for a summer sale and offer more than 20% discount.

As you can see the business needs have changed requiring a change in the code.

To accommodate the request we can simply write another if statement to the buy_with_discount function. Something like to indicate a 30% discount:

if int(0.7*self.price) == int(cash):
	print("Buying {} with 30%% discount".format(self.name))

But this is a wrong approach according to the open-closed principle. Because the business logic of the Car class should not be changed. Instead it should be extended. A common way to extend functionality in OOP (Object oriented programming) ย is to use polymorphism also known as inheritance.

Let's see how we can use inheritance in Python to solve this problem correctly.

Here's the code:

The first thing we do is to separate the discount business logic from the regular buy logic by creating a Discount class. Then we define the buy_with_discount function that simply prints that the car has been bought.

Here's where it gets interesting.

Next we need to implement the 20% discount logic. But remember, we are not allowed to modify the Discount class as per the principle. The only thing we can do is extend it. How do we extend it? By making a new class and having the new class inherit from the Discount class.

We define a new class Discount20 and make it inherit from the Discount class by passing Discount as a parameter in the class definition. ย The __init__ function calls the parent class's (which is Discount) __init__ method as indicated by the super() command. ย We initialize self.discount in an instance of the Discount class to 20. Next, we define a buy_with_discount function that uses the if condition we used in the incorrect code earlier to check if the money provided is 80% of the original value. If it is we issue a buy order by calling the buy_with_discount function of the parent class. Do you see how we are extending the original Discount class and the business logic inside it?

The cool thing is, let's say we want to offer 30% discount. How do we achieve this?
We simply create a new class called Discount30 and repeat the code changing only the values from 20 to 30 and 0.8 to 0.7. How awesome is that ๐Ÿ˜Ž

If you've made it this far, take a moment to pat yourself on the back for a job well done! ๐Ÿ‘

We call the two functions at initialization in the if __name__ condition.

The code can now be run and you will see two cars are bought. One at 70% discount and the other at 80% discount!

Wrap up

That's it for this post. Hope you learnt something useful that you can apply to your software projects. In short we looked at the open-closed principle and how it helps to make code more maintainable and testable. We have also seen how it improves the readability and quality of the code.

Again as always this principle might not seem like a huge deal since we have a small test example in this post. But in organizations with medium to large code bases especially where testing is part of the development process, the open-closed principle really is a game changer!


Enjoyed this article? Please consider subscribing using the form below๐Ÿ˜‡
It is 100% free. You get an email every time I post (about once a week) + access to premium content in the future.