SOLID Principles in Object Oriented Programming (OOP) – practice with Python

Tram Ho

What is SOLID?

SOLID is the 5 foundational principles in OOP (Object Oriented Programming) programming.

SOLID makes object-oriented program design flexible, easy to understand, and easy to maintain.

SOLID stands for the first 5 letters of each of the following principles:

  • S: Single Responsibility Principle (SRP)
  • O: Open-Closed principle (OCP)
  • L: Liskov Substitution Principle (LSP)
  • I: Interface Segregation Principle (ISP)
  • D: Dependency Inversion Principle (DIP)

SOLID in the direction of practice with Python

In this article, we will start with the Python program and go through each of the above principles in turn.

Specifically, why it violates that principle and how to redesign the code so that the program does not violate it anymore.

Thereby, helping to improve your object-oriented programming skills.


Program introduction:

In a purchasing application, there is an Order class that contains:

Properties:

– items (goods)

– quantities (quantity)

– prices (prices)

– status (status)

Methods:

– add_item: add a product including name, quantity, price to the order

– pay: make payment with payment method and security code

The above program also:

– create an order object and add 3 products with the name, quantity and price of each product.

– print the total amount of the order

– proceed with payment with debit payment method

Print results:

Principle S: Single responsibility principle

Principle content

First, let’s take a look at the content of the principle that begins with the letter S, or Single responsibility principle (SRP) from Wikipedia:

The Single-responsibility principle:

“There should never be more than one reason for a class to change.” In other words, every class should have only one responsibility.

=> Each feature class should have only one reason to change. That is to say, each object class should have only one responsibility.

This principle states that each class or method should have only one responsibility.

Thanks to that, when changing they are also changed with a certain purpose, and can be easily reused in many places.

Practice

Explain the reason for the violation

Observe the above example, the Order class contains the add_item method, which calculates the total_price is reasonable because both are related to the order.

But pay should be separate because it depends on factors that do not belong to the order such as payment method types payment_type.

So this code violates the SRP principle.

Improve

For improvement, separate payment into a separate class called PaymentProcesser.

This class has payment methods that are each separate.

Also remove the pay method in Order.

The code will change as follows:

And instead of calling order.pay(…) as before, will create an object for the payment method and call the pay_debit payment method.

The result is still the same, but the code no longer violates the S-rule.

Open-closed principle

Open-closed principle (OCP) from Wikipedia:

Principle content

The Open–closed principle: Software entities … should be open for extension, but closed for modification.

=> Software entities should only extend for inheritance and should not be modified.

This principle dictates that software entities such as object classes encourage extensibility by inheritance rather than modifying existing code.

Practice

Explain the reason for the violation

Same with the above example, if a new payment method is added, for example paypal, a new method needs to be added to the PaymentProcessor class.

This would violate this OCP guideline because it edited the PaymentProcessor class.

Improve

To process without violating OCP guidelines, it is possible to create an additional abstract class PaymentProccessor with the pay method being an abstractmethod.

And use this class to extend to create more specific classes for each type of payment method like DebitPaymentProcessor and write your own pay method.

Such a design, when used, will change to create DebitPaymentProcessor() and call its pay method.

Also with the new payment method, paypal, can extend from PaymentProcessor as PaypalPaymentProcessor. Also write a separate pay method for paypal without changing the existing code.

This will not violate the OCP principle anymore.

Liskov substitution principle

Principle content

The L principle, or Liskov principle (LSP) from Wikipedia:

The Liskov substitution principle:

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

=> Objects or methods of the subclass (derived class) can replace the object or method of the parent class without error.

This principle dictates that a subclass should be used in place of the parent class without violating the meaning of the parent class or even causing errors.

Practice

Explain the reason for the violation

Assuming with Paypal payment method, payment processing is different from card types, instead of security_code, you need to use email.

Then, the email value will be passed to the value of the security_code variable like this:

However, this way the paypal security_code will not have the meaning that the parent class PaymentProcessor expects.

This would violate the LSP principle because the PaypalPaymentProcessor subclass that uses the security_code variable as email does not have the same meaning as the superclass which is the security code.

Improve

To handle without violating LSP guidelines, here it is necessary to remove security_code from the parent class PaymentProcessor. Also, convert her to the initialization variable for each subclass that needs you CreditPaymentProcessor and DebitPaymentProcessor.

As for the PaypalPaymentProcessor class, initialized with the email_address variable specifically for this payment method.

The code will change as below:

After doing so, it can be seen that the subclass does not change the semantics of the parent class anymore. Therefore, there is no longer a violation of the LSP principle.

Principle I – Interface segregation principle

Principle content

The Interface segregation principle:

Clients should not be forced to depend upon interfaces that they do not use.

A subclass (extracted class) should not be forced to inherit interface methods that it does not use. Should separate many interfaces to inherit exactly what is needed.

This principle states that if a subclass is extracted from the superclass but does not use all the methods of the superclass. Or these methods are required by the subclass to inherit but are redundant.

Then this is the time when you need to separate many interfaces with different specific purposes so that the subclass only inherits what you need, instead of a large generic interface.

Practice

Assuming there is an additional verification method via message, the auth_sms in the PaymentProcessor class is responsible for multiplying the code and verifying the identity.

If verified, verified = True, then allow payment. And vice versa, it will report an unverified error.

However, SMS verification does not apply to credit card payment methods. Therefore, it will report an error that SMS verification with credit card payment is not supported.

The code for this scenario is as follows:

The class PaypalPaymentProcessor is similar to DebitPaymentProcessor so I will temporarily not record it.

In this situation, the CreditPaymentProcessor class did not need to use auth_sms, which was inherited from the parent class PaymentProcessor.

Therefore, the above code violates ISP guidelines.

Improve

When the child class does not use the method of the parent class anymore, it is time to separate the parent class into many different interfaces.

Create a new class extracted from PaymentProcessor with name PaymentProcessor_SMS and pass the auth_sms method here.

The DebitPaymentProcessor class and the PaypalPaymentProcessor class will inherit from PaymentProcessor_SMS because auth_sms needs to be used.

The CreditPaymentProcessor won’t need to handle the auth_sms because it’s no longer in the PaymentProcessor class.

The code will change to:

The above code is cleaner, but SMS verification is still in the payment process. Composition can be applied to separate this process into a separate interface for authentication only, naming the new class Auth_SMS to perform this authentication process separately.

Then each specific payment class will be passed to a corresponding authorizer for authentication.

This treatment will separate multiple interfaces with their own purposes and reuse them in the subclass. Therefore, no longer violating ISP guidelines.

Principle D – Dependency inversion principle

Principle content

The Dependency inversion principle:

Depend upon abstractions, [not] concretions.

=> High-level modules should not depend on low-level modules. If there is a dependency then both should depend on abstraction or interface.

This principle states that classes should not depend on each other, but should build interfaces to depend on.

Practice

Explain the reason for the violation

Let’s say there’s one more authorizer method that verifies that it’s not the robot in use (perhaps by capcha or selecting related images). Then it will be necessary to change the authorizer to no longer use SMS_Auth.

In the above code, you can see that the PaypalPaymentProcessor class is depending on the SMS_Auth class.

That would violate the DIP principle.

Improve

Build an abstract class representing Authorizer with is_authorized method.

Then for non-robot verification, will create a class extending from Authorizer, name NotARobot_Auth and verify in a separate way with not_a_robot method.

Also, the authorizer will be of type Authorizer instead of SMS_Auth. It can be adapted to either SMS_Auth or NotARobot_Auth.

The code will change to:

The above code no longer violates the DIP principle because neither the subclasses like PaypalPaymentProcessor nor the SMS_Auth class depend on each other but on the Authorizer interface.


If you read this far, you really want to learn about SOLID, thank you ^^

The 5 principles of SOLID and the above practice examples will help you develop more efficient object-oriented programming, clean code, and easy to extend and maintain.

Once you understand these principles, let’s start improving each part to improve coding skills.

The examples in this article I use from the video “ Uncle Bob’s SOLID principles made easy 🍀 – in Python! , you can check out this channel which has many great Python videos.

Happy reading!

Share the news now

Source : Viblo