Professional Documents
Culture Documents
October 3, 2022
0.0.1 SOLID
The rst ve principle of Object Orianted Design.
In software engineering, SOLID is a acronym for ve design principles intended to make object-
oriented designs more understandable, exible, and maintainable. The principles are a subset of
many principles promoted by American software engineer and instructor Robert C. Martin, rst
introduced in his 2000 paper Design Principles and Design Patterns.
Note While these principles can applied to any [Object Orianted] programming languages but in
this notebook we use Python. - But the supplimental material is in C# and PHP.
Example Consider an application that takes a list[collections] of shapes, it can be Circle or Square
and calculate the sum of the all areas. - First Create Square Class, you need to know the length
of the square.
[38]: class Square:
Then Create Circle Class, you need to know the radius of the circle.
[39]: class Circle:
def __init__(self, radius: int = 0) -> None:
self.radius = radius
Next Create AreaCalculator Class, it takes a list of shapes and calculate the sum of the all
areas.
The area pf Circle is πr2 and the area of Square is l2 .
1
[40]: class AreaCalculator:
def __init__(self, shapes: list) -> None:
self.shapes = shapes
To use the AreaCalculator class, you will need to instantiate the class and pass in an array of
shapes and print the output.
[41]: shapes = [Square(2), Square(3), Circle(4), Square(5), Circle(6.5)]
area = AreaCalculator(shapes)
print(area.output())
{"area": 220.905}
Here the problem arise with output method, for now it does not have problem but consider
the case when you have to convert output to other formats like XML, HTML or any other.
All of the logic would be handled by the AreaCalculator class. This would violate the single-
responsibility principle. The AreaCalculator class should only be concerned with the sum
of the areas of provided shapes. It should not care about how the output is formatted or to
output the result at all.
To resolve the problem, we can create a new class called SumCalculatorOutputter and move
the output logic to this class. The AreaCalculator class will now only be concerned with
calculating the sum of the areas of provided shapes. The SumCalculatorOutputter class will
be responsible for formatting the output.
[42]: class AreaCalculator:
def __init__(self, shapes: list) -> None:
self.shapes = shapes
2
# isinstance() is a built-in function that returns True
# if the given object is an instance of the specified type,
# otherwise False.
if isinstance(shape, Circle):
total += shape.radius ** 2 * 3.14
elif isinstance(shape, Square):
total += shape.length ** 2
return total
# Removed output method.
{"area": 220.905}
<xml><area>220.905</area></xml>
3
In order to solve this issue you have to remove sum from AreaCalculator class and then attach
to each shape class. This way you can add new shape without modifying the AreaCalculator
class.
[45]: from abc import abstractmethod
class ShapeInterface:
@abstractmethod
def area(self) -> int:
pass
class Square(ShapeInterface):
def __init__(self, length: int) -> None:
self.length = length
class Circle(ShapeInterface):
def __init__(self, radius: int) -> None:
self.radius = radius
class Triangle(ShapeInterface):
def __init__(self, base: int, height: int) -> None:
self.base = base
self.height = height
class AreaCalculator:
def __init__(self, shapes: list) -> None:
self.shapes = shapes
Above we use interace to enable that every shape class must have area method.
Python does not have interface, but we can use abc module to create abstract class.
You can nd more about abc module here
You can nd more about OOP in Notebook.
4
That satises the open-closed principle.
class Notification(ABC):
@abstractmethod
def notify(self, message, email):
pass
class Email(Notification):
def notify(self, message, email):
print(f'Send {message} to {email}')
class SMS(Notification):
def notify(self, message, phone):
print(f'Send {message} to {phone}')
In this example, we have three classes: Notification, Email, and SMS. The Email and SMS
classes inherit from the Notification class.
The Notication abstract class has notify() method that sends a message to an email address.
The Email class overrides the notify() method and sends an email to an email address.
The SMS class overrides the notify() method and sends an SMS to a phone number.
This violates the Liskov Substitution Principle. The SMS class should be substitutable for
the Notification class. The SMS class should be able to use the notify() method of the
Notification class without any issues. The SMS class should not have to change the signature
of the notify() method.
Conform with the Liskov substitution principle
First redene the notify() method in the Notification class so that it does not take email
parameter.
[47]: class Notification(ABC):
@abstractmethod
def notify(self, message):
5
pass
Now add NotificationManager class that takes a object of Notification and send notica-
tion to the user.
[50]: class NotificationManager:
def __init__(self, notification):
self.notification = notification
Finally create Email and SMS object and pass it to the NotificationManager class.
notification_manager = NotificationManager(sms_notification)
notification_manager.send('Hello Alice')
notification_manager.notification = email_notification
notification_manager.send('Hi Bob')
6
of a single interface and promote decoupling of software modules. - Decoupling means that the
software modules are independent of each other and have looser coupling. - Python does not have
interface, but we can use abc module to create abstract class. - This mimic the interface. - Consider
the Vehicle class. It has drive() and fly() methods. The Car class inherits from the Vehicle
class and implements the drive() method. The Airplane class inherits from the Vehicle class
and implements the fly() method. - But the Car class does not need the fly() method and the
Airplane class does not need the drive() method. This violates the interface segregation principle.
[52]: class Vehicle(ABC):
@abstractmethod
def drive(self):
pass
@abstractmethod
def fly(self):
pass
class Car(Vehicle):
def drive(self):
print('Car is driving')
def fly(self):
raise Exception('Car cannot fly')
class Airplane(Vehicle):
def drive(self):
raise Exception('Airplane cannot drive')
def fly(self):
print('Airplane is flying')
In order to solve this issue, we can create two separate interfaces: Drivable and Flyable.
The Car class and Airplane class will implement these interfaces.
[53]: class Flyable(ABC):
@abstractmethod
def fly(self):
pass
class Driveable(ABC):
@abstractmethod
def drive(self):
pass
class Car(Driveable):
def drive(self):
print('Car is driving')
7
class Airplane(Flyable):
def fly(self):
print('Airplane is flying')
car = Car()
airplane = Airplane()
car.drive()
airplane.fly()
Car is driving
Airplane is flying
This satises the interface segregation principle.
class PasswordReminder:
def __init__(self, connection: MySQLConnection) -> None:
self.connection = connection
print("Password Reset")
To solve this issue, we can create an interface called DBConnection and move the connect()
method to this interface. The MySQLConnection class will implement this interface. The
PasswordReminder class will depend on the Connection interface.
[55]: class DBConnectionInterface(ABC):
@abstractmethod
8
def connect(self):
pass
class MySQLConnection(DBConnectionInterface):
def connect(self):
# Handle the database connection.
return "Database Connection"
class PasswordReminder:
def __init__(self, connection: DBConnectionInterface) -> None:
self.connection = connection
print("Password reset")
mysql = MySQLConnection()
reset = PasswordReminder(mysql)
mysql.connect()
Password reset