How to Write Clean Code (in Python) With SOLID Principles | Principle 4

Created on Feb 5, 2022
Updated on Feb 7, 2022

Today, we discuss principle #4 of SOLID principles to help write clean code in Python.

Here are the first , second , and third principles if you missed them.

And let’s dive into the fourth design principle (the ‘I’ in SOLID).

We will explain the concept with a real example implemented in Python and shown in a UML diagram to better illustrate the design of the classes and the relationships between them and have a visual understanding of what’s going on.

So what is ISP?

Definition:

Clients of a class should not be forced to depend on those of its methods that they don’t use.

In the preceding blog post covering LSP, we split one interface (in our case ISettings ) into two IReadableSettings and IWritableSettings interfaces. That was intended so that one could choose a suitable interface based on the functionality.

Do you know that this solution was based on the Interface Segregation Principle; the 4th SOLID principle?

The client, mentioned in the definition, can be any part of the system that uses the class. Let’s see ISP in action with a more specific example than the previous one.

Example: E-Commerce Payment Design

Suppose you are developing an e-commerce website. You have a class that represents the customer’s shopping cart and associated order processing methods. Let’s define the IOrderProcessor interface as shown below:

<img src=“https://drive.google.com/uc?export=view&id=1ZzVhnvK2bxulpOh65P26dgl5OyUdabQ0”,alt=“IOrderProcessor class with three methods: validate_card_info(), validate_shipping_address(), and process_order(). Two classes inherit from it: OnlineOrderProcessor and CashOnDeliveryOrderProcessor.",width=“50%">

IOrderProcessor violates ISP. (Designed by Plantuml)

The question now: Why does the above UML show a bad design?

Remember the assumption that you only accept credit card payments. What if that assumption is no longer valid? and the company decides to accept cash-on-delivery payments in some areas.

At first glance, fixing this design might be straightforward as once you implement the CashOnDeliveryOrderProcessor class you can just raise a NotImplementedError for the credit card functionality validate_card_info() .

def validate_card_info(obj: CardInfo):
    raise NotImplementedError()

At this stage, your app may work as expected, but a potential problem may arise in the future.

Let’s assume that for some reason the online-based payments need extra functionalities. This will force you to modify the IOrderProcessor to include the extra methods. You will also need to implement these newly added methods in the OnlineOrderProcessor child. Not just that, you will also need to modify the CashOnDeliveryOrderProcessor child to define the same methods and throw NotImplementedError .

In other words, the CashOnDeliveryOrderProcessor will be changed because of methods it doesn’t need in the first place which is violating ISP. Also notice that for the implementation of the newly added methods, you just threw NotImplementedError which makes the CashOnDeliveryOrderProcessor violates LSP.

Now, the new design (shown in the figure below) splits the required operations into two separate interfaces:

  1. IOrderProcessor which includes only two methods: validate_shipping_address() and process_order() . These two methods are consumed by the OnlineOrderProcessor class as well as CashOnDeliveryOrderProcessor .
  2. IOnlineOrderProcessor which has only one method: validate_card_info() . This interface is implemented only by OnlineOrderProcessor .

Now, if you want to add specific functionalities to how online payments are processed, you only need to change the IOnlineOrderProcessor interface which will affect the OnlineOrderProcessor , not the CashOnDeliveryOrderProcessor . Thus, the new design conforms to ISP.

<img src=“https://drive.google.com/uc?export=view&id=1IwevolrZQiVgfpOUhD0di3_xwV-GaB6v”,alt=“Two interfaces: IOnlineOrderProcessor with validate_card_info() method, and IOrderProcessor with validate_shipping_address() and process_order() method. There are two classes beneath them. Both CashOnDeliveryOrderProcessor and OnlineOrderProcessor inherit from IOrderProcessor while OnlineOrderProcessor inherit from IOnlineOrderProcessor as well.",width=“50%">

Corrected design of the system. (Designed by Plantuml)

Final thoughts:

The Interface Segregation principle is applied when you notice that there is a client of a class that implements a method that is not used. In our example, we noticed that issue with the CashOnDeliveryOrderProcessor class which implemented validate_card_info() that was not needed.

See you in principle #5

Credit