diff --git a/1.srp.py b/1.srp.py index 1ca3abe..cd27e8b 100644 --- a/1.srp.py +++ b/1.srp.py @@ -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: @@ -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: @@ -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: @@ -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. """ \ No newline at end of file diff --git a/2.ocp.py b/2.ocp.py index d654ecf..7f4bcad 100644 --- a/2.ocp.py +++ b/2.ocp.py @@ -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): @@ -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 = [ @@ -88,13 +91,15 @@ 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. """ @@ -102,8 +107,9 @@ def animal_sound(animals: list): """ 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: @@ -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: diff --git a/3.lsp.py b/3.lsp.py index a06149f..49e51b0 100644 --- a/3.lsp.py +++ b/3.lsp.py @@ -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. """ @@ -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): @@ -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: @@ -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. """ diff --git a/4.isp.py b/4.isp.py index df6398a..5d9483c 100644 --- a/4.isp.py +++ b/4.isp.py @@ -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: """ @@ -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): @@ -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(), """ @@ -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: @@ -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. """ \ No newline at end of file diff --git a/5.dip.py b/5.dip.py index f7cc19f..ea82234 100644 --- a/5.dip.py +++ b/5.dip.py @@ -1,12 +1,15 @@ """ Dependency Inversion Principle -Dependency should be on abstractions not concretions -A. High-level modules should not depend upon low-level modules. Both should depend upon abstractions. -B. Abstractions should not depend on details. Details should depend upon abstractions. -There comes a point in software development where our app will be largely composed of modules. -When this happens, we have to clear things up by using dependency injection. -High-level components depending on low-level components to function. +Dependency should be on abstractions not concretions A. High-level modules +should not depend upon low-level modules. Both should depend upon abstractions. +B. Abstractions should not depend on details. Details should depend upon +abstractions. + +There comes a point in software development where our app will be largely +composed of modules. When this happens, we have to clear things up by using +dependency injection. High-level components depending on low-level components +to function. """ class XMLHttpService(XMLHttpRequestService): @@ -23,14 +26,18 @@ def post(self, url, options: dict): self.xml_http_service.request(url, 'POST') """ -Here, Http is the high-level component whereas HttpService is the low-level component. -This design violates DIP A: High-level modules should not depend on low-level level modules. It should depend upon its abstraction. - -Ths Http class is forced to depend upon the XMLHttpService class. -If we were to change the Http connection service, maybe we want to connect to the internet through cURL or even Mock the http service. -We will painstakingly have to move through all the instances of Http to edit the code and this violates the OCP principle. - -The Http class should care less the type of Http service you are using. We make a Connection interface: +Here, Http is the high-level component whereas HttpService is the low-level +component. This design violates DIP A: High-level modules should not depend on +low-level level modules. It should depend upon its abstraction. + +Ths Http class is forced to depend upon the XMLHttpService class. If we were to +change the Http connection service, maybe we want to connect to the internet +through cURL or even Mock the http service. We will painstakingly have to move +through all the instances of Http to edit the code and this violates the OCP +principle. + +The Http class should care less the type of Http service you are using. We make +a Connection interface: """ class Connection: @@ -38,7 +45,8 @@ def request(self, url: str, options: dict): raise NotImplementedError """ -The Connection interface has a request method. With this, we pass in an argument of type Connection to our Http class: +The Connection interface has a request method. With this, we pass in an argument +of type Connection to our Http class: """ class Http: @@ -52,10 +60,12 @@ def post(self, url, options: dict): self.http_connection.request(url, 'POST') """ -So now, no matter the type of Http connection service passed to Http it can easily connect to a network -without bothering to know the type of network connection. +So now, no matter the type of Http connection service passed to Http it can +easily connect to a network without bothering to know the type of network +connection. -We can now re-implement our XMLHttpService class to implement the Connection interface: +We can now re-implement our XMLHttpService class to implement the Connection +interface: """ class XMLHttpService(Connection): @@ -66,7 +76,8 @@ def request(self, url: str, options:dict): self.xhr.send() """ -We can create many Http Connection types and pass it to our Http class without any fuss about errors. +We can create many Http Connection types and pass it to our Http class without +any fuss about errors. """ class NodeHttpService(Connection): def request(self, url: str, options:dict): @@ -77,10 +88,12 @@ def request(self, url: str, options:dict): pass """ -Now, we can see that both high-level modules and low-level modules depend on abstractions. -Http class(high level module) depends on the Connection interface(abstraction) and -the Http service types(low level modules) in turn, depends on the Connection interface(abstraction). - -Also, this DIP will force us not to violate the Liskov Substitution Principle: -The Connection types Node-XML-MockHttpService are substitutable for their parent type Connection. +Now, we can see that both high-level modules and low-level modules depend on +abstractions. Http class(high level module) depends on the Connection +interface(abstraction) and the Http service types(low level modules) in turn, +depends on the Connection interface(abstraction). + +Also, this DIP will force us not to violate the Liskov Substitution Principle: +The Connection types Node-XML-MockHttpService are substitutable for their parent +type Connection. """