Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 26 additions & 16 deletions 1.srp.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""
Single Responsibility Principle
“…You had one job” — Loki to Skurge in Thor: Ragnarok

A class should have only one job.
If a class has more than one responsibility, it becomes coupled.
A change to one responsibility results to modification of the other responsibility.
“…You had one job” — Loki to Skurge in Thor:
Ragnarok

A class should have only one job. If a class has more than one responsibility,
it becomes coupled. A change to one responsibility results to modification of
the other responsibility.
"""

class Animal:
Expand All @@ -22,16 +24,22 @@ def save(self, animal: Animal):

How does it violate SRP?

SRP states that classes should have one responsibility, here, we can draw out two responsibilities: animal database management and animal properties management.
The constructor and get_name manage the Animal properties while the save manages the Animal storage on a database.
SRP states that classes should have one responsibility, here, we can draw out
two responsibilities: animal database management and animal properties
management. The constructor and get_name manage the Animal properties while the
save manages the Animal storage on a database.

How will this design cause issues in the future?

If the application changes in a way that it affects database management functions. The classes that make use of Animal properties will have to be touched and recompiled to compensate for the new changes.
If the application changes in a way that it affects database management
functions. The classes that make use of Animal properties will have to be
touched and recompiled to compensate for the new changes.

You see this system smells of rigidity, it’s like a domino effect, touch one card it affects all other cards in line.
You see this system smells of rigidity, it’s like a domino effect, touch one
card it affects all other cards in line.

To make this conform to SRP, we create another class that will handle the sole responsibility of storing an animal to a database:
To make this conform to SRP, we create another class that will handle the sole
responsibility of storing an animal to a database:
"""

class Animal:
Expand All @@ -50,15 +58,16 @@ def save(self, animal: Animal):
pass

"""
When designing our classes, we should aim to put related features together,
so whenever they tend to change they change for the same reason.
And we should try to separate features if they will change for different reasons. - Steve Fenton
When designing our classes, we should aim to put related features together, so
whenever they tend to change they change for the same reason. And we should try
to separate features if they will change for different reasons. - Steve Fenton
"""

"""
The downside of this solution is that the clients of the this code have to deal with two classes.
A common solution to this dilemma is to apply the Facade pattern.
Animal class will be the Facade for animal database management and animal properties management.
The downside of this solution is that the clients of the this code have to deal
with two classes. A common solution to this dilemma is to apply the Facade
pattern. Animal class will be the Facade for animal database management and
animal properties management.
"""

class Animal:
Expand All @@ -77,5 +86,6 @@ def save(self):


"""
The most important methods are kept in the Animal class and used as Facade for the lesser functions.
The most important methods are kept in the Animal class and used as Facade for
the lesser functions.
"""
43 changes: 25 additions & 18 deletions 2.ocp.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""
Open-Closed Principle
Software entities(Classes, modules, functions) should be open for extension, not modification.

Software entities(Classes, modules, functions) should be open for extension, not
modification.
"""
class Animal:
def __init__(self, name: str):
Expand All @@ -25,12 +27,13 @@ def animal_sound(animals: list):
animal_sound(animals)

"""
The function animal_sound does not conform to the open-closed principle because it cannot be closed against new kinds of animals.
If we add a new animal, Snake, We have to modify the animal_sound function.
You see, for every new animal, a new logic is added to the animal_sound function.
This is quite a simple example. When your application grows and becomes complex,
you will see that the if statement would be repeated over and over again
in the animal_sound function each time a new animal is added, all over the application.
The function animal_sound does not conform to the open-closed principle because
it cannot be closed against new kinds of animals. If we add a new animal,
Snake, We have to modify the animal_sound function. You see, for every new
animal, a new logic is added to the animal_sound function. This is quite a
simple example. When your application grows and becomes complex, you will see
that the if statement would be repeated over and over again in the animal_sound
function each time a new animal is added, all over the application.
"""

animals = [
Expand Down Expand Up @@ -88,22 +91,25 @@ def animal_sound(animals: list):
animal_sound(animals)

"""
Animal now has a virtual method make_sound. We have each animal extend the Animal class and implement the virtual make_sound method.
Animal now has a virtual method make_sound. We have each animal extend the
Animal class and implement the virtual make_sound method.

Every animal adds its own implementation on how it makes a sound in the make_sound.
The animal_sound iterates through the array of animal and just calls its make_sound method.
Every animal adds its own implementation on how it makes a sound in the
make_sound. The animal_sound iterates through the array of animal and just
calls its make_sound method.

Now, if we add a new animal, animal_sound doesn’t need to change.
All we need to do is add the new animal to the animal array.
Now, if we add a new animal, animal_sound doesn’t need to change. All we need
to do is add the new animal to the animal array.

animal_sound now conforms to the OCP principle.
"""

"""
Another example:

Let’s imagine you have a store, and you give a discount of 20% to your favorite customers using this class:
When you decide to offer double the 20% discount to VIP customers. You may modify the class like this:
Let’s imagine you have a store, and you give a discount of 20% to your favorite
customers using this class: When you decide to offer double the 20% discount to
VIP customers. You may modify the class like this:
"""

class Discount:
Expand All @@ -119,11 +125,12 @@ def give_discount(self):


"""
No, this fails the OCP principle. OCP forbids it. If we want to give a new percent discount maybe, to a diff.
type of customers, you will see that a new logic will be added.
No, this fails the OCP principle. OCP forbids it. If we want to give a new
percent discount maybe, to a diff. type of customers, you will see that a new
logic will be added.

To make it follow the OCP principle, we will add a new class that will extend the Discount.
In this new class, we would implement its new behavior:
To make it follow the OCP principle, we will add a new class that will extend
the Discount. In this new class, we would implement its new behavior:
"""

class Discount:
Expand Down
39 changes: 23 additions & 16 deletions 3.lsp.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""
Liskov Substitution Principle
A sub-class must be substitutable for its super-class.
The aim of this principle is to ascertain that a sub-class can assume the place of its super-class without errors.
If the code finds itself checking the type of class then, it must have violated this principle.

A sub-class must be substitutable for its super-class. The aim of this
principle is to ascertain that a sub-class can assume the place of its
super-class without errors. If the code finds itself checking the type of class
then, it must have violated this principle.

Let’s use our Animal example.
"""
Expand All @@ -19,13 +21,15 @@ def animal_leg_count(animals: list):
animal_leg_count(animals)

"""
To make this function follow the LSP principle, we will follow this LSP requirements postulated by Steve Fenton:
To make this function follow the LSP principle, we will follow this LSP
requirements postulated by Steve Fenton:

If the super-class (Animal) has a method that accepts a super-class type (Animal) parameter.
Its sub-class(Pigeon) should accept as argument a super-class type (Animal type) or sub-class type(Pigeon type).
If the super-class returns a super-class type (Animal).
Its sub-class should return a super-class type (Animal type) or sub-class type(Pigeon).
Now, we can re-implement animal_leg_count function:
If the super-class (Animal) has a method that accepts a super-class type
(Animal) parameter. Its sub-class(Pigeon) should accept as argument a
super-class type (Animal type) or sub-class type(Pigeon type). If the
super-class returns a super-class type (Animal). Its sub-class should return a
super-class type (Animal type) or sub-class type(Pigeon). Now, we can
re-implement animal_leg_count function:
"""

def animal_leg_count(animals: list):
Expand All @@ -35,11 +39,12 @@ def animal_leg_count(animals: list):
animal_leg_count(animals)

"""
The animal_leg_count function cares less the type of Animal passed, it just calls the leg_count method.
All it knows is that the parameter must be of an Animal type, either the Animal class or its sub-class.
The animal_leg_count function cares less the type of Animal passed, it just
calls the leg_count method. All it knows is that the parameter must be of an
Animal type, either the Animal class or its sub-class.

The Animal class now have to implement/define a leg_count method.
And its sub-classes have to implement the leg_count method:
The Animal class now have to implement/define a leg_count method. And its
sub-classes have to implement the leg_count method:
"""

class Animal:
Expand All @@ -53,8 +58,10 @@ def leg_count(self):


"""
When it’s passed to the animal_leg_count function, it returns the number of legs a lion has.
When it’s passed to the animal_leg_count function, it returns the number of legs
a lion has.

You see, the animal_leg_count doesn’t need to know the type of Animal to return its leg count,
it just calls the leg_count method of the Animal type because by contract a sub-class of Animal class must implement the leg_count function.
You see, the animal_leg_count doesn’t need to know the type of Animal to return
its leg count, it just calls the leg_count method of the Animal type because by
contract a sub-class of Animal class must implement the leg_count function.
"""
43 changes: 27 additions & 16 deletions 4.isp.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""
Interface Segregation Principle
Make fine grained interfaces that are client specific
Clients should not be forced to depend upon interfaces that they do not use.
This principle deals with the disadvantages of implementing big interfaces.

Make fine grained interfaces that are client specific Clients should not be
forced to depend upon interfaces that they do not use. This principle deals
with the disadvantages of implementing big interfaces.

Let’s look at the below IShape interface:
"""
Expand All @@ -18,8 +19,9 @@ def draw_circle(self):
raise NotImplementedError

"""
This interface draws squares, circles, rectangles. class Circle, Square or Rectangle implementing the IShape
interface must define the methods draw_square(), draw_rectangle(), draw_circle().
This interface draws squares, circles, rectangles. class Circle, Square or
Rectangle implementing the IShape interface must define the methods
draw_square(), draw_rectangle(), draw_circle().
"""

class Circle(IShape):
Expand Down Expand Up @@ -53,8 +55,9 @@ def draw_circle(self):
pass

"""
It’s quite funny looking at the code above. class Rectangle implements methods (draw_circle and draw_square) it has no use of,
likewise Square implementing draw_circle, and draw_rectangle, and class Circle (draw_square, draw_rectangle).
It’s quite funny looking at the code above. class Rectangle implements methods
(draw_circle and draw_square) it has no use of, likewise Square implementing
draw_circle, and draw_rectangle, and class Circle (draw_square, draw_rectangle).

If we add another method to the IShape interface, like draw_triangle(),
"""
Expand All @@ -74,18 +77,25 @@ def draw_triangle(self):


"""
the classes must implement the new method or error will be thrown.
The classes must implement the new method or error will be thrown.

We see that it is impossible to implement a shape that can draw a circle but not a rectangle or a square or a triangle.
We can just implement the methods to throw an error that shows the operation cannot be performed.
We see that it is impossible to implement a shape that can draw a circle but not
a rectangle or a square or a triangle. We can just implement the methods to
throw an error that shows the operation cannot be performed.

ISP frowns against the design of this IShape interface. clients (here Rectangle, Circle, and Square) should not be forced to depend on methods that they do not need or use.
Also, ISP states that interfaces should perform only one job (just like the SRP principle) any extra grouping of behavior should be abstracted away to another interface.
ISP frowns against the design of this IShape interface. clients (here Rectangle,
Circle, and Square) should not be forced to depend on methods that they do not
need or use. Also, ISP states that interfaces should perform only one job (just
like the SRP principle) any extra grouping of behavior should be abstracted away
to another interface.

Here, our IShape interface performs actions that should be handled independently by other interfaces.
Here, our IShape interface performs actions that should be handled independently
by other interfaces.

To make our IShape interface conform to the ISP principle, we segregate the actions to different interfaces.
Classes (Circle, Rectangle, Square, Triangle, etc) can just inherit from the IShape interface and implement their own draw behavior.
To make our IShape interface conform to the ISP principle, we segregate the
actions to different interfaces. Classes (Circle, Rectangle, Square, Triangle,
etc) can just inherit from the IShape interface and implement their own draw
behavior.
"""

class IShape:
Expand All @@ -105,5 +115,6 @@ def draw(self):
pass

"""
We can then use the I -interfaces to create Shape specifics like Semi Circle, Right-Angled Triangle, Equilateral Triangle, Blunt-Edged Rectangle, etc.
We can then use the I -interfaces to create Shape specifics like Semi Circle,
Right-Angled Triangle, Equilateral Triangle, Blunt-Edged Rectangle, etc.
"""
Loading