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

ISP in a real use case
ISP_no_uml

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:

IOrderProcessor violates ISP. (Designed by Plantuml)

  • validate_card_info() to validate the credit card information, such as card number, card verification code, and expiration date.
  • validate_shipping_address() to validate the shipping physical address. This might be necessary to make sure that the destination is in the area that the company can cover.
  • process_order() to initiate processing the order to be delivered to the destination.

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.

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

  • Beginning SOLID Principles and Design Patterns for ASP.NET Developers by Bipin Joshi

Articles in this series:

Published on medium

Buy me a cup of coffee

Join the conversation

Download the ebook

Download the eBook to write cleaner Python code

Get the ebook