├── .gitignore ├── LICENSE ├── README.md ├── design_patterns_python ├── 1_creational_patterns │ ├── 1_abstract_factory_pattern.md │ ├── 1_abstract_factory_pattern.py │ ├── 2_builder_pattern.md │ ├── 2_builder_pattern.py │ ├── 3_factory_method_pattern.md │ ├── 3_factory_method_pattern.py │ ├── 4_prototype_pattern.md │ ├── 4_prototype_pattern.py │ ├── 5_singleton_pattern.md │ ├── 5_singleton_pattern.py │ └── __init__.py ├── 2_structural_patterns │ ├── 1_adapter_pattern.md │ ├── 1_adapter_pattern.py │ ├── 2_bridge_pattern.md │ ├── 2_bridge_pattern.py │ ├── 3_composite_pattern.md │ ├── 3_composite_pattern.py │ ├── 4_decorator_pattern.md │ ├── 4_decorator_pattern.py │ ├── 5_facade_pattern.md │ ├── 5_facade_pattern.py │ ├── 6_flyweigh_pattern.md │ ├── 6_flyweigh_pattern.py │ ├── 7_proxy_pattern.md │ ├── 7_proxy_pattern.py │ └── __init__.py ├── 3_behavioral_patterns │ ├── 10_visitor_pattern.md │ ├── 10_visitor_pattern.py │ ├── 1_chain_of_responsibility_pattern.md │ ├── 1_chain_of_responsibility_pattern.py │ ├── 2_command_pattern.md │ ├── 2_command_pattern.py │ ├── 3_interpreter_pattern.md │ ├── 3_interpreter_pattern.py │ ├── 4_mediator_pattern.md │ ├── 4_mediator_pattern.py │ ├── 5_memento_pattern.md │ ├── 5_memento_pattern.py │ ├── 6_observer_pattern.md │ ├── 6_observer_pattern.py │ ├── 7_state_pattern.md │ ├── 7_state_pattern.py │ ├── 8_strategy_pattern.md │ ├── 8_strategy_pattern.py │ ├── 9_template_method_pattern.md │ ├── 9_template_method_pattern.py │ └── __init__.py └── __init__.py └── requirements-dev.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # design-patterns-python 2 | 3 | ## Overview 4 | 5 | This repository provides practical examples in Python for each of the renowned Gang of Four (GoF) design patterns. These patterns provide proven solutions to common design challenges. 6 | 7 | ## Why GoF Design Patterns? 8 | 9 | The Gang of Four, Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, authored the seminal book "Design Patterns: Elements of Reusable Object-Oriented Software." Their work has had a profound impact on the field of software engineering by providing a common vocabulary and set of proven solutions to common design problems. 10 | 11 | By exploring this repository, you will gain practical insights into how to implement these design patterns in real-world scenarios, fostering a deeper comprehension of their nuances and applicability. 12 | 13 | ## What to Expect 14 | 15 | Each design pattern in this repository is accompanied by an explanation and example code. 16 | -------------------------------------------------------------------------------- /design_patterns_python/1_creational_patterns/1_abstract_factory_pattern.md: -------------------------------------------------------------------------------- 1 | The Abstract Factory pattern is a creational design pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes. In Python, this pattern can be implemented using classes and interfaces. 2 | 3 | In this example, `AbstractFactory` is an abstract class that defines the interface for creating the product objects. `ConcreteFactory1` and `ConcreteFactory2` are two concrete factories that implement the interface and create different families of products. 4 | 5 | `AbstractProductA` and `AbstractProductB` are abstract classes that define the interface for the product objects. ConcreteProductA1, `ConcreteProductA2`, `ConcreteProductB1`, and `ConcreteProductB2` are concrete classes that implement the interface. 6 | 7 | The `client` function takes a factory object as an argument and uses it to create the product objects. It then calls methods on the product objects to perform some operation. 8 | 9 | When the `client` function is called with `ConcreteFactory1` as the argument, it creates `ConcreteProductA1` and `ConcreteProductB1` objects and calls their respective methods. Similarly, when it is called with `C`oncreteFactory2` as the argument, it creates `ConcreteProductA2` and `ConcreteProductB2` objects and calls their respective methods. 10 | 11 | This implementation of the Abstract Factory pattern provides a way to create families of related or dependent objects without specifying their concrete classes, making it easy to switch between different families of products. 12 | -------------------------------------------------------------------------------- /design_patterns_python/1_creational_patterns/1_abstract_factory_pattern.py: -------------------------------------------------------------------------------- 1 | class AbstractFactory: 2 | def create_product_a(self): 3 | pass 4 | 5 | def create_product_b(self): 6 | pass 7 | 8 | 9 | class ConcreteFactory1(AbstractFactory): 10 | def create_product_a(self): 11 | return ConcreteProductA1() 12 | 13 | def create_product_b(self): 14 | return ConcreteProductB1() 15 | 16 | 17 | class ConcreteFactory2(AbstractFactory): 18 | def create_product_a(self): 19 | return ConcreteProductA2() 20 | 21 | def create_product_b(self): 22 | return ConcreteProductB2() 23 | 24 | 25 | class AbstractProductA: 26 | def method_a(self): 27 | pass 28 | 29 | 30 | class ConcreteProductA1(AbstractProductA): 31 | def method_a(self): 32 | return "ConcreteProductA1.method_a()" 33 | 34 | 35 | class ConcreteProductA2(AbstractProductA): 36 | def method_a(self): 37 | return "ConcreteProductA2.method_a()" 38 | 39 | 40 | class AbstractProductB: 41 | def method_b(self): 42 | pass 43 | 44 | 45 | class ConcreteProductB1(AbstractProductB): 46 | def method_b(self): 47 | return "ConcreteProductB1.method_b()" 48 | 49 | 50 | class ConcreteProductB2(AbstractProductB): 51 | def method_b(self): 52 | return "ConcreteProductB2.method_b()" 53 | 54 | 55 | def client(factory): 56 | product_a = factory.create_product_a() 57 | product_b = factory.create_product_b() 58 | 59 | print(product_a.method_a()) 60 | print(product_b.method_b()) 61 | 62 | 63 | factory1 = ConcreteFactory1() 64 | client(factory1) 65 | 66 | factory2 = ConcreteFactory2() 67 | client(factory2) 68 | -------------------------------------------------------------------------------- /design_patterns_python/1_creational_patterns/2_builder_pattern.md: -------------------------------------------------------------------------------- 1 | The Builder pattern is a creational design pattern that separates the construction of a complex object from its representation, allowing the same construction process to create different representations. In Python, this pattern can be implemented using classes and interfaces. 2 | 3 | In this example, `Director` is a class that directs the construction process using a builder object. `Builder` is an abstract class that defines the interface for building the product object. `Product` is the complex object being built. 4 | 5 | `CarBuilder` and `TruckBuilder` are concrete builder classes that implement the `Builder` interface and build different representations of the `Product` object. They override the `set_engine` method to enforce engine type constraints. 6 | 7 | The `client` function creates a `Director` object and two builder objects (`CarBuilder` and `TruckBuilder`). It then directs the construction process using each builder object to create a different representation of the `Product` object. The resulting products are printed to the console. 8 | 9 | This implementation of the Builder pattern allows for a flexible construction process and easy switching between different representations of the same complex object. 10 | -------------------------------------------------------------------------------- /design_patterns_python/1_creational_patterns/2_builder_pattern.py: -------------------------------------------------------------------------------- 1 | class Director: 2 | def __init__(self, builder): 3 | self.builder = builder 4 | 5 | def construct_car(self): 6 | self.builder.set_seats(4) 7 | self.builder.set_engine("Gasoline") 8 | self.builder.set_wheels(4) 9 | 10 | def construct_truck(self): 11 | self.builder.set_seats(2) 12 | self.builder.set_engine("Diesel") 13 | self.builder.set_wheels(6) 14 | 15 | 16 | class Builder: 17 | def __init__(self): 18 | self.product = Product() 19 | 20 | def set_seats(self, seats): 21 | self.product.seats = seats 22 | 23 | def set_engine(self, engine): 24 | self.product.engine = engine 25 | 26 | def set_wheels(self, wheels): 27 | self.product.wheels = wheels 28 | 29 | def get_product(self): 30 | return self.product 31 | 32 | 33 | class Product: 34 | def __init__(self): 35 | self.seats = None 36 | self.engine = None 37 | self.wheels = None 38 | 39 | def __str__(self): 40 | return f"Seats: {self.seats}, Engine: {self.engine}, Wheels: {self.wheels}" 41 | 42 | 43 | class CarBuilder(Builder): 44 | def __init__(self): 45 | super().__init__() 46 | 47 | def set_engine(self, engine): 48 | if engine != "Gasoline": 49 | raise ValueError("Car engine must be gasoline") 50 | self.product.engine = engine 51 | 52 | 53 | class TruckBuilder(Builder): 54 | def __init__(self): 55 | super().__init__() 56 | 57 | def set_engine(self, engine): 58 | if engine != "Diesel": 59 | raise ValueError("Truck engine must be diesel") 60 | self.product.engine = engine 61 | 62 | 63 | def client(director): 64 | car_builder = CarBuilder() 65 | director.builder = car_builder 66 | director.construct_car() 67 | car_product = car_builder.get_product() 68 | print("Car:", car_product) 69 | 70 | truck_builder = TruckBuilder() 71 | director.builder = truck_builder 72 | director.construct_truck() 73 | truck_product = truck_builder.get_product() 74 | print("Truck:", truck_product) 75 | 76 | 77 | if __name__ == '__main__': 78 | director = Director(None) 79 | client(director) 80 | -------------------------------------------------------------------------------- /design_patterns_python/1_creational_patterns/3_factory_method_pattern.md: -------------------------------------------------------------------------------- 1 | The Factory Method pattern is a creational design pattern that provides an interface for creating objects, but allows subclasses to decide which class to instantiate. 2 | 3 | In this example, we have an `Animal` abstract class with a `speak` method, and two concrete classes `Dog` and `Cat` that inherit from `Animal` and implement the `speak` method. We also have an `AnimalFactory` class with a `get_animal` method that takes an `animal_type` parameter and returns the corresponding `Animal` object. 4 | 5 | In the `main` function, an instance of the `AnimalFactory` class is created, and `get_animal` method is called with the desired `animal_type`. The method then returns the corresponding `Animal` object, which is then used to call the `speak` method. 6 | 7 | Using this implementation of the Factory Method pattern, we can easily add new `Animal` classes and modify the `AnimalFactory` class to return the appropriate `Animal` object based on the `animal_type` parameter. This keeps the code flexible and easy to maintain. 8 | -------------------------------------------------------------------------------- /design_patterns_python/1_creational_patterns/3_factory_method_pattern.py: -------------------------------------------------------------------------------- 1 | class Animal: 2 | def __init__(self, name): 3 | self.name = name 4 | 5 | def speak(self): 6 | pass 7 | 8 | 9 | class Dog(Animal): 10 | def speak(self): 11 | return "Woof!" 12 | 13 | 14 | class Cat(Animal): 15 | def speak(self): 16 | return "Meow!" 17 | 18 | 19 | class AnimalFactory: 20 | def create_animal(self, name): 21 | if name == "dog": 22 | return Dog(name) 23 | elif name == "cat": 24 | return Cat(name) 25 | else: 26 | return None 27 | 28 | 29 | if __name__ == '__main__': 30 | dog = AnimalFactory().create_animal("dog") 31 | print(isinstance(dog, Dog)) # Output: True 32 | -------------------------------------------------------------------------------- /design_patterns_python/1_creational_patterns/4_prototype_pattern.md: -------------------------------------------------------------------------------- 1 | The Prototype pattern is a creational design pattern that allows you to create copies of objects without exposing their underlying implementation details. In Python, this pattern can be implemented using the `copy` module or by defining a `clone` method in the class. 2 | 3 | In this example, `Prototype` is an abstract class that defines a `clone` method that returns a copy of the object. `Person` is a concrete class that inherits from `Prototype` and defines its own implementation of `__init__` and `__str__`. 4 | 5 | The `__str__` method returns a string representation of the object, which is used to print the object to the console. The `main` function creates two `Person` objects, `person1` and `person2`. `person2` is a clone of `person1` created using the `clone` method. 6 | 7 | `person2` is then modified by changing its name and age. This modification does not affect `person1`, since `person2` is a separate object. 8 | 9 | This implementation of the Prototype pattern allows you to create copies of objects without exposing their underlying implementation details. It also allows you to modify the copied object without affecting the original object. 10 | -------------------------------------------------------------------------------- /design_patterns_python/1_creational_patterns/4_prototype_pattern.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | 4 | class Prototype: 5 | def clone(self): 6 | return copy.deepcopy(self) 7 | 8 | 9 | class Person(Prototype): 10 | def __init__(self, name, age): 11 | self.name = name 12 | self.age = age 13 | 14 | def __str__(self): 15 | return f"{self.name}, {self.age} years old" 16 | 17 | 18 | if __name__ == '__main__': 19 | person1 = Person("John", 30) 20 | person2 = person1.clone() 21 | person2.name = "Jane" 22 | person2.age = 25 23 | 24 | print(person1) 25 | print(person2) 26 | -------------------------------------------------------------------------------- /design_patterns_python/1_creational_patterns/5_singleton_pattern.md: -------------------------------------------------------------------------------- 1 | The Singleton pattern is a creational design pattern that ensures that a class has only one instance, and provides a global point of access to that instance. 2 | 3 | In this example, the `MySingleton` class has a private constructor that raises an exception if an instance of the class already exists. The `get_instance` method is a static method that returns the existing instance of the `MySingleton` class, or creates a new instance if one doesn't exist. The example shows that `s1` and `s2` are both instances of the `MySingleton` class, but since the `MySingleton` class only allows one instance to be created, they are both the same instance. 4 | -------------------------------------------------------------------------------- /design_patterns_python/1_creational_patterns/5_singleton_pattern.py: -------------------------------------------------------------------------------- 1 | class MySingleton: 2 | __instance = None 3 | 4 | def __init__(self): 5 | if MySingleton.__instance != None: 6 | raise Exception("Singleton instance already exists") 7 | else: 8 | MySingleton.__instance = self 9 | 10 | @staticmethod 11 | def get_instance(): 12 | if MySingleton.__instance == None: 13 | MySingleton() 14 | return MySingleton.__instance 15 | 16 | if __name__ == '__main__': 17 | s1 = MySingleton.get_instance() 18 | s2 = MySingleton.get_instance() 19 | 20 | print(s1 == s2) # Output: True 21 | -------------------------------------------------------------------------------- /design_patterns_python/1_creational_patterns/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterbaccaro/design-patterns-python/52ff942fc3cf44bced4cd3a000f0ad36151998dd/design_patterns_python/1_creational_patterns/__init__.py -------------------------------------------------------------------------------- /design_patterns_python/2_structural_patterns/1_adapter_pattern.md: -------------------------------------------------------------------------------- 1 | The Adapter pattern is a structural design pattern that allows objects with incompatible interfaces to work together. The idea is to create an adapter object that converts the interface of one object into another interface that the client expects. 2 | 3 | In this example, `Target` is the interface that the client expects, and `Adaptee` is the incompatible interface. `Adapter` is the class that adapts `Adaptee` to `Target`. 4 | 5 | The `Adapter` class takes an instance of `Adaptee` as a parameter in its constructor. The `request` method of `Adapter` calls the `specific_request` method of the `Adaptee`. 6 | 7 | When the client calls the `request` method on the `Adapter` object, it is actually calling the `specific_request` method of the `Adaptee` object, but through the adapted `Target` interface. 8 | 9 | Note that the `Adapter` class inherits from `Target`, so it has the same interface as the `Target` class, which allows it to be used interchangeably with the `Target` class. 10 | -------------------------------------------------------------------------------- /design_patterns_python/2_structural_patterns/1_adapter_pattern.py: -------------------------------------------------------------------------------- 1 | class Target: 2 | def request(self): 3 | pass 4 | 5 | 6 | class Adaptee: 7 | def specific_request(self): 8 | return "Specific request" 9 | 10 | 11 | class Adapter(Target): 12 | def __init__(self, adaptee): 13 | self.adaptee = adaptee 14 | 15 | def request(self): 16 | return self.adaptee.specific_request() 17 | 18 | 19 | if __name__ == '__main__': 20 | adaptee = Adaptee() 21 | adapter = Adapter(adaptee) 22 | print(adapter.request()) # Output: "Specific request" 23 | -------------------------------------------------------------------------------- /design_patterns_python/2_structural_patterns/2_bridge_pattern.md: -------------------------------------------------------------------------------- 1 | The Bridge pattern is a structural design pattern that separates an abstraction from its implementation, allowing them to vary independently. In Python, this pattern can be implemented using classes and interfaces. 2 | 3 | In this example, `Abstraction` is a class that represents the abstraction and delegates its implementation to an `Implementation` object. `Implementation` is an abstract class that defines the interface for the implementation. 4 | 5 | `ConcreteImplementationA` and `ConcreteImplementationB` are concrete classes that inherit from `Implementation` and provide their own implementations of `operation_implementation`. 6 | 7 | The `main` function creates two `Implementation` objects (`implementationA` and `implementationB`) and two `Abstraction` objects (`abstractionA` and `abstractionB`). Each `Abstraction` object is initialized with a different `Implementation` object. 8 | 9 | `operation` is called on each `Abstraction` object, which in turn calls `operation_implementation` on its corresponding `Implementation` object. The output of each call is printed to the console. 10 | 11 | This implementation of the Bridge pattern separates the abstraction from its implementation, allowing them to vary independently. It also allows new abstractions and implementations to be added without affecting the existing code. 12 | -------------------------------------------------------------------------------- /design_patterns_python/2_structural_patterns/2_bridge_pattern.py: -------------------------------------------------------------------------------- 1 | class Abstraction: 2 | def __init__(self, implementation): 3 | self.implementation = implementation 4 | 5 | def operation(self): 6 | return f"Abstraction: {self.implementation.operation_implementation()}" 7 | 8 | 9 | class Implementation: 10 | def operation_implementation(self): 11 | pass 12 | 13 | 14 | class ConcreteImplementationA(Implementation): 15 | def operation_implementation(self): 16 | return "ConcreteImplementationA: Hello World" 17 | 18 | 19 | class ConcreteImplementationB(Implementation): 20 | def operation_implementation(self): 21 | return "ConcreteImplementationB: Goodbye World" 22 | 23 | 24 | if __name__ == '__main__': 25 | implementationA = ConcreteImplementationA() 26 | abstractionA = Abstraction(implementationA) 27 | print(abstractionA.operation()) 28 | 29 | implementationB = ConcreteImplementationB() 30 | abstractionB = Abstraction(implementationB) 31 | print(abstractionB.operation()) 32 | -------------------------------------------------------------------------------- /design_patterns_python/2_structural_patterns/3_composite_pattern.md: -------------------------------------------------------------------------------- 1 | The Composite pattern is a structural design pattern that allows you to compose objects into tree-like structures to represent whole-part hierarchies. In Python, this pattern can be implemented using classes and interfaces. 2 | 3 | In this example, `Component` is an abstract class that defines the interface for the composite and leaf objects. `Leaf` is a concrete class that represents the leaf objects. `Composite` is a concrete class that represents the composite objects. 4 | 5 | `Composite` has an internal list of child components and provides methods for adding and removing child components. `operation` is called on the `Composite` object, which in turn calls `operation` on each child component and concatenates the results. 6 | 7 | The `main` function creates three `Component` objects (`leaf1`, `leaf2`, and `composite`) and composes them into a tree structure using the `add` method of `Composite`. `composite2` is a second `Composite` object that composes `composite` and a `Leaf` object. 8 | 9 | operation is called on composite2, which in turn calls operation on each child component and concatenates the results. The output is printed to the console. 10 | 11 | This implementation of the Composite pattern allows you to treat composite and leaf objects uniformly and to compose them into tree structures to represent whole-part hierarchies. It also allows you to add and remove components dynamically without affecting the existing code. 12 | -------------------------------------------------------------------------------- /design_patterns_python/2_structural_patterns/3_composite_pattern.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Component(ABC): 5 | @abstractmethod 6 | def operation(self): 7 | pass 8 | 9 | 10 | class Leaf(Component): 11 | def operation(self): 12 | return "Leaf operation" 13 | 14 | 15 | class Composite(Component): 16 | def __init__(self): 17 | self._children = [] 18 | 19 | def add(self, component): 20 | self._children.append(component) 21 | 22 | def remove(self, component): 23 | self._children.remove(component) 24 | 25 | def operation(self): 26 | results = [] 27 | for child in self._children: 28 | results.append(child.operation()) 29 | return f"Composite operation with {', '.join(results)}" 30 | 31 | 32 | if __name__ == '__main__': 33 | leaf1 = Leaf() 34 | leaf2 = Leaf() 35 | composite = Composite() 36 | composite.add(leaf1) 37 | composite.add(leaf2) 38 | 39 | composite2 = Composite() 40 | composite2.add(composite) 41 | composite2.add(Leaf()) 42 | 43 | print(composite2.operation()) 44 | -------------------------------------------------------------------------------- /design_patterns_python/2_structural_patterns/4_decorator_pattern.md: -------------------------------------------------------------------------------- 1 | The Decorator pattern is a structural design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. 2 | 3 | In this example, `Component` is the interface that all components implement, and `ConcreteComponent` is the base component that we want to decorate. `Decorator` is the base decorator class that follows the same interface as the `Component` class, and `ConcreteDecoratorA` and `ConcreteDecoratorB` are the concrete decorators that add functionality to the `ConcreteComponent`. 4 | 5 | The `Decorator` class takes an instance of `Component` as a parameter in its constructor. The `operation` method of `Decorator` calls the `operation` method of the component it wraps, and can add functionality before or after that method call. 6 | 7 | When a client calls the `operation` method on a decorated component, it calls the decorated component's `operation` method, which in turn calls the next decorator's `operation` method, and so on, until the last decorator calls the `operation` method of the base `ConcreteComponent`. 8 | -------------------------------------------------------------------------------- /design_patterns_python/2_structural_patterns/4_decorator_pattern.py: -------------------------------------------------------------------------------- 1 | class Component: 2 | def operation(self): 3 | pass 4 | 5 | 6 | class ConcreteComponent(Component): 7 | def operation(self): 8 | return "ConcreteComponent" 9 | 10 | 11 | class Decorator(Component): 12 | def __init__(self, component): 13 | self._component = component 14 | 15 | def operation(self): 16 | return self._component.operation() 17 | 18 | 19 | class ConcreteDecoratorA(Decorator): 20 | def operation(self): 21 | return f"ConcreteDecoratorA({self._component.operation()})" 22 | 23 | 24 | class ConcreteDecoratorB(Decorator): 25 | def operation(self): 26 | return f"ConcreteDecoratorB({self._component.operation()})" 27 | 28 | 29 | if __name__ == '__main__': 30 | component = ConcreteComponent() 31 | decorator_a = ConcreteDecoratorA(component) 32 | decorator_b = ConcreteDecoratorB(decorator_a) 33 | print(decorator_b.operation()) # Output: "ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent))" 34 | -------------------------------------------------------------------------------- /design_patterns_python/2_structural_patterns/5_facade_pattern.md: -------------------------------------------------------------------------------- 1 | The Facade pattern is a structural design pattern that provides a simplified interface to a complex system of classes, making it easier to use. In Python, this pattern can be implemented using classes and interfaces. 2 | 3 | In this example, `SubsystemA` and `SubsystemB` are two complex subsystems with their own interfaces and implementations. `Facade` is a simplified interface that provides access to the subsystems. 4 | 5 | `Facade` initializes instances of `SubsystemA` and `SubsystemB`. `operation` is called on `Facade`, which in turn calls `operation1` and `operation2` on each subsystem and concatenates the results. 6 | 7 | The `main` function creates a `Facade` object and calls `operation`. The output is printed to the console. 8 | 9 | This implementation of the Facade pattern simplifies the interface to a complex system of classes, making it easier to use. It also provides a layer of abstraction that isolates the client code from the subsystems, making it easier to change the implementation of the subsystems without affecting the client code. 10 | -------------------------------------------------------------------------------- /design_patterns_python/2_structural_patterns/5_facade_pattern.py: -------------------------------------------------------------------------------- 1 | class SubsystemA: 2 | def operation1(self): 3 | return "Subsystem A, Operation 1" 4 | 5 | def operation2(self): 6 | return "Subsystem A, Operation 2" 7 | 8 | 9 | class SubsystemB: 10 | def operation1(self): 11 | return "Subsystem B, Operation 1" 12 | 13 | def operation2(self): 14 | return "Subsystem B, Operation 2" 15 | 16 | 17 | class Facade: 18 | def __init__(self): 19 | self._subsystem_a = SubsystemA() 20 | self._subsystem_b = SubsystemB() 21 | 22 | def operation(self): 23 | results = [] 24 | results.append(self._subsystem_a.operation1()) 25 | results.append(self._subsystem_a.operation2()) 26 | results.append(self._subsystem_b.operation1()) 27 | results.append(self._subsystem_b.operation2()) 28 | return f"Facade operation with {', '.join(results)}" 29 | 30 | 31 | if __name__ == '__main__': 32 | facade = Facade() 33 | print(facade.operation()) 34 | -------------------------------------------------------------------------------- /design_patterns_python/2_structural_patterns/6_flyweigh_pattern.md: -------------------------------------------------------------------------------- 1 | The Flyweight pattern is a structural design pattern that allows you to share objects that are frequently used to save memory and improve performance. In Python, this pattern can be implemented using classes and interfaces. 2 | 3 | In this example, `Flyweight` is a class that represents a flyweight object with a shared state and an operation that takes a unique state. `FlyweightFactory` is a class that manages the flyweight objects and returns them to clients. 4 | 5 | `FlyweightFactory` initializes an empty dictionary of flyweights. `get_flyweight` takes a shared state as an argument, looks up the shared state in the dictionary of flyweights, and returns the existing flyweight if it exists, or creates a new flyweight if it does not. 6 | 7 | The `main` function creates a `FlyweightFactory` object and gets three flyweights with different shared states. The `operation` method is called on each flyweight with a unique state. The output is printed to the console. 8 | 9 | This implementation of the Flyweight pattern allows you to share objects that are frequently used to save memory and improve performance. It also allows you to create new objects with different unique states without duplicating objects with the same shared state. 10 | -------------------------------------------------------------------------------- /design_patterns_python/2_structural_patterns/6_flyweigh_pattern.py: -------------------------------------------------------------------------------- 1 | class Flyweight: 2 | def __init__(self, shared_state): 3 | self._shared_state = shared_state 4 | 5 | def operation(self, unique_state): 6 | return f"Flyweight operation with shared state '{self._shared_state}' and unique state '{unique_state}'" 7 | 8 | 9 | class FlyweightFactory: 10 | def __init__(self): 11 | self._flyweights = {} 12 | 13 | def get_flyweight(self, shared_state): 14 | if shared_state not in self._flyweights: 15 | self._flyweights[shared_state] = Flyweight(shared_state) 16 | return self._flyweights[shared_state] 17 | 18 | 19 | if __name__ == '__main__': 20 | factory = FlyweightFactory() 21 | flyweight1 = factory.get_flyweight("shared_state") 22 | flyweight2 = factory.get_flyweight("shared_state") 23 | flyweight3 = factory.get_flyweight("other_shared_state") 24 | 25 | print(flyweight1.operation("unique_state1")) 26 | print(flyweight2.operation("unique_state2")) 27 | print(flyweight3.operation("unique_state3")) 28 | -------------------------------------------------------------------------------- /design_patterns_python/2_structural_patterns/7_proxy_pattern.md: -------------------------------------------------------------------------------- 1 | The Proxy pattern is a structural design pattern that provides a surrogate or placeholder for another object to control access to it. In Python, this pattern can be implemented using classes and interfaces. 2 | 3 | In this example, `Subject` is an abstract class that defines the interface for `RealSubject` and `Proxy`. `RealSubject` is a class that implements the Subject interface and provides the real functionality of the system. `Proxy` is a class that implements the `Subject` interface and provides a surrogate or placeholder for `RealSubject`. 4 | 5 | `Proxy` initializes an instance of `RealSubject`. `request` is called on `Proxy`, which checks access using `_check_access` and returns the result of `RealSubject`'s `request` method if access is granted, or a default string if access is not granted. 6 | 7 | The `main` function creates a `Proxy` object and calls `request`. The output is printed to the console. 8 | 9 | This implementation of the Proxy pattern provides a surrogate or placeholder for RealSubject to control access to it. It also allows you to change the implementation of RealSubject without affecting the client code. 10 | -------------------------------------------------------------------------------- /design_patterns_python/2_structural_patterns/7_proxy_pattern.py: -------------------------------------------------------------------------------- 1 | class Subject: 2 | def request(self): 3 | pass 4 | 5 | 6 | class RealSubject(Subject): 7 | def request(self): 8 | return "RealSubject request" 9 | 10 | 11 | class Proxy(Subject): 12 | def __init__(self): 13 | self._real_subject = RealSubject() 14 | 15 | def request(self): 16 | if self._check_access(): 17 | return self._real_subject.request() 18 | else: 19 | return "Proxy request" 20 | 21 | def _check_access(self): 22 | return True 23 | 24 | 25 | if __name__ == '__main__': 26 | proxy = Proxy() 27 | print(proxy.request()) 28 | -------------------------------------------------------------------------------- /design_patterns_python/2_structural_patterns/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterbaccaro/design-patterns-python/52ff942fc3cf44bced4cd3a000f0ad36151998dd/design_patterns_python/2_structural_patterns/__init__.py -------------------------------------------------------------------------------- /design_patterns_python/3_behavioral_patterns/10_visitor_pattern.md: -------------------------------------------------------------------------------- 1 | The Visitor pattern is a behavioral design pattern that allows adding new operations to an existing class hierarchy without modifying the classes themselves. 2 | 3 | In this example, `Visitor` is an abstract class that defines a set of operations that can be performed on the Shape objects. The `Shape` class is an abstract class that defines an accept method, which takes a visitor object as a parameter. The `Circle` and `Square` classes inherit from the `Shape` class and implement the `accept` method. 4 | 5 | Two concrete `Visitor` classes, `DrawVisitor` and `PrintVisitor`, inherit from the `Visitor` class and implement the operations defined in the abstract class. The `DrawVisitor` class draws shapes, and the `PrintVisitor` class prints shapes. 6 | 7 | In the main function, a list of `Shape` objects is created, and two visitor objects, `DrawVisitor` and `PrintVisitor`, are instantiated. The `accept` method is called on each shape object with both visitors as parameters. This way, each shape object accepts the two visitors, and the appropriate method in each visitor is called for each shape. 8 | 9 | This implementation of the Visitor pattern allows adding new operations to the `Shape` hierarchy without modifying the `Shape` classes themselves. It also keeps related behavior in separate classes, making the code easier to maintain and extend. 10 | -------------------------------------------------------------------------------- /design_patterns_python/3_behavioral_patterns/10_visitor_pattern.py: -------------------------------------------------------------------------------- 1 | class Visitor: 2 | def visit_circle(self, circle): 3 | pass 4 | 5 | def visit_square(self, square): 6 | pass 7 | 8 | 9 | class Shape: 10 | def accept(self, visitor): 11 | pass 12 | 13 | 14 | class Circle(Shape): 15 | def accept(self, visitor): 16 | visitor.visit_circle(self) 17 | 18 | 19 | class Square(Shape): 20 | def accept(self, visitor): 21 | visitor.visit_square(self) 22 | 23 | 24 | class DrawVisitor(Visitor): 25 | def visit_circle(self, circle): 26 | print("Drawing a circle") 27 | 28 | def visit_square(self, square): 29 | print("Drawing a square") 30 | 31 | 32 | class PrintVisitor(Visitor): 33 | def visit_circle(self, circle): 34 | print("Printing a circle") 35 | 36 | def visit_square(self, square): 37 | print("Printing a square") 38 | 39 | 40 | if __name__ == '__main__': 41 | shapes = [Circle(), Square()] 42 | draw_visitor = DrawVisitor() 43 | print_visitor = PrintVisitor() 44 | 45 | for shape in shapes: 46 | shape.accept(draw_visitor) 47 | 48 | for shape in shapes: 49 | shape.accept(print_visitor) 50 | -------------------------------------------------------------------------------- /design_patterns_python/3_behavioral_patterns/1_chain_of_responsibility_pattern.md: -------------------------------------------------------------------------------- 1 | The Chain of Responsibility pattern is a behavioral design pattern that allows you to pass requests through a chain of handlers, with each handler having the ability to either handle the request or pass it on to the next handler in the chain. In Python, this pattern can be implemented using classes and interfaces. 2 | 3 | In this example, `Handler` is an abstract class that defines the interface for handling requests and passing them on to the next handler in the chain. `ConcreteHandler1` and `ConcreteHandler2` are classes that implement the `Handler` interface and handle requests that they are responsible for, or pass the request on to the next handler in the chain if they are not. 4 | 5 | `ConcreteHandler2` initializes an instance of `ConcreteHandler1` as its successor. `handle_request` is called on `ConcreteHandler2`, which tries to handle the request itself, or passes the request on to its successor if it cannot handle it. 6 | 7 | The `main` function creates `ConcreteHandler1` and `ConcreteHandler2` objects, with `ConcreteHandler2` as the successor of `ConcreteHandler1`. `handle_request` is called on `ConcreteHandler2` with three different requests. The output is printed to the console. 8 | 9 | This implementation of the Chain of Responsibility pattern allows you to pass requests through a chain of handlers, with each handler having the ability to either handle the request or pass it on to the next handler in the chain. It also allows you to change the order or composition of the handlers without affecting the client code. 10 | -------------------------------------------------------------------------------- /design_patterns_python/3_behavioral_patterns/1_chain_of_responsibility_pattern.py: -------------------------------------------------------------------------------- 1 | class Handler: 2 | def __init__(self, successor=None): 3 | self._successor = successor 4 | 5 | def handle_request(self, request): 6 | pass 7 | 8 | 9 | class ConcreteHandler1(Handler): 10 | def handle_request(self, request): 11 | if request == "Request1": 12 | return "ConcreteHandler1 handled the request" 13 | elif self._successor: 14 | return self._successor.handle_request(request) 15 | else: 16 | return "No handler found" 17 | 18 | 19 | class ConcreteHandler2(Handler): 20 | def handle_request(self, request): 21 | if request == "Request2": 22 | return "ConcreteHandler2 handled the request" 23 | elif self._successor: 24 | return self._successor.handle_request(request) 25 | else: 26 | return "No handler found" 27 | 28 | 29 | if __name__ == '__main__': 30 | handler1 = ConcreteHandler1() 31 | handler2 = ConcreteHandler2(handler1) 32 | 33 | print(handler2.handle_request("Request1")) 34 | print(handler2.handle_request("Request2")) 35 | print(handler2.handle_request("Request3")) 36 | -------------------------------------------------------------------------------- /design_patterns_python/3_behavioral_patterns/2_command_pattern.md: -------------------------------------------------------------------------------- 1 | The Command pattern is a behavioral design pattern that encapsulates a request as an object, thereby allowing you to parameterize clients with different requests, queue or log requests, and support undoable operations. In Python, this pattern can be implemented using classes and interfaces. 2 | 3 | In this example, `Command` is an abstract class that defines the interface for executing commands. `Receiver` is a class that provides the functionality that will be executed by the command. `ConcreteCommand` is a class that implements the `Command` interface and encapsulates a `Receiver` object. 4 | 5 | `Invoker` is a class that maintains a list of commands and provides a method to execute them. The `main` function creates a `Receiver` object, a `ConcreteCommand` object with the `Receiver` as its parameter, and an `Invoker` object. The `ConcreteCommand` is added to the `Invoker`'s list of commands, and the commands are executed. The output is printed to the console. 6 | 7 | This implementation of the Command pattern allows you to encapsulate a request as an object and pass it as a parameter to clients, queue or log requests, and support undoable operations. It also allows you to change the functionality of the system without affecting the client code. 8 | -------------------------------------------------------------------------------- /design_patterns_python/3_behavioral_patterns/2_command_pattern.py: -------------------------------------------------------------------------------- 1 | class Command: 2 | def execute(self): 3 | pass 4 | 5 | 6 | class Receiver: 7 | def action(self): 8 | return "Receiver action" 9 | 10 | 11 | class ConcreteCommand(Command): 12 | def __init__(self, receiver): 13 | self._receiver = receiver 14 | 15 | def execute(self): 16 | return self._receiver.action() 17 | 18 | 19 | class Invoker: 20 | def __init__(self): 21 | self._commands = [] 22 | 23 | def add_command(self, command): 24 | self._commands.append(command) 25 | 26 | def execute_commands(self): 27 | results = [] 28 | for command in self._commands: 29 | results.append(command.execute()) 30 | return results 31 | 32 | 33 | if __name__ == '__main__': 34 | receiver = Receiver() 35 | command = ConcreteCommand(receiver) 36 | invoker = Invoker() 37 | invoker.add_command(command) 38 | 39 | results = invoker.execute_commands() 40 | for result in results: 41 | print(result) 42 | -------------------------------------------------------------------------------- /design_patterns_python/3_behavioral_patterns/3_interpreter_pattern.md: -------------------------------------------------------------------------------- 1 | The Interpreter pattern is a behavioral design pattern that provides a way to evaluate language grammar or expressions. In Python, this pattern can be implemented using classes and interfaces. 2 | 3 | In this example, `Context` is a class that holds the input string and provides a method to get the next token from the input string. `Expression` is an abstract class that defines the interface for interpreting expressions. `TerminalExpression` is a class that implements the `Expression` interface and interprets a single character. `NonterminalExpression` is a class that implements the `Expression` interface and interprets a sequence of expressions. 4 | 5 | The `main` function creates a `Context` object with an input string, creates `TerminalExpression` objects for "A" and "B", and creates a `NonterminalExpression` object with a sequence of expressions for "ABA". The `NonterminalExpression` is evaluated using the Context, and the result is printed to the console. 6 | 7 | This implementation of the Interpreter pattern allows you to evaluate language grammar or expressions using a set of classes that represent the grammar or expressions. It also allows you to change the grammar or expressions without affecting the client code. 8 | -------------------------------------------------------------------------------- /design_patterns_python/3_behavioral_patterns/3_interpreter_pattern.py: -------------------------------------------------------------------------------- 1 | class Context: 2 | def __init__(self, input_string): 3 | self._input_string = input_string 4 | self._index = 0 5 | 6 | def get_next_token(self): 7 | if self._index < len(self._input_string): 8 | token = self._input_string[self._index] 9 | self._index += 1 10 | return token 11 | return None 12 | 13 | 14 | class Expression: 15 | def interpret(self, context): 16 | pass 17 | 18 | 19 | class TerminalExpression(Expression): 20 | def __init__(self, data): 21 | self._data = data 22 | 23 | def interpret(self, context): 24 | if context.get_next_token() == self._data: 25 | return True 26 | return False 27 | 28 | 29 | class NonterminalExpression(Expression): 30 | def __init__(self, expressions): 31 | self._expressions = expressions 32 | 33 | def interpret(self, context): 34 | for expression in self._expressions: 35 | if not expression.interpret(context): 36 | return False 37 | return True 38 | 39 | 40 | if __name__ == '__main__': 41 | input_string = "ABAABBA" 42 | context = Context(input_string) 43 | 44 | expression1 = TerminalExpression("A") 45 | expression2 = TerminalExpression("B") 46 | expression3 = NonterminalExpression([expression1, expression2, expression1]) 47 | 48 | result = expression3.interpret(context) 49 | print(f"{input_string} is {'' if result else 'not '}in the language") 50 | -------------------------------------------------------------------------------- /design_patterns_python/3_behavioral_patterns/4_mediator_pattern.md: -------------------------------------------------------------------------------- 1 | The Mediator pattern is a behavioral design pattern that allows objects to communicate with each other without knowing about each other. Instead, they communicate through a mediator object, which encapsulates the communication logic. In Python, this pattern can be implemented using classes and interfaces. 2 | 3 | In this example, `Mediator` is an abstract class that defines the interface for sending messages between objects. `ConcreteMediator` is a class that implements the `Mediator` interface and encapsulates the communication logic between two `Colleague` objects. 4 | 5 | `Colleague` is an abstract class that defines the interface for objects that communicate through the mediator. `Colleague1` and `Colleague2` are classes that implement the `Colleague` interface and communicate with each other through the `ConcreteMediator`. 6 | 7 | The `main` function creates a `ConcreteMediator` object, creates `Colleague1` and `Colleague2` objects with the `ConcreteMediator` as their parameter, and sends messages between the colleagues using their `send` methods. 8 | 9 | This implementation of the Mediator pattern allows objects to communicate with each other without knowing about each other. Instead, they communicate through a mediator object, which encapsulates the communication logic. It also allows you to add new objects to the system without affecting the existing objects or the mediator object. 10 | -------------------------------------------------------------------------------- /design_patterns_python/3_behavioral_patterns/4_mediator_pattern.py: -------------------------------------------------------------------------------- 1 | class Mediator: 2 | def send(self, message, sender): 3 | pass 4 | 5 | 6 | class ConcreteMediator(Mediator): 7 | def __init__(self): 8 | self._colleague1 = Colleague1(self) 9 | self._colleague2 = Colleague2(self) 10 | 11 | def send(self, message, sender): 12 | if sender == self._colleague1: 13 | self._colleague2.receive(message) 14 | else: 15 | self._colleague1.receive(message) 16 | 17 | 18 | class Colleague: 19 | def __init__(self, mediator): 20 | self._mediator = mediator 21 | 22 | 23 | class Colleague1(Colleague): 24 | def send(self, message): 25 | self._mediator.send(message, self) 26 | 27 | def receive(self, message): 28 | print(f"Colleague1 received message: {message}") 29 | 30 | 31 | class Colleague2(Colleague): 32 | def send(self, message): 33 | self._mediator.send(message, self) 34 | 35 | def receive(self, message): 36 | print(f"Colleague2 received message: {message}") 37 | 38 | 39 | if __name__ == '__main__': 40 | mediator = ConcreteMediator() 41 | colleague1 = Colleague1(mediator) 42 | colleague2 = Colleague2(mediator) 43 | 44 | colleague1.send("Hello from Colleague1") 45 | colleague2.send("Hello from Colleague2") 46 | -------------------------------------------------------------------------------- /design_patterns_python/3_behavioral_patterns/5_memento_pattern.md: -------------------------------------------------------------------------------- 1 | The Memento pattern is a behavioral design pattern that allows you to capture and store the current state of an object in a way that you can restore it later. In Python, this pattern can be implemented using classes and interfaces. 2 | 3 | In this example, Memento is a class that stores the state of an Originator object. Originator is a class that creates and restores Mementos and has a state that can be saved and restored. Caretaker is a class that manages Mementos and can restore the state of an Originator object to a previous state. 4 | 5 | The main function creates an Originator object with an initial state of "State1". It then creates a Caretaker object and adds a Memento to it that stores the current state of the Originator object. It changes the state of the Originator object to "State2" and adds another Memento to the Caretaker object. It then changes the state of the Originator object to "State3". 6 | 7 | The Originator object then restores its state from the first Memento in the Caretaker object, which sets its state back to "State1". It then restores its state from the second Memento in the Caretaker object, which sets its state back to "State2". 8 | 9 | This implementation of the Memento pattern allows you to capture and store the current state of an object in a way that you can restore it later. It also keeps the details of the object's state encapsulated within the object, and allows you to add new types of Mementos to the system without affecting the existing Mementos or the Originator object. 10 | -------------------------------------------------------------------------------- /design_patterns_python/3_behavioral_patterns/5_memento_pattern.py: -------------------------------------------------------------------------------- 1 | class Memento: 2 | def __init__(self, state): 3 | self._state = state 4 | 5 | def get_state(self): 6 | return self._state 7 | 8 | 9 | class Originator: 10 | def __init__(self, state): 11 | self._state = state 12 | 13 | def create_memento(self): 14 | return Memento(self._state) 15 | 16 | def set_memento(self, memento): 17 | self._state = memento.get_state() 18 | 19 | def get_state(self): 20 | return self._state 21 | 22 | def set_state(self, state): 23 | self._state = state 24 | print(f"State set to: {self._state}") 25 | 26 | 27 | class Caretaker: 28 | def __init__(self): 29 | self._mementos = [] 30 | 31 | def add_memento(self, memento): 32 | self._mementos.append(memento) 33 | 34 | def get_memento(self, index): 35 | return self._mementos[index] 36 | 37 | 38 | if __name__ == '__main__': 39 | originator = Originator("State1") 40 | print(f"Current state: {originator.get_state()}") 41 | 42 | caretaker = Caretaker() 43 | caretaker.add_memento(originator.create_memento()) 44 | 45 | originator.set_state("State2") 46 | print(f"Current state: {originator.get_state()}") 47 | 48 | caretaker.add_memento(originator.create_memento()) 49 | 50 | originator.set_state("State3") 51 | print(f"Current state: {originator.get_state()}") 52 | 53 | originator.set_memento(caretaker.get_memento(0)) 54 | print(f"Current state after restoring from Memento 0: {originator.get_state()}") 55 | 56 | originator.set_memento(caretaker.get_memento(1)) 57 | print(f"Current state after restoring from Memento 1: {originator.get_state()}") 58 | -------------------------------------------------------------------------------- /design_patterns_python/3_behavioral_patterns/6_observer_pattern.md: -------------------------------------------------------------------------------- 1 | The Observer pattern is a behavioral design pattern that allows one-to-many relationships between objects. In this pattern, when one object (the subject) changes state, all of its dependents (observers) are notified and updated automatically. 2 | 3 | In this example, `Observer` is a class that represents an observer that can receive updates from the subject. `Subject` is a class that represents a subject that can be observed by multiple observers. Observers can attach and detach themselves from the subject, and the subject can notify all of its observers when its state changes. 4 | 5 | The `main` function creates a `Subject` object and three `Observer` objects. It attaches all three observers to the subject, then notifies them with a message. It then detaches the second observer from the subject, and notifies the remaining observers with another message. 6 | 7 | This implementation of the Observer pattern allows you to create a one-to-many relationship between objects, where the subject can notify multiple observers of its state changes. It also keeps the details of the subject's state changes encapsulated within the subject, and allows you to add or remove observers without affecting the subject or the other observers. 8 | -------------------------------------------------------------------------------- /design_patterns_python/3_behavioral_patterns/6_observer_pattern.py: -------------------------------------------------------------------------------- 1 | class Observer: 2 | def __init__(self, name): 3 | self._name = name 4 | 5 | def update(self, message): 6 | print(f"{self._name} received message: {message}") 7 | 8 | 9 | class Subject: 10 | def __init__(self): 11 | self._observers = [] 12 | 13 | def attach(self, observer): 14 | self._observers.append(observer) 15 | 16 | def detach(self, observer): 17 | self._observers.remove(observer) 18 | 19 | def notify(self, message): 20 | for observer in self._observers: 21 | observer.update(message) 22 | 23 | 24 | if __name__ == '__main__': 25 | subject = Subject() 26 | 27 | observer1 = Observer("Observer1") 28 | observer2 = Observer("Observer2") 29 | observer3 = Observer("Observer3") 30 | 31 | subject.attach(observer1) 32 | subject.attach(observer2) 33 | subject.attach(observer3) 34 | 35 | subject.notify("Hello World!") 36 | 37 | subject.detach(observer2) 38 | 39 | subject.notify("Goodbye World!") 40 | -------------------------------------------------------------------------------- /design_patterns_python/3_behavioral_patterns/7_state_pattern.md: -------------------------------------------------------------------------------- 1 | The State pattern is a behavioral design pattern that allows an object to alter its behavior when its internal state changes. The pattern encapsulates state-specific behavior into separate classes, known as states, and allows the object to change its current state dynamically. 2 | 3 | In this example, `State` is an abstract class that defines the interface for all states. The `StartState` and `StopState` classes are concrete implementations of the `State` class that define the behavior of the object when it's in the corresponding state. 4 | 5 | The `Context` class represents the object whose behavior can change based on its internal state. It contains a reference to the current state, which can be set or retrieved through its `set_state` and `get_state` methods, respectively. The `do_action` method of the `Context` class delegates the behavior to the current state. 6 | 7 | In the `main` function, a `Context` object is created, and its state is initially set to `StartState`. The `do_action` method of the `StartState` object is called, which prints a message and sets the context's state to the `StartState` object. The same process is repeated with the `StopState` object, which changes the context's state to the `StopState` object and prints a different message. 8 | 9 | This implementation of the State pattern allows the object's behavior to change dynamically based on its internal state. It also encapsulates the state-specific behavior in separate classes, which makes it easier to add or modify states without affecting the other states or the context object. 10 | -------------------------------------------------------------------------------- /design_patterns_python/3_behavioral_patterns/7_state_pattern.py: -------------------------------------------------------------------------------- 1 | class State: 2 | def do_action(self, context): 3 | pass 4 | 5 | 6 | class StartState(State): 7 | def do_action(self, context): 8 | print("Player is in start state") 9 | context.set_state(self) 10 | 11 | 12 | class StopState(State): 13 | def do_action(self, context): 14 | print("Player is in stop state") 15 | context.set_state(self) 16 | 17 | 18 | class Context: 19 | def __init__(self): 20 | self._state = None 21 | 22 | def get_state(self): 23 | return self._state 24 | 25 | def set_state(self, state): 26 | self._state = state 27 | 28 | def do_action(self): 29 | self._state.do_action(self) 30 | 31 | 32 | if __name__ == '__main__': 33 | context = Context() 34 | 35 | start_state = StartState() 36 | start_state.do_action(context) 37 | print(f"Current state: {type(context.get_state()).__name__}") 38 | 39 | stop_state = StopState() 40 | stop_state.do_action(context) 41 | print(f"Current state: {type(context.get_state()).__name__}") 42 | -------------------------------------------------------------------------------- /design_patterns_python/3_behavioral_patterns/8_strategy_pattern.md: -------------------------------------------------------------------------------- 1 | The Strategy pattern is a behavioral design pattern that allows a client object to choose from a family of algorithms at runtime. It encapsulates each algorithm in a separate class and makes them interchangeable at runtime. 2 | 3 | In this example, `Strategy` is an abstract class that defines the interface for all algorithms. The `OperationAdd`, `OperationSubtract`, and `OperationMultiply` classes are concrete implementations of the `Strategy` class that encapsulate the addition, subtraction, and multiplication algorithms, respectively. 4 | 5 | The Context class represents the client object that uses the strategy objects. It contains a reference to the current strategy object and uses its `execute_strategy` method to delegate the algorithm execution to the current strategy object. 6 | 7 | In the `main` function, a `Context` object is created with an initial strategy of `OperationAdd`. The `execute_strategy` method of the `Context` object is called with two numbers, which returns the sum of the numbers. The same process is repeated with the other two strategies, which return the difference and product of the numbers, respectively. 8 | 9 | This implementation of the Strategy pattern allows the client object to choose from a family of algorithms at runtime. It also encapsulates each algorithm in a separate class, which makes it easier to add or modify algorithms without affecting the other algorithms or the client object. 10 | -------------------------------------------------------------------------------- /design_patterns_python/3_behavioral_patterns/8_strategy_pattern.py: -------------------------------------------------------------------------------- 1 | class Strategy: 2 | def do_operation(self, num1, num2): 3 | pass 4 | 5 | 6 | class OperationAdd(Strategy): 7 | def do_operation(self, num1, num2): 8 | return num1 + num2 9 | 10 | 11 | class OperationSubtract(Strategy): 12 | def do_operation(self, num1, num2): 13 | return num1 - num2 14 | 15 | 16 | class OperationMultiply(Strategy): 17 | def do_operation(self, num1, num2): 18 | return num1 * num2 19 | 20 | 21 | class Context: 22 | def __init__(self, strategy): 23 | self._strategy = strategy 24 | 25 | def execute_strategy(self, num1, num2): 26 | return self._strategy.do_operation(num1, num2) 27 | 28 | 29 | if __name__ == '__main__': 30 | context = Context(OperationAdd()) 31 | print(f"10 + 5 = {context.execute_strategy(10, 5)}") 32 | 33 | context = Context(OperationSubtract()) 34 | print(f"10 - 5 = {context.execute_strategy(10, 5)}") 35 | 36 | context = Context(OperationMultiply()) 37 | print(f"10 * 5 = {context.execute_strategy(10, 5)}") 38 | -------------------------------------------------------------------------------- /design_patterns_python/3_behavioral_patterns/9_template_method_pattern.md: -------------------------------------------------------------------------------- 1 | The Template Method pattern is a behavioral design pattern that defines the skeleton of an algorithm in a base class and lets subclasses override specific steps of the algorithm without changing its structure. 2 | 3 | In this example, `AbstractClass` is an abstract class that defines the template method, which is the algorithm's skeleton. The `template_method` calls three operations: `_operation1`, `_operation2`, and `_operation3`. The `_operation1` and `_operation3` methods are implemented in the `AbstractClass`, while the `_operation2` method is left unimplemented. 4 | 5 | `ConcreteClass1` and `ConcreteClass2` are concrete subclasses that inherit from the `AbstractClass`. They both implement the `_operation2` method differently, and `ConcreteClass2` also overrides the `_operation3` method. 6 | 7 | In the main function, two objects are created from `ConcreteClass1` and `ConcreteClass2`. When the `template_method` method is called on these objects, the algorithm's skeleton is executed, and the overridden methods in the subclasses are called accordingly. 8 | 9 | This implementation of the Template Method pattern allows subclasses to provide their own implementations of specific steps of the algorithm without changing its overall structure. It also encapsulates the algorithm in the base class, making it easier to modify or extend the algorithm in the future. 10 | -------------------------------------------------------------------------------- /design_patterns_python/3_behavioral_patterns/9_template_method_pattern.py: -------------------------------------------------------------------------------- 1 | class AbstractClass: 2 | def template_method(self): 3 | self._operation1() 4 | self._operation2() 5 | self._operation3() 6 | 7 | def _operation1(self): 8 | print("AbstractClass: _operation1") 9 | 10 | def _operation2(self): 11 | pass 12 | 13 | def _operation3(self): 14 | print("AbstractClass: _operation3") 15 | 16 | 17 | class ConcreteClass1(AbstractClass): 18 | def _operation2(self): 19 | print("ConcreteClass1: _operation2") 20 | 21 | 22 | class ConcreteClass2(AbstractClass): 23 | def _operation2(self): 24 | print("ConcreteClass2: _operation2") 25 | 26 | def _operation3(self): 27 | print("ConcreteClass2: _operation3") 28 | 29 | 30 | if __name__ == '__main__': 31 | concrete_class_1 = ConcreteClass1() 32 | concrete_class_1.template_method() 33 | 34 | concrete_class_2 = ConcreteClass2() 35 | concrete_class_2.template_method() 36 | -------------------------------------------------------------------------------- /design_patterns_python/3_behavioral_patterns/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterbaccaro/design-patterns-python/52ff942fc3cf44bced4cd3a000f0ad36151998dd/design_patterns_python/3_behavioral_patterns/__init__.py -------------------------------------------------------------------------------- /design_patterns_python/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterbaccaro/design-patterns-python/52ff942fc3cf44bced4cd3a000f0ad36151998dd/design_patterns_python/__init__.py -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | flake8==6.0.0 2 | mypy==1.1.1 --------------------------------------------------------------------------------