From 7aeb1172ef2fa06e65e9a6b3eb85903d7832bf0c Mon Sep 17 00:00:00 2001 From: Matthew Leingang Date: Fri, 21 Jun 2019 19:01:24 -0700 Subject: [PATCH 1/5] Wrap docstrings to 80 columns --- 1.srp.py | 42 +++++++++++++++++++++++-------------- 2.ocp.py | 43 ++++++++++++++++++++++---------------- 3.lsp.py | 39 +++++++++++++++++++++-------------- 4.isp.py | 43 ++++++++++++++++++++++++-------------- 5.dip.py | 63 ++++++++++++++++++++++++++++++++++---------------------- 5 files changed, 139 insertions(+), 91 deletions(-) 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. """ From 20d9d1ee3095a72cb4c8580fb81eb9689decf291 Mon Sep 17 00:00:00 2001 From: Kislov Dmitry Date: Tue, 3 Sep 2019 16:10:53 +1000 Subject: [PATCH 2/5] HttpService -> XMLHttpService --- 5.dip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/5.dip.py b/5.dip.py index ea82234..fb3182e 100644 --- a/5.dip.py +++ b/5.dip.py @@ -26,7 +26,7 @@ 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 +Here, Http is the high-level component whereas XMLHttpService 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. From 9ffd682b8db1f1928efca0504a3622a1ca8f317e Mon Sep 17 00:00:00 2001 From: Muhammad Karim Date: Wed, 24 Jun 2020 08:36:10 +1000 Subject: [PATCH 3/5] Create LICENSE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1b9e0be --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Muhammad Karim + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 7b9954ad864f0a643651e9d2541b7e5664e51cb6 Mon Sep 17 00:00:00 2001 From: Muhammad Karim Date: Wed, 24 Jun 2020 01:29:38 +0000 Subject: [PATCH 4/5] GitBook: [master] 2 pages modified --- README.md | 15 +++++++++------ SUMMARY.md | 4 ++++ 2 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 SUMMARY.md diff --git a/README.md b/README.md index fe9302d..8e302e2 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,12 @@ # solid.python -#### [SOLID Principles](https://en.wikipedia.org/wiki/SOLID) explained in Python with examples. +## [SOLID Principles](https://en.wikipedia.org/wiki/SOLID) explained in Python with examples. + +* [Single Responsibility Principle](https://github.com/heykarimoff/solid.python/blob/master/1.srp.py) +* [Open/Closed Principle](https://github.com/heykarimoff/solid.python/blob/master/2.ocp.py) +* [Liskov Substitution Principle](https://github.com/heykarimoff/solid.python/blob/master/3.lsp.py) +* [Interface Segregation Principle](https://github.com/heykarimoff/solid.python/blob/master/4.isp.py) +* [Dependency Inversion Principle](https://github.com/heykarimoff/solid.python/blob/master/5.dip.py) + + -- [Single Responsibility Principle](https://github.com/heykarimoff/solid.python/blob/master/1.srp.py) -- [Open/Closed Principle](https://github.com/heykarimoff/solid.python/blob/master/2.ocp.py) -- [Liskov Substitution Principle](https://github.com/heykarimoff/solid.python/blob/master/3.lsp.py) -- [Interface Segregation Principle](https://github.com/heykarimoff/solid.python/blob/master/4.isp.py) -- [Dependency Inversion Principle](https://github.com/heykarimoff/solid.python/blob/master/5.dip.py) diff --git a/SUMMARY.md b/SUMMARY.md new file mode 100644 index 0000000..17d1eca --- /dev/null +++ b/SUMMARY.md @@ -0,0 +1,4 @@ +# Table of contents + +* [solid.python](README.md) + From 8a0ca44217014202094437ef6b8fd73b2e55dcea Mon Sep 17 00:00:00 2001 From: kimo722504 Date: Wed, 9 Dec 2020 23:25:57 +0900 Subject: [PATCH 5/5] Modify minor typo and type hint --- 1.srp.py | 6 +++--- 5.dip.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/1.srp.py b/1.srp.py index cd27e8b..fda52a2 100644 --- a/1.srp.py +++ b/1.srp.py @@ -44,9 +44,9 @@ def save(self, animal: Animal): class Animal: def __init__(self, name: str): - self.name = name + self.name = name - def get_name(self): + def get_name(self) -> str: pass @@ -75,7 +75,7 @@ def __init__(self, name: str): self.name = name self.db = AnimalDB() - def get_name(self): + def get_name(self) -> str: return self.name def get(self, id): diff --git a/5.dip.py b/5.dip.py index fb3182e..adc6e4f 100644 --- a/5.dip.py +++ b/5.dip.py @@ -22,13 +22,13 @@ def __init__(self, xml_http_service: XMLHttpService): def get(self, url: str, options: dict): self.xml_http_service.request(url, 'GET') - def post(self, url, options: dict): + def post(self, url: str, options: dict): self.xml_http_service.request(url, 'POST') """ Here, Http is the high-level component whereas XMLHttpService 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. +low-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 @@ -56,7 +56,7 @@ def __init__(self, http_connection: Connection): def get(self, url: str, options: dict): self.http_connection.request(url, 'GET') - def post(self, url, options: dict): + def post(self, url: str, options: dict): self.http_connection.request(url, 'POST') """