How can applying the SOLID principles make the code better?

Rmag Breaking News

Applying the SOLID principles to your code can significantly enhance its quality, making it more understandable, flexible, maintainable, and scalable. Here’s how each principle contributes to improving code quality:

1. Single Responsibility Principle (SRP)

The Single Responsibility Principle states that a class should have only one reason to change. This principle aims to separate behaviours so that if bugs arise as a result of your change, it won’t affect other unrelated behaviours.
If each class has only one reason to change, you make the code easier to maintain. Changes are more localized, reducing the risk of introducing bugs. And since the responsibilities of each class are more clearly defined, such code is easier to read.
No SRP

class User:
def __init__(self, name, email):
self.name = name
self.email = email

def save(self):
pass

def send_email(self):
pass

SRP

class User:
def __init__(self, name, email):
self.name = name
self.email = email

class UserRepository:
def save(self, user):
pass

class EmailService:
def send_email(self, user):
pass

To address business responsibilities, we might consider what business logic or rules apply to the User and how they can be encapsulated within a class.

2. Open/Closed Principle (OCP)

Classes should be open for extension, but closed for modification. This means that you should be able to add new functionality without changing the existing code.
OCP allows new functionality to be added without modifying existing code, making the system more adaptable to change. And you minimize the risk of introducing bugs when extending the functionality of your system.

from abc import ABC, abstractmethod

class Payment(ABC):
@abstractmethod
def process_payment(self, amount):
pass

class CreditCardPayment(Payment):
def process_payment(self, amount):
print(fProcessing credit card payment of {amount})

class OnlinePayment(Payment):
def process_payment(self, amount):
print(fProcessing online payment of {amount})

class CryptoCurrencyPayment(Payment):
def process_payment(self, amount):
print(fProcessing crypto payment of {amount})

# New class that uses a specific payment method passed as an argument
class PaymentProcessing:
def concrete_payments(self, payment_method: Payment, amount):
payment_method.process_payment(amount)

credit_card_payment = CreditCardPayment()
some_object = PaymentProcessing()
some_object.concrete_payments(credit_card_payment, 100)

online_payment = OnlinePayment()
some_object.concrete_payments(online_payment, 200)

crypto_payment = CryptoCurrencyPayment()
some_object.concrete_payments(crypto_payment, 300)

3. Liskov Substitution Principle (LSP)

The Liskov Substitution Principle states that if a program is using a base class, it should be able to use any of its subclasses without the program knowing it. In other words, the subclasses should be substitutable for their base class.
LSP ensures that subclasses can be used in place of their base classes without altering the correctness of the program, making the code more robust. In addition, you can reuse code because code that works with the base class will also work with any of its subclasses.
No LSP

class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height

def area(self):
return self.width * self.height

def set_width(self, width):
self.width = width

def set_height(self, height):
self.height = height

class Square(Rectangle):
def __init__(self, side):
super().__init__(side, side)

def set_side(self, side):
self.width = side
self.height = side

LSP

class Shape:
def area(self):
pass

class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height

def area(self):
return self.width * self.height

class Circle(Shape):
def __init__(self, radius):
self.radius = radius

def area(self):
return 3.14 * (self.radius ** 2)

4. Interface Segregation Principle (ISP)

The Interface Segregation Principle states that no client should be forced to depend on interfaces they do not use. This means that a class should not have to implement methods it doesn’t use. Clients should not be forced to depend on methods that they do not use.
By ensuring that interfaces are small and focused, so classes are not forced to depend on interfaces they do not use. This makes the system more flexible and adaptable to change.
No ISP

class Worker:
def work(self):
pass

def eat(self):
pass

class Robot(Worker):
def work(self):
pass

def eat(self):
raise NotImplementedError(Robots dont eat.)

ISP

class Worker:
def work(self):
pass

class Eater:
def eat(self):
pass

class Human(Worker, Eater):
def work(self):
pass

def eat(self):
pass

class Robot(Worker):
def work(self):
pass

5. Dependency Inversion Principle (DIP)

High-level modules depend on abstractions rather than on low-level modules. This makes the code more flexible and easier to understand. It becomes easier to write unit tests for individual components. Each component can be tested in isolation, without needing to consider the behavior of other components.
No DIP

class LightBulb:
def __init__(self):
self.is_on_state = False

def turn_on(self):
self.is_on_state = True
print(Light is on)

def turn_off(self):
self.is_on_state = False
print(Light is off)

def is_on(self):
return self.is_on_state

class ElectricPowerSwitch:
def __init__(self):
self.light_bulb = LightBulb()

def press(self):
if self.light_bulb.is_on():
self.light_bulb.turn_off()
else:
self.light_bulb.turn_on()

switch = ElectricPowerSwitch()

switch.press()
switch.press()

DIP

from abc import ABC, abstractmethod

class Switchable(ABC):
@abstractmethod
def turn_on(self):
pass

@abstractmethod
def turn_off(self):
pass

@abstractmethod
def is_on(self):
pass

class LightBulb(Switchable):
def __init__(self):
self.is_on_state = False

def turn_on(self):
self.is_on_state = True
print(Light is on)

def turn_off(self):
self.is_on_state = False
print(Light is off)

def is_on(self):
return self.is_on_state

class ElectricPowerSwitch:
def __init__(self, switchable: Switchable):
self.switchable = switchable

def press(self):
if self.switchable.is_on():
self.switchable.turn_off()
else:
self.switchable.turn_on()

light_bulb = LightBulb()
switch = ElectricPowerSwitch(light_bulb)

switch.press()
switch.press()

Conclusion

By adhering to the SOLID principles, developers aim to create a system that is easy to understand, easy to change, and easy to maintain. These principles guide the design of software in a way that is robust, flexible, and adaptable to future changes. They help in creating a system that is not only functional but also scalable and maintainable, making it a better choice for long-term projects.

Image created by Bing

Leave a Reply

Your email address will not be published. Required fields are marked *