├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── abstract_factory ├── README.md ├── abstract_factory_concept.py ├── big_chair.py ├── big_table.py ├── chair_factory.py ├── client.py ├── factory_a.py ├── factory_b.py ├── furniture_factory.py ├── interface_chair.py ├── interface_furniture_factory.py ├── interface_table.py ├── medium_chair.py ├── medium_table.py ├── small_chair.py ├── small_table.py └── table_factory.py ├── adapter ├── README.md ├── adapter_concept.py ├── client.py ├── cube_a.py ├── cube_b.py ├── cube_b_adapter.py ├── interface_cube_a.py └── interface_cube_b.py ├── bridge ├── README.md ├── bridge_concept.py ├── circle.py ├── circle_implementer.py ├── client.py ├── interface_shape.py ├── interface_shape_implementer.py ├── square.py └── square_implementer.py ├── builder ├── README.md ├── builder_concept.py ├── castle_director.py ├── client.py ├── house.py ├── house_builder.py ├── houseboat_director.py ├── igloo_director.py └── interface_house_builder.py ├── chain_of_responsibility ├── README.md ├── atm_dispenser_chain.py ├── chain_of_responsibility_concept.py ├── client.py ├── dispenser10.py ├── dispenser20.py ├── dispenser50.py └── interface_dispenser.py ├── coding-conventions.md ├── command ├── README.md ├── client.py ├── command_concept.py ├── interface_switch.py ├── light.py ├── switch.py ├── switch_off_command.py └── switch_on_command.py ├── composite ├── README.md ├── client.py ├── composite_concept.py ├── file.py ├── folder.py └── interface_component.py ├── decorator ├── README.md ├── add.py ├── client.py ├── decorator_concept.py ├── interface_value.py ├── sub.py └── value.py ├── facade ├── README.md ├── client.py ├── facade_concept.py ├── game_api.py ├── game_engine.py ├── reports.py ├── users.py └── wallets.py ├── factory ├── README.md ├── big_chair.py ├── chair_factory.py ├── client.py ├── factory_concept.py ├── interface_chair.py ├── medium_chair.py └── small_chair.py ├── flyweight ├── README.md ├── client.py ├── column.py ├── flyweight.py ├── flyweight_concept.py ├── flyweight_factory.py ├── row.py └── table.py ├── img ├── abstract_factory_concept.svg ├── abstract_furniture_factory.svg ├── adapter_concept.svg ├── adapter_example.svg ├── aggregates.svg ├── basic_class.svg ├── bridge_concept.svg ├── bridge_example.svg ├── builder_concept.svg ├── builder_example.svg ├── by-nc.png ├── chain_of_responsibility_concept.svg ├── chain_of_responsibility_example.svg ├── class_extends.svg ├── class_implements.svg ├── command_concept.svg ├── command_example.svg ├── composite_concept.svg ├── composite_example.svg ├── composition.svg ├── decorator_concept.svg ├── decorator_example.svg ├── design_patterns_in_python_book.jpg ├── design_patterns_in_python_book_125x178.jpg ├── directed_association.svg ├── dp_python_125.gif ├── dp_python_250.jpg ├── dp_typescript_250.jpg ├── facade_concept.svg ├── facade_example.svg ├── factory_concept.svg ├── factory_example.svg ├── factory_swimlane.svg ├── flag_au.gif ├── flag_ca.gif ├── flag_de.gif ├── flag_es.gif ├── flag_fr.gif ├── flag_in.gif ├── flag_it.gif ├── flag_jp.gif ├── flag_uk.gif ├── flag_us.gif ├── flyweight_concept.svg ├── flyweight_example.svg ├── ide_hint.jpg ├── interpreter_ast.svg ├── interpreter_ast_roman_numeral.svg ├── interpreter_concept.svg ├── interpreter_example.svg ├── iterator_concept.svg ├── iterator_example.svg ├── mediator_concept.svg ├── mediator_example.svg ├── memento_concept.svg ├── memento_example.svg ├── observer_concept.svg ├── observer_example.svg ├── prototype_concept.svg ├── prototype_example.svg ├── proxy_concept.svg ├── proxy_example.svg ├── pseudocode_annotation.svg ├── singleton_concept.svg ├── singleton_example.svg ├── skillshare_btn_sm.gif ├── state_concept.svg ├── state_example.svg ├── strategy_concept.svg ├── strategy_example.svg ├── template_concept.svg ├── template_example.svg ├── udemy_btn_sm.gif ├── visitor_concept.svg ├── visitor_example.svg └── yt_btn_sm.gif ├── interpreter ├── README.md ├── abstract_expression.py ├── add.py ├── client.py ├── interpreter_concept.py ├── number.py ├── roman_numeral.py ├── sentence_parser.py └── subtract.py ├── iterator ├── README.md ├── client.py └── iterator_concept.py ├── mediator ├── README.md ├── client.py ├── component.py ├── interface_component.py ├── mediator.py └── mediator_concept.py ├── memento ├── README.md ├── caretaker.py ├── client.py ├── game_character.py ├── memento.py └── memento_concept.py ├── observer ├── README.md ├── bar_graph_view.py ├── client.py ├── data_controller.py ├── data_model.py ├── interface_data_controller.py ├── interface_data_model.py ├── interface_data_view.py ├── observer_concept.py ├── pie_graph_view.py └── table_view.py ├── prototype ├── README.md ├── client.py ├── document.py ├── interface_prototype.py └── prototype_concept.py ├── proxy ├── README.md ├── client.py ├── interface_proteus.py ├── leopard.py ├── lion.py ├── proxy_concept.py └── serpent.py ├── singleton ├── README.md ├── client.py ├── game1.py ├── game2.py ├── game3.py ├── interface_game.py ├── leaderboard.py └── singleton_concept.py ├── state ├── README.md ├── client.py └── state_concept.py ├── strategy ├── README.md ├── client.py └── strategy_concept.py ├── template ├── README.md ├── abstract_document.py ├── client.py ├── html_document.py ├── template_concept.py └── text_document.py └── visitor ├── README.md ├── client.py └── visitor_concept.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [Sean-Bradley]# Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: seanwasere 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: sean_bradley 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://sean-bradley.medium.com/membership']# Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | site/ 3 | __pycache__/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | All code, images, text, videos and downloads from 2 | 3 | Repository : https://github.com/Sean-Bradley/Design-Patterns-In-Python 4 | Website : https://sbcode.net/python 5 | Book : https://www.amazon.com/dp/B08XLJ8Z2J : ASIN B08XLJ8Z2J 6 | EBook : https://www.amazon.com/dp/B08Z282SBC : ASIN B08Z282SBC 7 | YouTube : https://www.youtube.com/playlist?list=PLKWUX7aMnlEJzRvCXnwFEdk_WJDNjMDOo 8 | Udemy : https://www.udemy.com/course/design-patterns-in-python/?referralCode=7493DBBBF97FF2B0D24D 9 | Skillshare : https://skl.sh/34SM2Xg 10 | 11 | Copyright (c) 2019-2022, Sean Bradley 12 | All rights reserved. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Design Patterns In Python 2 | 3 | This repository focuses on the 23 famous GoF (Gang of Four) Design Patterns implemented in Python. 4 | 5 | It is supplementary to my book titled **Design Patterns In Python** (ASIN : B08XLJ8Z2J) 6 | 7 | 8 | 9 |    https://www.amazon.com/dp/B08XLJ8Z2J
10 |    https://www.amazon.co.uk/dp/B08XLJ8Z2J
11 |    https://www.amazon.in/dp/B08Z282SBC
12 |    https://www.amazon.de/dp/B08XLJ8Z2J
13 |    https://www.amazon.fr/dp/B08XLJ8Z2J
14 |    https://www.amazon.es/dp/B08XLJ8Z2J
15 |    https://www.amazon.it/dp/B08XLJ8Z2J
16 |    https://www.amazon.co.jp/dp/B08XLJ8Z2J
17 |    https://www.amazon.ca/dp/B08XLJ8Z2J
18 |    https://www.amazon.com.au/dp/B08XLJ8Z2J 19 | 20 | ## Course Access 21 | 22 | There are 3 possible ways to access the video content in this course, 23 | 24 | 1. Udemy : [https://www.udemy.com/course/design-patterns-in-python/?referralCode=7493DBBBF97FF2B0D24D](https://www.udemy.com/course/design-patterns-in-python/?referralCode=7493DBBBF97FF2B0D24D) 25 | - Get **Udemy Discount Coupons** at [https://sbcode.net/coupons](https://sbcode.net/coupons) 26 | - Certificate of Completion 27 | - 30 Day Money Back Guarantee 28 | 2. YouTube Membership : [https://www.youtube.com/channel/UCmUILI2AWt2MSUgPlZwFdOg/join](https://www.youtube.com/channel/UCmUILI2AWt2MSUgPlZwFdOg/join) 29 | - Cancel Membership Anytime 30 | 3. Book : [https://amzn.to/466lBN6](https://amzn.to/466lBN6) : ASIN B08XLJ8Z2J 31 | - **Book** includes FREE Video Access Codes to view videos from the official documentation website at [https://sbcode.net/python/](https://sbcode.net/python/) 32 | 33 | All the code examples in the book can be found in these pages. 34 | 35 | --- 36 | 37 | **TIP** 38 | 39 | > [Design Patterns In python](https://www.amazon.com/dp/B08XLJ8Z2J) **(Paperback/Kindle)** includes Video Access Codes to view videos for FREE from the official documentation website at [https://sbcode.net/python/](https://sbcode.net/python/) 40 | 41 | --- 42 | 43 | **TIP** 44 | 45 | > Get **Udemy Discount Coupons** at [https://sbcode.net/coupons](https://sbcode.net/coupons) 46 | 47 | --- 48 | 49 | ## Overview 50 | 51 | A Design Pattern is a description or template that can be repeatedly applied to a commonly recurring problem in software design. 52 | 53 | A familiarity of Design Patterns will be very useful when planning, discussing, managing and documenting your applications from now on and into the future. 54 | 55 | Also, throughout the book, as each design pattern is discussed and demonstrated using example code, I also introduce new python coding concepts with each new design pattern. So that as you progress through the book and try out the examples, you will also get experience and familiarity with some finer details of programming with python. 56 | 57 | So, in this book, you will learn about these 23 Design Patterns, 58 | 59 | - Creational 60 | - [Factory](factory) 61 | - [Abstract Factory](abstract_factory) 62 | - [Builder](builder) 63 | - [Prototype](prototype) 64 | - [Singleton](singleton) 65 | - Structural 66 | - [Decorator](decorator) 67 | - [Adapter](adapter) 68 | - [Facade](facade) 69 | - [Bridge](bridge) 70 | - [Composite](composite) 71 | - [Flyweight](flyweight) 72 | - [Proxy](proxy) 73 | - Behavioral 74 | - [Command](command) 75 | - [Chain of Responsibility](chain_of_responsibility) 76 | - [Observer Pattern](observer) 77 | - [Interpreter](interpreter) 78 | - [Iterator](iterator) 79 | - [Mediator](mediator) 80 | - [Memento](memento) 81 | - [State](state) 82 | - [Strategy](strategy) 83 | - [Template](template) 84 | - [Visitor](visitor) 85 | 86 | ## Pattern Types 87 | 88 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 89 | 90 | ## Class Scope and Object Scope Patterns 91 | 92 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 93 | -------------------------------------------------------------------------------- /abstract_factory/abstract_factory_concept.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "Abstract Factory Concept Sample Code" 3 | from abc import ABCMeta, abstractmethod 4 | from factory_a import FactoryA 5 | from factory_b import FactoryB 6 | 7 | 8 | class IAbstractFactory(metaclass=ABCMeta): 9 | "Abstract Factory Interface" 10 | 11 | @staticmethod 12 | @abstractmethod 13 | def create_object(factory): 14 | "The static Abstract factory interface method" 15 | 16 | 17 | class AbstractFactory(IAbstractFactory): 18 | "The Abstract Factory Concrete Class" 19 | 20 | @staticmethod 21 | def create_object(factory): 22 | "Static get_factory method" 23 | try: 24 | if factory in ['aa', 'ab', 'ac']: 25 | return FactoryA.create_object(factory[1]) 26 | if factory in ['ba', 'bb', 'bc']: 27 | return FactoryB.create_object(factory[1]) 28 | raise Exception('No Factory Found') 29 | except Exception as _e: 30 | print(_e) 31 | return None 32 | 33 | 34 | # The Client 35 | PRODUCT = AbstractFactory.create_object('ab') 36 | print(f"{PRODUCT.__class__}") 37 | 38 | PRODUCT = AbstractFactory.create_object('bc') 39 | print(f"{PRODUCT.__class__}") 40 | -------------------------------------------------------------------------------- /abstract_factory/big_chair.py: -------------------------------------------------------------------------------- 1 | "A Class of Chair" 2 | from interface_chair import IChair 3 | 4 | 5 | class BigChair(IChair): # pylint: disable=too-few-public-methods 6 | "The Big Chair Concrete Class that implements the IChair interface" 7 | 8 | def __init__(self): 9 | self._height = 80 10 | self._width = 80 11 | self._depth = 80 12 | 13 | def get_dimensions(self): 14 | return { 15 | "width": self._width, 16 | "depth": self._depth, 17 | "height": self._height 18 | } 19 | -------------------------------------------------------------------------------- /abstract_factory/big_table.py: -------------------------------------------------------------------------------- 1 | "A Class of Table" 2 | from interface_table import ITable 3 | 4 | 5 | class BigTable(ITable): # pylint: disable=too-few-public-methods 6 | "The Big Chair Concrete Class implements the ITable interface" 7 | 8 | def __init__(self): 9 | self._height = 60 10 | self._width = 120 11 | self._depth = 80 12 | 13 | def get_dimensions(self): 14 | return { 15 | "width": self._width, 16 | "depth": self._depth, 17 | "height": self._height 18 | } 19 | -------------------------------------------------------------------------------- /abstract_factory/chair_factory.py: -------------------------------------------------------------------------------- 1 | "The Factory Class" 2 | from small_chair import SmallChair 3 | from medium_chair import MediumChair 4 | from big_chair import BigChair 5 | 6 | 7 | class ChairFactory: # pylint: disable=too-few-public-methods 8 | "The Factory Class" 9 | 10 | @staticmethod 11 | def get_chair(chair): 12 | "A static method to get a chair" 13 | try: 14 | if chair == 'BigChair': 15 | return BigChair() 16 | if chair == 'MediumChair': 17 | return MediumChair() 18 | if chair == 'SmallChair': 19 | return SmallChair() 20 | raise Exception('Chair Not Found') 21 | except Exception as _e: 22 | print(_e) 23 | return None 24 | -------------------------------------------------------------------------------- /abstract_factory/client.py: -------------------------------------------------------------------------------- 1 | "Abstract Factory Use Case Example Code" 2 | from furniture_factory import FurnitureFactory 3 | 4 | 5 | FURNITURE = FurnitureFactory.get_furniture("SmallChair") 6 | print(f"{FURNITURE.__class__} : {FURNITURE.get_dimensions()}") 7 | 8 | FURNITURE = FurnitureFactory.get_furniture("MediumTable") 9 | print(f"{FURNITURE.__class__} : {FURNITURE.get_dimensions()}") 10 | -------------------------------------------------------------------------------- /abstract_factory/factory_a.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "FactoryA Sample Code" 3 | from abc import ABCMeta, abstractmethod 4 | 5 | 6 | class IProduct(metaclass=ABCMeta): 7 | "A Hypothetical Class Interface (Product)" 8 | 9 | @staticmethod 10 | @abstractmethod 11 | def create_object(): 12 | "An abstract interface method" 13 | 14 | 15 | class ConcreteProductA(IProduct): 16 | "A Concrete Class that implements the IProduct interface" 17 | 18 | def __init__(self): 19 | self.name = "ConcreteProductA" 20 | 21 | def create_object(self): 22 | return self 23 | 24 | 25 | class ConcreteProductB(IProduct): 26 | "A Concrete Class that implements the IProduct interface" 27 | 28 | def __init__(self): 29 | self.name = "ConcreteProductB" 30 | 31 | def create_object(self): 32 | return self 33 | 34 | 35 | class ConcreteProductC(IProduct): 36 | "A Concrete Class that implements the IProduct interface" 37 | 38 | def __init__(self): 39 | self.name = "ConcreteProductC" 40 | 41 | def create_object(self): 42 | return self 43 | 44 | 45 | class FactoryA: 46 | "The FactoryA Class" 47 | 48 | @staticmethod 49 | def create_object(some_property): 50 | "A static method to get a concrete product" 51 | try: 52 | if some_property == 'a': 53 | return ConcreteProductA() 54 | if some_property == 'b': 55 | return ConcreteProductB() 56 | if some_property == 'c': 57 | return ConcreteProductC() 58 | raise Exception('Class Not Found') 59 | except Exception as _e: 60 | print(_e) 61 | return None 62 | -------------------------------------------------------------------------------- /abstract_factory/factory_b.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "FactoryB Sample Code" 3 | from abc import ABCMeta, abstractmethod 4 | 5 | 6 | class IProduct(metaclass=ABCMeta): 7 | "A Hypothetical Class Interface (Product)" 8 | 9 | @staticmethod 10 | @abstractmethod 11 | def create_object(): 12 | "An abstract interface method" 13 | 14 | 15 | class ConcreteProductA(IProduct): 16 | "A Concrete Class that implements the IProduct interface" 17 | 18 | def __init__(self): 19 | self.name = "ConcreteProductA" 20 | 21 | def create_object(self): 22 | return self 23 | 24 | 25 | class ConcreteProductB(IProduct): 26 | "A Concrete Class that implements the IProduct interface" 27 | 28 | def __init__(self): 29 | self.name = "ConcreteProductB" 30 | 31 | def create_object(self): 32 | return self 33 | 34 | 35 | class ConcreteProductC(IProduct): 36 | "A Concrete Class that implements the IProduct interface" 37 | 38 | def __init__(self): 39 | self.name = "ConcreteProductC" 40 | 41 | def create_object(self): 42 | return self 43 | 44 | 45 | class FactoryB: 46 | "The FactoryB Class" 47 | 48 | @staticmethod 49 | def create_object(some_property): 50 | "A static method to get a concrete product" 51 | try: 52 | if some_property == 'a': 53 | return ConcreteProductA() 54 | if some_property == 'b': 55 | return ConcreteProductB() 56 | if some_property == 'c': 57 | return ConcreteProductC() 58 | raise Exception('Class Not Found') 59 | except Exception as _e: 60 | print(_e) 61 | return None 62 | -------------------------------------------------------------------------------- /abstract_factory/furniture_factory.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "Abstract Furniture Factory" 3 | from interface_furniture_factory import IFurnitureFactory 4 | from chair_factory import ChairFactory 5 | from table_factory import TableFactory 6 | 7 | 8 | class FurnitureFactory(IFurnitureFactory): 9 | "The Abstract Factory Concrete Class" 10 | 11 | @staticmethod 12 | def get_furniture(furniture): 13 | "Static get_factory method" 14 | try: 15 | if furniture in ['SmallChair', 'MediumChair', 'BigChair']: 16 | return ChairFactory.get_chair(furniture) 17 | if furniture in ['SmallTable', 'MediumTable', 'BigTable']: 18 | return TableFactory.get_table(furniture) 19 | raise Exception('No Factory Found') 20 | except Exception as _e: 21 | print(_e) 22 | return None 23 | -------------------------------------------------------------------------------- /abstract_factory/interface_chair.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "The Chair Interface" 3 | from abc import ABCMeta, abstractmethod 4 | 5 | 6 | class IChair(metaclass=ABCMeta): 7 | "The Chair Interface (Product)" 8 | 9 | @staticmethod 10 | @abstractmethod 11 | def get_dimensions(): 12 | "A static interface method" 13 | -------------------------------------------------------------------------------- /abstract_factory/interface_furniture_factory.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "The Abstract Factory Interface" 3 | from abc import ABCMeta, abstractmethod 4 | 5 | 6 | class IFurnitureFactory(metaclass=ABCMeta): 7 | "Abstract Furniture Factory Interface" 8 | 9 | @staticmethod 10 | @abstractmethod 11 | def get_furniture(furniture): 12 | "The static Abstract factory interface method" 13 | -------------------------------------------------------------------------------- /abstract_factory/interface_table.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "The Table Interface" 3 | from abc import ABCMeta, abstractmethod 4 | 5 | 6 | class ITable(metaclass=ABCMeta): 7 | "The Table Interface (Product)" 8 | 9 | @staticmethod 10 | @abstractmethod 11 | def get_dimensions(): 12 | "A static interface method" 13 | -------------------------------------------------------------------------------- /abstract_factory/medium_chair.py: -------------------------------------------------------------------------------- 1 | "A Class of Chair" 2 | from interface_chair import IChair 3 | 4 | 5 | class MediumChair(IChair): # pylint: disable=too-few-public-methods 6 | """The Medium Chair Concrete Class implements the IChair interface""" 7 | 8 | def __init__(self): 9 | self._height = 60 10 | self._width = 60 11 | self._depth = 60 12 | 13 | def get_dimensions(self): 14 | return { 15 | "width": self._width, 16 | "depth": self._depth, 17 | "height": self._height 18 | } 19 | -------------------------------------------------------------------------------- /abstract_factory/medium_table.py: -------------------------------------------------------------------------------- 1 | "A Class of Table" 2 | from interface_table import ITable 3 | 4 | 5 | class MediumTable(ITable): # pylint: disable=too-few-public-methods 6 | "The Medium Table Concrete Class implements the ITable interface" 7 | 8 | def __init__(self): 9 | self._height = 60 10 | self._width = 110 11 | self._depth = 70 12 | 13 | def get_dimensions(self): 14 | return { 15 | "width": self._width, 16 | "depth": self._depth, 17 | "height": self._height 18 | } 19 | -------------------------------------------------------------------------------- /abstract_factory/small_chair.py: -------------------------------------------------------------------------------- 1 | "A Class of Chair" 2 | from interface_chair import IChair 3 | 4 | 5 | class SmallChair(IChair): # pylint: disable=too-few-public-methods 6 | "The Small Chair Concrete Class implements the IChair interface" 7 | 8 | def __init__(self): 9 | self._height = 40 10 | self._width = 40 11 | self._depth = 40 12 | 13 | def get_dimensions(self): 14 | return { 15 | "width": self._width, 16 | "depth": self._depth, 17 | "height": self._height 18 | } 19 | -------------------------------------------------------------------------------- /abstract_factory/small_table.py: -------------------------------------------------------------------------------- 1 | "A Class of Table" 2 | from interface_table import ITable 3 | 4 | 5 | class SmallTable(ITable): # pylint: disable=too-few-public-methods 6 | "The Small Table Concrete Class implements the ITable interface" 7 | 8 | def __init__(self): 9 | self._height = 60 10 | self._width = 100 11 | self._depth = 60 12 | 13 | def get_dimensions(self): 14 | return { 15 | "width": self._width, 16 | "depth": self._depth, 17 | "height": self._height 18 | } 19 | -------------------------------------------------------------------------------- /abstract_factory/table_factory.py: -------------------------------------------------------------------------------- 1 | "The Factory Class" 2 | from small_table import SmallTable 3 | from medium_table import MediumTable 4 | from big_table import BigTable 5 | 6 | 7 | class TableFactory: # pylint: disable=too-few-public-methods 8 | "The Factory Class" 9 | 10 | @staticmethod 11 | def get_table(table): 12 | "A static method to get a table" 13 | try: 14 | if table == 'BigTable': 15 | return BigTable() 16 | if table == 'MediumTable': 17 | return MediumTable() 18 | if table == 'SmallTable': 19 | return SmallTable() 20 | raise Exception('Table Not Found') 21 | except Exception as _e: 22 | print(_e) 23 | return None 24 | -------------------------------------------------------------------------------- /adapter/adapter_concept.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | # pylint: disable=arguments-differ 3 | "Adapter Concept Sample Code" 4 | from abc import ABCMeta, abstractmethod 5 | 6 | 7 | class IA(metaclass=ABCMeta): 8 | "An interface for an object" 9 | @staticmethod 10 | @abstractmethod 11 | def method_a(): 12 | "An abstract method A" 13 | 14 | 15 | class ClassA(IA): 16 | "A Sample Class the implements IA" 17 | 18 | def method_a(self): 19 | print("method A") 20 | 21 | 22 | class IB(metaclass=ABCMeta): 23 | "An interface for an object" 24 | @staticmethod 25 | @abstractmethod 26 | def method_b(): 27 | "An abstract method B" 28 | 29 | 30 | class ClassB(IB): 31 | "A Sample Class the implements IB" 32 | 33 | def method_b(self): 34 | print("method B") 35 | 36 | 37 | class ClassBAdapter(IA): 38 | "ClassB does not have a method_a, so we can create an adapter" 39 | 40 | def __init__(self): 41 | self.class_b = ClassB() 42 | 43 | def method_a(self): 44 | "calls the class b method_b instead" 45 | self.class_b.method_b() 46 | 47 | 48 | # The Client 49 | # Before the adapter I need to test the objects class to know which 50 | # method to call. 51 | ITEMS = [ClassA(), ClassB()] 52 | for item in ITEMS: 53 | if isinstance(item, ClassB): 54 | item.method_b() 55 | else: 56 | item.method_a() 57 | 58 | # After creating an adapter for ClassB I can reuse the same method 59 | # signature as ClassA (preferred) 60 | ITEMS = [ClassA(), ClassBAdapter()] 61 | for item in ITEMS: 62 | item.method_a() 63 | -------------------------------------------------------------------------------- /adapter/client.py: -------------------------------------------------------------------------------- 1 | "Adapter Example Use Case" 2 | 3 | import time 4 | import random 5 | from cube_a import CubeA 6 | from cube_b_adapter import CubeBAdapter 7 | 8 | 9 | # client 10 | TOTALCUBES = 5 11 | COUNTER = 0 12 | while COUNTER < TOTALCUBES: 13 | # produce 5 cubes from which ever supplier can manufacture it first 14 | WIDTH = random.randint(1, 10) 15 | HEIGHT = random.randint(1, 10) 16 | DEPTH = random.randint(1, 10) 17 | CUBE = CubeA() 18 | SUCCESS = CUBE.manufacture(WIDTH, HEIGHT, DEPTH) 19 | if SUCCESS: 20 | print( 21 | f"Company A building Cube id:{id(CUBE)}, " 22 | f"{CUBE.width}x{CUBE.height}x{CUBE.depth}") 23 | COUNTER = COUNTER + 1 24 | else: # try other manufacturer 25 | print("Company A is busy, trying company B") 26 | CUBE = CubeBAdapter() 27 | SUCCESS = CUBE.manufacture(WIDTH, HEIGHT, DEPTH) 28 | if SUCCESS: 29 | print( 30 | f"Company B building Cube id:{id(CUBE)}, " 31 | f"{CUBE.width}x{CUBE.height}x{CUBE.depth}") 32 | COUNTER = COUNTER + 1 33 | else: 34 | print("Company B is busy, trying company A") 35 | # wait some time before manufacturing a new cube 36 | time.sleep(1) 37 | 38 | print(f"{TOTALCUBES} cubes have been manufactured") 39 | -------------------------------------------------------------------------------- /adapter/cube_a.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "A Class of Cube from Company A" 3 | import time 4 | from interface_cube_a import ICubeA 5 | 6 | 7 | class CubeA(ICubeA): 8 | "A hypothetical Cube tool from company A" 9 | # a static variable indicating the last time a cube was manufactured 10 | last_time = int(time.time()) 11 | 12 | def __init__(self): 13 | self.width = self.height = self.depth = 0 14 | 15 | def manufacture(self, width, height, depth): 16 | self.width = width 17 | self.height = height 18 | self.depth = depth 19 | # if not busy, then manufacture a cube with dimensions 20 | now = int(time.time()) 21 | if now > int(CubeA.last_time + 1): 22 | CubeA.last_time = now 23 | return True 24 | return False # busy 25 | -------------------------------------------------------------------------------- /adapter/cube_b.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "A Class of Cube from Company B" 3 | import time 4 | from interface_cube_b import ICubeB 5 | 6 | 7 | class CubeB(ICubeB): 8 | "A hypothetical Cube tool from company B" 9 | # a static variable indicating the last time a cube was manufactured 10 | last_time = int(time.time()) 11 | 12 | def create(self, top_left_front, bottom_right_back): 13 | now = int(time.time()) 14 | if now > int(CubeB.last_time + 2): 15 | CubeB.last_time = now 16 | return True 17 | return False # busy 18 | -------------------------------------------------------------------------------- /adapter/cube_b_adapter.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "An adapter for CubeB so that it can be used like Cube A" 3 | from interface_cube_a import ICubeA 4 | from cube_b import CubeB 5 | 6 | 7 | class CubeBAdapter(ICubeA): 8 | "Adapter for CubeB that implements ICubeA" 9 | 10 | def __init__(self): 11 | self.cube = CubeB() 12 | self.width = self.height = self.depth = 0 13 | 14 | def manufacture(self, width, height, depth): 15 | self.width = width 16 | self.height = height 17 | self.depth = depth 18 | 19 | success = self.cube.create( 20 | [0-width/2, 0-height/2, 0-depth/2], 21 | [0+width/2, 0+height/2, 0+depth/2] 22 | ) 23 | return success 24 | -------------------------------------------------------------------------------- /adapter/interface_cube_a.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "An interface to implement" 3 | from abc import ABCMeta, abstractmethod 4 | 5 | 6 | class ICubeA(metaclass=ABCMeta): 7 | "An interface for an object" 8 | @staticmethod 9 | @abstractmethod 10 | def manufacture(width, height, depth): 11 | "manufactures a cube" 12 | -------------------------------------------------------------------------------- /adapter/interface_cube_b.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "An interface to implement" 3 | from abc import ABCMeta, abstractmethod 4 | 5 | 6 | class ICubeB(metaclass=ABCMeta): 7 | "An interface for an object" 8 | @staticmethod 9 | @abstractmethod 10 | def create(top_left_front, bottom_right_back): 11 | "Manufactures a Cube with coords offset [0, 0, 0]" 12 | -------------------------------------------------------------------------------- /bridge/bridge_concept.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | # pylint: disable=arguments-differ 3 | "Bridge Pattern Concept Sample Code" 4 | from abc import ABCMeta, abstractmethod 5 | 6 | class IAbstraction(metaclass=ABCMeta): 7 | "The Abstraction Interface" 8 | 9 | @staticmethod 10 | @abstractmethod 11 | def method(*args): 12 | "The method handle" 13 | 14 | class RefinedAbstractionA(IAbstraction): 15 | "A Refined Abstraction" 16 | 17 | def __init__(self, implementer): 18 | self.implementer = implementer() 19 | 20 | def method(self, *args): 21 | self.implementer.method(*args) 22 | 23 | class RefinedAbstractionB(IAbstraction): 24 | "A Refined Abstraction" 25 | 26 | def __init__(self, implementer): 27 | self.implementer = implementer() 28 | 29 | def method(self, *args): 30 | self.implementer.method(*args) 31 | 32 | class IImplementer(metaclass=ABCMeta): 33 | "The Implementer Interface" 34 | 35 | @staticmethod 36 | @abstractmethod 37 | def method(*args: tuple) -> None: 38 | "The method implementation" 39 | 40 | class ConcreteImplementerA(IImplementer): 41 | "A Concrete Implementer" 42 | 43 | @staticmethod 44 | def method(*args: tuple) -> None: 45 | print(args) 46 | 47 | class ConcreteImplementerB(IImplementer): 48 | "A Concrete Implementer" 49 | 50 | @staticmethod 51 | def method(*args: tuple) -> None: 52 | for arg in args: 53 | print(arg) 54 | 55 | # The Client 56 | REFINED_ABSTRACTION_A = RefinedAbstractionA(ConcreteImplementerA) 57 | REFINED_ABSTRACTION_A.method('a', 'b', 'c') 58 | 59 | REFINED_ABSTRACTION_B = RefinedAbstractionB(ConcreteImplementerB) 60 | REFINED_ABSTRACTION_B.method('a', 'b', 'c') 61 | -------------------------------------------------------------------------------- /bridge/circle.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "A Circle Abstraction" 3 | from interface_shape import IShape 4 | 5 | 6 | class Circle(IShape): 7 | "The Circle is a Refined Abstraction" 8 | 9 | def __init__(self, implementer): 10 | self.implementer = implementer() 11 | 12 | def draw(self): 13 | self.implementer.draw_implementation() 14 | -------------------------------------------------------------------------------- /bridge/circle_implementer.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "A Circle Implementer" 3 | from interface_shape_implementer import IShapeImplementer 4 | 5 | 6 | class CircleImplementer(IShapeImplementer): 7 | "A Circle Implementer" 8 | 9 | def draw_implementation(self): 10 | print(" ******") 11 | print(" ** **") 12 | print(" * *") 13 | print("* *") 14 | print("* *") 15 | print(" * *") 16 | print(" ** **") 17 | print(" ******") 18 | -------------------------------------------------------------------------------- /bridge/client.py: -------------------------------------------------------------------------------- 1 | "Bridge Pattern Concept Sample Code" 2 | 3 | from circle_implementer import CircleImplementer 4 | from square_implementer import SquareImplementer 5 | from circle import Circle 6 | from square import Square 7 | 8 | CIRCLE = Circle(CircleImplementer) 9 | CIRCLE.draw() 10 | 11 | SQUARE = Square(SquareImplementer) 12 | SQUARE.draw() 13 | -------------------------------------------------------------------------------- /bridge/interface_shape.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "The Shape Abstraction Interface" 3 | from abc import ABCMeta, abstractmethod 4 | 5 | 6 | class IShape(metaclass=ABCMeta): 7 | "The Shape Abstraction Interface" 8 | 9 | @staticmethod 10 | @abstractmethod 11 | def draw(): 12 | "The method that will be handled at the shapes implementer" 13 | -------------------------------------------------------------------------------- /bridge/interface_shape_implementer.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "A Shape Implementor Interface" 3 | from abc import ABCMeta, abstractmethod 4 | 5 | 6 | class IShapeImplementer(metaclass=ABCMeta): 7 | "Shape Implementer" 8 | 9 | @staticmethod 10 | @abstractmethod 11 | def draw_implementation(): 12 | "The method that the refined abstractions will implement" 13 | -------------------------------------------------------------------------------- /bridge/square.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "A Square Abstraction" 3 | from interface_shape import IShape 4 | 5 | 6 | class Square(IShape): 7 | "The Square is a Refined Abstraction" 8 | 9 | def __init__(self, implementer): 10 | self.implementer = implementer() 11 | 12 | def draw(self): 13 | self.implementer.draw_implementation() 14 | -------------------------------------------------------------------------------- /bridge/square_implementer.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "A Square Implementer" 3 | from interface_shape_implementer import IShapeImplementer 4 | 5 | 6 | class SquareImplementer(IShapeImplementer): 7 | "A Square Implementer" 8 | 9 | def draw_implementation(self): 10 | print("**************") 11 | print("* *") 12 | print("* *") 13 | print("* *") 14 | print("* *") 15 | print("* *") 16 | print("* *") 17 | print("**************") 18 | -------------------------------------------------------------------------------- /builder/builder_concept.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | # pylint: disable=arguments-differ 3 | "Builder Concept Sample Code" 4 | from abc import ABCMeta, abstractmethod 5 | 6 | 7 | class IBuilder(metaclass=ABCMeta): 8 | "The Builder Interface" 9 | 10 | @staticmethod 11 | @abstractmethod 12 | def build_part_a(): 13 | "Build part a" 14 | 15 | @staticmethod 16 | @abstractmethod 17 | def build_part_b(): 18 | "Build part b" 19 | 20 | @staticmethod 21 | @abstractmethod 22 | def build_part_c(): 23 | "Build part c" 24 | 25 | @staticmethod 26 | @abstractmethod 27 | def get_result(): 28 | "Return the final product" 29 | 30 | 31 | class Builder(IBuilder): 32 | "The Concrete Builder." 33 | 34 | def __init__(self): 35 | self.product = Product() 36 | 37 | def build_part_a(self): 38 | self.product.parts.append('a') 39 | return self 40 | 41 | def build_part_b(self): 42 | self.product.parts.append('b') 43 | return self 44 | 45 | def build_part_c(self): 46 | self.product.parts.append('c') 47 | return self 48 | 49 | def get_result(self): 50 | return self.product 51 | 52 | 53 | class Product(): 54 | "The Product" 55 | 56 | def __init__(self): 57 | self.parts = [] 58 | 59 | 60 | class Director: 61 | "The Director, building a complex representation." 62 | 63 | @staticmethod 64 | def construct(): 65 | "Constructs and returns the final product" 66 | return Builder()\ 67 | .build_part_a()\ 68 | .build_part_b()\ 69 | .build_part_c()\ 70 | .get_result() 71 | 72 | 73 | # The Client 74 | PRODUCT = Director.construct() 75 | print(PRODUCT.parts) 76 | -------------------------------------------------------------------------------- /builder/castle_director.py: -------------------------------------------------------------------------------- 1 | "A Director Class" 2 | from house_builder import HouseBuilder 3 | 4 | 5 | class CastleDirector: # pylint: disable=too-few-public-methods 6 | "One of the Directors, that can build a complex representation." 7 | 8 | @staticmethod 9 | def construct(): 10 | "Constructs and returns the final product" 11 | return HouseBuilder()\ 12 | .set_building_type("Castle")\ 13 | .set_wall_material("Sandstone")\ 14 | .set_number_doors(100)\ 15 | .set_number_windows(200)\ 16 | .get_result() 17 | -------------------------------------------------------------------------------- /builder/client.py: -------------------------------------------------------------------------------- 1 | "House Builder Example Code" 2 | 3 | from igloo_director import IglooDirector 4 | from castle_director import CastleDirector 5 | from houseboat_director import HouseBoatDirector 6 | 7 | IGLOO = IglooDirector.construct() 8 | CASTLE = CastleDirector.construct() 9 | HOUSEBOAT = HouseBoatDirector.construct() 10 | 11 | print(IGLOO.construction()) 12 | print(CASTLE.construction()) 13 | print(HOUSEBOAT.construction()) 14 | -------------------------------------------------------------------------------- /builder/house.py: -------------------------------------------------------------------------------- 1 | "The Product" 2 | 3 | 4 | class House(): # pylint: disable=too-few-public-methods 5 | "The Product" 6 | 7 | def __init__(self, building_type="Apartment", doors=0, 8 | windows=0, wall_material="Brick"): 9 | # brick, wood, straw, ice 10 | self.wall_material = wall_material 11 | # Apartment, Bungalow, Caravan, Hut, Castle, Duplex, 12 | # HouseBoat, Igloo 13 | self.building_type = building_type 14 | self.doors = doors 15 | self.windows = windows 16 | 17 | def construction(self): 18 | "Returns a string describing the construction" 19 | return f"This is a {self.wall_material} "\ 20 | f"{self.building_type} with {self.doors} "\ 21 | f"door(s) and {self.windows} window(s)." 22 | -------------------------------------------------------------------------------- /builder/house_builder.py: -------------------------------------------------------------------------------- 1 | "The Builder Class" 2 | from interface_house_builder import IHouseBuilder 3 | from house import House 4 | 5 | 6 | class HouseBuilder(IHouseBuilder): 7 | "The House Builder." 8 | 9 | def __init__(self): 10 | self.house = House() 11 | 12 | def set_building_type(self, building_type): 13 | self.house.building_type = building_type 14 | return self 15 | 16 | def set_wall_material(self, wall_material): 17 | self.house.wall_material = wall_material 18 | return self 19 | 20 | def set_number_doors(self, number): 21 | self.house.doors = number 22 | return self 23 | 24 | def set_number_windows(self, number): 25 | self.house.windows = number 26 | return self 27 | 28 | def get_result(self): 29 | return self.house 30 | -------------------------------------------------------------------------------- /builder/houseboat_director.py: -------------------------------------------------------------------------------- 1 | "A Director Class" 2 | from house_builder import HouseBuilder 3 | 4 | 5 | class HouseBoatDirector: # pylint: disable=too-few-public-methods 6 | "One of the Directors, that can build a complex representation." 7 | 8 | @staticmethod 9 | def construct(): 10 | "Constructs and returns the final product" 11 | return HouseBuilder()\ 12 | .set_building_type("House Boat")\ 13 | .set_wall_material("Wood")\ 14 | .set_number_doors(6)\ 15 | .set_number_windows(8)\ 16 | .get_result() 17 | -------------------------------------------------------------------------------- /builder/igloo_director.py: -------------------------------------------------------------------------------- 1 | "A Director Class" 2 | from house_builder import HouseBuilder 3 | 4 | 5 | class IglooDirector: # pylint: disable=too-few-public-methods 6 | "One of the Directors, that can build a complex representation." 7 | 8 | @staticmethod 9 | def construct(): 10 | """Constructs and returns the final product 11 | Note that in this IglooDirector, it has omitted the set_number_of 12 | windows call since this Igloo will have no windows. 13 | """ 14 | return HouseBuilder()\ 15 | .set_building_type("Igloo")\ 16 | .set_wall_material("Ice")\ 17 | .set_number_doors(1)\ 18 | .get_result() 19 | -------------------------------------------------------------------------------- /builder/interface_house_builder.py: -------------------------------------------------------------------------------- 1 | "The Builder Interface" 2 | from abc import ABCMeta, abstractmethod 3 | 4 | 5 | class IHouseBuilder(metaclass=ABCMeta): 6 | "The House Builder Interface" 7 | 8 | @staticmethod 9 | @abstractmethod 10 | def set_building_type(building_type): 11 | "Build type" 12 | 13 | @staticmethod 14 | @abstractmethod 15 | def set_wall_material(wall_material): 16 | "Build material" 17 | 18 | @staticmethod 19 | @abstractmethod 20 | def set_number_doors(number): 21 | "Number of doors" 22 | 23 | @staticmethod 24 | @abstractmethod 25 | def set_number_windows(number): 26 | "Number of windows" 27 | 28 | @staticmethod 29 | @abstractmethod 30 | def get_result(): 31 | "Return the final product" 32 | -------------------------------------------------------------------------------- /chain_of_responsibility/atm_dispenser_chain.py: -------------------------------------------------------------------------------- 1 | "The ATM Dispenser Chain" 2 | from dispenser10 import Dispenser10 3 | from dispenser20 import Dispenser20 4 | from dispenser50 import Dispenser50 5 | 6 | 7 | class ATMDispenserChain: # pylint: disable=too-few-public-methods 8 | "The Chain Client" 9 | 10 | def __init__(self): 11 | # initializing the successors chain 12 | self.chain1 = Dispenser50() 13 | self.chain2 = Dispenser20() 14 | self.chain3 = Dispenser10() 15 | # Setting a default successor chain that will process the 50s first, 16 | # the 20s second and the 10s last. The successor chain will be 17 | # recalculated dynamically at runtime. 18 | self.chain1.next_successor(self.chain2) 19 | self.chain2.next_successor(self.chain3) 20 | -------------------------------------------------------------------------------- /chain_of_responsibility/chain_of_responsibility_concept.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "The Chain Of Responsibility Pattern Concept" 3 | import random 4 | from abc import ABCMeta, abstractmethod 5 | 6 | 7 | class IHandler(metaclass=ABCMeta): 8 | "The Handler Interface that the Successors should implement" 9 | @staticmethod 10 | @abstractmethod 11 | def handle(payload): 12 | "A method to implement" 13 | 14 | 15 | class Successor1(IHandler): 16 | "A Concrete Handler" 17 | @staticmethod 18 | def handle(payload): 19 | print(f"Successor1 payload = {payload}") 20 | test = random.randint(1, 2) 21 | if test == 1: 22 | payload = payload + 1 23 | payload = Successor1().handle(payload) 24 | if test == 2: 25 | payload = payload - 1 26 | payload = Successor2().handle(payload) 27 | return payload 28 | 29 | 30 | class Successor2(IHandler): 31 | "A Concrete Handler" 32 | @staticmethod 33 | def handle(payload): 34 | print(f"Successor2 payload = {payload}") 35 | test = random.randint(1, 3) 36 | if test == 1: 37 | payload = payload * 2 38 | payload = Successor1().handle(payload) 39 | if test == 2: 40 | payload = payload / 2 41 | payload = Successor2().handle(payload) 42 | return payload 43 | 44 | 45 | class Chain(): 46 | "A chain with a default first successor" 47 | @staticmethod 48 | def start(payload): 49 | "Setting the first successor that will modify the payload" 50 | return Successor1().handle(payload) 51 | 52 | 53 | # The Client 54 | CHAIN = Chain() 55 | PAYLOAD = 1 56 | OUT = CHAIN.start(PAYLOAD) 57 | print(f"Finished result = {OUT}") 58 | -------------------------------------------------------------------------------- /chain_of_responsibility/client.py: -------------------------------------------------------------------------------- 1 | "An ATM Dispenser that dispenses denominations of notes" 2 | import sys 3 | from atm_dispenser_chain import ATMDispenserChain 4 | 5 | ATM = ATMDispenserChain() 6 | AMOUNT = int(input("Enter amount to withdrawal : ")) 7 | if AMOUNT < 10 or AMOUNT % 10 != 0: 8 | print("Amount should be positive and in multiple of 10s.") 9 | sys.exit() 10 | # process the request 11 | ATM.chain1.handle(AMOUNT) 12 | print("Now go spoil yourself") 13 | -------------------------------------------------------------------------------- /chain_of_responsibility/dispenser10.py: -------------------------------------------------------------------------------- 1 | "A dispenser of £10 notes" 2 | from interface_dispenser import IDispenser 3 | 4 | 5 | class Dispenser10(IDispenser): 6 | "Dispenses £10s if applicable, otherwise continues to next successor" 7 | 8 | def __init__(self): 9 | self._successor = None 10 | 11 | def next_successor(self, successor): 12 | "Set the next successor" 13 | self._successor = successor 14 | 15 | def handle(self, amount): 16 | "Handle the dispensing of notes" 17 | if amount >= 10: 18 | num = amount // 10 19 | remainder = amount % 10 20 | print(f"Dispensing {num} £10 note") 21 | if remainder != 0: 22 | self._successor.handle(remainder) 23 | else: 24 | self._successor.handle(amount) 25 | -------------------------------------------------------------------------------- /chain_of_responsibility/dispenser20.py: -------------------------------------------------------------------------------- 1 | "A dispenser of £20 notes" 2 | from interface_dispenser import IDispenser 3 | 4 | 5 | class Dispenser20(IDispenser): 6 | "Dispenses £20s if applicable, otherwise continues to next successor" 7 | 8 | def __init__(self): 9 | self._successor = None 10 | 11 | def next_successor(self, successor): 12 | "Set the next successor" 13 | self._successor = successor 14 | 15 | def handle(self, amount): 16 | "Handle the dispensing of notes" 17 | if amount >= 20: 18 | num = amount // 20 19 | remainder = amount % 20 20 | print(f"Dispensing {num} £20 note(s)") 21 | if remainder != 0: 22 | self._successor.handle(remainder) 23 | else: 24 | self._successor.handle(amount) 25 | -------------------------------------------------------------------------------- /chain_of_responsibility/dispenser50.py: -------------------------------------------------------------------------------- 1 | "A dispenser of £50 notes" 2 | from interface_dispenser import IDispenser 3 | 4 | 5 | class Dispenser50(IDispenser): 6 | "Dispenses £50s if applicable, otherwise continues to next successor" 7 | 8 | def __init__(self): 9 | self._successor = None 10 | 11 | def next_successor(self, successor): 12 | "Set the next successor" 13 | self._successor = successor 14 | 15 | def handle(self, amount): 16 | "Handle the dispensing of notes" 17 | if amount >= 50: 18 | num = amount // 50 19 | remainder = amount % 50 20 | print(f"Dispensing {num} £50 note(s)") 21 | if remainder != 0: 22 | self._successor.handle(remainder) 23 | else: 24 | self._successor.handle(amount) 25 | -------------------------------------------------------------------------------- /chain_of_responsibility/interface_dispenser.py: -------------------------------------------------------------------------------- 1 | "The ATM Notes Dispenser Interface" 2 | from abc import ABCMeta, abstractmethod 3 | 4 | 5 | class IDispenser(metaclass=ABCMeta): 6 | "Methods to implement" 7 | @staticmethod 8 | @abstractmethod 9 | def next_successor(successor): 10 | """Set the next handler in the chain""" 11 | 12 | @staticmethod 13 | @abstractmethod 14 | def handle(amount): 15 | """Handle the event""" 16 | -------------------------------------------------------------------------------- /command/README.md: -------------------------------------------------------------------------------- 1 | # Command Design Pattern 2 | 3 | ## Videos 4 | 5 | Section | Video Links 6 | -|- 7 | Command Overview | Command Overview Command Overview Command Overview 8 | Command Use Case | Command Use Case Command Use Case Command Use Case 9 | Single Leading Underscore | Single Leading Underscore Single Leading Underscore Single Leading Underscore 10 | 11 | ## Book 12 | 13 | Cover | Links 14 | -|- 15 | ![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    https://www.amazon.com/dp/B08XLJ8Z2J
   https://www.amazon.co.uk/dp/B08XLJ8Z2J
   https://www.amazon.in/dp/B08Z282SBC
   https://www.amazon.de/dp/B08XLJ8Z2J
   https://www.amazon.fr/dp/B08XLJ8Z2J
   https://www.amazon.es/dp/B08XLJ8Z2J
   https://www.amazon.it/dp/B08XLJ8Z2J
   https://www.amazon.co.jp/dp/B08XLJ8Z2J
   https://www.amazon.ca/dp/B08XLJ8Z2J
   https://www.amazon.com.au/dp/B08Z282SBC 16 | 17 | ## Overview 18 | 19 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 20 | 21 | ## Terminology 22 | 23 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 24 | 25 | ## Command Pattern UML Diagram 26 | 27 | ![The Command Pattern UML Diagram](/img/command_concept.svg) 28 | 29 | ## Source Code 30 | 31 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 32 | 33 | ## Output 34 | 35 | ``` bash 36 | python ./command/command_concept.py 37 | Executing Command 1 38 | Executing Command 2 39 | Executing Command 1 40 | Executing Command 2 41 | ``` 42 | 43 | ## Example Use Case 44 | 45 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 46 | 47 | ## Example UML Diagram 48 | 49 | ![The Command Pattern UML Diagram](/img/command_example.svg) 50 | 51 | ## Output 52 | 53 | ``` bash 54 | python ./command/client.py 55 | Light turned ON 56 | Light turned OFF 57 | Light turned ON 58 | Light turned OFF 59 | 11:23:35 : ON 60 | 11:23:35 : OFF 61 | 11:23:35 : ON 62 | 11:23:35 : OFF 63 | Light turned ON 64 | Light turned OFF 65 | ``` 66 | 67 | ## New Coding Concepts 68 | 69 | ### _Single Leading Underscore 70 | 71 | The single leading underscore **`_variable`**, on your class variables is a useful indicator to other developers that this property should be considered private. 72 | 73 | Private, in C style languages, means that the variable/field/property is hidden and cannot be accessed outside of the class. It can only be used internally by its own class methods. 74 | 75 | Python does not have a public/private accessor concept so the variable is not actually private and can still be used outside of the class in other modules. 76 | 77 | It is just a useful construct that you will see developers use as a recommendation not to reference this variable directly outside of this class, but use a dedicated method or property instead. 78 | 79 | ## Summary 80 | 81 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ -------------------------------------------------------------------------------- /command/client.py: -------------------------------------------------------------------------------- 1 | "The Command Pattern Use Case Example. A smart light Switch" 2 | from light import Light 3 | from switch import Switch 4 | from switch_on_command import SwitchOnCommand 5 | from switch_off_command import SwitchOffCommand 6 | 7 | # Create a receiver 8 | LIGHT = Light() 9 | 10 | # Create Commands 11 | SWITCH_ON = SwitchOnCommand(LIGHT) 12 | SWITCH_OFF = SwitchOffCommand(LIGHT) 13 | 14 | # Register the commands with the invoker 15 | SWITCH = Switch() 16 | SWITCH.register("ON", SWITCH_ON) 17 | SWITCH.register("OFF", SWITCH_OFF) 18 | 19 | # Execute the commands that are registered on the Invoker 20 | SWITCH.execute("ON") 21 | SWITCH.execute("OFF") 22 | SWITCH.execute("ON") 23 | SWITCH.execute("OFF") 24 | 25 | # show history 26 | SWITCH.show_history() 27 | 28 | # replay last two executed commands 29 | SWITCH.replay_last(2) 30 | -------------------------------------------------------------------------------- /command/command_concept.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=arguments-differ 2 | "The Command Pattern Concept" 3 | from abc import ABCMeta, abstractmethod 4 | 5 | 6 | class ICommand(metaclass=ABCMeta): # pylint: disable=too-few-public-methods 7 | "The command interface, that all commands will implement" 8 | @staticmethod 9 | @abstractmethod 10 | def execute(): 11 | "The required execute method that all command objects will use" 12 | 13 | 14 | class Invoker: 15 | "The Invoker Class" 16 | 17 | def __init__(self): 18 | self._commands = {} 19 | 20 | def register(self, command_name, command): 21 | "Register commands in the Invoker" 22 | self._commands[command_name] = command 23 | 24 | def execute(self, command_name): 25 | "Execute any registered commands" 26 | if command_name in self._commands.keys(): 27 | self._commands[command_name].execute() 28 | else: 29 | print(f"Command [{command_name}] not recognised") 30 | 31 | 32 | class Receiver: 33 | "The Receiver" 34 | 35 | @staticmethod 36 | def run_command_1(): 37 | "A set of instructions to run" 38 | print("Executing Command 1") 39 | 40 | @staticmethod 41 | def run_command_2(): 42 | "A set of instructions to run" 43 | print("Executing Command 2") 44 | 45 | 46 | class Command1(ICommand): # pylint: disable=too-few-public-methods 47 | """A Command object, that implements the ICommand interface and 48 | runs the command on the designated receiver""" 49 | 50 | def __init__(self, receiver): 51 | self._receiver = receiver 52 | 53 | def execute(self): 54 | self._receiver.run_command_1() 55 | 56 | 57 | class Command2(ICommand): # pylint: disable=too-few-public-methods 58 | """A Command object, that implements the ICommand interface and 59 | runs the command on the designated receiver""" 60 | 61 | def __init__(self, receiver): 62 | self._receiver = receiver 63 | 64 | def execute(self): 65 | self._receiver.run_command_2() 66 | 67 | 68 | # Create a receiver 69 | RECEIVER = Receiver() 70 | 71 | # Create Commands 72 | COMMAND1 = Command1(RECEIVER) 73 | COMMAND2 = Command2(RECEIVER) 74 | 75 | # Register the commands with the invoker 76 | INVOKER = Invoker() 77 | INVOKER.register("1", COMMAND1) 78 | INVOKER.register("2", COMMAND2) 79 | 80 | # Execute the commands that are registered on the Invoker 81 | INVOKER.execute("1") 82 | INVOKER.execute("2") 83 | INVOKER.execute("1") 84 | INVOKER.execute("2") 85 | -------------------------------------------------------------------------------- /command/interface_switch.py: -------------------------------------------------------------------------------- 1 | "The switch interface, that all commands will implement" 2 | from abc import ABCMeta, abstractmethod 3 | 4 | 5 | class ISwitch(metaclass=ABCMeta): # pylint: disable=too-few-public-methods 6 | "The switch interface, that all commands will implement" 7 | 8 | @staticmethod 9 | @abstractmethod 10 | def execute(): 11 | "The required execute method that all command objects will use" 12 | -------------------------------------------------------------------------------- /command/light.py: -------------------------------------------------------------------------------- 1 | "The Light. The Receiver" 2 | 3 | 4 | class Light: 5 | "The Receiver" 6 | 7 | @staticmethod 8 | def turn_on(): 9 | "A set of instructions to run" 10 | print("Light turned ON") 11 | 12 | @staticmethod 13 | def turn_off(): 14 | "A set of instructions to run" 15 | print("Light turned OFF") 16 | -------------------------------------------------------------------------------- /command/switch.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Switch (Invoker) Class. 3 | You can flick the switch and it then invokes a registered command 4 | """ 5 | from datetime import datetime 6 | import time 7 | 8 | 9 | class Switch: 10 | "The Invoker Class." 11 | 12 | def __init__(self): 13 | self._commands = {} 14 | self._history = [] 15 | 16 | def show_history(self): 17 | "Print the history of each time a command was invoked" 18 | for row in self._history: 19 | print( 20 | f"{datetime.fromtimestamp(row[0]).strftime('%H:%M:%S')}" 21 | f" : {row[1]}" 22 | ) 23 | 24 | def register(self, command_name, command): 25 | "Register commands in the Invoker" 26 | self._commands[command_name] = command 27 | 28 | def execute(self, command_name): 29 | "Execute any registered commands" 30 | if command_name in self._commands.keys(): 31 | self._commands[command_name].execute() 32 | self._history.append((time.time(), command_name)) 33 | else: 34 | print(f"Command [{command_name}] not recognised") 35 | 36 | def replay_last(self, number_of_commands): 37 | "Replay the last N commands" 38 | commands = self._history[-number_of_commands:] 39 | for command in commands: 40 | self._commands[command[1]].execute() 41 | #or if you want to record these replays in history 42 | #self.execute(command[1]) 43 | -------------------------------------------------------------------------------- /command/switch_off_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | A Command object, that implements the ISwitch interface and runs the 3 | command on the designated receiver 4 | """ 5 | from interface_switch import ISwitch 6 | 7 | 8 | class SwitchOffCommand(ISwitch): # pylint: disable=too-few-public-methods 9 | "Switch Off Command" 10 | 11 | def __init__(self, light): 12 | self._light = light 13 | 14 | def execute(self): 15 | self._light.turn_off() 16 | -------------------------------------------------------------------------------- /command/switch_on_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | A Command object, that implements the ISwitch interface and runs the 3 | command on the designated receiver 4 | """ 5 | from interface_switch import ISwitch 6 | 7 | 8 | class SwitchOnCommand(ISwitch): # pylint: disable=too-few-public-methods 9 | "Switch On Command" 10 | 11 | def __init__(self, light): 12 | self._light = light 13 | 14 | def execute(self): 15 | self._light.turn_on() 16 | -------------------------------------------------------------------------------- /composite/client.py: -------------------------------------------------------------------------------- 1 | "A use case of the composite pattern." 2 | 3 | from folder import Folder 4 | from file import File 5 | 6 | FILESYSTEM = Folder("root") 7 | FILE_1 = File("abc.txt") 8 | FILE_2 = File("123.txt") 9 | FILESYSTEM.attach(FILE_1) 10 | FILESYSTEM.attach(FILE_2) 11 | FOLDER_A = Folder("folder_a") 12 | FILESYSTEM.attach(FOLDER_A) 13 | FILE_3 = File("xyz.txt") 14 | FOLDER_A.attach(FILE_3) 15 | FOLDER_B = Folder("folder_b") 16 | FILE_4 = File("456.txt") 17 | FOLDER_B.attach(FILE_4) 18 | FILESYSTEM.attach(FOLDER_B) 19 | FILESYSTEM.dir() 20 | 21 | # now move FOLDER_A and its contents to FOLDER_B 22 | print() 23 | FOLDER_B.attach(FOLDER_A) 24 | FILESYSTEM.dir() 25 | -------------------------------------------------------------------------------- /composite/composite_concept.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=arguments-differ 2 | "The Composite pattern concept" 3 | from abc import ABCMeta, abstractmethod 4 | 5 | 6 | class IComponent(metaclass=ABCMeta): 7 | """ 8 | A component interface describing the common 9 | fields and methods of leaves and composites 10 | """ 11 | 12 | reference_to_parent = None 13 | 14 | @staticmethod 15 | @abstractmethod 16 | def method(): 17 | "A method each Leaf and composite container should implement" 18 | 19 | @staticmethod 20 | @abstractmethod 21 | def detach(): 22 | "Called before a leaf is attached to a composite" 23 | 24 | 25 | class Leaf(IComponent): 26 | "A Leaf can be added to a composite, but not a leaf" 27 | 28 | def method(self): 29 | parent_id = (id(self.reference_to_parent) 30 | if self.reference_to_parent is not None else None) 31 | print( 32 | f"\t\tid:{id(self)}\tParent:\t{parent_id}" 33 | ) 34 | 35 | def detach(self): 36 | "Detaching this leaf from its parent composite" 37 | if self.reference_to_parent is not None: 38 | self.reference_to_parent.delete(self) 39 | 40 | 41 | class Composite(IComponent): 42 | "A composite can contain leaves and composites" 43 | 44 | def __init__(self): 45 | self.components = [] 46 | 47 | def method(self): 48 | parent_id = (id(self.reference_to_parent) 49 | if self.reference_to_parent is not None else None) 50 | print( 51 | f"\tid:{id(self)}\tParent:\t{parent_id}\t" 52 | f"Components:{len(self.components)}") 53 | 54 | for component in self.components: 55 | component.method() 56 | 57 | def attach(self, component): 58 | """ 59 | Detach leaf/composite from any current parent reference and 60 | then set the parent reference to this composite (self) 61 | """ 62 | component.detach() 63 | component.reference_to_parent = self 64 | self.components.append(component) 65 | 66 | def delete(self, component): 67 | "Removes leaf/composite from this composite self.components" 68 | self.components.remove(component) 69 | 70 | def detach(self): 71 | "Detaching this composite from its parent composite" 72 | if self.reference_to_parent is not None: 73 | self.reference_to_parent.delete(self) 74 | self.reference_to_parent = None 75 | 76 | 77 | # The Client 78 | LEAF_A = Leaf() 79 | LEAF_B = Leaf() 80 | COMPOSITE_1 = Composite() 81 | COMPOSITE_2 = Composite() 82 | 83 | print(f"LEAF_A\t\tid:{id(LEAF_A)}") 84 | print(f"LEAF_B\t\tid:{id(LEAF_B)}") 85 | print(f"COMPOSITE_1\tid:{id(COMPOSITE_1)}") 86 | print(f"COMPOSITE_2\tid:{id(COMPOSITE_2)}") 87 | 88 | # Attach LEAF_A to COMPOSITE_1 89 | COMPOSITE_1.attach(LEAF_A) 90 | 91 | # Instead, attach LEAF_A to COMPOSITE_2 92 | COMPOSITE_2.attach(LEAF_A) 93 | 94 | # Attach COMPOSITE1 to COMPOSITE_2 95 | COMPOSITE_2.attach(COMPOSITE_1) 96 | 97 | print() 98 | LEAF_B.method() # not in any composites 99 | COMPOSITE_2.method() # COMPOSITE_2 contains both COMPOSITE_1 and LEAF_A 100 | -------------------------------------------------------------------------------- /composite/file.py: -------------------------------------------------------------------------------- 1 | "A File class" 2 | from interface_component import IComponent 3 | 4 | 5 | class File(IComponent): 6 | "The File Class. The files are leaves" 7 | 8 | def __init__(self, name): 9 | self.name = name 10 | 11 | def dir(self, indent): 12 | parent_id = (id(self.reference_to_parent) 13 | if self.reference_to_parent is not None else None) 14 | print( 15 | f"{indent} {self.name}\t\t" 16 | f"id:{id(self)}\tParent:\t{parent_id}" 17 | ) 18 | 19 | def detach(self): 20 | "Detaching this file (leaf) from its parent composite" 21 | if self.reference_to_parent is not None: 22 | self.reference_to_parent.delete(self) 23 | -------------------------------------------------------------------------------- /composite/folder.py: -------------------------------------------------------------------------------- 1 | "A Folder, that acts as a composite." 2 | from interface_component import IComponent 3 | 4 | 5 | class Folder(IComponent): 6 | "The Folder class can contain other folders and files" 7 | 8 | def __init__(self, name): 9 | self.name = name 10 | self.components = [] 11 | 12 | def dir(self, indent=""): 13 | print( 14 | f"{indent} {self.name}\t\tid:{id(self)}\t" 15 | f"Components: {len(self.components)}") 16 | for component in self.components: 17 | component.dir(indent + "..") 18 | 19 | def attach(self, component): 20 | """ 21 | Detach file/folder from any current parent reference 22 | and then set the parent reference to this folder 23 | """ 24 | component.detach() 25 | component.reference_to_parent = self 26 | self.components.append(component) 27 | 28 | def delete(self, component): 29 | """ 30 | Removes file/folder from this folder so that self.components" 31 | is cleaned 32 | """ 33 | self.components.remove(component) 34 | 35 | def detach(self): 36 | "Detaching this folder from its parent folder" 37 | if self.reference_to_parent is not None: 38 | self.reference_to_parent.delete(self) 39 | self.reference_to_parent = None 40 | -------------------------------------------------------------------------------- /composite/interface_component.py: -------------------------------------------------------------------------------- 1 | """ 2 | A component interface describing the common 3 | fields and methods of leaves and composites 4 | """ 5 | from abc import ABCMeta, abstractmethod 6 | 7 | 8 | class IComponent(metaclass=ABCMeta): 9 | "The Component Interface" 10 | 11 | reference_to_parent = None 12 | 13 | @staticmethod 14 | @abstractmethod 15 | def dir(indent): 16 | "A method each Leaf and composite container should implement" 17 | 18 | @staticmethod 19 | @abstractmethod 20 | def detach(): 21 | """ 22 | Called before a leaf is attached to a composite 23 | so that it can clean any parent references 24 | """ 25 | -------------------------------------------------------------------------------- /decorator/add.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "The Add Decorator" 3 | from interface_value import IValue 4 | 5 | 6 | class Add(IValue): 7 | "A Decorator that Adds a number to a number" 8 | 9 | def __init__(self, val1, val2): 10 | # val1 and val2 can be int or the custom Value 11 | # object that contains the `value` attribute 12 | val1 = getattr(val1, 'value', val1) 13 | val2 = getattr(val2, 'value', val2) 14 | self.value = val1 + val2 15 | 16 | def __str__(self): 17 | return str(self.value) 18 | -------------------------------------------------------------------------------- /decorator/client.py: -------------------------------------------------------------------------------- 1 | "Decorator Use Case Example Code" 2 | from value import Value 3 | from add import Add 4 | from sub import Sub 5 | 6 | A = Value(1) 7 | B = Value(2) 8 | C = Value(5) 9 | 10 | print(Add(A, B)) 11 | print(Add(A, 100)) 12 | print(Sub(C, A)) 13 | print(Sub(Add(C, B), A)) 14 | print(Sub(100, 101)) 15 | print(Add(Sub(Add(C, B), A), 100)) 16 | print(Sub(123, Add(C, C))) 17 | print(Add(Sub(Add(C, 10), A), 100)) 18 | print(A) 19 | print(B) 20 | print(C) 21 | -------------------------------------------------------------------------------- /decorator/decorator_concept.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | # pylint: disable=arguments-differ 3 | "Decorator Concept Sample Code" 4 | from abc import ABCMeta, abstractmethod 5 | 6 | 7 | class IComponent(metaclass=ABCMeta): 8 | "Methods the component must implement" 9 | @staticmethod 10 | @abstractmethod 11 | def method(): 12 | "A method to implement" 13 | 14 | 15 | class Component(IComponent): 16 | "A component that can be decorated or not" 17 | 18 | def method(self): 19 | "An example method" 20 | return "Component Method" 21 | 22 | 23 | class Decorator(IComponent): 24 | "The Decorator also implements the IComponent" 25 | 26 | def __init__(self, obj): 27 | "Set a reference to the decorated object" 28 | self.object = obj 29 | 30 | def method(self): 31 | "A method to implement" 32 | return f"Decorator Method({self.object.method()})" 33 | 34 | 35 | # The Client 36 | COMPONENT = Component() 37 | print(COMPONENT.method()) 38 | print(Decorator(COMPONENT).method()) 39 | -------------------------------------------------------------------------------- /decorator/interface_value.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "The Interface that Value should implement" 3 | from abc import ABCMeta, abstractmethod 4 | 5 | 6 | class IValue(metaclass=ABCMeta): 7 | "Methods the component must implement" 8 | @staticmethod 9 | @abstractmethod 10 | def __str__(): 11 | "Override the object to return the value attribute by default" 12 | -------------------------------------------------------------------------------- /decorator/sub.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "The Subtract Decorator" 3 | from interface_value import IValue 4 | 5 | 6 | class Sub(IValue): 7 | "A Decorator that subtracts a number from a number" 8 | 9 | def __init__(self, val1, val2): 10 | # val1 and val2 can be int or the custom Value 11 | # object that contains the `value` attribute 12 | val1 = getattr(val1, 'value', val1) 13 | val2 = getattr(val2, 'value', val2) 14 | self.value = val1 - val2 15 | 16 | def __str__(self): 17 | return str(self.value) 18 | -------------------------------------------------------------------------------- /decorator/value.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "The Custom Value class" 3 | from interface_value import IValue 4 | 5 | 6 | class Value(IValue): 7 | "A component that can be decorated or not" 8 | 9 | def __init__(self, value): 10 | self.value = value 11 | 12 | def __str__(self): 13 | return str(self.value) 14 | -------------------------------------------------------------------------------- /facade/client.py: -------------------------------------------------------------------------------- 1 | "The Facade Example Use Case" 2 | import time 3 | from decimal import Decimal 4 | from game_api import GameAPI 5 | 6 | USER = {"user_name": "sean"} 7 | USER_ID = GameAPI.register_user(USER) 8 | 9 | time.sleep(1) 10 | 11 | GameAPI.submit_entry(USER_ID, Decimal('5')) 12 | 13 | time.sleep(1) 14 | 15 | print() 16 | print("---- Gamestate Snapshot ----") 17 | print(GameAPI.game_state()) 18 | 19 | time.sleep(1) 20 | 21 | HISTORY = GameAPI.get_history() 22 | 23 | print() 24 | print("---- Reports History ----") 25 | for row in HISTORY: 26 | print(f"{row} : {HISTORY[row][0]} : {HISTORY[row][1]}") 27 | 28 | print() 29 | print("---- Gamestate Snapshot ----") 30 | print(GameAPI.game_state()) 31 | -------------------------------------------------------------------------------- /facade/facade_concept.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "The Facade pattern concept" 3 | 4 | 5 | class SubSystemClassA: 6 | "A hypothetically complicated class" 7 | @staticmethod 8 | def method(): 9 | "A hypothetically complicated method" 10 | return "A" 11 | 12 | 13 | class SubSystemClassB: 14 | "A hypothetically complicated class" 15 | @staticmethod 16 | def method(value): 17 | "A hypothetically complicated method" 18 | return value 19 | 20 | 21 | class SubSystemClassC: 22 | "A hypothetically complicated class" 23 | @staticmethod 24 | def method(value): 25 | "A hypothetically complicated method" 26 | return value 27 | 28 | 29 | class Facade(): 30 | "A simplified facade offering the services of subsystems" 31 | @staticmethod 32 | def sub_system_class_a(): 33 | "Use the subsystems method" 34 | return SubSystemClassA().method() 35 | 36 | @staticmethod 37 | def sub_system_class_b(value): 38 | "Use the subsystems method" 39 | return SubSystemClassB().method(value) 40 | 41 | @staticmethod 42 | def sub_system_class_c(value): 43 | "Use the subsystems method" 44 | return SubSystemClassC().method(value) 45 | 46 | 47 | # The Client 48 | # call potentially complicated subsystems directly 49 | print(SubSystemClassA.method()) 50 | print(SubSystemClassB.method("B")) 51 | print(SubSystemClassC.method({"C": [1, 2, 3]})) 52 | 53 | # or use the simplified facade 54 | print(Facade().sub_system_class_a()) 55 | print(Facade().sub_system_class_b("B")) 56 | print(Facade().sub_system_class_c({"C": [1, 2, 3]})) 57 | -------------------------------------------------------------------------------- /facade/game_api.py: -------------------------------------------------------------------------------- 1 | "The Game API facade" 2 | from decimal import Decimal 3 | from users import Users 4 | from wallets import Wallets 5 | from game_engine import GameEngine 6 | from reports import Reports 7 | 8 | 9 | class GameAPI(): 10 | "The Game API facade" 11 | @staticmethod 12 | def get_balance(user_id: str) -> Decimal: 13 | "Get a players balance" 14 | return Wallets.get_balance(user_id) 15 | 16 | @staticmethod 17 | def game_state() -> dict: 18 | "Get the current game state" 19 | return GameEngine().get_game_state() 20 | 21 | @staticmethod 22 | def get_history() -> dict: 23 | "get the game history" 24 | return Reports.get_history() 25 | 26 | @staticmethod 27 | def change_pwd(user_id: str, password: str) -> bool: 28 | "change users password" 29 | return Users.change_pwd(user_id, password) 30 | 31 | @staticmethod 32 | def submit_entry(user_id: str, entry: Decimal) -> bool: 33 | "submit a bet" 34 | return GameEngine().submit_entry(user_id, entry) 35 | 36 | @staticmethod 37 | def register_user(value: dict[str, str]) -> str: # Python 3.9 38 | # def register_user(value) -> str: # Python 3.8 and earlier 39 | "register a new user and returns the new id" 40 | return Users.register_user(value) 41 | -------------------------------------------------------------------------------- /facade/game_engine.py: -------------------------------------------------------------------------------- 1 | "The Game Engine" 2 | import time 3 | from decimal import Decimal 4 | from wallets import Wallets 5 | from reports import Reports 6 | 7 | 8 | class GameEngine(): 9 | "The Game Engine" 10 | _instance = None 11 | _start_time: int = 0 12 | _clock: int = 0 13 | _entries: list[tuple[str, Decimal]] = [] # Python 3.9 14 | # _entries = [] # Python 3.8 or earlier 15 | _game_open = True 16 | 17 | def __new__(cls): 18 | if cls._instance is None: 19 | cls._instance = GameEngine 20 | cls._start_time = int(time.time()) 21 | cls._clock = 60 22 | return cls._instance 23 | 24 | @classmethod 25 | def get_game_state(cls) -> dict: 26 | "Get a snapshot of the current game state" 27 | now = int(time.time()) 28 | time_remaining = cls._start_time - now + cls._clock 29 | if time_remaining < 0: 30 | time_remaining = 0 31 | cls._game_open = False 32 | return { 33 | "clock": time_remaining, 34 | "game_open": cls._game_open, 35 | "entries": cls._entries 36 | } 37 | 38 | @classmethod 39 | def submit_entry(cls, user_id: str, entry: Decimal) -> bool: 40 | "Submit a new entry for the user in this game" 41 | now = int(time.time()) 42 | time_remaining = cls._start_time - now + cls._clock 43 | if time_remaining > 0: 44 | if Wallets.get_balance(user_id) > Decimal('1'): 45 | if Wallets.adjust_balance(user_id, Decimal('-1')): 46 | cls._entries.append((user_id, entry)) 47 | Reports.log_event( 48 | f"New entry `{entry}` submitted by `{user_id}`") 49 | return True 50 | Reports.log_event( 51 | f"Problem adjusting balance for `{user_id}`") 52 | return False 53 | Reports.log_event(f"User Balance for `{user_id}` to low") 54 | return False 55 | Reports.log_event("Game Closed") 56 | return False 57 | -------------------------------------------------------------------------------- /facade/reports.py: -------------------------------------------------------------------------------- 1 | "A Singleton Dictionary of Reported Events" 2 | import time 3 | 4 | 5 | class Reports(): 6 | "A Singleton Dictionary of Reported Events" 7 | _reports: dict[int, tuple[float, str]] = {} # Python 3.9 8 | # _reports = {} # Python 3.8 or earlier 9 | _row_id = 0 10 | 11 | def __new__(cls): 12 | return cls 13 | 14 | @classmethod 15 | def get_history(cls) -> dict: 16 | "A method to retrieve all historic events" 17 | return cls._reports 18 | 19 | @classmethod 20 | def log_event(cls, event: str) -> bool: 21 | "A method to add a new event to the record" 22 | cls._reports[cls._row_id] = (time.time(), event) 23 | cls._row_id = cls._row_id + 1 24 | return True 25 | -------------------------------------------------------------------------------- /facade/users.py: -------------------------------------------------------------------------------- 1 | "A Singleton Dictionary of Users" 2 | from decimal import Decimal 3 | from wallets import Wallets 4 | from reports import Reports 5 | 6 | 7 | class Users(): 8 | "A Singleton Dictionary of Users" 9 | _users: dict[str, dict[str, str]] = {} # Python 3.9 10 | # _users = {} # Python 3.8 or earlier 11 | 12 | def __new__(cls): 13 | return cls 14 | 15 | @classmethod 16 | def register_user(cls, new_user: dict[str, str]) -> str: # Python 3.9 17 | # def register_user(cls, new_user) -> str: # Python 3.8 or earlier 18 | "register a user" 19 | if not new_user["user_name"] in cls._users: 20 | # generate really complicated unique user_id. 21 | # Using the existing user_name as the id for simplicity 22 | user_id = new_user["user_name"] 23 | cls._users[user_id] = new_user 24 | Reports.log_event(f"new user `{user_id}` created") 25 | # create a wallet for the new user 26 | Wallets().create_wallet(user_id) 27 | # give the user a sign up bonus 28 | Reports.log_event( 29 | f"Give new user `{user_id}` sign up bonus of 10") 30 | Wallets().adjust_balance(user_id, Decimal(10)) 31 | return user_id 32 | return "" 33 | 34 | @classmethod 35 | def edit_user(cls, user_id: str, user: dict): 36 | "do nothing" 37 | print(user_id) 38 | print(user) 39 | return False 40 | 41 | @classmethod 42 | def change_pwd(cls, user_id: str, password: str): 43 | "do nothing" 44 | print(user_id) 45 | print(password) 46 | return False 47 | -------------------------------------------------------------------------------- /facade/wallets.py: -------------------------------------------------------------------------------- 1 | "A Singleton Dictionary of User Wallets" 2 | from decimal import Decimal 3 | from reports import Reports 4 | 5 | 6 | class Wallets(): 7 | "A Singleton Dictionary of User Wallets" 8 | _wallets: dict[str, Decimal] = {} # Python 3.9 9 | # _wallets = {} # Python 3.8 or earlier 10 | 11 | def __new__(cls): 12 | return cls 13 | 14 | @classmethod 15 | def create_wallet(cls, user_id: str) -> bool: 16 | "A method to initialize a users wallet" 17 | if not user_id in cls._wallets: 18 | cls._wallets[user_id] = Decimal('0') 19 | Reports.log_event( 20 | f"wallet for `{user_id}` created and set to 0") 21 | return True 22 | return False 23 | 24 | @classmethod 25 | def get_balance(cls, user_id: str) -> Decimal: 26 | "A method to check a users balance" 27 | Reports.log_event( 28 | f"Balance check for `{user_id}` = {cls._wallets[user_id]}") 29 | return cls._wallets[user_id] 30 | 31 | @classmethod 32 | def adjust_balance(cls, user_id: str, amount: Decimal) -> Decimal: 33 | "A method to adjust a user balance up or down" 34 | cls._wallets[user_id] = cls._wallets[user_id] + Decimal(amount) 35 | Reports.log_event( 36 | f"Balance adjustment for `{user_id}`. " 37 | f"New balance = {cls._wallets[user_id]}" 38 | ) 39 | return cls._wallets[user_id] 40 | -------------------------------------------------------------------------------- /factory/big_chair.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "A Class of Chair" 3 | from interface_chair import IChair 4 | 5 | 6 | class BigChair(IChair): 7 | "The Big Chair Concrete Class implements the IChair interface" 8 | 9 | def __init__(self): 10 | self._height = 80 11 | self._width = 80 12 | self._depth = 80 13 | 14 | def get_dimensions(self): 15 | return { 16 | "width": self._width, 17 | "depth": self._depth, 18 | "height": self._height 19 | } 20 | -------------------------------------------------------------------------------- /factory/chair_factory.py: -------------------------------------------------------------------------------- 1 | "The Factory Class" 2 | from small_chair import SmallChair 3 | from medium_chair import MediumChair 4 | from big_chair import BigChair 5 | 6 | 7 | class ChairFactory: # pylint: disable=too-few-public-methods 8 | "The Factory Class" 9 | 10 | @staticmethod 11 | def get_chair(chair): 12 | "A static method to get a chair" 13 | if chair == 'BigChair': 14 | return BigChair() 15 | if chair == 'MediumChair': 16 | return MediumChair() 17 | if chair == 'SmallChair': 18 | return SmallChair() 19 | return None 20 | -------------------------------------------------------------------------------- /factory/client.py: -------------------------------------------------------------------------------- 1 | "Factory Use Case Example Code" 2 | 3 | from chair_factory import ChairFactory 4 | 5 | # The Client 6 | CHAIR = ChairFactory.get_chair("SmallChair") 7 | print(CHAIR.get_dimensions()) 8 | -------------------------------------------------------------------------------- /factory/factory_concept.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | # pylint: disable=arguments-differ 3 | "The Factory Concept" 4 | from abc import ABCMeta, abstractmethod 5 | 6 | 7 | class IProduct(metaclass=ABCMeta): 8 | "A Hypothetical Class Interface (Product)" 9 | 10 | @staticmethod 11 | @abstractmethod 12 | def create_object(): 13 | "An abstract interface method" 14 | 15 | 16 | class ConcreteProductA(IProduct): 17 | "A Concrete Class that implements the IProduct interface" 18 | 19 | def __init__(self): 20 | self.name = "ConcreteProductA" 21 | 22 | def create_object(self): 23 | return self 24 | 25 | 26 | class ConcreteProductB(IProduct): 27 | "A Concrete Class that implements the IProduct interface" 28 | 29 | def __init__(self): 30 | self.name = "ConcreteProductB" 31 | 32 | def create_object(self): 33 | return self 34 | 35 | 36 | class ConcreteProductC(IProduct): 37 | "A Concrete Class that implements the IProduct interface" 38 | 39 | def __init__(self): 40 | self.name = "ConcreteProductC" 41 | 42 | def create_object(self): 43 | return self 44 | 45 | 46 | class Creator: 47 | "The Factory Class" 48 | 49 | @staticmethod 50 | def create_object(some_property): 51 | "A static method to get a concrete product" 52 | if some_property == 'a': 53 | return ConcreteProductA() 54 | if some_property == 'b': 55 | return ConcreteProductB() 56 | if some_property == 'c': 57 | return ConcreteProductC() 58 | return None 59 | 60 | 61 | # The Client 62 | PRODUCT = Creator.create_object('b') 63 | print(PRODUCT.name) 64 | -------------------------------------------------------------------------------- /factory/interface_chair.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "The Chair Interface" 3 | from abc import ABCMeta, abstractmethod 4 | 5 | 6 | class IChair(metaclass=ABCMeta): 7 | "The Chair Interface (Product)" 8 | 9 | @staticmethod 10 | @abstractmethod 11 | def get_dimensions(): 12 | "A static interface method" 13 | -------------------------------------------------------------------------------- /factory/medium_chair.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "A Class of Chair" 3 | from interface_chair import IChair 4 | 5 | 6 | class MediumChair(IChair): 7 | "The Medium Chair Concrete Class implements the IChair interface" 8 | 9 | def __init__(self): 10 | self._height = 60 11 | self._width = 60 12 | self._depth = 60 13 | 14 | def get_dimensions(self): 15 | return { 16 | "width": self._width, 17 | "depth": self._depth, 18 | "height": self._height 19 | } 20 | -------------------------------------------------------------------------------- /factory/small_chair.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "A Class of Chair" 3 | from interface_chair import IChair 4 | 5 | 6 | class SmallChair(IChair): 7 | "The Small Chair Concrete Class implements the IChair interface" 8 | 9 | def __init__(self): 10 | self._height = 40 11 | self._width = 40 12 | self._depth = 40 13 | 14 | def get_dimensions(self): 15 | return { 16 | "width": self._width, 17 | "depth": self._depth, 18 | "height": self._height 19 | } 20 | -------------------------------------------------------------------------------- /flyweight/README.md: -------------------------------------------------------------------------------- 1 | # Flyweight Design Pattern 2 | 3 | ## Videos 4 | 5 | Section | Video Links 6 | -|- 7 | Flyweight Overview | Flyweight Overview Flyweight Overview Flyweight Overview 8 | Flyweight Use Case | Flyweight Use Case Flyweight Use Case Flyweight Use Case 9 | String Justification | String Justification String Justification String Justification 10 | 11 | ## Book 12 | 13 | Cover | Links 14 | -|- 15 | ![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    https://www.amazon.com/dp/B08XLJ8Z2J
   https://www.amazon.co.uk/dp/B08XLJ8Z2J
   https://www.amazon.in/dp/B08Z282SBC
   https://www.amazon.de/dp/B08XLJ8Z2J
   https://www.amazon.fr/dp/B08XLJ8Z2J
   https://www.amazon.es/dp/B08XLJ8Z2J
   https://www.amazon.it/dp/B08XLJ8Z2J
   https://www.amazon.co.jp/dp/B08XLJ8Z2J
   https://www.amazon.ca/dp/B08XLJ8Z2J
   https://www.amazon.com.au/dp/B08Z282SBC 16 | 17 | ## Overview 18 | 19 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 20 | 21 | ## Terminology 22 | 23 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 24 | 25 | ## Flyweight UML Diagram 26 | 27 | ![Flyweight Pattern UML Diagram](/img/flyweight_concept.svg) 28 | 29 | ## Source Code 30 | 31 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 32 | 33 | ## Output 34 | 35 | ``` bash 36 | python ./flyweight/flyweight_concept.py 37 | abracadabra 38 | abracadabra has 11 letters 39 | FlyweightFactory has 5 flyweights 40 | ``` 41 | 42 | ## Example Use Case 43 | 44 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 45 | 46 | ## Example UML Diagram 47 | 48 | ![Flyweight Pattern Use Case UML Diagram](/img/flyweight_example.svg) 49 | 50 | ## Output 51 | 52 | ``` bash 53 | python ./flyweight/client.py 54 | ----------------------------------------- 55 | |abra | 112233 | cadabra| 56 | |racadab | 12345 | 332211| 57 | |cadabra | 445566 | aa 22 bb| 58 | ----------------------------------------- 59 | FlyweightFactory has 12 flyweights 60 | ``` 61 | 62 | ## New Coding Concepts 63 | 64 | ### String Justification 65 | 66 | In [/flyweight/column.py](/flyweight/column.py), there are commands `center()`, `ljust()` and `rjust()` . 67 | 68 | These are special commands on strings that allow you to pad strings and align them left, right, center depending on total string length. 69 | 70 | eg, 71 | 72 | ``` powershell 73 | >>> "abcd".center(10) 74 | ' abcd ' 75 | ``` 76 | 77 | ``` powershell 78 | >>> "abcd".rjust(10) 79 | ' abcd' 80 | ``` 81 | 82 | ``` powershell 83 | >>> "abcd".ljust(10) 84 | 'abcd ' 85 | ``` 86 | 87 | ## Summary 88 | 89 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ -------------------------------------------------------------------------------- /flyweight/client.py: -------------------------------------------------------------------------------- 1 | "The Flyweight Use Case Example" 2 | 3 | from table import Table 4 | from flyweight_factory import FlyweightFactory 5 | 6 | TABLE = Table(3, 3) 7 | 8 | TABLE.rows[0].columns[0].data = "abra" 9 | TABLE.rows[0].columns[1].data = "112233" 10 | TABLE.rows[0].columns[2].data = "cadabra" 11 | TABLE.rows[1].columns[0].data = "racadab" 12 | TABLE.rows[1].columns[1].data = "12345" 13 | TABLE.rows[1].columns[2].data = "332211" 14 | TABLE.rows[2].columns[0].data = "cadabra" 15 | TABLE.rows[2].columns[1].data = "445566" 16 | TABLE.rows[2].columns[2].data = "aa 22 bb" 17 | 18 | TABLE.rows[0].columns[0].justify = 1 19 | TABLE.rows[1].columns[0].justify = 1 20 | TABLE.rows[2].columns[0].justify = 1 21 | TABLE.rows[0].columns[2].justify = 2 22 | TABLE.rows[1].columns[2].justify = 2 23 | TABLE.rows[2].columns[2].justify = 2 24 | TABLE.rows[0].columns[1].width = 15 25 | TABLE.rows[1].columns[1].width = 15 26 | TABLE.rows[2].columns[1].width = 15 27 | 28 | TABLE.draw() 29 | 30 | print(f"FlyweightFactory has {FlyweightFactory.get_count()} flyweights") 31 | -------------------------------------------------------------------------------- /flyweight/column.py: -------------------------------------------------------------------------------- 1 | "A Column that is used in a Row" 2 | from flyweight_factory import FlyweightFactory 3 | 4 | class Column(): # pylint: disable=too-few-public-methods 5 | """ 6 | The columns are the contexts. 7 | They will share the Flyweights via the FlyweightsFactory. 8 | `data`, `width` and `justify` are extrinsic values. They are outside 9 | of the flyweights. 10 | """ 11 | 12 | def __init__(self, data="", width=11, justify=0) -> None: 13 | self.data = data 14 | self.width = width 15 | self.justify = justify # 0:center, 1:left, 2:right 16 | 17 | def get_data(self): 18 | "Get the flyweight value from the factory, and apply the extrinsic values" 19 | ret = "" 20 | for data in self.data: 21 | ret = ret + FlyweightFactory.get_flyweight(data).code 22 | ret = f"{ret.center(self.width)}" if self.justify == 0 else ret 23 | ret = f"{ret.ljust(self.width)}" if self.justify == 1 else ret 24 | ret = f"{ret.rjust(self.width)}" if self.justify == 2 else ret 25 | return ret 26 | -------------------------------------------------------------------------------- /flyweight/flyweight.py: -------------------------------------------------------------------------------- 1 | "The Flyweight that contains an intrinsic value called code" 2 | 3 | 4 | class Flyweight(): # pylint: disable=too-few-public-methods 5 | "The Flyweight that contains an intrinsic value called code" 6 | 7 | def __init__(self, code: str) -> None: 8 | self.code = code 9 | -------------------------------------------------------------------------------- /flyweight/flyweight_concept.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "The Flyweight Concept" 3 | 4 | 5 | class IFlyweight(): 6 | "Nothing to implement" 7 | 8 | 9 | class Flyweight(IFlyweight): 10 | "The Concrete Flyweight" 11 | 12 | def __init__(self, code: str) -> None: 13 | self.code = code 14 | 15 | 16 | class FlyweightFactory(): 17 | "Creating the FlyweightFactory as a singleton" 18 | 19 | _flyweights: dict[str, Flyweight] = {} # Python 3.9 20 | # _flyweights = {} # Python 3.8 or earlier 21 | 22 | def __new__(cls): 23 | return cls 24 | 25 | @classmethod 26 | def get_flyweight(cls, code: str) -> Flyweight: 27 | "A static method to get a flyweight based on a code" 28 | if not code in cls._flyweights: 29 | cls._flyweights[code] = Flyweight(code) 30 | return cls._flyweights[code] 31 | 32 | @classmethod 33 | def get_count(cls) -> int: 34 | "Return the number of flyweights in the cache" 35 | return len(cls._flyweights) 36 | 37 | 38 | class Context(): 39 | """ 40 | An example context that holds references to the flyweights in a 41 | particular order and converts the code to an ascii letter 42 | """ 43 | 44 | def __init__(self, codes: str) -> None: 45 | self.codes = list(codes) 46 | 47 | def output(self): 48 | "The context specific output that uses flyweights" 49 | ret = "" 50 | for code in self.codes: 51 | ret = ret + FlyweightFactory.get_flyweight(code).code 52 | return ret 53 | 54 | 55 | # The Client 56 | CONTEXT = Context("abracadabra") 57 | 58 | # use flyweights in a context 59 | print(CONTEXT.output()) 60 | 61 | print(f"abracadabra has {len('abracadabra')} letters") 62 | print(f"FlyweightFactory has {FlyweightFactory.get_count()} flyweights") 63 | -------------------------------------------------------------------------------- /flyweight/flyweight_factory.py: -------------------------------------------------------------------------------- 1 | "Creating the FlyweightFactory as a singleton" 2 | from flyweight import Flyweight 3 | 4 | 5 | class FlyweightFactory(): 6 | "Creating the FlyweightFactory as a singleton" 7 | 8 | _flyweights: dict[str, Flyweight] = {} # Python 3.9 9 | # _flyweights = {} # Python 3.8 or earlier 10 | 11 | def __new__(cls): 12 | return cls 13 | 14 | @classmethod 15 | def get_flyweight(cls, code: str) -> Flyweight: 16 | "A static method to get a flyweight based on a code" 17 | if not code in cls._flyweights: 18 | cls._flyweights[code] = Flyweight(code) 19 | return cls._flyweights[code] 20 | 21 | @classmethod 22 | def get_count(cls) -> int: 23 | "Return the number of flyweights in the cache" 24 | return len(cls._flyweights) 25 | -------------------------------------------------------------------------------- /flyweight/row.py: -------------------------------------------------------------------------------- 1 | "A Row in the Table" 2 | from column import Column 3 | 4 | 5 | class Row(): # pylint: disable=too-few-public-methods 6 | "A Row in the Table" 7 | 8 | def __init__(self, column_count: int) -> None: 9 | self.columns = [] 10 | for _ in range(column_count): 11 | self.columns.append(Column()) 12 | 13 | def get_data(self): 14 | "Format the row before returning it to the table" 15 | ret = "" 16 | for column in self.columns: 17 | ret = f"{ret}{column.get_data()}|" 18 | return ret 19 | -------------------------------------------------------------------------------- /flyweight/table.py: -------------------------------------------------------------------------------- 1 | "A Formatted Table that includes rows and columns" 2 | 3 | from row import Row 4 | 5 | 6 | class Table(): # pylint: disable=too-few-public-methods 7 | "A Formatted Table" 8 | 9 | def __init__(self, row_count: int, column_count: int) -> None: 10 | self.rows = [] 11 | for _ in range(row_count): 12 | self.rows.append(Row(column_count)) 13 | 14 | def draw(self): 15 | "Draws the table formatted in the console" 16 | max_row_length = 0 17 | rows = [] 18 | for row in self.rows: 19 | row_data = row.get_data() 20 | rows.append(f"|{row_data}") 21 | row_length = len(row_data) + 1 22 | if max_row_length < row_length: 23 | max_row_length = row_length 24 | print("-" * max_row_length) 25 | for row in rows: 26 | print(row) 27 | print("-" * max_row_length) 28 | -------------------------------------------------------------------------------- /img/by-nc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sean-Bradley/Design-Patterns-In-Python/b79e663966f10a78b1f709a2341568faa677b849/img/by-nc.png -------------------------------------------------------------------------------- /img/design_patterns_in_python_book.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sean-Bradley/Design-Patterns-In-Python/b79e663966f10a78b1f709a2341568faa677b849/img/design_patterns_in_python_book.jpg -------------------------------------------------------------------------------- /img/design_patterns_in_python_book_125x178.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sean-Bradley/Design-Patterns-In-Python/b79e663966f10a78b1f709a2341568faa677b849/img/design_patterns_in_python_book_125x178.jpg -------------------------------------------------------------------------------- /img/dp_python_125.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sean-Bradley/Design-Patterns-In-Python/b79e663966f10a78b1f709a2341568faa677b849/img/dp_python_125.gif -------------------------------------------------------------------------------- /img/dp_python_250.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sean-Bradley/Design-Patterns-In-Python/b79e663966f10a78b1f709a2341568faa677b849/img/dp_python_250.jpg -------------------------------------------------------------------------------- /img/dp_typescript_250.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sean-Bradley/Design-Patterns-In-Python/b79e663966f10a78b1f709a2341568faa677b849/img/dp_typescript_250.jpg -------------------------------------------------------------------------------- /img/flag_au.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sean-Bradley/Design-Patterns-In-Python/b79e663966f10a78b1f709a2341568faa677b849/img/flag_au.gif -------------------------------------------------------------------------------- /img/flag_ca.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sean-Bradley/Design-Patterns-In-Python/b79e663966f10a78b1f709a2341568faa677b849/img/flag_ca.gif -------------------------------------------------------------------------------- /img/flag_de.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sean-Bradley/Design-Patterns-In-Python/b79e663966f10a78b1f709a2341568faa677b849/img/flag_de.gif -------------------------------------------------------------------------------- /img/flag_es.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sean-Bradley/Design-Patterns-In-Python/b79e663966f10a78b1f709a2341568faa677b849/img/flag_es.gif -------------------------------------------------------------------------------- /img/flag_fr.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sean-Bradley/Design-Patterns-In-Python/b79e663966f10a78b1f709a2341568faa677b849/img/flag_fr.gif -------------------------------------------------------------------------------- /img/flag_in.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sean-Bradley/Design-Patterns-In-Python/b79e663966f10a78b1f709a2341568faa677b849/img/flag_in.gif -------------------------------------------------------------------------------- /img/flag_it.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sean-Bradley/Design-Patterns-In-Python/b79e663966f10a78b1f709a2341568faa677b849/img/flag_it.gif -------------------------------------------------------------------------------- /img/flag_jp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sean-Bradley/Design-Patterns-In-Python/b79e663966f10a78b1f709a2341568faa677b849/img/flag_jp.gif -------------------------------------------------------------------------------- /img/flag_uk.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sean-Bradley/Design-Patterns-In-Python/b79e663966f10a78b1f709a2341568faa677b849/img/flag_uk.gif -------------------------------------------------------------------------------- /img/flag_us.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sean-Bradley/Design-Patterns-In-Python/b79e663966f10a78b1f709a2341568faa677b849/img/flag_us.gif -------------------------------------------------------------------------------- /img/ide_hint.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sean-Bradley/Design-Patterns-In-Python/b79e663966f10a78b1f709a2341568faa677b849/img/ide_hint.jpg -------------------------------------------------------------------------------- /img/iterator_example.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
Client Application
Client Application
NumberWheel+ index: int+ next(): objectreturn self.index * 2 % 11
-------------------------------------------------------------------------------- /img/prototype_concept.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
Client Application
Client Application
MyClass+ field: type+ clone(type): typeIPrototype+ clone(type): type
-------------------------------------------------------------------------------- /img/prototype_example.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
Client Application
Client Application
Document+ name : string+ list : [][]+ clone(mode):IPrototype+ clone(mode)
-------------------------------------------------------------------------------- /img/singleton_concept.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
Client Application
Client Application
Singleton+ value: type+ __new__(cls)
-------------------------------------------------------------------------------- /img/skillshare_btn_sm.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sean-Bradley/Design-Patterns-In-Python/b79e663966f10a78b1f709a2341568faa677b849/img/skillshare_btn_sm.gif -------------------------------------------------------------------------------- /img/template_example.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | TextDocument+ title()+ text()+ footer()AbstractDocument+ create_document(text)+ title()+ description()+ author()+ nackground_colour()+ text(text)+ footer()+ print()HTMLDocument+ title()+ text()+ print() -------------------------------------------------------------------------------- /img/udemy_btn_sm.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sean-Bradley/Design-Patterns-In-Python/b79e663966f10a78b1f709a2341568faa677b849/img/udemy_btn_sm.gif -------------------------------------------------------------------------------- /img/yt_btn_sm.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sean-Bradley/Design-Patterns-In-Python/b79e663966f10a78b1f709a2341568faa677b849/img/yt_btn_sm.gif -------------------------------------------------------------------------------- /interpreter/abstract_expression.py: -------------------------------------------------------------------------------- 1 | "An Abstract Expression" 2 | # pylint: disable=too-few-public-methods 3 | class AbstractExpression(): 4 | """ 5 | All Terminal and Non-Terminal expressions will implement an 6 | `interpret` method 7 | """ 8 | @staticmethod 9 | def interpret(): 10 | """ 11 | The `interpret` method gets called recursively for 12 | each AbstractExpression 13 | """ 14 | -------------------------------------------------------------------------------- /interpreter/add.py: -------------------------------------------------------------------------------- 1 | "Add Expression. This is a Non-Terminal Expression" 2 | from abstract_expression import AbstractExpression 3 | 4 | 5 | class Add(AbstractExpression): 6 | "Non-Terminal Expression." 7 | 8 | def __init__(self, left, right): 9 | self.left = left 10 | self.right = right 11 | 12 | def interpret(self): 13 | return self.left.interpret() + self.right.interpret() 14 | 15 | def __repr__(self): 16 | return f"({self.left} Add {self.right})" 17 | -------------------------------------------------------------------------------- /interpreter/client.py: -------------------------------------------------------------------------------- 1 | "The Interpreter Pattern Use Case Example" 2 | 3 | from sentence_parser import Parser 4 | 5 | # The sentence complies with a simple grammar of 6 | # Number -> Operator -> Number -> etc, 7 | SENTENCE = "5 + IV - 3 + VII - 2" 8 | # SENTENCE = "V + IV - III + 7 - II" 9 | # SENTENCE= "CIX + V" 10 | # SENTENCE = "CIX + V - 3 + VII - 2" 11 | # SENTENCE = "MMMCMXCIX - CXIX + MCXXII - MMMCDXII - XVIII - CCXXXV" 12 | print(SENTENCE) 13 | 14 | AST_ROOT = Parser.parse(SENTENCE) 15 | 16 | # Interpret recursively through the full AST starting from the root. 17 | print(AST_ROOT.interpret()) 18 | 19 | # Print out a representation of the AST_ROOT 20 | print(AST_ROOT) 21 | -------------------------------------------------------------------------------- /interpreter/interpreter_concept.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | # pylint: disable=arguments-differ 3 | "The Interpreter Pattern Concept" 4 | 5 | 6 | class AbstractExpression(): 7 | "All Terminal and Non-Terminal expressions will implement an `interpret` method" 8 | @staticmethod 9 | def interpret(): 10 | """ 11 | The `interpret` method gets called recursively for each 12 | AbstractExpression 13 | """ 14 | 15 | 16 | class Number(AbstractExpression): 17 | "Terminal Expression" 18 | 19 | def __init__(self, value): 20 | self.value = int(value) 21 | 22 | def interpret(self): 23 | return self.value 24 | 25 | def __repr__(self): 26 | return str(self.value) 27 | 28 | 29 | class Add(AbstractExpression): 30 | "Non-Terminal Expression." 31 | 32 | def __init__(self, left, right): 33 | self.left = left 34 | self.right = right 35 | 36 | def interpret(self): 37 | return self.left.interpret() + self.right.interpret() 38 | 39 | def __repr__(self): 40 | return f"({self.left} Add {self.right})" 41 | 42 | 43 | class Subtract(AbstractExpression): 44 | "Non-Terminal Expression" 45 | 46 | def __init__(self, left, right): 47 | self.left = left 48 | self.right = right 49 | 50 | def interpret(self): 51 | return self.left.interpret() - self.right.interpret() 52 | 53 | def __repr__(self): 54 | return f"({self.left} Subtract {self.right})" 55 | 56 | 57 | # The Client 58 | # The sentence complies with a simple grammar of 59 | # Number -> Operator -> Number -> etc, 60 | SENTENCE = "5 + 4 - 3 + 7 - 2" 61 | print(SENTENCE) 62 | 63 | # Split the sentence into individual expressions that will be added to 64 | # an Abstract Syntax Tree (AST) as Terminal and Non-Terminal expressions 65 | TOKENS = SENTENCE.split(" ") 66 | print(TOKENS) 67 | 68 | # Manually Creating an Abstract Syntax Tree from the tokens 69 | AST: list[AbstractExpression] = [] # Python 3.9 70 | # AST = [] # Python 3.8 or earlier 71 | AST.append(Add(Number(TOKENS[0]), Number(TOKENS[2]))) # 5 + 4 72 | AST.append(Subtract(AST[0], Number(TOKENS[4]))) # ^ - 3 73 | AST.append(Add(AST[1], Number(TOKENS[6]))) # ^ + 7 74 | AST.append(Subtract(AST[2], Number(TOKENS[8]))) # ^ - 2 75 | 76 | # Use the final AST row as the root node. 77 | AST_ROOT = AST.pop() 78 | 79 | # Interpret recursively through the full AST starting from the root. 80 | print(AST_ROOT.interpret()) 81 | 82 | # Print out a representation of the AST_ROOT 83 | print(AST_ROOT) 84 | -------------------------------------------------------------------------------- /interpreter/number.py: -------------------------------------------------------------------------------- 1 | "A Number. This is a leaf node Expression" 2 | from abstract_expression import AbstractExpression 3 | 4 | class Number(AbstractExpression): 5 | "Terminal Expression" 6 | 7 | def __init__(self, value): 8 | self.value = int(value) 9 | 10 | def interpret(self): 11 | return self.value 12 | 13 | def __repr__(self): 14 | return str(self.value) 15 | -------------------------------------------------------------------------------- /interpreter/roman_numeral.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "Roman Numeral Expression. This is a Non-Terminal Expression" 3 | from abstract_expression import AbstractExpression 4 | from number import Number 5 | 6 | 7 | class RomanNumeral(AbstractExpression): 8 | "Non Terminal expression" 9 | 10 | def __init__(self, roman_numeral): 11 | self.roman_numeral = roman_numeral 12 | self.context = [roman_numeral, 0] 13 | 14 | def interpret(self): 15 | RomanNumeral1000.interpret(self.context) 16 | RomanNumeral100.interpret(self.context) 17 | RomanNumeral10.interpret(self.context) 18 | RomanNumeral1.interpret(self.context) 19 | return Number(self.context[1]).interpret() 20 | 21 | def __repr__(self): 22 | return f"{self.roman_numeral}({self.context[1]})" 23 | 24 | 25 | class RomanNumeral1(RomanNumeral): 26 | "Roman Numerals 1 - 9" 27 | one = "I" 28 | four = "IV" 29 | five = "V" 30 | nine = "IX" 31 | multiplier = 1 32 | 33 | @classmethod 34 | def interpret(cls, *args): 35 | 36 | context = args[0] 37 | 38 | if not context[0]: 39 | return Number(context[1]).interpret() 40 | 41 | if context[0][0: 2] == cls.nine: 42 | context[1] += (9 * cls.multiplier) 43 | context[0] = context[0][2:] 44 | elif context[0][0] == cls.five: 45 | context[1] += (5 * cls.multiplier) 46 | context[0] = context[0][1:] 47 | elif context[0][0: 2] == cls.four: 48 | context[1] += + (4 * cls.multiplier) 49 | context[0] = context[0][2:] 50 | 51 | while context[0] and context[0][0] == cls.one: 52 | context[1] += (1 * cls.multiplier) 53 | context[0] = context[0][1:] 54 | 55 | return Number(context[1]).interpret() 56 | 57 | 58 | class RomanNumeral10(RomanNumeral1): 59 | "Roman Numerals 10 - 99" 60 | one = "X" 61 | four = "XL" 62 | five = "L" 63 | nine = "XC" 64 | multiplier = 10 65 | 66 | 67 | class RomanNumeral100(RomanNumeral1): 68 | "Roman Numerals 100 - 999" 69 | one = "C" 70 | four = "CD" 71 | five = "D" 72 | nine = "CM" 73 | multiplier = 100 74 | 75 | 76 | class RomanNumeral1000(RomanNumeral1): 77 | "Roman Numerals 1000 - 3999" 78 | one = "M" 79 | four = "" 80 | five = "" 81 | nine = "" 82 | multiplier = 1000 83 | -------------------------------------------------------------------------------- /interpreter/sentence_parser.py: -------------------------------------------------------------------------------- 1 | "A Custom Parser for creating an Abstract Syntax Tree" 2 | 3 | from number import Number 4 | from add import Add 5 | from subtract import Subtract 6 | from roman_numeral import RomanNumeral 7 | 8 | 9 | class Parser: 10 | "Dynamically create the Abstract Syntax Tree" 11 | 12 | @classmethod 13 | def parse(cls, sentence): 14 | "Create the AST from the sentence" 15 | 16 | tokens = sentence.split(" ") 17 | print(tokens) 18 | 19 | tree = [] # Abstract Syntax Tree 20 | while len(tokens) > 1: 21 | 22 | left_expression = cls.decide_left_expression(tree, tokens) 23 | 24 | # get the operator, make the token list shorter 25 | operator = tokens.pop(0) 26 | 27 | right = tokens[0] 28 | 29 | if not right.isdigit(): 30 | tree.append(RomanNumeral(tokens[0])) 31 | if operator == '-': 32 | tree.append(Subtract(left_expression, tree[-1])) 33 | if operator == '+': 34 | tree.append(Add(left_expression, tree[-1])) 35 | else: 36 | right_expression = Number(right) 37 | if not tree: 38 | # Empty Data Structures return False by default 39 | if operator == '-': 40 | tree.append( 41 | Subtract(left_expression, right_expression)) 42 | if operator == '+': 43 | tree.append( 44 | Add(left_expression, right_expression)) 45 | else: 46 | if operator == '-': 47 | tree.append(Subtract(tree[-1], right_expression)) 48 | if operator == '+': 49 | tree.append(Add(tree[-1], right_expression)) 50 | 51 | return tree.pop() 52 | 53 | @staticmethod 54 | def decide_left_expression(tree, tokens): 55 | """ 56 | On the First iteration, the left expression can be either a 57 | number or roman numeral. Every consecutive expression is 58 | reference to an existing AST row 59 | """ 60 | left = tokens.pop(0) 61 | left_expression = None 62 | if not tree: # only applicable if first round 63 | if not left.isdigit(): # if 1st token a roman numeral 64 | tree.append(RomanNumeral(left)) 65 | left_expression = tree[-1] 66 | else: 67 | left_expression = Number(left) 68 | else: 69 | left_expression = tree[-1] 70 | return left_expression 71 | -------------------------------------------------------------------------------- /interpreter/subtract.py: -------------------------------------------------------------------------------- 1 | "Subtract Expression. This is a Non-Terminal Expression" 2 | from abstract_expression import AbstractExpression 3 | 4 | 5 | class Subtract(AbstractExpression): 6 | "Non-Terminal Expression" 7 | 8 | def __init__(self, left, right): 9 | self.left = left 10 | self.right = right 11 | 12 | def interpret(self): 13 | return self.left.interpret() - self.right.interpret() 14 | 15 | def __repr__(self): 16 | return f"({self.left} Subtract {self.right})" 17 | -------------------------------------------------------------------------------- /iterator/client.py: -------------------------------------------------------------------------------- 1 | "The Iterator Pattern Concept" 2 | 3 | 4 | class NumberWheel(): # pylint: disable=too-few-public-methods 5 | "The concrete iterator (iterable)" 6 | 7 | def __init__(self): 8 | self.index = 0 9 | 10 | def next(self): 11 | """Return a new number next in the wheel""" 12 | self.index = self.index + 1 13 | return self.index * 2 % 11 14 | 15 | 16 | # The Client 17 | NUMBERWHEEL = NumberWheel() 18 | 19 | for i in range(22): 20 | print(NUMBERWHEEL.next(), end=", ") 21 | -------------------------------------------------------------------------------- /iterator/iterator_concept.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | # pylint: disable=arguments-differ 3 | "The Iterator Pattern Concept" 4 | from abc import ABCMeta, abstractmethod 5 | 6 | 7 | class IIterator(metaclass=ABCMeta): 8 | "An Iterator Interface" 9 | @staticmethod 10 | @abstractmethod 11 | def has_next(): 12 | "Returns Boolean whether at end of collection or not" 13 | 14 | @staticmethod 15 | @abstractmethod 16 | def next(): 17 | "Return the object in collection" 18 | 19 | 20 | class Iterable(IIterator): 21 | "The concrete iterator (iterable)" 22 | 23 | def __init__(self, aggregates): 24 | self.index = 0 25 | self.aggregates = aggregates 26 | 27 | def next(self): 28 | if self.index < len(self.aggregates): 29 | aggregate = self.aggregates[self.index] 30 | self.index += 1 31 | return aggregate 32 | raise Exception("AtEndOfIteratorException", "At End of Iterator") 33 | 34 | def has_next(self): 35 | return self.index < len(self.aggregates) 36 | 37 | 38 | class IAggregate(metaclass=ABCMeta): 39 | "An interface that the aggregates should implement" 40 | @staticmethod 41 | @abstractmethod 42 | def method(): 43 | "a method to implement" 44 | 45 | 46 | class Aggregate(IAggregate): 47 | "A concrete object" 48 | @staticmethod 49 | def method(): 50 | print("This method has been invoked") 51 | 52 | 53 | # The Client 54 | AGGREGATES = [Aggregate(), Aggregate(), Aggregate(), Aggregate()] 55 | # AGGREGATES is a python list that is already iterable by default. 56 | 57 | # but we can create own own iterator on top anyway. 58 | ITERABLE = Iterable(AGGREGATES) 59 | 60 | while ITERABLE.has_next(): 61 | ITERABLE.next().method() 62 | -------------------------------------------------------------------------------- /mediator/README.md: -------------------------------------------------------------------------------- 1 | # Mediator Design Pattern 2 | 3 | ## Videos 4 | 5 | Section | Video Links 6 | -|- 7 | Mediator Overview | Mediator Overview Mediator Overview Mediator Overview 8 | Mediator Use Case | Mediator Use Case Mediator Use Case Mediator Use Case 9 | 10 | ## Book 11 | 12 | Cover | Links 13 | -|- 14 | ![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    https://www.amazon.com/dp/B08XLJ8Z2J
   https://www.amazon.co.uk/dp/B08XLJ8Z2J
   https://www.amazon.in/dp/B08Z282SBC
   https://www.amazon.de/dp/B08XLJ8Z2J
   https://www.amazon.fr/dp/B08XLJ8Z2J
   https://www.amazon.es/dp/B08XLJ8Z2J
   https://www.amazon.it/dp/B08XLJ8Z2J
   https://www.amazon.co.jp/dp/B08XLJ8Z2J
   https://www.amazon.ca/dp/B08XLJ8Z2J
   https://www.amazon.com.au/dp/B08Z282SBC 15 | 16 | ## Overview 17 | 18 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 19 | 20 | ## Terminology 21 | 22 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 23 | 24 | ## Mediator UML Diagram 25 | 26 | ![Mediator Pattern UML Diagram](/img/mediator_concept.svg) 27 | 28 | ## Source Code 29 | 30 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 31 | 32 | ## Output 33 | 34 | ``` bash 35 | python ./mediator/mediator_concept.py 36 | COLLEAGUE1 <--> Here is the Colleague2 specific data you asked for 37 | COLLEAGUE2 <--> Here is the Colleague1 specific data you asked for 38 | ``` 39 | 40 | ## Example Use Case 41 | 42 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 43 | 44 | ## Example UML Diagram 45 | 46 | ![Mediator Pattern UML Diagram](/img/mediator_example.svg) 47 | 48 | ## Output 49 | 50 | ``` bash 51 | python ./mediator/client.py 52 | Component1: >>> Out >>> : data A 53 | Component2: <<< In <<< : data A 54 | Component3: <<< In <<< : data A 55 | Component2: >>> Out >>> : data B 56 | Component3: <<< In <<< : data B 57 | Component1: <<< In <<< : data B 58 | Component3: >>> Out >>> : data C 59 | Component2: <<< In <<< : data C 60 | Component1: <<< In <<< : data C 61 | ``` 62 | 63 | ## Summary 64 | 65 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ -------------------------------------------------------------------------------- /mediator/client.py: -------------------------------------------------------------------------------- 1 | "The Mediator Use Case Example" 2 | from component import Component 3 | from mediator import Mediator 4 | 5 | MEDIATOR = Mediator() 6 | COMPONENT1 = Component(MEDIATOR, "Component1") 7 | COMPONENT2 = Component(MEDIATOR, "Component2") 8 | COMPONENT3 = Component(MEDIATOR, "Component3") 9 | MEDIATOR.add(COMPONENT1) 10 | MEDIATOR.add(COMPONENT2) 11 | MEDIATOR.add(COMPONENT3) 12 | 13 | COMPONENT1.notify("data A") 14 | COMPONENT2.notify("data B") 15 | COMPONENT3.notify("data C") 16 | -------------------------------------------------------------------------------- /mediator/component.py: -------------------------------------------------------------------------------- 1 | "Each component stays synchronized through a mediator" 2 | from interface_component import IComponent 3 | 4 | 5 | class Component(IComponent): 6 | "Each component stays synchronized through a mediator" 7 | 8 | def __init__(self, mediator, name): 9 | self._mediator = mediator 10 | self._name = name 11 | 12 | def notify(self, message): 13 | print(self._name + ": >>> Out >>> : " + message) 14 | self._mediator.notify(message, self) 15 | 16 | def receive(self, message): 17 | print(self._name + ": <<< In <<< : " + message) 18 | -------------------------------------------------------------------------------- /mediator/interface_component.py: -------------------------------------------------------------------------------- 1 | "An interface that each component will implement" 2 | from abc import ABCMeta, abstractmethod 3 | 4 | 5 | class IComponent(metaclass=ABCMeta): 6 | "An interface that each component will implement" 7 | 8 | @staticmethod 9 | @abstractmethod 10 | def notify(message): 11 | "The required notify method" 12 | 13 | @staticmethod 14 | @abstractmethod 15 | def receive(message): 16 | "The required receive method" 17 | -------------------------------------------------------------------------------- /mediator/mediator.py: -------------------------------------------------------------------------------- 1 | "The Subject that all components will stay synchronized with" 2 | 3 | 4 | class Mediator(): 5 | "A Subject whose notify method is mediated" 6 | 7 | def __init__(self): 8 | self._components = set() 9 | 10 | def add(self, component): 11 | "Add components" 12 | self._components.add(component) 13 | 14 | def notify(self, message, originator): 15 | "Add components except for the originator component" 16 | for component in self._components: 17 | if component != originator: 18 | component.receive(message) 19 | -------------------------------------------------------------------------------- /mediator/mediator_concept.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "Mediator Concept Sample Code" 3 | 4 | 5 | class Mediator(): 6 | "The Mediator Concrete Class" 7 | 8 | def __init__(self): 9 | self.colleague1 = Colleague1() 10 | self.colleague2 = Colleague2() 11 | 12 | def colleague1_method(self): 13 | "Calls the method provided by Colleague1" 14 | return self.colleague1.method_1() 15 | 16 | def colleague2_method(self): 17 | "Calls the method provided by Colleague2" 18 | return self.colleague2.method_2() 19 | 20 | 21 | class Colleague1(): 22 | "This Colleague provides data for Colleague2" 23 | 24 | @staticmethod 25 | def method_1(): 26 | "A simple method" 27 | return "Here is the Colleague1 specific data you asked for" 28 | 29 | 30 | class Colleague2(): 31 | "This Colleague provides data for Colleague1" 32 | 33 | @staticmethod 34 | def method_2(): 35 | "A simple method" 36 | return "Here is the Colleague2 specific data you asked for" 37 | 38 | 39 | # The Client 40 | MEDIATOR = Mediator() 41 | 42 | # Colleague1 wants some data from Colleague2 43 | DATA = MEDIATOR.colleague2_method() 44 | print(f"COLLEAGUE1 <--> {DATA}") 45 | 46 | # Colleague2 wants some data from Colleague1 47 | DATA = MEDIATOR.colleague1_method() 48 | print(f"COLLEAGUE2 <--> {DATA}") 49 | -------------------------------------------------------------------------------- /memento/caretaker.py: -------------------------------------------------------------------------------- 1 | "The Save/Restore Game functionality" 2 | 3 | 4 | class CareTaker(): 5 | "Guardian. Provides a narrow interface to the mementos" 6 | 7 | def __init__(self, originator): 8 | self._originator = originator 9 | self._mementos = [] 10 | 11 | def save(self): 12 | "Store a new Memento of the Characters current state" 13 | print("CareTaker: Game Save") 14 | memento = self._originator.memento 15 | self._mementos.append(memento) 16 | 17 | def restore(self, index): 18 | """ 19 | Replace the Characters current attributes with the state 20 | stored in the saved Memento 21 | """ 22 | print("CareTaker: Restoring Characters attributes from Memento") 23 | memento = self._mementos[index] 24 | self._originator.memento = memento 25 | -------------------------------------------------------------------------------- /memento/client.py: -------------------------------------------------------------------------------- 1 | "Memento example Use Case" 2 | 3 | from game_character import GameCharacter 4 | from caretaker import CareTaker 5 | 6 | GAME_CHARACTER = GameCharacter() 7 | CARETAKER = CareTaker(GAME_CHARACTER) 8 | 9 | # start the game 10 | GAME_CHARACTER.register_kill() 11 | GAME_CHARACTER.move_forward(1) 12 | GAME_CHARACTER.add_inventory("sword") 13 | GAME_CHARACTER.register_kill() 14 | GAME_CHARACTER.add_inventory("rifle") 15 | GAME_CHARACTER.move_forward(1) 16 | print(GAME_CHARACTER) 17 | 18 | # save progress 19 | CARETAKER.save() 20 | 21 | GAME_CHARACTER.register_kill() 22 | GAME_CHARACTER.move_forward(1) 23 | GAME_CHARACTER.progress_to_next_level() 24 | GAME_CHARACTER.register_kill() 25 | GAME_CHARACTER.add_inventory("motorbike") 26 | GAME_CHARACTER.move_forward(10) 27 | GAME_CHARACTER.register_kill() 28 | print(GAME_CHARACTER) 29 | 30 | # save progress 31 | CARETAKER.save() 32 | GAME_CHARACTER.move_forward(1) 33 | GAME_CHARACTER.progress_to_next_level() 34 | GAME_CHARACTER.register_kill() 35 | print(GAME_CHARACTER) 36 | 37 | # decide you made a mistake, go back to first save 38 | CARETAKER.restore(0) 39 | print(GAME_CHARACTER) 40 | 41 | # continue 42 | GAME_CHARACTER.register_kill() 43 | -------------------------------------------------------------------------------- /memento/game_character.py: -------------------------------------------------------------------------------- 1 | "The Game Character whose state changes" 2 | from memento import Memento 3 | 4 | 5 | class GameCharacter(): 6 | "The Game Character whose state changes" 7 | 8 | def __init__(self): 9 | self._score = 0 10 | self._inventory = set() 11 | self._level = 0 12 | self._location = {"x": 0, "y": 0, "z": 0} 13 | 14 | @property 15 | def score(self): 16 | "A `getter` for the objects score" 17 | return self._score 18 | 19 | def register_kill(self): 20 | "The character kills its enemies as it progesses" 21 | self._score += 100 22 | 23 | def add_inventory(self, item): 24 | "The character finds objects in the game" 25 | self._inventory.add(item) 26 | 27 | def progress_to_next_level(self): 28 | "The characer progresses to the next level" 29 | self._level += 1 30 | 31 | def move_forward(self, amount): 32 | "The character moves around the environment" 33 | self._location["z"] += amount 34 | 35 | def __str__(self): 36 | return( 37 | f"Score: {self._score}, " 38 | f"Level: {self._level}, " 39 | f"Location: {self._location}\n" 40 | f"Inventory: {self._inventory}\n" 41 | ) 42 | 43 | @ property 44 | def memento(self): 45 | "A `getter` for the characters attributes as a Memento" 46 | return Memento( 47 | self._score, 48 | self._inventory.copy(), 49 | self._level, 50 | self._location.copy()) 51 | 52 | @ memento.setter 53 | def memento(self, memento): 54 | self._score = memento.score 55 | self._inventory = memento.inventory 56 | self._level = memento.level 57 | self._location = memento.location 58 | -------------------------------------------------------------------------------- /memento/memento.py: -------------------------------------------------------------------------------- 1 | "A Memento to store character attributes" 2 | 3 | 4 | class Memento(): # pylint: disable=too-few-public-methods 5 | "A container of characters attributes" 6 | 7 | def __init__(self, score, inventory, level, location): 8 | self.score = score 9 | self.inventory = inventory 10 | self.level = level 11 | self.location = location 12 | -------------------------------------------------------------------------------- /memento/memento_concept.py: -------------------------------------------------------------------------------- 1 | "Memento pattern concept" 2 | 3 | 4 | class Memento(): # pylint: disable=too-few-public-methods 5 | "A container of state" 6 | 7 | def __init__(self, state): 8 | self.state = state 9 | 10 | 11 | class Originator(): 12 | "The Object in the application whose state changes" 13 | 14 | def __init__(self): 15 | self._state = "" 16 | 17 | @property 18 | def state(self): 19 | "A `getter` for the objects state" 20 | return self._state 21 | 22 | @state.setter 23 | def state(self, state): 24 | print(f"Originator: Setting state to `{state}`") 25 | self._state = state 26 | 27 | @property 28 | def memento(self): 29 | "A `getter` for the objects state but packaged as a Memento" 30 | print("Originator: Providing Memento of state to caretaker.") 31 | return Memento(self._state) 32 | 33 | @memento.setter 34 | def memento(self, memento): 35 | self._state = memento.state 36 | print( 37 | f"Originator: State after restoring from Memento: " 38 | f"`{self._state}`") 39 | 40 | 41 | class CareTaker(): 42 | "Guardian. Provides a narrow interface to the mementos" 43 | 44 | def __init__(self, originator): 45 | self._originator = originator 46 | self._mementos = [] 47 | 48 | def create(self): 49 | "Store a new Memento of the Originators current state" 50 | print("CareTaker: Getting a copy of Originators current state") 51 | memento = self._originator.memento 52 | self._mementos.append(memento) 53 | 54 | def restore(self, index): 55 | """ 56 | Replace the Originators current state with the state 57 | stored in the saved Memento 58 | """ 59 | print("CareTaker: Restoring Originators state from Memento") 60 | memento = self._mementos[index] 61 | self._originator.memento = memento 62 | 63 | 64 | # The Client 65 | ORIGINATOR = Originator() 66 | CARETAKER = CareTaker(ORIGINATOR) 67 | 68 | # originators state can change periodically due to application events 69 | ORIGINATOR.state = "State #1" 70 | ORIGINATOR.state = "State #2" 71 | 72 | # lets backup the originators 73 | CARETAKER.create() 74 | 75 | # more changes, and then another backup 76 | ORIGINATOR.state = "State #3" 77 | CARETAKER.create() 78 | 79 | # more changes 80 | ORIGINATOR.state = "State #4" 81 | print(ORIGINATOR.state) 82 | 83 | # restore from first backup 84 | CARETAKER.restore(0) 85 | print(ORIGINATOR.state) 86 | 87 | # restore from second backup 88 | CARETAKER.restore(1) 89 | print(ORIGINATOR.state) 90 | -------------------------------------------------------------------------------- /observer/bar_graph_view.py: -------------------------------------------------------------------------------- 1 | "An observer" 2 | from interface_data_view import IDataView 3 | 4 | 5 | class BarGraphView(IDataView): 6 | "The concrete observer" 7 | 8 | def __init__(self, observable): 9 | self._observable = observable 10 | self._id = self._observable.subscribe(self) 11 | 12 | def notify(self, data): 13 | print(f"BarGraph, id:{self._id}") 14 | self.draw(data) 15 | 16 | def draw(self, data): 17 | print(f"Drawing a Bar graph using data:{data}") 18 | 19 | def delete(self): 20 | self._observable.unsubscribe(self._id) 21 | -------------------------------------------------------------------------------- /observer/client.py: -------------------------------------------------------------------------------- 1 | "Observer Design Pattern Concept" 2 | 3 | from data_model import DataModel 4 | from data_controller import DataController 5 | from pie_graph_view import PieGraphView 6 | from bar_graph_view import BarGraphView 7 | from table_view import TableView 8 | 9 | # A local data view that the hypothetical external controller updates 10 | DATA_MODEL = DataModel() 11 | 12 | # Add some visualisation that use the dataview 13 | PIE_GRAPH_VIEW = PieGraphView(DATA_MODEL) 14 | BAR_GRAPH_VIEW = BarGraphView(DATA_MODEL) 15 | TABLE_VIEW = TableView(DATA_MODEL) 16 | 17 | 18 | # A hypothetical data controller running in a different process 19 | DATA_CONTROLLER = DataController() 20 | 21 | # The hypothetical external data controller updates some data 22 | DATA_CONTROLLER.notify([1, 2, 3]) 23 | 24 | # Client now removes a local BAR_GRAPH 25 | BAR_GRAPH_VIEW.delete() 26 | 27 | # The hypothetical external data controller updates the data again 28 | DATA_CONTROLLER.notify([4, 5, 6]) 29 | -------------------------------------------------------------------------------- /observer/data_controller.py: -------------------------------------------------------------------------------- 1 | "A Data Conroller that is a Subject" 2 | from interface_data_controller import IDataController 3 | 4 | 5 | class DataController(IDataController): 6 | "A Subject (a.k.a Observable)" 7 | 8 | _observers = set() 9 | 10 | def __new__(cls): 11 | return cls 12 | 13 | @classmethod 14 | def subscribe(cls, observer): 15 | cls._observers.add(observer) 16 | 17 | @classmethod 18 | def unsubscribe(cls, observer): 19 | cls._observers.remove(observer) 20 | 21 | @classmethod 22 | def notify(cls, *args): 23 | for observer in cls._observers: 24 | observer.notify(*args) 25 | -------------------------------------------------------------------------------- /observer/data_model.py: -------------------------------------------------------------------------------- 1 | "A Data Model that observes the Data Controller" 2 | from interface_data_model import IDataModel 3 | from data_controller import DataController 4 | 5 | 6 | class DataModel(IDataModel): 7 | "A Subject (a.k.a Observable)" 8 | 9 | def __init__(self): 10 | self._observers = {} 11 | self._counter = 0 12 | # subscribing to an external hypothetical data controller 13 | self._data_controller = DataController() 14 | self._data_controller.subscribe(self) 15 | 16 | def subscribe(self, observer): 17 | self._counter = self._counter + 1 18 | self._observers[self._counter] = observer 19 | return self._counter 20 | 21 | def unsubscribe(self, observer_id): 22 | self._observers.pop(observer_id) 23 | 24 | def notify(self, data): 25 | for observer in self._observers: 26 | self._observers[observer].notify(data) 27 | -------------------------------------------------------------------------------- /observer/interface_data_controller.py: -------------------------------------------------------------------------------- 1 | "A Data Controller Interface" 2 | from abc import ABCMeta, abstractmethod 3 | 4 | 5 | class IDataController(metaclass=ABCMeta): 6 | "A Subject Interface" 7 | @staticmethod 8 | @abstractmethod 9 | def subscribe(observer): 10 | "The subscribe method" 11 | 12 | @staticmethod 13 | @abstractmethod 14 | def unsubscribe(observer): 15 | "The unsubscribe method" 16 | 17 | @staticmethod 18 | @abstractmethod 19 | def notify(observer): 20 | "The notify method" 21 | -------------------------------------------------------------------------------- /observer/interface_data_model.py: -------------------------------------------------------------------------------- 1 | "A Data Model Interface" 2 | from abc import ABCMeta, abstractmethod 3 | 4 | 5 | class IDataModel(metaclass=ABCMeta): 6 | "A Subject Interface" 7 | 8 | @staticmethod 9 | @abstractmethod 10 | def subscribe(observer): 11 | "The subscribe method" 12 | 13 | @staticmethod 14 | @abstractmethod 15 | def unsubscribe(observer_id): 16 | "The unsubscribe method" 17 | 18 | @staticmethod 19 | @abstractmethod 20 | def notify(data): 21 | "The notify method" 22 | -------------------------------------------------------------------------------- /observer/interface_data_view.py: -------------------------------------------------------------------------------- 1 | "The Data View interface" 2 | from abc import ABCMeta, abstractmethod 3 | 4 | 5 | class IDataView(metaclass=ABCMeta): 6 | "A method for the Observer to implement" 7 | 8 | @staticmethod 9 | @abstractmethod 10 | def notify(data): 11 | "Receive notifications" 12 | 13 | @staticmethod 14 | @abstractmethod 15 | def draw(data): 16 | "Draw the view" 17 | 18 | @staticmethod 19 | @abstractmethod 20 | def delete(): 21 | "a delete method to remove observer specific resources" 22 | -------------------------------------------------------------------------------- /observer/observer_concept.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | # pylint: disable=arguments-differ 3 | "Observer Design Pattern Concept" 4 | 5 | from abc import ABCMeta, abstractmethod 6 | 7 | 8 | class IObservable(metaclass=ABCMeta): 9 | "The Subject Interface" 10 | 11 | @staticmethod 12 | @abstractmethod 13 | def subscribe(observer): 14 | "The subscribe method" 15 | 16 | @staticmethod 17 | @abstractmethod 18 | def unsubscribe(observer): 19 | "The unsubscribe method" 20 | 21 | @staticmethod 22 | @abstractmethod 23 | def notify(observer): 24 | "The notify method" 25 | 26 | 27 | class Subject(IObservable): 28 | "The Subject (a.k.a Observable)" 29 | 30 | def __init__(self): 31 | self._observers = set() 32 | 33 | def subscribe(self, observer): 34 | self._observers.add(observer) 35 | 36 | def unsubscribe(self, observer): 37 | self._observers.remove(observer) 38 | 39 | def notify(self, *args): 40 | for observer in self._observers: 41 | observer.notify(*args) 42 | 43 | 44 | class IObserver(metaclass=ABCMeta): 45 | "A method for the Observer to implement" 46 | 47 | @staticmethod 48 | @abstractmethod 49 | def notify(observable, *args): 50 | "Receive notifications" 51 | 52 | 53 | class Observer(IObserver): 54 | "The concrete observer" 55 | 56 | def __init__(self, observable): 57 | observable.subscribe(self) 58 | 59 | def notify(self, *args): 60 | print(f"Observer id:{id(self)} received {args}") 61 | 62 | 63 | # The Client 64 | SUBJECT = Subject() 65 | OBSERVER_A = Observer(SUBJECT) 66 | OBSERVER_B = Observer(SUBJECT) 67 | 68 | SUBJECT.notify("First Notification", [1, 2, 3]) 69 | 70 | SUBJECT.unsubscribe(OBSERVER_B) 71 | SUBJECT.notify("Second Notification", {"A": 1, "B": 2, "C": 3}) 72 | -------------------------------------------------------------------------------- /observer/pie_graph_view.py: -------------------------------------------------------------------------------- 1 | "An observer" 2 | from interface_data_view import IDataView 3 | 4 | 5 | class PieGraphView(IDataView): 6 | "The concrete observer" 7 | 8 | def __init__(self, observable): 9 | self._observable = observable 10 | self._id = self._observable.subscribe(self) 11 | 12 | def notify(self, data): 13 | print(f"PieGraph, id:{self._id}") 14 | self.draw(data) 15 | 16 | def draw(self, data): 17 | print(f"Drawing a Pie graph using data:{data}") 18 | 19 | def delete(self): 20 | self._observable.unsubscribe(self._id) 21 | -------------------------------------------------------------------------------- /observer/table_view.py: -------------------------------------------------------------------------------- 1 | "An observer" 2 | from interface_data_view import IDataView 3 | 4 | 5 | class TableView(IDataView): 6 | "The concrete observer" 7 | 8 | def __init__(self, observable): 9 | self._observable = observable 10 | self._id = self._observable.subscribe(self) 11 | 12 | def notify(self, data): 13 | print(f"TableView, id:{self._id}") 14 | self.draw(data) 15 | 16 | def draw(self, data): 17 | print(f"Drawing a Table view using data:{data}") 18 | 19 | def delete(self): 20 | self._observable.unsubscribe(self._id) 21 | -------------------------------------------------------------------------------- /prototype/README.md: -------------------------------------------------------------------------------- 1 | # Prototype Design Pattern 2 | 3 | ## Videos 4 | 5 | Section | Video Links 6 | -|- 7 | Prototype Overview | Prototype Overview Prototype Overview Prototype Overview 8 | Prototype Use Case | Prototype Use Case Prototype Use Case Prototype Use Case 9 | Python **id()** function | python id function python id function python id function 10 | 11 | ## Book 12 | 13 | Cover | Links 14 | -|- 15 | ![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    https://www.amazon.com/dp/B08XLJ8Z2J
   https://www.amazon.co.uk/dp/B08XLJ8Z2J
   https://www.amazon.in/dp/B08Z282SBC
   https://www.amazon.de/dp/B08XLJ8Z2J
   https://www.amazon.fr/dp/B08XLJ8Z2J
   https://www.amazon.es/dp/B08XLJ8Z2J
   https://www.amazon.it/dp/B08XLJ8Z2J
   https://www.amazon.co.jp/dp/B08XLJ8Z2J
   https://www.amazon.ca/dp/B08XLJ8Z2J
   https://www.amazon.com.au/dp/B08Z282SBC 16 | 17 | ## Overview 18 | 19 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 20 | 21 | ## Terminology 22 | 23 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 24 | 25 | ## Prototype UML Diagram 26 | 27 | ![Prototype UML Diagram](/img/prototype_concept.svg) 28 | 29 | ## Source Code 30 | 31 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 32 | 33 | ## Summary 34 | 35 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ -------------------------------------------------------------------------------- /prototype/client.py: -------------------------------------------------------------------------------- 1 | "Prototype Use Case Example Code" 2 | from document import Document 3 | 4 | # Creating a document containing a list of two lists 5 | ORIGINAL_DOCUMENT = Document("Original", [[1, 2, 3, 4], [5, 6, 7, 8]]) 6 | print(ORIGINAL_DOCUMENT) 7 | print() 8 | 9 | DOCUMENT_COPY_1 = ORIGINAL_DOCUMENT.clone(1) # shallow copy 10 | DOCUMENT_COPY_1.name = "Copy 1" 11 | # This also modified ORIGINAL_DOCUMENT because of the shallow copy 12 | # when using mode 1 13 | DOCUMENT_COPY_1.list[1][2] = 200 14 | print(DOCUMENT_COPY_1) 15 | print(ORIGINAL_DOCUMENT) 16 | print() 17 | 18 | DOCUMENT_COPY_2 = ORIGINAL_DOCUMENT.clone(2) # 2 level shallow copy 19 | DOCUMENT_COPY_2.name = "Copy 2" 20 | # This does NOT modify ORIGINAL_DOCUMENT because it changes the 21 | # list[1] reference that was deep copied when using mode 2 22 | DOCUMENT_COPY_2.list[1] = [9, 10, 11, 12] 23 | print(DOCUMENT_COPY_2) 24 | print(ORIGINAL_DOCUMENT) 25 | print() 26 | 27 | DOCUMENT_COPY_3 = ORIGINAL_DOCUMENT.clone(2) # 2 level shallow copy 28 | DOCUMENT_COPY_3.name = "Copy 3" 29 | # This does modify ORIGINAL_DOCUMENT because it changes the element of 30 | # list[1][0] that was NOT deep copied recursively when using mode 2 31 | DOCUMENT_COPY_3.list[1][0] = "1234" 32 | print(DOCUMENT_COPY_3) 33 | print(ORIGINAL_DOCUMENT) 34 | print() 35 | 36 | DOCUMENT_COPY_4 = ORIGINAL_DOCUMENT.clone(3) # deep copy (recursive) 37 | DOCUMENT_COPY_4.name = "Copy 4" 38 | # This does NOT modify ORIGINAL_DOCUMENT because it 39 | # deep copies all levels recursively when using mode 3 40 | DOCUMENT_COPY_4.list[1][0] = "5678" 41 | print(DOCUMENT_COPY_4) 42 | print(ORIGINAL_DOCUMENT) 43 | print() 44 | -------------------------------------------------------------------------------- /prototype/document.py: -------------------------------------------------------------------------------- 1 | "A sample document to be used in the Prototype example" 2 | import copy # a python library useful for deep copying 3 | from interface_prototype import IProtoType 4 | 5 | 6 | class Document(IProtoType): 7 | "A Concrete Class" 8 | 9 | def __init__(self, name, l): 10 | self.name = name 11 | self.list = l 12 | 13 | def clone(self, mode): 14 | " This clone method uses different copy techniques " 15 | if mode == 1: 16 | # results in a 1 level shallow copy of the Document 17 | doc_list = self.list 18 | if mode == 2: 19 | # results in a 2 level shallow copy of the Document 20 | # since it also create new references for the 1st level list 21 | # elements aswell 22 | doc_list = self.list.copy() 23 | if mode == 3: 24 | # recursive deep copy. Slower but results in a new copy 25 | # where no sub elements are shared by reference 26 | doc_list = copy.deepcopy(self.list) 27 | 28 | return type(self)( 29 | self.name, # a shallow copy is returned of the name property 30 | doc_list # copy method decided by mode argument 31 | ) 32 | 33 | def __str__(self): 34 | " Overriding the default __str__ method for our object." 35 | return f"{id(self)}\tname={self.name}\tlist={self.list}" 36 | -------------------------------------------------------------------------------- /prototype/interface_prototype.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "Prototype Concept Sample Code" 3 | from abc import ABCMeta, abstractmethod 4 | 5 | 6 | class IProtoType(metaclass=ABCMeta): 7 | "interface with clone method" 8 | @staticmethod 9 | @abstractmethod 10 | def clone(mode): 11 | """The clone, deep or shallow. 12 | It is up to you how you want to implement 13 | the details in your concrete class""" 14 | -------------------------------------------------------------------------------- /prototype/prototype_concept.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | # pylint: disable=arguments-differ 3 | "Prototype Concept Sample Code" 4 | from abc import ABCMeta, abstractmethod 5 | 6 | 7 | class IProtoType(metaclass=ABCMeta): 8 | "interface with clone method" 9 | @staticmethod 10 | @abstractmethod 11 | def clone(): 12 | """The clone, deep or shallow. 13 | It is up to you how you want to implement 14 | the details in your concrete class""" 15 | 16 | 17 | class MyClass(IProtoType): 18 | "A Concrete Class" 19 | 20 | def __init__(self, field): 21 | self.field = field # any value of any type 22 | 23 | def clone(self): 24 | " This clone method uses a shallow copy technique " 25 | return type(self)( 26 | self.field # a shallow copy is returned 27 | # self.field.copy() # this is also a shallow copy, but has 28 | # also shallow copied the first level of the field. So it 29 | # is essentially a shallow copy but 2 levels deep. To 30 | # recursively deep copy collections containing inner 31 | # collections, 32 | # eg lists of lists, 33 | # Use https://docs.python.org/3/library/copy.html instead. 34 | # See example below. 35 | ) 36 | 37 | def __str__(self): 38 | return f"{id(self)}\tfield={self.field}\ttype={type(self.field)}" 39 | 40 | 41 | # The Client 42 | OBJECT1 = MyClass([1, 2, 3, 4]) # Create the object containing a list 43 | print(f"OBJECT1 {OBJECT1}") 44 | 45 | OBJECT2 = OBJECT1.clone() # Clone 46 | 47 | # Change the value of one of the list elements in OBJECT2, 48 | # to see if it also modifies the list element in OBJECT1. 49 | # If it changed OBJECT1s copy also, then the clone was done 50 | # using a 1 level shallow copy process. 51 | # Modify the clone method above to try a 2 level shallow copy instead 52 | # and compare the output 53 | OBJECT2.field[1] = 101 54 | 55 | # Comparing OBJECT1 and OBJECT2 56 | print(f"OBJECT2 {OBJECT2}") 57 | print(f"OBJECT1 {OBJECT1}") 58 | -------------------------------------------------------------------------------- /proxy/client.py: -------------------------------------------------------------------------------- 1 | "The Proxy Example Use Case" 2 | 3 | from lion import Lion 4 | 5 | PROTEUS = Lion() 6 | PROTEUS.tell_me_your_form() 7 | PROTEUS.tell_me_the_future() 8 | PROTEUS.tell_me_your_form() 9 | PROTEUS.tell_me_the_future() 10 | PROTEUS.tell_me_your_form() 11 | PROTEUS.tell_me_the_future() 12 | PROTEUS.tell_me_your_form() 13 | PROTEUS.tell_me_the_future() 14 | PROTEUS.tell_me_your_form() 15 | PROTEUS.tell_me_the_future() 16 | -------------------------------------------------------------------------------- /proxy/interface_proteus.py: -------------------------------------------------------------------------------- 1 | "The Proteus Interface" 2 | 3 | from abc import ABCMeta, abstractmethod 4 | 5 | 6 | class IProteus(metaclass=ABCMeta): # pylint: disable=too-few-public-methods 7 | "A Greek mythological character that can change to many forms" 8 | 9 | @staticmethod 10 | @abstractmethod 11 | def tell_me_the_future(): 12 | "Proteus will change form rather than tell you the future" 13 | 14 | @staticmethod 15 | @abstractmethod 16 | def tell_me_your_form(): 17 | "The form of Proteus is elusive like the sea" 18 | -------------------------------------------------------------------------------- /proxy/leopard.py: -------------------------------------------------------------------------------- 1 | "A Leopard Class" 2 | import random 3 | from interface_proteus import IProteus 4 | import lion 5 | import serpent 6 | 7 | 8 | class Leopard(IProteus): # pylint: disable=too-few-public-methods 9 | "Proteus in the form of a Leopard" 10 | 11 | name = "Leopard" 12 | 13 | def tell_me_the_future(self): 14 | "Proteus will change to something random" 15 | self.__class__ = serpent.Serpent if random.randint(0, 1) else lion.Lion 16 | 17 | @classmethod 18 | def tell_me_your_form(cls): 19 | print("I am the form of a " + cls.name) 20 | -------------------------------------------------------------------------------- /proxy/lion.py: -------------------------------------------------------------------------------- 1 | "A Lion Class" 2 | import random 3 | from interface_proteus import IProteus 4 | import leopard 5 | import serpent 6 | 7 | 8 | class Lion(IProteus): # pylint: disable=too-few-public-methods 9 | "Proteus in the form of a Lion" 10 | 11 | name = "Lion" 12 | 13 | def tell_me_the_future(self): 14 | "Proteus will change to something random" 15 | self.__class__ = leopard.Leopard if random.randint( 16 | 0, 1) else serpent.Serpent 17 | 18 | @classmethod 19 | def tell_me_your_form(cls): 20 | print("I am the form of a " + cls.name) 21 | -------------------------------------------------------------------------------- /proxy/proxy_concept.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | # pylint: disable=arguments-differ 3 | "A Proxy Concept Example" 4 | 5 | from abc import ABCMeta, abstractmethod 6 | 7 | 8 | class ISubject(metaclass=ABCMeta): 9 | "An interface implemented by both the Proxy and Real Subject" 10 | @staticmethod 11 | @abstractmethod 12 | def request(): 13 | "A method to implement" 14 | 15 | 16 | class RealSubject(ISubject): 17 | "The actual real object that the proxy is representing" 18 | 19 | def __init__(self): 20 | # hypothetically enormous amounts of data 21 | self.enormous_data = [1, 2, 3] 22 | 23 | def request(self): 24 | return self.enormous_data 25 | 26 | 27 | class Proxy(ISubject): 28 | """ 29 | The proxy. In this case the proxy will act as a cache for 30 | `enormous_data` and only populate the enormous_data when it 31 | is actually necessary 32 | """ 33 | 34 | def __init__(self): 35 | self.enormous_data = [] 36 | self.real_subject = RealSubject() 37 | 38 | def request(self): 39 | """ 40 | Using the proxy as a cache, and loading data into it only if 41 | it is needed 42 | """ 43 | if not self.enormous_data: 44 | print("pulling data from RealSubject") 45 | self.enormous_data = self.real_subject.request() 46 | return self.enormous_data 47 | print("pulling data from Proxy cache") 48 | return self.enormous_data 49 | 50 | 51 | # The Client 52 | SUBJECT = Proxy() 53 | # use SUBJECT 54 | print(id(SUBJECT)) 55 | # load the enormous amounts of data because now we want to show it. 56 | print(SUBJECT.request()) 57 | # show the data again, but this time it retrieves it from the local cache 58 | print(SUBJECT.request()) 59 | -------------------------------------------------------------------------------- /proxy/serpent.py: -------------------------------------------------------------------------------- 1 | "A Serpent Class" 2 | import random 3 | from interface_proteus import IProteus 4 | import lion 5 | import leopard 6 | 7 | 8 | class Serpent(IProteus): # pylint: disable=too-few-public-methods 9 | "Proteus in the form of a Serpent" 10 | 11 | name = "Serpent" 12 | 13 | def tell_me_the_future(self): 14 | "Proteus will change to something random" 15 | self.__class__ = leopard.Leopard if random.randint(0, 1) else lion.Lion 16 | 17 | @classmethod 18 | def tell_me_your_form(cls): 19 | print("I am the form of a " + cls.name) 20 | -------------------------------------------------------------------------------- /singleton/client.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | 3 | "Singleton Use Case Example Code." 4 | 5 | from game1 import Game1 6 | from game2 import Game2 7 | from game3 import Game3 8 | 9 | 10 | # The Client 11 | # All games share and manage the same leaderboard because it is a singleton. 12 | GAME1 = Game1() 13 | GAME1.add_winner(2, "Cosmo") 14 | 15 | GAME2 = Game2() 16 | GAME2.add_winner(3, "Sean") 17 | 18 | GAME3 = Game3() 19 | GAME3.add_winner(1, "Emmy") 20 | 21 | GAME1.leaderboard.print() 22 | GAME2.leaderboard.print() 23 | GAME3.leaderboard.print() 24 | -------------------------------------------------------------------------------- /singleton/game1.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "A Game Class that uses the Leaderboard Singleton" 3 | 4 | from leaderboard import Leaderboard 5 | from interface_game import IGame 6 | 7 | 8 | class Game1(IGame): 9 | "Game1 implements IGame" 10 | 11 | def __init__(self): 12 | self.leaderboard = Leaderboard() 13 | 14 | def add_winner(self, position, name): 15 | self.leaderboard.add_winner(position, name) 16 | -------------------------------------------------------------------------------- /singleton/game2.py: -------------------------------------------------------------------------------- 1 | "A Game Class that uses the Leaderboard Singleton" 2 | 3 | from leaderboard import Leaderboard 4 | from interface_game import IGame 5 | 6 | 7 | class Game2(IGame): # pylint: disable=too-few-public-methods 8 | "Game2 implements IGame" 9 | 10 | def __init__(self): 11 | self.leaderboard = Leaderboard() 12 | 13 | def add_winner(self, position, name): 14 | self.leaderboard.add_winner(position, name) 15 | -------------------------------------------------------------------------------- /singleton/game3.py: -------------------------------------------------------------------------------- 1 | "A Game Class that uses the Leaderboard Singleton" 2 | from game2 import Game2 3 | 4 | 5 | class Game3(Game2): # pylint: disable=too-few-public-methods 6 | """Game 3 Inherits from Game 2 instead of implementing IGame""" 7 | -------------------------------------------------------------------------------- /singleton/interface_game.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | 3 | "A Game Interface" 4 | 5 | from abc import ABCMeta, abstractmethod 6 | 7 | 8 | class IGame(metaclass=ABCMeta): 9 | "A Game Interface" 10 | @staticmethod 11 | @abstractmethod 12 | def add_winner(position, name): 13 | "Must implement add_winner" 14 | -------------------------------------------------------------------------------- /singleton/leaderboard.py: -------------------------------------------------------------------------------- 1 | "A Leaderboard Singleton Class" 2 | 3 | 4 | class Leaderboard(): 5 | "The Leaderboard as a Singleton" 6 | _table = {} 7 | 8 | def __new__(cls): 9 | return cls 10 | 11 | @classmethod 12 | def print(cls): 13 | "A class level method" 14 | print("-----------Leaderboard-----------") 15 | for key, value in sorted(cls._table.items()): 16 | print(f"|\t{key}\t|\t{value}\t|") 17 | print() 18 | 19 | @classmethod 20 | def add_winner(cls, position, name): 21 | "A class level method" 22 | cls._table[position] = name 23 | -------------------------------------------------------------------------------- /singleton/singleton_concept.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "Singleton Concept Sample Code" 3 | import copy 4 | 5 | 6 | class Singleton(): 7 | "The Singleton Class" 8 | value = [] 9 | 10 | def __new__(cls): 11 | return cls 12 | 13 | # def __init__(self): 14 | # print("in init") 15 | 16 | @staticmethod 17 | def static_method(): 18 | "Use @staticmethod if no inner variables required" 19 | 20 | @classmethod 21 | def class_method(cls): 22 | "Use @classmethod to access class level variables" 23 | print(cls.value) 24 | 25 | 26 | # The Client 27 | # All uses of singleton point to the same memory address (id) 28 | print(f"id(Singleton)\t= {id(Singleton)}") 29 | 30 | OBJECT1 = Singleton() 31 | print(f"id(OBJECT1)\t= {id(OBJECT1)}") 32 | 33 | OBJECT2 = copy.deepcopy(OBJECT1) 34 | print(f"id(OBJECT2)\t= {id(OBJECT2)}") 35 | 36 | OBJECT3 = Singleton() 37 | print(f"id(OBJECT1)\t= {id(OBJECT3)}") 38 | -------------------------------------------------------------------------------- /state/README.md: -------------------------------------------------------------------------------- 1 | # State Design Pattern 2 | 3 | ## Videos 4 | 5 | Section | Video Links 6 | -|- 7 | State Overview | State Overview State Overview State Overview 8 | State Use Case | State Use Case State Use Case State Use Case 9 | **\_\_call\_\_** Attribute | Dunder __call__ Attribute Dunder __call__ Attribute Dunder __call__ Attribute 10 | 11 | ## Book 12 | 13 | Cover | Links 14 | -|- 15 | ![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    https://www.amazon.com/dp/B08XLJ8Z2J
   https://www.amazon.co.uk/dp/B08XLJ8Z2J
   https://www.amazon.in/dp/B08Z282SBC
   https://www.amazon.de/dp/B08XLJ8Z2J
   https://www.amazon.fr/dp/B08XLJ8Z2J
   https://www.amazon.es/dp/B08XLJ8Z2J
   https://www.amazon.it/dp/B08XLJ8Z2J
   https://www.amazon.co.jp/dp/B08XLJ8Z2J
   https://www.amazon.ca/dp/B08XLJ8Z2J
   https://www.amazon.com.au/dp/B08Z282SBC 16 | 17 | ## Overview 18 | 19 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 20 | 21 | ## Terminology 22 | 23 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 24 | 25 | ## State UML Diagram 26 | 27 | ![State UML Diagram](/img/state_concept.svg) 28 | 29 | ## Source Code 30 | 31 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 32 | 33 | ### Output 34 | 35 | ``` bash 36 | python.exe ./state/state_concept.py 37 | I am ConcreteStateB 38 | I am ConcreteStateA 39 | I am ConcreteStateB 40 | I am ConcreteStateA 41 | I am ConcreteStateC 42 | ``` 43 | 44 | ## State Example Use Case 45 | 46 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 47 | 48 | ## State Example Use Case UML Diagram 49 | 50 | ![State Example Use Case UML Diagram](/img/state_example.svg) 51 | 52 | ## Output 53 | 54 | ``` bash 55 | python.exe ./state/client.py 56 | Task Started 57 | Task Running 58 | Task Finished 59 | Task Started 60 | Task Running 61 | ``` 62 | 63 | ## New Coding Concepts 64 | 65 | ### Dunder `__call__` Method 66 | 67 | Overloading the `__call__` method makes an instance of a class callable like a function when by default it isn't. You need to call a method within the class directly. 68 | 69 | ``` python 70 | class ExampleClass: 71 | @staticmethod 72 | def do_this_by_default(): 73 | print("doing this") 74 | 75 | EXAMPLE = ExampleClass() 76 | EXAMPLE.do_this_by_default() # needs to be explicitly called to execute 77 | ``` 78 | 79 | If you want a default method in your class, you can point to it using by the `__call__` method. 80 | 81 | ``` python 82 | class ExampleClass: 83 | @staticmethod 84 | def do_this_by_default(): 85 | print("doing this") 86 | 87 | __call__ = do_this_by_default 88 | 89 | EXAMPLE = ExampleClass() 90 | EXAMPLE() # function now gets called by default 91 | ``` 92 | 93 | ## Summary 94 | 95 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ -------------------------------------------------------------------------------- /state/client.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "The State Use Case Example" 3 | from abc import ABCMeta, abstractmethod 4 | 5 | 6 | class Context(): 7 | "This is the object whose behavior will change" 8 | 9 | def __init__(self): 10 | 11 | self.state_handles = [ 12 | Started(), 13 | Running(), 14 | Finished() 15 | ] 16 | self._handle = iter(self.state_handles) 17 | 18 | def request(self): 19 | "Each time the request is called, a new class will handle it" 20 | try: 21 | self._handle.__next__()() 22 | except StopIteration: 23 | # resetting so it loops 24 | self._handle = iter(self.state_handles) 25 | 26 | 27 | class IState(metaclass=ABCMeta): 28 | "A State Interface" 29 | 30 | @staticmethod 31 | @abstractmethod 32 | def __call__(): 33 | "Set the default method" 34 | 35 | 36 | class Started(IState): 37 | "A ConcreteState Subclass" 38 | 39 | @staticmethod 40 | def method(): 41 | "A task of this class" 42 | print("Task Started") 43 | 44 | __call__ = method 45 | 46 | 47 | class Running(IState): 48 | "A ConcreteState Subclass" 49 | 50 | @staticmethod 51 | def method(): 52 | "A task of this class" 53 | print("Task Running") 54 | 55 | __call__ = method 56 | 57 | 58 | class Finished(IState): 59 | "A ConcreteState Subclass" 60 | 61 | @staticmethod 62 | def method(): 63 | "A task of this class" 64 | print("Task Finished") 65 | 66 | __call__ = method 67 | 68 | 69 | # The Client 70 | CONTEXT = Context() 71 | CONTEXT.request() 72 | CONTEXT.request() 73 | CONTEXT.request() 74 | CONTEXT.request() 75 | CONTEXT.request() 76 | CONTEXT.request() 77 | -------------------------------------------------------------------------------- /state/state_concept.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "The State Pattern Concept" 3 | from abc import ABCMeta, abstractmethod 4 | import random 5 | 6 | class Context(): 7 | "This is the object whose behavior will change" 8 | 9 | def __init__(self): 10 | self.state_handles = [ConcreteStateA(), 11 | ConcreteStateB(), 12 | ConcreteStateC()] 13 | self.handle = None 14 | 15 | def request(self): 16 | """A method of the state that dynamically changes which 17 | class it uses depending on the value of self.handle""" 18 | self.handle = self.state_handles[random.randint(0, 2)] 19 | return self.handle 20 | 21 | class IState(metaclass=ABCMeta): 22 | "A State Interface" 23 | 24 | @staticmethod 25 | @abstractmethod 26 | def __str__(): 27 | "Set the default method" 28 | 29 | class ConcreteStateA(IState): 30 | "A ConcreteState Subclass" 31 | 32 | def __str__(self): 33 | return "I am ConcreteStateA" 34 | 35 | class ConcreteStateB(IState): 36 | "A ConcreteState Subclass" 37 | 38 | def __str__(self): 39 | return "I am ConcreteStateB" 40 | 41 | class ConcreteStateC(IState): 42 | "A ConcreteState Subclass" 43 | 44 | def __str__(self): 45 | return "I am ConcreteStateC" 46 | 47 | # The Client 48 | CONTEXT = Context() 49 | print(CONTEXT.request()) 50 | print(CONTEXT.request()) 51 | print(CONTEXT.request()) 52 | print(CONTEXT.request()) 53 | print(CONTEXT.request()) 54 | -------------------------------------------------------------------------------- /strategy/README.md: -------------------------------------------------------------------------------- 1 | # Strategy Design Pattern 2 | 3 | ## Videos 4 | 5 | Section | Video Links 6 | -|- 7 | Strategy Overview | Strategy Overview Strategy Overview Strategy Overview 8 | Strategy Use Case | Strategy Use Case Strategy Use Case Strategy Use Case 9 | 10 | ## Book 11 | 12 | Cover | Links 13 | -|- 14 | ![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    https://www.amazon.com/dp/B08XLJ8Z2J
   https://www.amazon.co.uk/dp/B08XLJ8Z2J
   https://www.amazon.in/dp/B08Z282SBC
   https://www.amazon.de/dp/B08XLJ8Z2J
   https://www.amazon.fr/dp/B08XLJ8Z2J
   https://www.amazon.es/dp/B08XLJ8Z2J
   https://www.amazon.it/dp/B08XLJ8Z2J
   https://www.amazon.co.jp/dp/B08XLJ8Z2J
   https://www.amazon.ca/dp/B08XLJ8Z2J
   https://www.amazon.com.au/dp/B08Z282SBC 15 | 16 | ## Overview 17 | 18 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 19 | 20 | ## Terminology 21 | 22 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 23 | 24 | ## Strategy UML Diagram 25 | 26 | ![Strategy UML Diagram](/img/strategy_concept.svg) 27 | 28 | ## Source Code 29 | 30 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 31 | 32 | ## Output 33 | 34 | ``` bash 35 | python ./strategy/strategy_concept.py 36 | I am ConcreteStrategyA 37 | I am ConcreteStrategyB 38 | I am ConcreteStrategyC 39 | ``` 40 | 41 | ## Strategy Example Use Case 42 | 43 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 44 | 45 | ## Strategy Example Use Case UML Diagram 46 | 47 | ![Strategy Example Use Case UML Diagram](/img/strategy_example.svg) 48 | 49 | ## Output 50 | 51 | ``` bash 52 | python ./strategy/client.py 53 | I am Walking. New position = [1, 0] 54 | I am Running. New position = [3, 0] 55 | I am Crawling. New position = [3.5, 0] 56 | ``` 57 | 58 | ## Summary 59 | 60 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ -------------------------------------------------------------------------------- /strategy/client.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "The Strategy Pattern Example Use Case" 3 | from abc import ABCMeta, abstractmethod 4 | 5 | 6 | class GameCharacter(): 7 | "This is the context whose strategy will change" 8 | 9 | position = [0, 0] 10 | 11 | @classmethod 12 | def move(cls, movement_style): 13 | "The movement algorithm has been decided by the client" 14 | movement_style(cls.position) 15 | 16 | 17 | class IMove(metaclass=ABCMeta): 18 | "A Concrete Strategy Interface" 19 | 20 | @staticmethod 21 | @abstractmethod 22 | def __call__(): 23 | "Implementors must select the default method" 24 | 25 | 26 | class Walking(IMove): 27 | "A Concrete Strategy Subclass" 28 | 29 | @staticmethod 30 | def walk(position): 31 | "A walk algorithm" 32 | position[0] += 1 33 | print(f"I am Walking. New position = {position}") 34 | 35 | __call__ = walk 36 | 37 | 38 | class Running(IMove): 39 | "A Concrete Strategy Subclass" 40 | 41 | @staticmethod 42 | def run(position): 43 | "A run algorithm" 44 | position[0] += 2 45 | print(f"I am Running. New position = {position}") 46 | 47 | __call__ = run 48 | 49 | 50 | class Crawling(IMove): 51 | "A Concrete Strategy Subclass" 52 | 53 | @staticmethod 54 | def crawl(position): 55 | "A crawl algorithm" 56 | position[0] += 0.5 57 | print(f"I am Crawling. New position = {position}") 58 | 59 | __call__ = crawl 60 | 61 | 62 | # The Client 63 | GAME_CHARACTER = GameCharacter() 64 | GAME_CHARACTER.move(Walking()) 65 | # Character sees the enemy 66 | GAME_CHARACTER.move(Running()) 67 | # Character finds a small cave to hide in 68 | GAME_CHARACTER.move(Crawling()) 69 | -------------------------------------------------------------------------------- /strategy/strategy_concept.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "The Strategy Pattern Concept" 3 | from abc import ABCMeta, abstractmethod 4 | 5 | 6 | class Context(): 7 | "This is the object whose behavior will change" 8 | 9 | @staticmethod 10 | def request(strategy): 11 | """The request is handled by the class passed in""" 12 | return strategy() 13 | 14 | 15 | class IStrategy(metaclass=ABCMeta): 16 | "A strategy Interface" 17 | 18 | @staticmethod 19 | @abstractmethod 20 | def __str__(): 21 | "Implement the __str__ dunder" 22 | 23 | 24 | class ConcreteStrategyA(IStrategy): 25 | "A Concrete Strategy Subclass" 26 | 27 | def __str__(self): 28 | return "I am ConcreteStrategyA" 29 | 30 | 31 | class ConcreteStrategyB(IStrategy): 32 | "A Concrete Strategy Subclass" 33 | 34 | def __str__(self): 35 | return "I am ConcreteStrategyB" 36 | 37 | 38 | class ConcreteStrategyC(IStrategy): 39 | "A Concrete Strategy Subclass" 40 | 41 | def __str__(self): 42 | return "I am ConcreteStrategyC" 43 | 44 | 45 | # The Client 46 | CONTEXT = Context() 47 | 48 | print(CONTEXT.request(ConcreteStrategyA)) 49 | print(CONTEXT.request(ConcreteStrategyB)) 50 | print(CONTEXT.request(ConcreteStrategyC)) 51 | -------------------------------------------------------------------------------- /template/README.md: -------------------------------------------------------------------------------- 1 | # Template Method Design Pattern 2 | 3 | ## Videos 4 | 5 | Section | Video Links 6 | -|- 7 | Template Method Overview | Template Method Overview Template Method Overview Template Method Overview 8 | Template Method Use Case | Template Method Use Case Template Method Use Case Template Method Use Case 9 | 10 | ## Book 11 | 12 | Cover | Links 13 | -|- 14 | ![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    https://www.amazon.com/dp/B08XLJ8Z2J
   https://www.amazon.co.uk/dp/B08XLJ8Z2J
   https://www.amazon.in/dp/B08Z282SBC
   https://www.amazon.de/dp/B08XLJ8Z2J
   https://www.amazon.fr/dp/B08XLJ8Z2J
   https://www.amazon.es/dp/B08XLJ8Z2J
   https://www.amazon.it/dp/B08XLJ8Z2J
   https://www.amazon.co.jp/dp/B08XLJ8Z2J
   https://www.amazon.ca/dp/B08XLJ8Z2J
   https://www.amazon.com.au/dp/B08Z282SBC 15 | 16 | ## Overview 17 | 18 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 19 | 20 | ## Terminology 21 | 22 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 23 | 24 | ## Template Method UML Diagram 25 | 26 | ![Template Method UML Diagram](/img/template_concept.svg) 27 | 28 | ## Source Code 29 | 30 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 31 | 32 | ## Output 33 | 34 | ``` bash 35 | python ./template/template_concept.py 36 | Class_A : Step Two (overridden) 37 | Step Three is a hook that prints this line by default. 38 | Class_B : Step One (overridden) 39 | Class_B : Step Two. (overridden) 40 | Class_B : Step Three. (overridden) 41 | ``` 42 | 43 | ## Template Method Example Use Case 44 | 45 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ 46 | 47 | ## Template Method Use Case UML Diagram 48 | 49 | ![Template Method Use Case UML Diagram](/img/template_example.svg) 50 | 51 | ## Output 52 | 53 | ``` bash 54 | python ./template/client.py 55 | ---------------------- 56 | title : New Text Document 57 | background_colour : white 58 | text : Some Text 59 | footer : -- Page 1 -- 60 | 61 | 62 | 63 | New HTML Document 64 | 69 | 70 | 71 |

Line 1

72 |

Line 2

73 | 74 | 75 | ``` 76 | 77 | ## Summary 78 | 79 | _... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ -------------------------------------------------------------------------------- /template/abstract_document.py: -------------------------------------------------------------------------------- 1 | "An abstract document containing a combination of hooks and abstract methods" 2 | from abc import ABCMeta, abstractmethod 3 | 4 | 5 | class AbstractDocument(metaclass=ABCMeta): 6 | "A template class containing a template method and primitive methods" 7 | 8 | @staticmethod 9 | @abstractmethod 10 | def title(document): 11 | "must implement" 12 | 13 | @staticmethod 14 | def description(document): 15 | "optional" 16 | 17 | @staticmethod 18 | def author(document): 19 | "optional" 20 | 21 | @staticmethod 22 | def background_colour(document): 23 | "optional with a default behavior" 24 | document["background_colour"] = "white" 25 | 26 | @staticmethod 27 | @abstractmethod 28 | def text(document, text): 29 | "must implement" 30 | 31 | @staticmethod 32 | def footer(document): 33 | "optional" 34 | 35 | @staticmethod 36 | def print(document): 37 | "optional with a default behavior" 38 | print("----------------------") 39 | for attribute in document: 40 | print(f"{attribute}\t: {document[attribute]}") 41 | print() 42 | 43 | @classmethod 44 | def create_document(cls, text): 45 | "The template method" 46 | _document = {} 47 | cls.title(_document) 48 | cls.description(_document) 49 | cls.author(_document) 50 | cls.background_colour(_document) 51 | cls.text(_document, text) 52 | cls.footer(_document) 53 | cls.print(_document) 54 | -------------------------------------------------------------------------------- /template/client.py: -------------------------------------------------------------------------------- 1 | "The Template Pattern Use Case Example" 2 | from text_document import TextDocument 3 | from html_document import HTMLDocument 4 | 5 | TEXT_DOCUMENT = TextDocument() 6 | TEXT_DOCUMENT.create_document("Some Text") 7 | 8 | HTML_DOCUMENT = HTMLDocument() 9 | HTML_DOCUMENT.create_document("Line 1\nLine 2") 10 | -------------------------------------------------------------------------------- /template/html_document.py: -------------------------------------------------------------------------------- 1 | "A HTML document concrete class of AbstractDocument" 2 | from abstract_document import AbstractDocument 3 | 4 | 5 | class HTMLDocument(AbstractDocument): 6 | "Prints out a HTML formatted document" 7 | @staticmethod 8 | def title(document): 9 | document["title"] = "New HTML Document" 10 | 11 | @staticmethod 12 | def text(document, text): 13 | "Putting multiple lines into there own p tags" 14 | lines = text.splitlines() 15 | markup = "" 16 | for line in lines: 17 | markup = markup + "

" + f"{line}

\n" 18 | document["text"] = markup[:-1] 19 | 20 | @staticmethod 21 | def print(document): 22 | "overriding print to output with html tags" 23 | print("") 24 | print(" ") 25 | for attribute in document: 26 | if attribute in ["title", "description", "author"]: 27 | print( 28 | f" <{attribute}>{document[attribute]}" 29 | f"" 30 | ) 31 | if attribute == "background_colour": 32 | print(" ") 39 | print(" ") 40 | print(" ") 41 | print(f"{document['text']}") 42 | print(" ") 43 | print("") 44 | -------------------------------------------------------------------------------- /template/template_concept.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | "The Template Method Pattern Concept" 3 | from abc import ABCMeta, abstractmethod 4 | 5 | 6 | class AbstractClass(metaclass=ABCMeta): 7 | "A template class containing a template method and primitive methods" 8 | 9 | @staticmethod 10 | def step_one(): 11 | """ 12 | Hooks are normally empty in the abstract class. The 13 | implementing class can optionally override providing a custom 14 | implementation 15 | """ 16 | 17 | @staticmethod 18 | @abstractmethod 19 | def step_two(): 20 | """ 21 | An abstract method that must be overridden in the implementing 22 | class. It has been given `@abstractmethod` decorator so that 23 | pylint shows the error. 24 | """ 25 | 26 | @staticmethod 27 | def step_three(): 28 | """ 29 | Hooks can also contain default behavior and can be optionally 30 | overridden 31 | """ 32 | print("Step Three is a hook that prints this line by default.") 33 | 34 | @classmethod 35 | def template_method(cls): 36 | """ 37 | This is the template method that the subclass will call. 38 | The subclass (implementing class) doesn't need to override this 39 | method since it has would have already optionally overridden 40 | the following methods with its own implementations 41 | """ 42 | cls.step_one() 43 | cls.step_two() 44 | cls.step_three() 45 | 46 | 47 | class ConcreteClassA(AbstractClass): 48 | "A concrete class that only overrides step two" 49 | @staticmethod 50 | def step_two(): 51 | print("Class_A : Step Two (overridden)") 52 | 53 | 54 | class ConcreteClassB(AbstractClass): 55 | "A concrete class that only overrides steps one, two and three" 56 | @staticmethod 57 | def step_one(): 58 | print("Class_B : Step One (overridden)") 59 | 60 | @staticmethod 61 | def step_two(): 62 | print("Class_B : Step Two. (overridden)") 63 | 64 | @staticmethod 65 | def step_three(): 66 | print("Class_B : Step Three. (overridden)") 67 | 68 | 69 | # The Client 70 | CLASS_A = ConcreteClassA() 71 | CLASS_A.template_method() 72 | 73 | CLASS_B = ConcreteClassB() 74 | CLASS_B.template_method() 75 | -------------------------------------------------------------------------------- /template/text_document.py: -------------------------------------------------------------------------------- 1 | "A text document concrete class of AbstractDocument" 2 | from abstract_document import AbstractDocument 3 | 4 | 5 | class TextDocument(AbstractDocument): 6 | "Prints out a text document" 7 | @staticmethod 8 | def title(document): 9 | document["title"] = "New Text Document" 10 | 11 | @staticmethod 12 | def text(document, text): 13 | document["text"] = text 14 | 15 | @staticmethod 16 | def footer(document): 17 | document["footer"] = "-- Page 1 --" 18 | -------------------------------------------------------------------------------- /visitor/client.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | # pylint: disable=arguments-differ 3 | "The Visitor Pattern Use Case Example" 4 | from abc import ABCMeta, abstractmethod 5 | 6 | 7 | class IVisitor(metaclass=ABCMeta): 8 | "An interface that custom Visitors should implement" 9 | @staticmethod 10 | @abstractmethod 11 | def visit(element): 12 | "Visitors visit Elements/Objects within the application" 13 | 14 | 15 | class IVisitable(metaclass=ABCMeta): 16 | """ 17 | An interface that concrete objects should implement that allows 18 | the visitor to traverse a hierarchical structure of objects 19 | """ 20 | @staticmethod 21 | @abstractmethod 22 | def accept(visitor): 23 | """ 24 | The Visitor traverses and accesses each object through this 25 | method 26 | """ 27 | 28 | 29 | class AbstractCarPart(): 30 | "The Abstract Car Part" 31 | @property 32 | def name(self): 33 | "a name for the part" 34 | return self._name 35 | 36 | @name.setter 37 | def name(self, value): 38 | self._name = value 39 | 40 | @property 41 | def sku(self): 42 | "The Stock Keeping Unit (sku)" 43 | return self._sku 44 | 45 | @sku.setter 46 | def sku(self, value): 47 | self._sku = value 48 | 49 | @property 50 | def price(self): 51 | "The price per unit" 52 | return self._price 53 | 54 | @price.setter 55 | def price(self, value): 56 | self._price = value 57 | 58 | 59 | class Body(AbstractCarPart, IVisitable): 60 | "A part of the car" 61 | 62 | def __init__(self, name, sku, price): 63 | self.name = name 64 | self.sku = sku 65 | self.price = price 66 | 67 | def accept(self, visitor): 68 | visitor.visit(self) 69 | 70 | 71 | class Engine(AbstractCarPart, IVisitable): 72 | "A part of the car" 73 | 74 | def __init__(self, name, sku, price): 75 | self.name = name 76 | self.sku = sku 77 | self.price = price 78 | 79 | def accept(self, visitor): 80 | visitor.visit(self) 81 | 82 | 83 | class Wheel(AbstractCarPart, IVisitable): 84 | "A part of the car" 85 | 86 | def __init__(self, name, sku, price): 87 | self.name = name 88 | self.sku = sku 89 | self.price = price 90 | 91 | def accept(self, visitor): 92 | visitor.visit(self) 93 | 94 | 95 | class Car(AbstractCarPart, IVisitable): 96 | "A Car with parts" 97 | 98 | def __init__(self, name): 99 | self.name = name 100 | self._parts = [ 101 | Body("Utility", "ABC-123-21", 1001), 102 | Engine("V8 engine", "DEF-456-21", 2555), 103 | Wheel("FrontLeft", "GHI-789FL-21", 136), 104 | Wheel("FrontRight", "GHI-789FR-21", 136), 105 | Wheel("BackLeft", "GHI-789BL-21", 152), 106 | Wheel("BackRight", "GHI-789BR-21", 152), 107 | ] 108 | 109 | def accept(self, visitor): 110 | for parts in self._parts: 111 | parts.accept(visitor) 112 | visitor.visit(self) 113 | 114 | 115 | class PrintPartsVisitor(IVisitor): 116 | "Print out the part name and sku" 117 | @staticmethod 118 | def visit(element): 119 | if hasattr(element, 'sku'): 120 | print(f"{element.name}\t:{element.sku}".expandtabs(6)) 121 | 122 | 123 | class TotalPriceVisitor(IVisitor): 124 | "Print out the total cost of the parts in the car" 125 | total_price = 0 126 | 127 | @classmethod 128 | def visit(cls, element): 129 | if hasattr(element, 'price'): 130 | cls.total_price += element.price 131 | return cls.total_price 132 | 133 | 134 | # The Client 135 | CAR = Car("DeLorean") 136 | 137 | # Print out the part name and sku using the PrintPartsVisitor 138 | CAR.accept(PrintPartsVisitor()) 139 | 140 | # Calculate the total prince of the parts using the TotalPriceVisitor 141 | TOTAL_PRICE_VISITOR = TotalPriceVisitor() 142 | CAR.accept(TOTAL_PRICE_VISITOR) 143 | print(f"Total Price = {TOTAL_PRICE_VISITOR.total_price}") 144 | -------------------------------------------------------------------------------- /visitor/visitor_concept.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | # pylint: disable=arguments-differ 3 | "The Visitor Pattern Concept" 4 | from abc import ABCMeta, abstractmethod 5 | 6 | class IVisitor(metaclass=ABCMeta): 7 | "An interface that custom Visitors should implement" 8 | @staticmethod 9 | @abstractmethod 10 | def visit(element): 11 | "Visitors visit Elements/Objects within the application" 12 | 13 | class IVisitable(metaclass=ABCMeta): 14 | """ 15 | An interface the concrete objects should implement that allows 16 | the visitor to traverse a hierarchical structure of objects 17 | """ 18 | @staticmethod 19 | @abstractmethod 20 | def accept(visitor): 21 | """ 22 | The Visitor traverses and accesses each object through this 23 | method 24 | """ 25 | 26 | class Element(IVisitable): 27 | "An Object that can be part of any hierarchy" 28 | 29 | def __init__(self, name, value, parent=None): 30 | self.name = name 31 | self.value = value 32 | self.elements = set() 33 | if parent: 34 | parent.elements.add(self) 35 | 36 | def accept(self, visitor): 37 | "required by the Visitor that will traverse" 38 | for element in self.elements: 39 | element.accept(visitor) 40 | visitor.visit(self) 41 | 42 | # The Client 43 | # Creating an example object hierarchy. 44 | Element_A = Element("A", 101) 45 | Element_B = Element("B", 305, Element_A) 46 | Element_C = Element("C", 185, Element_A) 47 | Element_D = Element("D", -30, Element_B) 48 | 49 | # Now Rather than changing the Element class to support custom 50 | # operations, we can utilise the accept method that was 51 | # implemented in the Element class because of the addition of 52 | # the IVisitable interface 53 | 54 | class PrintElementNamesVisitor(IVisitor): 55 | "Create a visitor that prints the Element names" 56 | @staticmethod 57 | def visit(element): 58 | print(element.name) 59 | 60 | # Using the PrintElementNamesVisitor to traverse the object hierarchy 61 | Element_A.accept(PrintElementNamesVisitor) 62 | 63 | class CalculateElementTotalsVisitor(IVisitor): 64 | "Create a visitor that totals the Element values" 65 | total_value = 0 66 | 67 | @classmethod 68 | def visit(cls, element): 69 | cls.total_value += element.value 70 | return cls.total_value 71 | 72 | # Using the CalculateElementTotalsVisitor to traverse the 73 | # object hierarchy 74 | TOTAL = CalculateElementTotalsVisitor() 75 | Element_A.accept(CalculateElementTotalsVisitor) 76 | print(TOTAL.total_value) 77 | --------------------------------------------------------------------------------