├── .gitignore ├── README.md ├── behavioral ├── chain_of_responsibility │ ├── README.md │ ├── characters │ │ ├── __init__.py │ │ ├── creature.py │ │ ├── goblin.py │ │ └── goblin_king.py │ ├── client_app.py │ ├── game.py │ ├── query.py │ └── what_to_query_enum.py ├── command │ ├── README.md │ ├── bank_account.py │ ├── client_app.py │ ├── command.py │ └── commands │ │ ├── __init__.py │ │ ├── bank_account_command.py │ │ ├── composite_bank_account_command.py │ │ └── money_transfer_command.py ├── hw_jira_workflow │ ├── README.md │ ├── client_app.py │ ├── jira.py │ ├── jira_context.py │ ├── state.py │ └── state_impl │ │ ├── __init__.py │ │ ├── closed_state.py │ │ ├── in_progress_state.py │ │ ├── opened_state.py │ │ ├── resolved_state.py │ │ └── verified_state.py ├── interpreter │ ├── README.md │ ├── abstract_expression.py │ ├── client_app.py │ ├── expression_parser.py │ ├── expressions │ │ ├── __init__.py │ │ ├── add_expression.py │ │ ├── number_expression.py │ │ └── sub_expression.py │ └── variable.py ├── iterator │ ├── README.md │ ├── client_app.py │ ├── iterators │ │ ├── __init__.py │ │ ├── in_order_traversal_iterator.py │ │ ├── post_order_traversal_iterator.py │ │ └── pre_order_traversal_iterator.py │ └── node.py ├── mediator │ ├── README.md │ ├── client_app.py │ ├── mediator.py │ ├── participant.py │ └── room.py ├── memento │ ├── README.md │ ├── bank_account.py │ ├── client_app.py │ ├── command.py │ ├── command_impl │ │ └── bank_account_command.py │ ├── memento.py │ └── memento_impl │ │ └── bank_account_snapshot.py ├── observer │ ├── README.md │ ├── client_app.py │ ├── concrete_creatures │ │ ├── cat.py │ │ └── rat.py │ ├── creature.py │ └── game.py ├── state │ ├── README.md │ ├── client_app.py │ ├── lock_context.py │ ├── state.py │ └── state_impl │ │ ├── error_state.py │ │ ├── locked_state.py │ │ └── open_state.py ├── strategy │ ├── README.md │ ├── client_app.py │ ├── impl │ │ ├── __init__.py │ │ ├── ordinary_discriminant_strategy.py │ │ └── real_discriminant_strategy.py │ ├── quadratic_equation_solver.py │ └── strategy.py ├── template_method │ ├── README.md │ ├── card_game.py │ ├── client_app.py │ ├── creature.py │ └── impl │ │ ├── __init__.py │ │ ├── permanent_damage_card_game.py │ │ └── temporary_damage_card_game.py └── visitor │ ├── README.md │ ├── alternative_python │ ├── client_app.py │ ├── expressions │ │ ├── __init__.py │ │ ├── addition_expr.py │ │ ├── multiplication_expr.py │ │ └── subtraction_expr.py │ ├── value.py │ ├── visitor.py │ └── visitor_impl │ │ ├── expr_evaluator.py │ │ └── expr_printer.py │ └── classic │ ├── client_app.py │ ├── component_expr.py │ ├── expressions │ ├── __init__.py │ ├── addition_expr.py │ ├── multiplication_expr.py │ ├── subtraction_expr.py │ └── value_expr.py │ ├── visitor.py │ └── visitor_impl │ ├── expr_evaluator.py │ └── expr_printer.py ├── creational ├── builder │ ├── README.md │ ├── builders │ │ └── code_builder.py │ ├── class_type.py │ ├── client_app.py │ └── field_type.py ├── factory │ ├── README.md │ ├── client_app.py │ ├── factories │ │ └── person_factory.py │ └── person.py ├── hw_pizza_station │ ├── README.md │ ├── abstract_factory │ │ ├── __init__.py │ │ ├── bakery_factory.py │ │ └── impl │ │ │ ├── dnipro_bakery.py │ │ │ ├── kyiv_bakery.py │ │ │ └── lviv_bakery.py │ ├── builders │ │ ├── __init__.py │ │ ├── impl │ │ │ ├── cheese_pizza_builder.py │ │ │ ├── meat_pizza_builder.py │ │ │ ├── pepperoni_pizza_builder.py │ │ │ └── veggie_pizza_builder.py │ │ └── pizza_builder.py │ ├── client_app.py │ ├── enums │ │ ├── pizza_bakery.py │ │ ├── pizza_components.py │ │ └── pizza_type.py │ └── pizza.py ├── prototype │ ├── README.md │ ├── client_app.py │ ├── line.py │ └── point.py └── singleton │ ├── README.md │ ├── client_app.py │ └── subjects │ ├── connection.py │ └── singleton.py └── structural ├── adapter ├── README.md ├── adapters │ └── square_to_rectangle_adapter.py ├── client_app.py ├── rectangle.py └── square.py ├── bridge ├── README.md ├── abstractions │ ├── __init__.py │ ├── shape_abstraction.py │ ├── square_abstraction.py │ └── triangle_abstraction.py ├── client_app.py └── impl │ ├── __init__.py │ ├── raster_renderer_impl.py │ ├── renderer_impl.py │ └── vector_renderer_impl.py ├── composite ├── README.md ├── client_app.py └── components │ ├── __init__.py │ ├── many_values.py │ ├── single_value.py │ └── value.py ├── decorator ├── README.md ├── client_app.py ├── components │ ├── __init__.py │ ├── circle.py │ ├── shape.py │ └── square.py └── decorators │ ├── __init__.py │ ├── colored_shape.py │ └── decorator.py ├── facade ├── README.md ├── client_app.py ├── magic_square_generator.py └── subsystems │ ├── __init__.py │ ├── generator.py │ ├── splitter.py │ └── verifier.py ├── flyweight ├── README.md ├── client_app.py ├── sentence.py └── word_formatting.py └── proxy ├── README.md ├── client_app.py └── impl ├── __init__.py ├── person.py ├── proxy_person.py └── real_person.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # PyCharm 132 | .idea/ 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Design Patterns in Python 2 | Implementation of different Design Patterns according to the tasks from the 'Design Patterns in Python' course 3 | https://www.udemy.com/course/design-patterns-python/ 4 | -------------------------------------------------------------------------------- /behavioral/chain_of_responsibility/README.md: -------------------------------------------------------------------------------- 1 | # Chain of Responsibility (CoR) pattern using Python 2 | Implementation of Chain of Responsibility Design Pattern according to the task from 'Design Patterns in Python' course 3 | https://www.udemy.com/course/design-patterns-python/ 4 | 5 | # Task 6 | You are given a game scenario with classes Goblin and GoblinKing. Please implement the following rules: 7 | * A goblin has base 1 attack/1 defense (1/1), a goblin king is 3/3. 8 | * When the Goblin King is in play, every other goblin gets +1 Attack. 9 | * Goblins get +1 to Defense for every other Goblin in play (a GoblinKing is a Goblin!). 10 | 11 | Example: 12 | * Suppose you have 3 ordinary goblins in play. Each one is a 1/3 (1/1 + 0/2 defense bonus) 13 | * A goblin king comes into play. Now every goblin is a 2/4 (1/1 + 0/3 defense bonus from each other + 1/0 from goblin king) 14 | 15 | The state of all the goblins has to be consistent as goblins are added and removed from the game. 16 | -------------------------------------------------------------------------------- /behavioral/chain_of_responsibility/characters/__init__.py: -------------------------------------------------------------------------------- 1 | from behavioral.chain_of_responsibility.characters.creature import Creature 2 | from behavioral.chain_of_responsibility.characters.goblin import Goblin 3 | from behavioral.chain_of_responsibility.characters.goblin_king import GoblinKing 4 | -------------------------------------------------------------------------------- /behavioral/chain_of_responsibility/characters/creature.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from abc import ABC, abstractmethod 3 | from typing import TYPE_CHECKING 4 | 5 | if TYPE_CHECKING: 6 | from behavioral.chain_of_responsibility.game import Game 7 | from behavioral.chain_of_responsibility.query import Query 8 | 9 | 10 | class Creature(ABC): 11 | """ 12 | Class represents default creature. 13 | """ 14 | def __init__(self, game: Game, name: str, attack: int, defense: int) -> None: 15 | self.game = game 16 | self.name = name 17 | self.initial_defense = defense 18 | self.initial_attack = attack 19 | 20 | @property 21 | @abstractmethod 22 | def attack(self) -> int: 23 | pass 24 | 25 | @property 26 | @abstractmethod 27 | def defense(self) -> int: 28 | pass 29 | 30 | @abstractmethod 31 | def query(self, source: Creature, query: Query) -> None: 32 | pass 33 | 34 | def __str__(self) -> str: 35 | return f'{self.__class__.__name__} named {self.name} ({self.attack}/{self.defense})' 36 | -------------------------------------------------------------------------------- /behavioral/chain_of_responsibility/characters/goblin.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import TYPE_CHECKING 3 | 4 | from behavioral.chain_of_responsibility.query import Query 5 | from behavioral.chain_of_responsibility.what_to_query_enum import WhatToQuery 6 | from .creature import Creature 7 | 8 | if TYPE_CHECKING: 9 | from behavioral.chain_of_responsibility.game import Game 10 | 11 | 12 | class Goblin(Creature): 13 | """ 14 | Class represents Goblin. 15 | """ 16 | def __init__(self, game: Game, name: str, attack: int = 1, defense: int = 1) -> None: 17 | super().__init__(game, name, attack, defense) 18 | 19 | @property 20 | def attack(self) -> int: 21 | q = Query(self.initial_attack, WhatToQuery.ATTACK) 22 | self.game.apply_query(self, q) 23 | return q.value 24 | 25 | @property 26 | def defense(self) -> int: 27 | q = Query(self.initial_defense, WhatToQuery.DEFENSE) 28 | self.game.apply_query(self, q) 29 | return q.value 30 | 31 | def query(self, source: Creature, query: Query) -> None: 32 | if self != source and query.what_to_query == WhatToQuery.DEFENSE: 33 | query.value += 1 34 | -------------------------------------------------------------------------------- /behavioral/chain_of_responsibility/characters/goblin_king.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import TYPE_CHECKING 3 | 4 | from behavioral.chain_of_responsibility.what_to_query_enum import WhatToQuery 5 | from .goblin import Goblin 6 | 7 | if TYPE_CHECKING: 8 | from .creature import Creature 9 | from behavioral.chain_of_responsibility.game import Game 10 | from behavioral.chain_of_responsibility.query import Query 11 | 12 | 13 | class GoblinKing(Goblin): 14 | """ 15 | Class represents Goblin King. 16 | """ 17 | def __init__(self, game: Game, name: str): 18 | super().__init__(game, name, 3, 3) 19 | 20 | def query(self, source: Creature, query: Query): 21 | if self != source and query.what_to_query == WhatToQuery.ATTACK: 22 | query.value += 1 23 | -------------------------------------------------------------------------------- /behavioral/chain_of_responsibility/client_app.py: -------------------------------------------------------------------------------- 1 | from behavioral.chain_of_responsibility.game import Game 2 | from behavioral.chain_of_responsibility.characters import Goblin, GoblinKing 3 | 4 | 5 | def main() -> None: 6 | """ 7 | Client app 8 | """ 9 | 10 | game = Game() 11 | goblin_alex = Goblin(game, 'Alex') 12 | game.creatures.add(goblin_alex) 13 | 14 | goblin_luca = Goblin(game, 'Luca') 15 | game.creatures.add(goblin_luca) 16 | print("There are characters in the Game:") 17 | print(goblin_alex) 18 | print(goblin_luca) 19 | 20 | goblin_unused = Goblin(game, 'Unused') # Note: we don't add this creature to the Game 21 | game.creatures.add(goblin_alex) # Note: we tried to add this creature to the Game twice 22 | print("\nThere are characters in the Game after some modifications (Nothing should be changed):") 23 | print(goblin_alex) 24 | print(goblin_luca) 25 | 26 | goblin_arthur = GoblinKing(game, 'Arthur') 27 | game.creatures.add(goblin_arthur) 28 | 29 | print("\nThere are characters in the Game after adding the King Goblin:") 30 | print(goblin_alex) 31 | print(goblin_luca) 32 | print(goblin_arthur) 33 | 34 | game.creatures.remove(goblin_luca) 35 | print("\nThere are characters in the Game after removing one of them:") 36 | print(goblin_alex) 37 | print(f'{goblin_luca} - Note: characteristics are default as it is not in the Game now') 38 | print(goblin_arthur) 39 | 40 | print("\nThere is our other unused character, as it is not in the game, his characteristics are default:") 41 | print(goblin_unused) 42 | 43 | 44 | if __name__ == '__main__': 45 | main() 46 | -------------------------------------------------------------------------------- /behavioral/chain_of_responsibility/game.py: -------------------------------------------------------------------------------- 1 | from behavioral.chain_of_responsibility.query import Query 2 | from behavioral.chain_of_responsibility.characters.creature import Creature 3 | 4 | 5 | class Game: 6 | """ 7 | The class is responsible for containing all creatures and applying queries to them. 8 | """ 9 | def __init__(self) -> None: 10 | self.creatures = set() 11 | 12 | def apply_query(self, given_creature: Creature, query: Query) -> None: 13 | if given_creature in self.creatures: 14 | for creature in self.creatures: 15 | creature.query(given_creature, query) 16 | -------------------------------------------------------------------------------- /behavioral/chain_of_responsibility/query.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from behavioral.chain_of_responsibility.what_to_query_enum import WhatToQuery 4 | 5 | 6 | class Query: 7 | """ 8 | The class is responsible for containing possible fields of query. 9 | """ 10 | def __init__(self, default_value: Any, what_to_query: WhatToQuery) -> None: 11 | self.value = default_value 12 | self.what_to_query = what_to_query 13 | -------------------------------------------------------------------------------- /behavioral/chain_of_responsibility/what_to_query_enum.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class WhatToQuery(Enum): 5 | """ 6 | Enum is responsible for containing possible types of characteristics. 7 | """ 8 | ATTACK = 1 9 | DEFENSE = 2 10 | -------------------------------------------------------------------------------- /behavioral/command/README.md: -------------------------------------------------------------------------------- 1 | # Command pattern using Python 2 | Implementation of Command Design Pattern according to the task from 'Design Patterns in Python' course 3 | https://www.udemy.com/course/design-patterns-python/ 4 | -------------------------------------------------------------------------------- /behavioral/command/bank_account.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class BankAccount: 5 | """ 6 | The BankAccount class contains some important business logic. They know how to 7 | perform all kinds of operations (like deposit/withdraw). 8 | """ 9 | class Action(Enum): 10 | """ 11 | Enum is responsible for containing possible types of actions on bank account. 12 | """ 13 | DEPOSIT = 0 14 | WITHDRAW = 1 15 | 16 | OVERDRAFT_LIMIT = -500 17 | 18 | def __init__(self, balance: int = 0) -> None: 19 | self.balance = balance 20 | 21 | def deposit(self, amount: int) -> bool: 22 | self.balance += amount 23 | print(f'Deposited {amount}, balance = {self.balance}') 24 | return True 25 | 26 | def withdraw(self, amount: int) -> bool: 27 | successful = False 28 | if self.balance - amount >= BankAccount.OVERDRAFT_LIMIT: 29 | self.balance -= amount 30 | print(f'Withdrew {amount}, balance = {self.balance}') 31 | successful = True 32 | 33 | return successful 34 | 35 | def __str__(self) -> str: 36 | return f'Balance = {self.balance}' 37 | -------------------------------------------------------------------------------- /behavioral/command/client_app.py: -------------------------------------------------------------------------------- 1 | from behavioral.command.bank_account import BankAccount 2 | from behavioral.command.commands import BankAccountCommand, CompositeBankAccountCommand, MoneyTransferCommand 3 | 4 | 5 | def main() -> None: 6 | """ 7 | Client app 8 | """ 9 | # Example 1: You have an account with zero balance, then add 1000 twice using the composite command. 10 | # Try to undo the command and check the results. 11 | ba = BankAccount() 12 | 13 | deposit1 = BankAccountCommand(ba, BankAccount.Action.DEPOSIT, 1000) 14 | deposit2 = BankAccountCommand(ba, BankAccount.Action.DEPOSIT, 1000) 15 | composite = CompositeBankAccountCommand([deposit1, deposit2]) 16 | 17 | composite.execute() 18 | print(ba) 19 | composite.undo() 20 | print(ba) 21 | print('='*50) 22 | 23 | # Example 2: You have two accounts, try to transfer the money from one to another account using composite command. 24 | # Try to undo the command and check the results. 25 | ba1 = BankAccount(100) 26 | ba2 = BankAccount() 27 | amount = 1000 # no transactions should happen, but second is happened while do execute() 28 | 29 | wc = BankAccountCommand(ba1, BankAccount.Action.WITHDRAW, amount) 30 | dc = BankAccountCommand(ba2, BankAccount.Action.DEPOSIT, amount) 31 | transfer = CompositeBankAccountCommand([wc, dc]) 32 | 33 | transfer.execute() 34 | print(f'BankAccount 1: {ba1}\nBankAccount 2: {ba2}') 35 | transfer.undo() 36 | print(f'BankAccount 1: {ba1}\nBankAccount 2: {ba2}') 37 | print('='*50) 38 | 39 | # Example 3: You have two accounts, try to transfer the money from one to another account using transfer command. 40 | # Try to undo the command and check the results. This time problems from previous example were handled correctly. 41 | ba1 = BankAccount(100) 42 | ba2 = BankAccount() 43 | amount = 1000 44 | 45 | transfer = MoneyTransferCommand(ba1, ba2, amount) 46 | 47 | transfer.execute() 48 | print(f'BankAccount 1: {ba1}\nBankAccount 2: {ba2}') 49 | transfer.undo() 50 | print(f'BankAccount 1: {ba1}\nBankAccount 2: {ba2}') 51 | print(transfer.success) 52 | 53 | 54 | if __name__ == '__main__': 55 | main() 56 | -------------------------------------------------------------------------------- /behavioral/command/command.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Command(ABC): 5 | """ 6 | The Command interface declares methods for executing commands. 7 | """ 8 | def __init__(self) -> None: 9 | self.success = False 10 | 11 | @abstractmethod 12 | def execute(self) -> None: 13 | pass 14 | 15 | @abstractmethod 16 | def undo(self) -> None: 17 | pass 18 | -------------------------------------------------------------------------------- /behavioral/command/commands/__init__.py: -------------------------------------------------------------------------------- 1 | from behavioral.command.commands.bank_account_command import BankAccountCommand 2 | from behavioral.command.commands.composite_bank_account_command import CompositeBankAccountCommand 3 | from behavioral.command.commands.money_transfer_command import MoneyTransferCommand 4 | -------------------------------------------------------------------------------- /behavioral/command/commands/bank_account_command.py: -------------------------------------------------------------------------------- 1 | from behavioral.command.bank_account import BankAccount 2 | from behavioral.command.command import Command 3 | 4 | 5 | class BankAccountCommand(Command): 6 | """ 7 | The BankAccountCommand class implements methods of Command interface in order to interact with BankAccount. 8 | """ 9 | def __init__(self, account: BankAccount, action: BankAccount.Action, amount: int) -> None: 10 | super().__init__() 11 | self.amount = amount 12 | self.action = action 13 | self.account = account 14 | 15 | def execute(self) -> None: 16 | if self.action == BankAccount.Action.DEPOSIT: 17 | self.success = self.account.deposit(self.amount) 18 | elif self.action == BankAccount.Action.WITHDRAW: 19 | self.success = self.account.withdraw(self.amount) 20 | 21 | def undo(self) -> None: 22 | if self.success: 23 | # frankly speaking this is not correct to undo a deposit by withdrawing, but I used it to simplify 24 | if self.action == BankAccount.Action.DEPOSIT: 25 | self.account.withdraw(self.amount) 26 | elif self.action == BankAccount.Action.WITHDRAW: 27 | self.account.deposit(self.amount) 28 | -------------------------------------------------------------------------------- /behavioral/command/commands/composite_bank_account_command.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from behavioral.command.command import Command 4 | from behavioral.command.commands.bank_account_command import BankAccountCommand 5 | 6 | 7 | class CompositeBankAccountCommand(Command, list): 8 | """ 9 | The CompositeBankAccountCommand class implements a mechanism to execute several commands in a row. 10 | """ 11 | def __init__(self, commands: List[BankAccountCommand]) -> None: 12 | super().__init__() 13 | for command in commands: 14 | self.append(command) 15 | 16 | def execute(self) -> None: 17 | for command in self: 18 | command.execute() 19 | 20 | def undo(self) -> None: 21 | for command in reversed(self): 22 | command.undo() 23 | -------------------------------------------------------------------------------- /behavioral/command/commands/money_transfer_command.py: -------------------------------------------------------------------------------- 1 | from behavioral.command.bank_account import BankAccount 2 | from behavioral.command.commands.bank_account_command import BankAccountCommand 3 | from behavioral.command.commands.composite_bank_account_command import CompositeBankAccountCommand 4 | 5 | 6 | class MoneyTransferCommand(CompositeBankAccountCommand): 7 | """ 8 | The MoneyTransferCommand class implements a mechanism to save money transfer 9 | between different accounts based on CompositeBankAccountCommand class. 10 | """ 11 | def __init__(self, from_acct: BankAccount, to_acct: BankAccount, amount: int) -> None: 12 | super().__init__([ 13 | BankAccountCommand(from_acct, 14 | BankAccount.Action.WITHDRAW, 15 | amount), 16 | BankAccountCommand(to_acct, 17 | BankAccount.Action.DEPOSIT, 18 | amount)]) 19 | 20 | def execute(self) -> None: 21 | ok = True 22 | for command in self: 23 | if ok: 24 | command.execute() 25 | ok = command.success 26 | else: 27 | command.success = False 28 | self.success = ok 29 | -------------------------------------------------------------------------------- /behavioral/hw_jira_workflow/README.md: -------------------------------------------------------------------------------- 1 | # HomeWork task 'JIRA Workflow': State pattern using Python 2 | 3 | This task roughly implements the workflow of the JIRA in the issue-tracking system. 4 | 5 | * From the 'Opened' state it can be transferred to 'In Progress' or 'Resolved' 6 | * From the 'In Progress' state it can be transferred to 'Opened' or 'Resolved' 7 | * From the 'Resolved' state it can be transferred to 'Opened' or 'Verified' 8 | * From the 'Verified' state it can be transferred to 'Opened' or 'Closed' 9 | * From the 'Closed' state it can be transferred to 'Opened' 10 | 11 | Other state transitions are prohibited. 12 | -------------------------------------------------------------------------------- /behavioral/hw_jira_workflow/client_app.py: -------------------------------------------------------------------------------- 1 | from behavioral.hw_jira_workflow.jira import Jira 2 | from behavioral.hw_jira_workflow.jira_context import JiraContext 3 | 4 | 5 | def main() -> None: 6 | """ 7 | Client app 8 | """ 9 | key_menu: str = "" 10 | jira_context: JiraContext = JiraContext(Jira("WPP-111", "Junior")) 11 | menu_actions = { 12 | "1": jira_context.start_work_on_jira, 13 | "2": jira_context.stop_work_on_jira, 14 | "3": jira_context.resolve_jira, 15 | "4": jira_context.reopen_jira, 16 | "5": jira_context.verify_jira, 17 | "6": jira_context.close_jira, 18 | "7": jira_context.change_assignee, 19 | "8": jira_context.print_jira, 20 | } 21 | 22 | while key_menu != "Q": 23 | print("\n 1 - Start Progress on JIRA" + " 2 - Stop Progress on JIRA ") 24 | print(" 3 - Resolve JIRA " + " 4 - Reopen JIRA ") 25 | print(" 5 - Verify JIRA " + " 6 - Close JIRA ") 26 | print(" 7 - Change assignee" + " 8 - Print JIRA details") 27 | print(" Q - exit") 28 | print("\n Please, select menu point.") 29 | 30 | key_menu = str(input()).upper() 31 | if (action := menu_actions.get(key_menu)) is not None: 32 | if action == jira_context.change_assignee: 33 | assignee_name = input("Enter assignee name (e.g. Junior, Middle, Senior, Manager): ") 34 | action(assignee_name) 35 | else: 36 | action() 37 | 38 | 39 | if __name__ == '__main__': 40 | main() 41 | -------------------------------------------------------------------------------- /behavioral/hw_jira_workflow/jira.py: -------------------------------------------------------------------------------- 1 | class Jira: 2 | """ 3 | Contains some of the important components of the Jira. 4 | """ 5 | def __init__(self, key: str, reporter: str) -> None: 6 | self.key = key 7 | self.reporter = reporter 8 | self.assignee = "" 9 | 10 | def __str__(self) -> str: 11 | return f"Jira: '{self.key}' - reporter is '{self.reporter}', assignee is " \ 12 | f"'{self.assignee if self.assignee != '' else 'Unassigned'}')" 13 | 14 | def __repr__(self) -> str: 15 | return f"Jira: {self.key})" 16 | -------------------------------------------------------------------------------- /behavioral/hw_jira_workflow/jira_context.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from behavioral.hw_jira_workflow.state import State 4 | from behavioral.hw_jira_workflow.jira import Jira 5 | from behavioral.hw_jira_workflow.state_impl.opened_state import OpenedState 6 | 7 | 8 | class JiraContext: 9 | """ 10 | The Context defines the interface of interest to clients. 11 | It also maintains a reference to an instance of a State subclass, which represents the current state of the Context. 12 | The Context forces the State to respond to user input instead of itself. 13 | The reaction may be different depending on which State is currently active. 14 | """ 15 | 16 | def __init__(self, jira: Jira) -> None: 17 | self.__jira = jira 18 | self.__state = OpenedState() 19 | 20 | def set_state(self, state: State) -> None: 21 | self.__state = state 22 | 23 | def start_work_on_jira(self) -> None: 24 | self.__state.start_progress(self) 25 | 26 | def stop_work_on_jira(self) -> None: 27 | self.__state.stop_progress(self) 28 | 29 | def resolve_jira(self) -> None: 30 | self.__state.resolve(self) 31 | 32 | def reopen_jira(self) -> None: 33 | self.__state.reopen(self) 34 | 35 | def verify_jira(self) -> None: 36 | self.__state.verify(self) 37 | 38 | def close_jira(self) -> None: 39 | self.__state.close(self) 40 | 41 | def change_assignee(self, assignee_name: str) -> None: 42 | self.__jira.assignee = assignee_name 43 | 44 | def get_reporter(self) -> str: 45 | return self.__jira.reporter 46 | 47 | def get_assignee(self) -> str: 48 | return self.__jira.assignee 49 | 50 | def print_jira(self) -> None: 51 | print(self.__jira) 52 | -------------------------------------------------------------------------------- /behavioral/hw_jira_workflow/state.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from abc import ABC 3 | from typing import TYPE_CHECKING 4 | 5 | if TYPE_CHECKING: 6 | from jira_context import JiraContext 7 | 8 | 9 | class State(ABC): 10 | """ 11 | The base State class declares methods that all Concrete State should implement. 12 | We create default for all methods realisation. 13 | """ 14 | 15 | def start_progress(self, context: JiraContext) -> None: 16 | print("start progress command - is not allowed!") 17 | 18 | def stop_progress(self, context: JiraContext) -> None: 19 | print("stop progress command - is not allowed!") 20 | 21 | def resolve(self, context: JiraContext) -> None: 22 | print("resolve command - is not allowed!") 23 | 24 | def reopen(self, context: JiraContext) -> None: 25 | print("reopen command - is not allowed!") 26 | 27 | def verify(self, context: JiraContext) -> None: 28 | print("verify command - is not allowed!") 29 | 30 | def close(self, context: JiraContext) -> None: 31 | print("close command - is not allowed!") 32 | -------------------------------------------------------------------------------- /behavioral/hw_jira_workflow/state_impl/__init__.py: -------------------------------------------------------------------------------- 1 | from behavioral.hw_jira_workflow.state_impl.closed_state import ClosedState 2 | from behavioral.hw_jira_workflow.state_impl.in_progress_state import InProgressState 3 | from behavioral.hw_jira_workflow.state_impl.opened_state import OpenedState 4 | from behavioral.hw_jira_workflow.state_impl.resolved_state import ResolvedState 5 | from behavioral.hw_jira_workflow.state_impl.verified_state import VerifiedState 6 | -------------------------------------------------------------------------------- /behavioral/hw_jira_workflow/state_impl/closed_state.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from behavioral.hw_jira_workflow.state import State 6 | 7 | if TYPE_CHECKING: 8 | from behavioral.hw_jira_workflow.jira_context import JiraContext 9 | 10 | 11 | class ClosedState(State): 12 | """ 13 | Described 'Closed' state of JIRA. 14 | """ 15 | 16 | def reopen(self, jira_context: JiraContext) -> None: 17 | jira_context.change_assignee("") 18 | 19 | from . import OpenedState 20 | jira_context.set_state(OpenedState()) 21 | print("JIRA state changed from 'Resolved' to 'Opened' state") -------------------------------------------------------------------------------- /behavioral/hw_jira_workflow/state_impl/in_progress_state.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from behavioral.hw_jira_workflow.state import State 6 | 7 | if TYPE_CHECKING: 8 | from behavioral.hw_jira_workflow.jira_context import JiraContext 9 | 10 | 11 | class InProgressState(State): 12 | """ 13 | Described 'In Progress' state of JIRA 14 | """ 15 | 16 | def stop_progress(self, jira_context: JiraContext) -> None: 17 | from . import OpenedState 18 | jira_context.set_state(OpenedState()) 19 | print("JIRA state changed from 'In Progress' to 'Opened' state") 20 | 21 | def resolve(self, jira_context: JiraContext) -> None: 22 | jira_context.change_assignee(jira_context.get_reporter()) 23 | 24 | from . import ResolvedState 25 | jira_context.set_state(ResolvedState()) 26 | print("JIRA state changed from 'In Progress' to 'Resolved' state") 27 | -------------------------------------------------------------------------------- /behavioral/hw_jira_workflow/state_impl/opened_state.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from behavioral.hw_jira_workflow.state import State 6 | 7 | if TYPE_CHECKING: 8 | from behavioral.hw_jira_workflow.jira_context import JiraContext 9 | 10 | 11 | class OpenedState(State): 12 | """ 13 | Described 'Opened' state of JIRA 14 | """ 15 | 16 | def start_progress(self, jira_context: JiraContext) -> None: 17 | from . import InProgressState 18 | jira_context.set_state(InProgressState()) 19 | print("JIRA state changed from 'Opened' to 'In Progress' state") 20 | 21 | def resolve(self, jira_context: JiraContext) -> None: 22 | jira_context.change_assignee(jira_context.get_reporter()) 23 | 24 | from . import ResolvedState 25 | jira_context.set_state(ResolvedState()) 26 | print("JIRA state changed from 'Opened' to 'Resolved' state") 27 | -------------------------------------------------------------------------------- /behavioral/hw_jira_workflow/state_impl/resolved_state.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from behavioral.hw_jira_workflow.state import State 6 | 7 | if TYPE_CHECKING: 8 | from behavioral.hw_jira_workflow.jira_context import JiraContext 9 | 10 | 11 | class ResolvedState(State): 12 | """ 13 | Described 'Resolved' state of JIRA. 14 | """ 15 | 16 | def reopen(self, jira_context: JiraContext) -> None: 17 | jira_context.change_assignee("") 18 | 19 | from . import OpenedState 20 | jira_context.set_state(OpenedState()) 21 | print("JIRA state changed from 'Resolved' to 'Opened' state") 22 | 23 | def verify(self, jira_context: JiraContext) -> None: 24 | if jira_context.get_assignee().lower() in (jira_context.get_reporter().lower(), 'senior'): 25 | jira_context.change_assignee("Manager") 26 | 27 | from . import VerifiedState 28 | jira_context.set_state(VerifiedState()) 29 | print("JIRA state changed from 'Resolved' to 'Verified' state") 30 | else: 31 | print("Only reporter or any senior can verify JIRA. Please change assignee!") 32 | 33 | -------------------------------------------------------------------------------- /behavioral/hw_jira_workflow/state_impl/verified_state.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from behavioral.hw_jira_workflow.state import State 6 | 7 | if TYPE_CHECKING: 8 | from behavioral.hw_jira_workflow.jira_context import JiraContext 9 | 10 | 11 | class VerifiedState(State): 12 | """ 13 | Described 'Verified' state of JIRA. 14 | """ 15 | 16 | def reopen(self, jira_context: JiraContext) -> None: 17 | jira_context.change_assignee("") 18 | 19 | from . import OpenedState 20 | jira_context.set_state(OpenedState()) 21 | print("JIRA state changed from 'Verified' to 'Opened' state") 22 | 23 | def close(self, jira_context: JiraContext) -> None: 24 | if jira_context.get_assignee().lower() == 'manager': 25 | 26 | from . import ClosedState 27 | jira_context.set_state(ClosedState()) 28 | print("JIRA state changed from 'Verified' to 'Closed' state") 29 | else: 30 | print("Only manager can close JIRA. Please change assignee!") 31 | -------------------------------------------------------------------------------- /behavioral/interpreter/README.md: -------------------------------------------------------------------------------- 1 | # Interpreter pattern using Python 2 | Implementation of Interpreter Design Pattern according to the task from 'Design Patterns in Python' course 3 | https://www.udemy.com/course/design-patterns-python/ 4 | 5 | I made the task more difficult in order to follow a full structure of Interpreter Design Pattern. 6 | 7 | # Task 8 | Calculate the expression 9 | Rules: 10 | * Numbers could be an only integer 11 | * Supported operators: +, - 12 | * Braces not supported 13 | * Variables could be defined only as single-letter (e.g. 'x') 14 | * If a variable is used, but not defined use default value - 0 15 | * In case of parsing failure - return error message 16 | -------------------------------------------------------------------------------- /behavioral/interpreter/abstract_expression.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class AbstractExpression(ABC): 5 | """ 6 | Declare an abstract Interpret operation that is common to all nodes 7 | in the abstract syntax tree. 8 | """ 9 | @abstractmethod 10 | def interpret(self) -> int: 11 | pass 12 | -------------------------------------------------------------------------------- /behavioral/interpreter/client_app.py: -------------------------------------------------------------------------------- 1 | from behavioral.interpreter.expression_parser import ExpressionParser 2 | from behavioral.interpreter.variable import Variable 3 | 4 | 5 | def main() -> None: 6 | """ 7 | Client app 8 | """ 9 | Variable.names['x'] = 5 10 | Variable.names['y'] = 2 11 | 12 | expression = "x+9 - 13 + z + 7-y" 13 | print("x=5\ny=2") 14 | print(f'Expression: {expression}') 15 | try: 16 | expr_parser = ExpressionParser(expression) 17 | 18 | abstract_syntax_tree = expr_parser.build_ast() 19 | ast_root = abstract_syntax_tree.pop() 20 | print(f'Result: {ast_root.interpret()}') 21 | 22 | except ValueError as err: 23 | print(f'Error: {err}') 24 | 25 | 26 | if __name__ == '__main__': 27 | main() 28 | -------------------------------------------------------------------------------- /behavioral/interpreter/expression_parser.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | import re 3 | from typing import List, Union 4 | 5 | from behavioral.interpreter.variable import Variable 6 | from behavioral.interpreter.abstract_expression import AbstractExpression 7 | from behavioral.interpreter.expressions import NumberExpression, AddExpression, SubExpression 8 | 9 | 10 | class ExpressionParser: 11 | """ 12 | Class is used to parse an expression and add the ability to automatically create 13 | an Abstract Syntax Tree for future processing by the Interpreter. 14 | """ 15 | 16 | def __init__(self, expression: str) -> None: 17 | self.expression = expression 18 | 19 | def _parse(self) -> List[Union[NumberExpression, str]]: 20 | result = [] 21 | i = 0 22 | non_whitespace_expr = self.expression.replace(" ", "") 23 | 24 | while i < len(non_whitespace_expr): 25 | char = non_whitespace_expr[i] 26 | if not re.search(r"[0-9a-zA-Z+-]", char): 27 | raise ValueError("Invalid symbols in the expression. " 28 | "Supported only integer numbers, single-letter variables, '+' and '-'!") 29 | elif char == '+': 30 | result.append('+') 31 | elif char == '-': 32 | result.append('-') 33 | elif char.isalpha(): 34 | # if var is not defined - use 0, otherwise use defined value 35 | var_value = Variable.names.get(char) if Variable.names.get(char) else 0 36 | result.append(NumberExpression(var_value)) 37 | if i < len(non_whitespace_expr) - 1: 38 | next_char = non_whitespace_expr[i + 1] 39 | if next_char.isalpha(): 40 | raise ValueError("Supported only single-letter variables") 41 | else: # Must be a number 42 | digits = [char] 43 | for j in range(i + 1, len(non_whitespace_expr)): 44 | if non_whitespace_expr[j].isdigit(): 45 | digits.append(non_whitespace_expr[j]) 46 | i += 1 47 | else: 48 | number = int(''.join(digits)) 49 | result.append(NumberExpression(number)) 50 | break 51 | else: 52 | result.append(NumberExpression(int(''.join(digits)))) 53 | 54 | i += 1 55 | 56 | return result 57 | 58 | def build_ast(self) -> List[AbstractExpression]: 59 | tokens = self._parse() 60 | ast: List[AbstractExpression] = [] 61 | 62 | if tokens is not None: 63 | ast_head = 0 64 | sign_index = 1 65 | 66 | for i in tokens[1:-1:2]: 67 | if i == '+': 68 | ast.append( 69 | AddExpression(ast[ast_head-1] if len(ast) else tokens[sign_index - 1], tokens[sign_index + 1]) 70 | ) 71 | else: 72 | ast.append( 73 | SubExpression(ast[ast_head-1], tokens[sign_index + 1]) 74 | ) 75 | 76 | ast_head += 1 77 | sign_index += 2 78 | 79 | else: 80 | if not len(ast): 81 | ast.append(tokens[sign_index - 1]) 82 | 83 | return ast 84 | -------------------------------------------------------------------------------- /behavioral/interpreter/expressions/__init__.py: -------------------------------------------------------------------------------- 1 | from behavioral.interpreter.expressions.number_expression import NumberExpression 2 | from behavioral.interpreter.expressions.add_expression import AddExpression 3 | from behavioral.interpreter.expressions.sub_expression import SubExpression 4 | -------------------------------------------------------------------------------- /behavioral/interpreter/expressions/add_expression.py: -------------------------------------------------------------------------------- 1 | from behavioral.interpreter.abstract_expression import AbstractExpression 2 | 3 | 4 | class AddExpression(AbstractExpression): 5 | """ 6 | Non-Terminal Expression. 7 | Implement an Interpret operation for non-terminal symbols in the grammar. Add in this particular case. 8 | """ 9 | def __init__(self, left: AbstractExpression, right: AbstractExpression) -> None: 10 | self.left = left 11 | self.right = right 12 | 13 | def __str__(self) -> str: 14 | return f"({self.left} ADD {self.right})" 15 | 16 | def interpret(self) -> int: 17 | return self.left.interpret() + self.right.interpret() 18 | -------------------------------------------------------------------------------- /behavioral/interpreter/expressions/number_expression.py: -------------------------------------------------------------------------------- 1 | from behavioral.interpreter.abstract_expression import AbstractExpression 2 | 3 | 4 | class NumberExpression(AbstractExpression): 5 | """ 6 | Terminal Expression. 7 | Implement an interpret operation associated with terminal symbols in 8 | the grammar. 9 | """ 10 | def __init__(self, value: int) -> None: 11 | self.value = value 12 | 13 | def __str__(self) -> str: 14 | return f"{self.value}" 15 | 16 | def interpret(self) -> int: 17 | return self.value 18 | -------------------------------------------------------------------------------- /behavioral/interpreter/expressions/sub_expression.py: -------------------------------------------------------------------------------- 1 | from behavioral.interpreter.abstract_expression import AbstractExpression 2 | 3 | 4 | class SubExpression(AbstractExpression): 5 | """ 6 | Non-Terminal Expression. 7 | Implement an Interpret operation for non-terminal symbols in the grammar. Subtract in this particular case. 8 | """ 9 | def __init__(self, left: AbstractExpression, right: AbstractExpression) -> None: 10 | self.left = left 11 | self.right = right 12 | 13 | def __str__(self) -> str: 14 | return f"({self.left} SUB {self.right})" 15 | 16 | def interpret(self) -> int: 17 | return self.left.interpret() - self.right.interpret() 18 | -------------------------------------------------------------------------------- /behavioral/interpreter/variable.py: -------------------------------------------------------------------------------- 1 | class Variable: 2 | """ 3 | Class contains dictionary that stores variables nad their values. 4 | """ 5 | names = {} 6 | -------------------------------------------------------------------------------- /behavioral/iterator/README.md: -------------------------------------------------------------------------------- 1 | # Iterator pattern using Python 2 | Implementation of Iterator Design Pattern according to the task from 'Design Patterns in Python' course 3 | https://www.udemy.com/course/design-patterns-python/ 4 | 5 | I extended the task in order to make it more difficult and follow the full structure of the Iterator Design Pattern. 6 | -------------------------------------------------------------------------------- /behavioral/iterator/client_app.py: -------------------------------------------------------------------------------- 1 | from behavioral.iterator.node import Node 2 | 3 | 4 | def main() -> None: 5 | """ 6 | Client app 7 | """ 8 | # 0 9 | # / \ 10 | # / \ 11 | # / \ 12 | # 1 2 13 | # / \ / \ 14 | # 3 4 5 6 15 | # \ \ / 16 | # 7 8 9 17 | 18 | # in-order: 3147058296 19 | # preorder: 0134725869 20 | # postorder: 3741085962 21 | 22 | root = Node(0, 23 | Node(1, Node(3), Node(4, None, Node(7))), 24 | Node(2, Node(5, None, Node(8)), Node(6, Node(9))), 25 | ) 26 | print("In-order (default) traversal of tree: ", end='') 27 | for node in root: 28 | print(node.value, end='') 29 | 30 | print("\nPre-order traversal of tree: ", end='') 31 | pre_order_traversal = root.get_pre_order_traversal_iterator() 32 | for node in pre_order_traversal: 33 | print(node.value, end='') 34 | 35 | # This Iterator is not properly implemented. Just to show the capabilities of Iterator Design pattern 36 | print("\nPost-order traversal of tree: ", end='') 37 | post_order_traversal = root.get_post_order_traversal_iterator() 38 | for node in post_order_traversal: 39 | print(node.value, end='') 40 | 41 | 42 | if __name__ == '__main__': 43 | main() 44 | -------------------------------------------------------------------------------- /behavioral/iterator/iterators/__init__.py: -------------------------------------------------------------------------------- 1 | from behavioral.iterator.iterators.in_order_traversal_iterator import InOrderTraversalIterator 2 | from behavioral.iterator.iterators.pre_order_traversal_iterator import PreOrderTraversalIterator 3 | from behavioral.iterator.iterators.post_order_traversal_iterator import PostOrderTraversalIterator 4 | -------------------------------------------------------------------------------- /behavioral/iterator/iterators/in_order_traversal_iterator.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from collections.abc import Iterator 3 | from typing import TYPE_CHECKING 4 | 5 | if TYPE_CHECKING: 6 | from behavioral.iterator.node import Node 7 | 8 | 9 | class InOrderTraversalIterator(Iterator): 10 | """ 11 | Concrete Iterators implement various traversal algorithms. These classes 12 | store the current traversal position at all times. 13 | This class contains a complete implementation of In-Order Traverse iteration. 14 | """ 15 | 16 | # `_current` attribute stores the current traversal position. 17 | _current: Node = None 18 | 19 | def __init__(self, root: Node) -> None: 20 | self.root = self._current = root 21 | self.yielded_start = False 22 | while self._current.left: 23 | self._current = self._current.left 24 | 25 | def __next__(self) -> Node: 26 | if not self.yielded_start: 27 | self.yielded_start = True 28 | return self._current 29 | 30 | if self._current.right: 31 | self._current = self._current.right 32 | while self._current.left: 33 | self._current = self._current.left 34 | return self._current 35 | else: 36 | p = self._current.parent 37 | while p and self._current == p.right: 38 | self._current = p 39 | p = p.parent 40 | self._current = p 41 | if self._current: 42 | return self._current 43 | else: 44 | raise StopIteration 45 | -------------------------------------------------------------------------------- /behavioral/iterator/iterators/post_order_traversal_iterator.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from collections.abc import Iterator 3 | from typing import TYPE_CHECKING 4 | 5 | if TYPE_CHECKING: 6 | from behavioral.iterator.node import Node 7 | 8 | 9 | class PostOrderTraversalIterator(Iterator): 10 | """ 11 | Concrete Iterators implement various traversal algorithms. 12 | This class is not implemented properly. 13 | It is just a placeholder to show capabilities and example of applying Iterator Design pattern. 14 | """ 15 | 16 | # `_current` attribute stores the current traversal position. 17 | _current = None 18 | 19 | def __init__(self, root: Node) -> None: 20 | self.root = root 21 | self.root = '3741085962' 22 | 23 | def __next__(self) -> Node: 24 | if self.root != '': 25 | self._current = self.root[0] 26 | self.root = self.root[1:] 27 | 28 | from behavioral.iterator.node import Node # Workaround due to a circular import 29 | return Node(self._current) 30 | else: 31 | raise StopIteration 32 | -------------------------------------------------------------------------------- /behavioral/iterator/iterators/pre_order_traversal_iterator.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from collections.abc import Iterator 3 | from typing import TYPE_CHECKING 4 | 5 | if TYPE_CHECKING: 6 | from behavioral.iterator.node import Node 7 | 8 | 9 | class PreOrderTraversalIterator(Iterator): 10 | """ 11 | Concrete Iterators implement various traversal algorithms. These classes 12 | store the current traversal position at all times. 13 | This class contains a complete implementation of Pre-Order Traverse iteration. 14 | """ 15 | 16 | # `_current` attribute stores the current traversal position. 17 | _current: Node = None 18 | 19 | def __init__(self, root: Node) -> None: 20 | self.root = self._current = root 21 | self.yielded_start = False 22 | self.next_time_exit = False 23 | 24 | def __next__(self) -> Node: 25 | if not self.yielded_start: 26 | self.yielded_start = True 27 | return self._current 28 | 29 | if self._current.left: 30 | self._current = self._current.left 31 | return self._current 32 | elif self._current.right: 33 | if self._current.left: 34 | self._current = self._current.left 35 | elif self._current.right: 36 | self._current = self._current.right 37 | return self._current 38 | 39 | else: 40 | p = self._current.parent 41 | if p != self.root: 42 | if self._current == p.left: 43 | if p.right: 44 | self._current = p.right 45 | return self._current 46 | 47 | else: 48 | while not p == self.root: 49 | p = p.parent 50 | if p.right and self._current not in [child for child in p.right if child is not None]: 51 | self._current = p.right 52 | return self._current 53 | 54 | if not self.next_time_exit: 55 | self.next_time_exit = True 56 | 57 | if p.right: 58 | self._current = p.right 59 | return self._current 60 | 61 | raise StopIteration 62 | -------------------------------------------------------------------------------- /behavioral/iterator/node.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from collections.abc import Iterable, Iterator 3 | from typing import Optional 4 | 5 | from iterators import InOrderTraversalIterator, PreOrderTraversalIterator, PostOrderTraversalIterator 6 | 7 | 8 | class Node(Iterable): 9 | """ 10 | Compound collection of nodes. Each Node can contain left/right or both Nodes inside. 11 | """ 12 | def __init__(self, value: int, left: Optional[Node] = None, right: Optional[Node] = None): 13 | self.right = right 14 | self.left = left 15 | self.value = value 16 | 17 | self.parent = None 18 | 19 | if left: 20 | self.left.parent = self 21 | if right: 22 | self.right.parent = self 23 | 24 | def __iter__(self) -> Iterator: 25 | """ 26 | The __iter__() method returns the iterator object itself, by default we 27 | return the in-order traversal iterator. 28 | """ 29 | return InOrderTraversalIterator(self) 30 | 31 | def get_pre_order_traversal_iterator(self) -> Iterator: 32 | return PreOrderTraversalIterator(self) 33 | 34 | def get_post_order_traversal_iterator(self) -> Iterator: 35 | return PostOrderTraversalIterator(self) 36 | 37 | def __str__(self): 38 | return f"Node({self.value})" 39 | -------------------------------------------------------------------------------- /behavioral/mediator/README.md: -------------------------------------------------------------------------------- 1 | # Mediator pattern using Python 2 | Implementation of Mediator Design Pattern according to the task from 'Design Patterns in Python' course 3 | https://www.udemy.com/course/design-patterns-python/ 4 | 5 | #### This task was implemented using the events. 6 | Rules: 7 | * Each participant in the room can increase the points of each participant in this room except itself. 8 | * Each participant in the room can decrease the points of each participant in this room except itself. 9 | * Each participant knows nothing about other participants. 10 | -------------------------------------------------------------------------------- /behavioral/mediator/client_app.py: -------------------------------------------------------------------------------- 1 | from participant import Participant 2 | from room import Room 3 | 4 | 5 | def main() -> None: 6 | """ 7 | Client app 8 | """ 9 | room = Room() 10 | tim = Participant('Tim', room) 11 | alex = Participant('Alex', room) 12 | 13 | print(f"Tim have {tim.points} point(s).\nAlex have {alex.points} point(s).") 14 | 15 | # Tim increases points of each room participant on 2 16 | tim.increase(2) 17 | tony = Participant('Tony', room) 18 | 19 | print(f"\nTim have {tim.points} point(s).\nAlex have {alex.points} point(s).\nTony have {tony.points} point(s).") 20 | 21 | # Alex decreases points of each room participant on 4 22 | alex.decrease(4) 23 | 24 | print(f"\nTim have {tim.points} point(s).\nAlex have {alex.points} point(s).\nTony have {tony.points} point(s).") 25 | 26 | # Tony increases points of each room participant on 8 27 | tony.increase(8) 28 | print(f"\nTim have {tim.points} point(s).\nAlex have {alex.points} point(s).\nTony have {tony.points} point(s).") 29 | 30 | 31 | if __name__ == '__main__': 32 | main() 33 | -------------------------------------------------------------------------------- /behavioral/mediator/mediator.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Any, Callable 3 | 4 | 5 | class Mediator(ABC): 6 | """ 7 | The Mediator interface declares a method used by components to notify the 8 | mediator about various events. The Mediator may react to these events and 9 | pass the execution to other components. 10 | """ 11 | @abstractmethod 12 | def notify(self, sender: object, event: Callable, value: Any) -> None: 13 | pass 14 | -------------------------------------------------------------------------------- /behavioral/mediator/participant.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Callable, TYPE_CHECKING 3 | 4 | if TYPE_CHECKING: 5 | from behavioral.mediator.room import Room 6 | 7 | 8 | class Participant: 9 | """ 10 | Regular person in our room. 11 | Particular participant knows nothing about other participants and cannot contact any of them directly 12 | """ 13 | def __init__(self, name: str, room: Room) -> None: 14 | self.name = name 15 | self.points = 0 16 | self.room = room 17 | room.alert.append(self.mediator_alert) 18 | 19 | def mediator_alert(self, sender: Participant, event: Callable, value: int) -> None: 20 | if sender != self: 21 | if event.__name__ == self.increase.__name__: 22 | self.points += value 23 | if event.__name__ == self.decrease.__name__: 24 | self.points -= value 25 | 26 | def increase(self, value: int) -> None: 27 | self.room.notify(self, self.increase, value) 28 | 29 | def decrease(self, value: int) -> None: 30 | self.room.notify(self, self.decrease, value) 31 | -------------------------------------------------------------------------------- /behavioral/mediator/room.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Any, Callable, TYPE_CHECKING 3 | 4 | from mediator import Mediator 5 | if TYPE_CHECKING: 6 | from participant import Participant 7 | 8 | 9 | class Event(list): 10 | def __call__(self, *args, **kwargs): 11 | for item in self: 12 | item(*args, **kwargs) 13 | 14 | 15 | class Room(Mediator): 16 | """ 17 | The Room class implements notify method of Mediator interface. 18 | In this case method send an event to all participants with reference to this Concrete Mediator. 19 | """ 20 | def __init__(self) -> None: 21 | self.alert = Event() 22 | 23 | def notify(self, sender: Participant, event: Callable, value: Any) -> None: 24 | self.alert(sender, event, value) 25 | -------------------------------------------------------------------------------- /behavioral/memento/README.md: -------------------------------------------------------------------------------- 1 | # Memento pattern using Python 2 | Implementation of Memento Design Pattern according to the task from 'Design Patterns in Python' course 3 | https://www.udemy.com/course/design-patterns-python/ 4 | 5 | Implementation of this task is based on Memento and Command and design patterns. 6 | 7 | Users can create a bank account with some balance on it. There is a default overdraft limit: -500. 8 | 9 | Users can create deposit or withdrawal transactions. 10 | Using Memento pattern was implemented correct implementation of 'Undo' operation. 11 | -------------------------------------------------------------------------------- /behavioral/memento/bank_account.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | from behavioral.memento.memento_impl.bank_account_snapshot import BankAccountSnapshot 4 | 5 | 6 | class BankAccount: 7 | """ 8 | The BankAccount class contains some important business logic. They know how to 9 | perform all kinds of operations (like deposit/withdraw). 10 | """ 11 | class Action(Enum): 12 | """ 13 | Enum is responsible for containing possible types of actions on bank account. 14 | """ 15 | DEPOSIT = 0 16 | WITHDRAW = 1 17 | 18 | OVERDRAFT_LIMIT = -500 19 | 20 | def __init__(self, balance: int = 0) -> None: 21 | self._balance = balance 22 | 23 | def _set_balance(self, balance: int) -> None: 24 | self._balance = balance 25 | 26 | def deposit(self, amount: int) -> bool: 27 | self._balance += amount 28 | print(f'Deposited {amount}, balance = {self._balance}') 29 | return True 30 | 31 | def withdraw(self, amount: int) -> bool: 32 | successful = False 33 | if self._balance - amount >= BankAccount.OVERDRAFT_LIMIT: 34 | self._balance -= amount 35 | print(f'Withdrew {amount}, balance = {self._balance}') 36 | successful = True 37 | 38 | return successful 39 | 40 | def save(self) -> BankAccountSnapshot: 41 | return BankAccountSnapshot(self, self._balance) 42 | 43 | def __str__(self) -> str: 44 | return f'Balance = {self._balance}' 45 | -------------------------------------------------------------------------------- /behavioral/memento/client_app.py: -------------------------------------------------------------------------------- 1 | from behavioral.memento.bank_account import BankAccount 2 | from behavioral.memento.command_impl.bank_account_command import BankAccountCommand 3 | 4 | 5 | def main() -> None: 6 | """ 7 | Client app 8 | """ 9 | ba = BankAccount() 10 | print(f"Init value: {ba}, Overdraft limit = {ba.OVERDRAFT_LIMIT}\n") 11 | deposit = BankAccountCommand(ba, BankAccount.Action.DEPOSIT, 1000) 12 | print("Make 1000$ deposit twice.") 13 | deposit.execute() 14 | deposit.execute() 15 | print(f"Result: {ba}\n") 16 | print("Undo a deposit once.") 17 | deposit.undo() 18 | print(f"Result: {ba}\n") 19 | 20 | print("Undo a deposit. This time twice twice.") 21 | deposit.undo() 22 | print(f"Result after first undo: {ba}") 23 | deposit.undo() 24 | print(f"Result after second undo: {ba}\n") 25 | 26 | print("Make 1000$ deposit once.") 27 | deposit.execute() 28 | print(f"Result after deposit: {ba}\n") 29 | 30 | wc = BankAccountCommand(ba, BankAccount.Action.WITHDRAW, 1000) 31 | print("Make 1000$ withdrawal once.") 32 | wc.execute() 33 | print(f"Result after withdrawal: {ba}\n") 34 | print("Undo a withdrawal. This time twice twice.") 35 | wc.undo() 36 | print(f"Result after first undo: {ba}") 37 | wc.undo() 38 | print(f"Result after second undo: {ba}\n") 39 | print('='*50) 40 | 41 | 42 | if __name__ == '__main__': 43 | main() 44 | -------------------------------------------------------------------------------- /behavioral/memento/command.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Command(ABC): 5 | """ 6 | The Command interface declares methods for executing commands. 7 | """ 8 | def __init__(self) -> None: 9 | self._snapshots = [] 10 | 11 | @abstractmethod 12 | def execute(self) -> None: 13 | pass 14 | 15 | @abstractmethod 16 | def undo(self) -> None: 17 | pass 18 | -------------------------------------------------------------------------------- /behavioral/memento/command_impl/bank_account_command.py: -------------------------------------------------------------------------------- 1 | from behavioral.memento.bank_account import BankAccount 2 | from behavioral.memento.command import Command 3 | 4 | 5 | class BankAccountCommand(Command): 6 | """ 7 | The BankAccountCommand class implements methods of Command interface in order to interact with BankAccount. 8 | """ 9 | def __init__(self, account: BankAccount, action: BankAccount.Action, amount: int) -> None: 10 | super().__init__() 11 | self._account = account 12 | self.action = action 13 | self.amount = amount 14 | 15 | def execute(self) -> None: 16 | self._backup() 17 | success = False 18 | if self.action == BankAccount.Action.DEPOSIT: 19 | success = self._account.deposit(self.amount) 20 | elif self.action == BankAccount.Action.WITHDRAW: 21 | success = self._account.withdraw(self.amount) 22 | if not success: 23 | self.undo() 24 | 25 | def undo(self) -> None: 26 | if not len(self._snapshots): 27 | print(f" " 28 | f"Nothing to undo") 29 | return 30 | snapshot = self._snapshots.pop() 31 | snapshot.restore() 32 | print(f" " 33 | f"Restored snapshot: {snapshot.get_name()}") 34 | 35 | def _backup(self) -> None: 36 | snapshot = self._account.save() 37 | print(f" " 38 | f"Saved snapshot: {snapshot.get_name()}") 39 | 40 | self._snapshots.append(snapshot) 41 | -------------------------------------------------------------------------------- /behavioral/memento/memento.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Memento(ABC): 5 | """ 6 | The Memento interface provides a way to retrieve the memento's metadata, 7 | such as creation date or name. However, it doesn't expose the Originator's 8 | state. 9 | """ 10 | 11 | @abstractmethod 12 | def get_name(self) -> str: 13 | pass 14 | 15 | @abstractmethod 16 | def get_date(self) -> str: 17 | pass 18 | -------------------------------------------------------------------------------- /behavioral/memento/memento_impl/bank_account_snapshot.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from datetime import datetime 3 | from typing import TYPE_CHECKING 4 | 5 | from memento import Memento 6 | 7 | if TYPE_CHECKING: 8 | from behavioral.memento.bank_account import BankAccount 9 | 10 | 11 | class BankAccountSnapshot(Memento): 12 | """ 13 | Concrete implementation of Memento class in order to save/restore BankAccount balance. 14 | """ 15 | def __init__(self, bank_account: BankAccount, balance: int) -> None: 16 | self._bank_account = bank_account 17 | self._balance = balance 18 | self._date = str(datetime.now())[:19] 19 | 20 | def get_name(self) -> str: 21 | return f"{self._date} / BankAccount(balance={self._balance})" 22 | 23 | def get_date(self) -> str: 24 | return self._date 25 | 26 | def restore(self) -> None: 27 | self._bank_account._set_balance(self._balance) 28 | -------------------------------------------------------------------------------- /behavioral/observer/README.md: -------------------------------------------------------------------------------- 1 | # Observer pattern using Python 2 | Implementation of Observer Design Pattern according to the task from 'Design Patterns in Python' course 3 | https://www.udemy.com/course/design-patterns-python/ 4 | 5 | The base task from udemy was extended. 6 | I've added another creature - Cat and highlighted the abstract base class where implemented Context Manager. 7 | 8 | Rules: 9 | * When Cat enters the Game it add the attack for all other cats on 2 points. 10 | * When Cat leaves the Game it subtracts 2 points on all cats in the Game. 11 | * When Rat enters the Game it add the attack for all other rats on 1 point. 12 | * When Rat leaves the Game it subtracts 1 point on all rats in the Game. 13 | -------------------------------------------------------------------------------- /behavioral/observer/client_app.py: -------------------------------------------------------------------------------- 1 | from game import Game 2 | from behavioral.observer.concrete_creatures.rat import Rat 3 | from behavioral.observer.concrete_creatures.cat import Cat 4 | 5 | 6 | def main() -> None: 7 | """ 8 | Client app 9 | """ 10 | game = Game() 11 | cat = Cat(game) 12 | rat = Rat(game) 13 | 14 | print(f"Cat attack={cat.attack}") 15 | print(f"Rat attack={rat.attack}\n") 16 | 17 | rat2 = Rat(game) 18 | print(f"Cat attack={cat.attack}") 19 | print(f"Rat attack={rat.attack}") 20 | print(f"Rat2 attack={rat2.attack}\n") 21 | 22 | with Rat(game) as rat3: 23 | print(f"Cat attack={cat.attack}") 24 | print(f"Rat attack={rat.attack}") 25 | print(f"Rat2 attack={rat2.attack}") 26 | print(f"Rat3 attack={rat3.attack}\n") 27 | with Cat(game) as cat2: 28 | print(f"Cat attack={cat.attack}") 29 | print(f"Cat2 attack={cat2.attack}") 30 | print(f"Rat attack={rat.attack}") 31 | print(f"Rat2 attack={rat2.attack}") 32 | print(f"Rat3 attack={rat3.attack}\n") 33 | print(f"Cat attack={cat.attack}") 34 | print(f"Rat attack={rat.attack}") 35 | print(f"Rat2 attack={rat2.attack}") 36 | print(f"Rat3 attack={rat3.attack}\n") 37 | 38 | print(f"Cat attack={cat.attack}") 39 | print(f"Rat attack={rat.attack}") 40 | print(f"Rat2 attack={rat2.attack}") 41 | 42 | 43 | if __name__ == '__main__': 44 | main() 45 | -------------------------------------------------------------------------------- /behavioral/observer/concrete_creatures/cat.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import TYPE_CHECKING 3 | 4 | from behavioral.observer.creature import Creature 5 | 6 | if TYPE_CHECKING: 7 | from behavioral.observer.game import Game 8 | 9 | 10 | class Cat(Creature): 11 | """ 12 | Concrete creature in the Game - Cat. 13 | When Cat enters the Game it add the attack for all other cats on 2 points. 14 | When Cat leaves the Game it subtracts 2 points on all cats in the Game. 15 | """ 16 | def __init__(self, game: Game) -> None: 17 | super().__init__(game) 18 | self.attack = 2 19 | 20 | game.enters.append(self.subscribe) 21 | game.notify.append(self.notify) 22 | game.dies.append(self.unsubscribe) 23 | 24 | self.game.enters(self) 25 | 26 | def subscribe(self, cat: Cat) -> None: 27 | if isinstance(cat, Cat) and cat != self: 28 | self.attack += 2 29 | self.game.notify(cat) 30 | 31 | def notify(self, cat: Cat) -> None: 32 | if cat == self: 33 | self.attack += 2 34 | 35 | def unsubscribe(self, cat: Cat) -> None: 36 | if isinstance(cat, Cat) and cat != self: 37 | self.attack -= 2 38 | -------------------------------------------------------------------------------- /behavioral/observer/concrete_creatures/rat.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import TYPE_CHECKING 3 | 4 | from behavioral.observer.creature import Creature 5 | 6 | if TYPE_CHECKING: 7 | from behavioral.observer.game import Game 8 | 9 | 10 | class Rat(Creature): 11 | """ 12 | Concrete creature in the Game - Rat. 13 | When Rat enters the Game it add the attack for all other rats on 1 point. 14 | When Rat leaves the Game it subtracts 1 point on all rats in the Game. 15 | """ 16 | 17 | def __init__(self, game: Game) -> None: 18 | super().__init__(game) 19 | self.attack = 1 20 | 21 | game.enters.append(self.subscribe) 22 | game.notify.append(self.notify) 23 | game.dies.append(self.unsubscribe) 24 | 25 | self.game.enters(self) 26 | 27 | def subscribe(self, rat: Rat) -> None: 28 | if isinstance(rat, Rat) and rat != self: 29 | self.attack += 1 30 | self.game.notify(rat) 31 | 32 | def notify(self, rat: Rat) -> None: 33 | if rat == self: 34 | self.attack += 1 35 | 36 | def unsubscribe(self, rat: Rat) -> None: 37 | if isinstance(rat, Rat): 38 | self.attack -= 1 39 | -------------------------------------------------------------------------------- /behavioral/observer/creature.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from abc import ABC, abstractmethod 3 | from typing import TYPE_CHECKING 4 | 5 | if TYPE_CHECKING: 6 | from game import Game 7 | 8 | 9 | class Creature(ABC): 10 | """ 11 | Base abstract class of any creature in the game. Here we are implementing a Context Manager as a Class. 12 | Added abstract methods to implement Event Manager logic of Observer design pattern. 13 | """ 14 | def __init__(self, game: Game) -> None: 15 | self.game = game 16 | 17 | def __enter__(self) -> Creature: 18 | return self 19 | 20 | def __exit__(self, exc_type, exc_val, exc_tb) -> None: 21 | self.game.dies(self) 22 | 23 | @abstractmethod 24 | def subscribe(self, creature: Creature) -> None: 25 | pass 26 | 27 | @abstractmethod 28 | def notify(self, creature: Creature) -> None: 29 | pass 30 | 31 | @abstractmethod 32 | def unsubscribe(self, creature: Creature) -> None: 33 | pass 34 | -------------------------------------------------------------------------------- /behavioral/observer/game.py: -------------------------------------------------------------------------------- 1 | class Event(list): 2 | def __call__(self, *args, **kwargs): 3 | for item in self: 4 | item(*args, **kwargs) 5 | 6 | 7 | class Game: 8 | """ 9 | Game class stores events of entering or leaving creatures from the game. 10 | Also stores notifying events for other creatures about that. 11 | """ 12 | def __init__(self): 13 | self.enters = Event() 14 | self.dies = Event() 15 | self.notify = Event() 16 | -------------------------------------------------------------------------------- /behavioral/state/README.md: -------------------------------------------------------------------------------- 1 | # State pattern using Python 2 | Implementation of State Design Pattern according to the task from 'Design Patterns in Python' course 3 | https://www.udemy.com/course/design-patterns-python/ 4 | 5 | The base task from udemy was extended. 6 | Task implemented using classic State pattern. 7 | 8 | States: 9 | * Locked 10 | * Open 11 | * Error 12 | 13 | User enters digits one by one for the password and immediately gets a response whether the sequence is correct or must be reset. 14 | -------------------------------------------------------------------------------- /behavioral/state/client_app.py: -------------------------------------------------------------------------------- 1 | from behavioral.state.lock_context import LockContext 2 | 3 | 4 | def main() -> None: 5 | """ 6 | Client app 7 | """ 8 | key_menu: str = "" 9 | password = [1, 2, 3, 4] 10 | context: LockContext = LockContext(password) 11 | 12 | while key_menu != "Q": 13 | print(" 1 - Enter digit") 14 | print(" 2 - Reset trial pass") 15 | print(" 3 - Set new password") 16 | print(" Q - exit") 17 | print(" Please, select menu point.") 18 | key_menu = str(input()).upper() 19 | 20 | if key_menu == "1": 21 | digit = int(input("Enter your digit: ")) 22 | context.enter_digit(digit) 23 | elif key_menu == "2": 24 | context.reset_lock() 25 | elif key_menu == "3": 26 | new_pass = [] 27 | length = int(input("Enter length of password: ")) 28 | for digit in range(length): 29 | new_pass.append(int(input("Enter your digit: "))) 30 | 31 | context.set_new_password(new_pass) 32 | 33 | 34 | if __name__ == '__main__': 35 | main() 36 | -------------------------------------------------------------------------------- /behavioral/state/lock_context.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from behavioral.state.state import State 4 | from behavioral.state.state_impl.locked_state import LockedState 5 | 6 | 7 | class LockContext: 8 | """ 9 | The Context defines the interface of interest to clients. 10 | It also maintains a reference to an instance of a State subclass, which represents the current state of the Context. 11 | The Context forces the State to respond to user input instead of itself. 12 | The reaction may be different depending on which State is currently active. 13 | """ 14 | 15 | def __init__(self, combination: List[int]) -> None: 16 | self.__password = "".join(str(combination)) 17 | self.pass_length = len(self.__password) 18 | self.__state = LockedState() 19 | self.trial_password = '' 20 | 21 | def set_state(self, state: State) -> None: 22 | self.__state = state 23 | 24 | def enter_digit(self, digit: int) -> None: 25 | self.__state.enter(self, digit) 26 | 27 | def reset_lock(self) -> None: 28 | self.__state.reset(self) 29 | 30 | def set_new_password(self, combination: List[int]) -> None: 31 | self.__state.set_password(self, combination) 32 | 33 | def is_password_matched(self) -> bool: 34 | is_matched = False 35 | if self.pass_length != len(self.trial_password): 36 | if self.__password.startswith(self.trial_password): 37 | is_matched = True 38 | else: 39 | if self.__password == self.trial_password: 40 | is_matched = True 41 | 42 | return is_matched 43 | 44 | def _update_pass(self, combination: List[int]) -> None: 45 | self.__password = "".join([str(c) for c in combination]) 46 | self.pass_length = len(self.__password) 47 | -------------------------------------------------------------------------------- /behavioral/state/state.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from abc import ABC, abstractmethod 3 | from typing import List, TYPE_CHECKING 4 | 5 | if TYPE_CHECKING: 6 | from lock_context import LockContext 7 | 8 | 9 | class State(ABC): 10 | """ 11 | The base State class declares methods that all Concrete State should implement. 12 | """ 13 | @abstractmethod 14 | def enter(self, context: LockContext, digit: int) -> None: 15 | pass 16 | 17 | @abstractmethod 18 | def reset(self, context: LockContext) -> None: 19 | pass 20 | 21 | @abstractmethod 22 | def set_password(self, context: LockContext, combination: List[int]) -> None: 23 | pass 24 | -------------------------------------------------------------------------------- /behavioral/state/state_impl/error_state.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import List, TYPE_CHECKING 3 | 4 | from behavioral.state.state import State 5 | 6 | if TYPE_CHECKING: 7 | from behavioral.state.lock_context import LockContext 8 | 9 | 10 | class ErrorState(State): 11 | """ 12 | Concrete States implement various behaviors, associated with a state of the Context. 13 | Concrete States themselves can transfer the context into other States. 14 | From ErrorState the Context could be moved only to LockedState. 15 | """ 16 | 17 | def enter(self, context: LockContext, digit: int) -> None: 18 | print("You cannot enter a new digit. You can only reset your entered combination as it is wrong.") 19 | 20 | def reset(self, context: LockContext) -> None: 21 | print("Reset of the entered combination is performed.") 22 | context.trial_password = '' 23 | from behavioral.state.state_impl.locked_state import LockedState 24 | context.set_state(LockedState()) 25 | 26 | def set_password(self, context: LockContext, combination: List[int]) -> None: 27 | print("You cannot set a new password. You can only reset your entered combination.") 28 | -------------------------------------------------------------------------------- /behavioral/state/state_impl/locked_state.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import List, TYPE_CHECKING 3 | 4 | from behavioral.state.state import State 5 | from behavioral.state.state_impl.error_state import ErrorState 6 | from behavioral.state.state_impl.open_state import OpenState 7 | 8 | if TYPE_CHECKING: 9 | from behavioral.state.lock_context import LockContext 10 | 11 | 12 | class LockedState(State): 13 | """ 14 | Concrete States implement various behaviors, associated with a state of the Context. 15 | Concrete States themselves can transfer the context into other States. 16 | From LockedState the Context could be moved to any other State. 17 | """ 18 | 19 | def enter(self, context: LockContext, digit: int) -> None: 20 | context.trial_password += f'{digit}' 21 | print(f"Enter {digit}. Entered passphrase: {context.trial_password}") 22 | print("Check if password correct...") 23 | if context.pass_length != len(context.trial_password): 24 | if not context.is_password_matched(): 25 | print("Password is not correct. Please reset entered combination.") 26 | context.set_state(ErrorState()) 27 | else: 28 | print("You have entered the correct digit! Move on and enter the next digit.") 29 | else: 30 | if context.is_password_matched(): 31 | print("Password is correct. Now safe is opened.") 32 | context.set_state(OpenState()) 33 | else: 34 | print("Password is not correct. Please reset entered combination.") 35 | context.set_state(ErrorState()) 36 | 37 | def reset(self, context: LockContext) -> None: 38 | print("Reset of the entered combination is performed.") 39 | context.trial_password = '' 40 | context.set_state(LockedState()) 41 | 42 | def set_password(self, context: LockContext, combination: List[int]) -> None: 43 | print("You cannot set a new password while safe is locked.") 44 | -------------------------------------------------------------------------------- /behavioral/state/state_impl/open_state.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import List, TYPE_CHECKING 3 | 4 | from behavioral.state.state import State 5 | 6 | 7 | if TYPE_CHECKING: 8 | from behavioral.state.lock_context import LockContext 9 | 10 | 11 | class OpenState(State): 12 | """ 13 | Concrete States implement various behaviors, associated with a state of the Context. 14 | Concrete States themselves can transfer the context into other States. 15 | From OpenState the Context could be moved only to LockedState if set new password. 16 | """ 17 | 18 | def enter(self, context: LockContext, digit: int) -> None: 19 | print("You cannot enter a new digit. You are already unlocked.") 20 | 21 | def reset(self, context: LockContext) -> None: 22 | print("You cannot reset entered combination. You are already unlocked.") 23 | 24 | def set_password(self, context: LockContext, combination: List[int]) -> None: 25 | print("Set new password and lock the safe.") 26 | context._update_pass(combination) 27 | 28 | from behavioral.state.state_impl.locked_state import LockedState 29 | context.set_state(LockedState()) 30 | -------------------------------------------------------------------------------- /behavioral/strategy/README.md: -------------------------------------------------------------------------------- 1 | # Strategy pattern using Python 2 | Implementation of Strategy Design Pattern according to the task from 'Design Patterns in Python' course 3 | https://www.udemy.com/course/design-patterns-python/ 4 | 5 | In this task, a quadratic equation solver was implemented. It always returns complex numbers as defined by the API. 6 | 7 | Users can follow two different strategies, Real or Ordinary Discriminant. 8 | 9 | In the case of using OrdinaryDiscriminantStrategy, the discriminant cannot be less than 0, so the discriminant result will be NaN (not a number). 10 | Hence, the results of solving of quadratic equation will have NaN in both real and imaginary parts. 11 | -------------------------------------------------------------------------------- /behavioral/strategy/client_app.py: -------------------------------------------------------------------------------- 1 | import cmath 2 | 3 | from behavioral.strategy.quadratic_equation_solver import QuadraticEquationSolver 4 | from behavioral.strategy.impl import RealDiscriminantStrategy, OrdinaryDiscriminantStrategy 5 | 6 | 7 | def main() -> None: 8 | """ 9 | Client app 10 | """ 11 | strategy = OrdinaryDiscriminantStrategy() 12 | solver = QuadraticEquationSolver(strategy) 13 | results = solver.solve(1, 10, 16) 14 | print(f"Results of 'x^2 + 10x + 16 = 0' equation using Ordinary Discriminant Strategy" 15 | f"should be equal to '{complex(-2, 0)}' and '{complex(-8, 0)}' complex numbers.") 16 | print(f"Actual results are: x1 = {results[0]}, x2 = {results[1]}\n") 17 | 18 | solver.strategy = RealDiscriminantStrategy() 19 | results = solver.solve(1, 10, 16) 20 | print(f"Results of 'x^2 + 10x + 16 = 0' equation using Real Discriminant Strategy" 21 | f"should be equal to '{complex(-2, 0)}' and '{complex(-8, 0)}' complex numbers.") 22 | print(f"Actual results are: x1 = {results[0]}, x2 = {results[1]}\n") 23 | 24 | solver.strategy = RealDiscriminantStrategy() 25 | results = solver.solve(1, 4, 5) 26 | print(f"Results of 'x^2 + 4x + 5 = 0' equation using Real Discriminant Strategy" 27 | f"should be equal to '{complex(-2, 1)}' and '{complex(-2, -1)}' complex numbers.") 28 | print(f"Actual results are: x1 = {results[0]}, x2 = {results[1]}\n") 29 | 30 | solver.strategy = OrdinaryDiscriminantStrategy() 31 | results = solver.solve(1, 4, 5) 32 | # Discriminant = 4*4 - 4*1*5 = -4 33 | # In case of using Ordinary Discriminant Strategy we return NAN 34 | # So the result imaginary and real part of complex number should be equal to NAN 35 | print(f"Results of 'x^2 + 4x + 5 = 0' equation using Ordinary Discriminant Strategy should be equal to " 36 | f"'{complex(cmath.nan, cmath.nan)}' and '{complex(cmath.nan, cmath.nan)}' complex numbers.") 37 | print(f"Actual results are: x1 = {results[0]}, x2 = {results[1]}\n") 38 | 39 | print(f"Real part of first result is NAN: {cmath.isnan(results[0].real)}") 40 | print(f"Real part of second result is NAN: {cmath.isnan(results[0].real)}") 41 | print(f"Imaginary part of first result is NAN: {cmath.isnan(results[0].real)}") 42 | print(f"Imaginary part of second result is NAN: {cmath.isnan(results[0].real)}") 43 | 44 | 45 | if __name__ == '__main__': 46 | main() 47 | -------------------------------------------------------------------------------- /behavioral/strategy/impl/__init__.py: -------------------------------------------------------------------------------- 1 | from behavioral.strategy.impl.real_discriminant_strategy import RealDiscriminantStrategy 2 | from behavioral.strategy.impl.ordinary_discriminant_strategy import OrdinaryDiscriminantStrategy 3 | -------------------------------------------------------------------------------- /behavioral/strategy/impl/ordinary_discriminant_strategy.py: -------------------------------------------------------------------------------- 1 | import cmath 2 | 3 | from behavioral.strategy.strategy import DiscriminantStrategy 4 | 5 | 6 | class OrdinaryDiscriminantStrategy(DiscriminantStrategy): 7 | """ 8 | Concrete Strategies implement the algorithm while following the base Strategy 9 | interface. The interface makes them interchangeable in the Context. 10 | 11 | If discriminant is negative, the return value is NaN (not a number)! 12 | """ 13 | def calculate_discriminant(self, a: int, b: int, c: int) -> float: 14 | discriminant = b*b - 4 * a * c 15 | if discriminant < 0: 16 | discriminant = cmath.nan 17 | return discriminant 18 | -------------------------------------------------------------------------------- /behavioral/strategy/impl/real_discriminant_strategy.py: -------------------------------------------------------------------------------- 1 | from behavioral.strategy.strategy import DiscriminantStrategy 2 | 3 | 4 | class RealDiscriminantStrategy(DiscriminantStrategy): 5 | """ 6 | Concrete Strategies implement the algorithm while following the base Strategy 7 | interface. The interface makes them interchangeable in the Context. 8 | 9 | If discriminant is negative, we return it as-is. 10 | """ 11 | def calculate_discriminant(self, a: int, b: int, c: int) -> int: 12 | discriminant = b*b - 4*a*c 13 | return discriminant 14 | -------------------------------------------------------------------------------- /behavioral/strategy/quadratic_equation_solver.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | import cmath 3 | 4 | from behavioral.strategy.strategy import DiscriminantStrategy 5 | 6 | 7 | class QuadraticEquationSolver: 8 | """ 9 | This is kind of Context class. It defines the interface of interest to clients. 10 | In our case it only solves the quadratic equation. 11 | """ 12 | def __init__(self, strategy: DiscriminantStrategy) -> None: 13 | self._strategy = strategy 14 | 15 | @property 16 | def strategy(self) -> DiscriminantStrategy: 17 | return self._strategy 18 | 19 | @strategy.setter 20 | def strategy(self, strategy: DiscriminantStrategy) -> None: 21 | self._strategy = strategy 22 | 23 | def solve(self, a: int, b: int, c: int) -> Tuple[complex, complex]: 24 | """ Returns a pair of complex (!) values """ 25 | sqrt_of_discriminant = cmath.sqrt(self._strategy.calculate_discriminant(a, b, c)) 26 | x1 = (-b + sqrt_of_discriminant) / (2 * a) 27 | x2 = (-b - sqrt_of_discriminant) / (2 * a) 28 | return x1, x2 29 | -------------------------------------------------------------------------------- /behavioral/strategy/strategy.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Union 3 | 4 | 5 | class DiscriminantStrategy(ABC): 6 | """ 7 | The Strategy interface declares operations common to all supported versions 8 | of some algorithm. 9 | 10 | The Context uses this interface to call the algorithm defined by concrete implementations of Strategy. 11 | """ 12 | 13 | @abstractmethod 14 | def calculate_discriminant(self, a: int, b: int, c: int) -> Union[int, float]: 15 | pass 16 | -------------------------------------------------------------------------------- /behavioral/template_method/README.md: -------------------------------------------------------------------------------- 1 | # Template Method pattern using Python 2 | Implementation of Template Method Design Pattern according to the task from 'Design Patterns in Python' course 3 | https://www.udemy.com/course/design-patterns-python/ 4 | 5 | 6 | In this task, a card game was implemented. There are two different types of logic of how damage is dealt - Temporary Damage and Permanent Damage. 7 | 8 | The general algorithm of the game is the same, but the damage is done differently. 9 | 10 | For TemporaryDamageCardGame unless the creature has been killed its health returns to the original value at the end of the combat. 11 | So, the creature can be killed only by one shot. 12 | 13 | For PermanentDamageCardGame in order to kill some creature, you can take several shots. 14 | Also in this type of Game Jokers card have some additional effects. 15 | -------------------------------------------------------------------------------- /behavioral/template_method/card_game.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import List, final 3 | 4 | from behavioral.template_method.creature import Creature 5 | 6 | 7 | class CardGame(ABC): 8 | """ 9 | The Abstract Class defines a template method (combat) that contains a skeleton of algorithm. 10 | CardGame implements the logic for two(our limitation) creatures fighting each other. 11 | However, the exact mechanics of how damage is dealt is different. 12 | """ 13 | def __init__(self, creatures: List[Creature]) -> None: 14 | self.creatures = creatures 15 | self.winner = None 16 | 17 | @final 18 | def combat(self, c1_index, c2_index) -> None: 19 | self._hit(self.creatures[c1_index], self.creatures[c2_index]) 20 | self._hit(self.creatures[c2_index], self.creatures[c1_index]) 21 | if self._is_game_with_jokers: 22 | self._joker_do(self.creatures[c1_index], self.creatures[c2_index]) 23 | self.__find_winner(self.creatures[c1_index], self.creatures[c2_index]) 24 | 25 | @abstractmethod 26 | def _hit(self, attacker: Creature, defender: Creature) -> None: 27 | pass 28 | 29 | def __find_winner(self, creature1: Creature, creature2: Creature) -> None: 30 | if creature1.health > 0 >= creature2.health: 31 | self.winner = creature1 32 | elif creature2.health > 0 >= creature1.health: 33 | self.winner = creature2 34 | elif creature1.health <= 0 and creature1.health <= 0: 35 | self.winner = -1 36 | 37 | @property 38 | def _is_game_with_jokers(self) -> bool: 39 | return False 40 | 41 | def _joker_do(self, creature1: Creature, creature2: Creature) -> None: 42 | """If you call me that you MUST TO override me""" 43 | raise ChildProcessError("The derived class must have an implementation") 44 | -------------------------------------------------------------------------------- /behavioral/template_method/client_app.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | from behavioral.template_method.creature import Creature 4 | from behavioral.template_method.card_game import CardGame 5 | from behavioral.template_method.impl import TemporaryDamageCardGame, PermanentDamageCardGame 6 | 7 | 8 | def main() -> None: 9 | """ 10 | Client app 11 | """ 12 | c1: Creature = Creature("Player_1", randint(1, 5), randint(3, 8)) 13 | c2: Creature = Creature("Player_2", randint(1, 5), randint(3, 8)) 14 | print(f"{c1} has {c1.attack} attack and {c1.health} health") 15 | print(f"{c2} has {c2.attack} attack and {c2.health} health") 16 | 17 | game: CardGame = PermanentDamageCardGame([c1, c2]) 18 | 19 | while game.winner is None: 20 | game.combat(0, 1) 21 | print(f"{c1} has {c1.health} health") 22 | print(f"{c2} has {c2.health} health") 23 | if game.winner != -1: 24 | print(f"Winner is {game.winner}") 25 | else: 26 | print("Draw") 27 | 28 | print(f"{'*'*40}") 29 | 30 | c1: Creature = Creature("Player_1", randint(1, 3), randint(1, 3)) 31 | c2: Creature = Creature("Player_2", randint(1, 3), randint(1, 3)) 32 | print(f"{c1} has {c1.attack} attack and {c1.health} health") 33 | print(f"{c2} has {c2.attack} attack and {c2.health} health") 34 | 35 | game: CardGame = TemporaryDamageCardGame([c1, c2]) 36 | game.combat(0, 1) 37 | 38 | print(f"{c1} has {c1.health} health") 39 | print(f"{c2} has {c2.health} health") 40 | if game.winner is not None and game.winner != -1: 41 | print(f"Winner is {game.winner}") 42 | else: 43 | print("Draw") 44 | 45 | print(f"{'*'*40}") 46 | 47 | c1: Creature = Creature("Player_1", randint(1, 5), randint(15, 20), is_joker=True) 48 | c2: Creature = Creature("Player_2", randint(1, 5), randint(15, 20), is_joker=False) 49 | print(f"{c1} has {c1.attack} attack and {c1.health} health") 50 | print(f"{c2} has {c2.attack} attack and {c2.health} health") 51 | 52 | game: CardGame = PermanentDamageCardGame([c1, c2]) 53 | 54 | while game.winner is None: 55 | game.combat(0, 1) 56 | print(f"{c1} has {c1.health} health") 57 | print(f"{c2} has {c2.health} health") 58 | if game.winner != -1: 59 | print(f"Winner is {game.winner}") 60 | else: 61 | print("Draw") 62 | 63 | 64 | if __name__ == '__main__': 65 | main() 66 | -------------------------------------------------------------------------------- /behavioral/template_method/creature.py: -------------------------------------------------------------------------------- 1 | class Creature: 2 | """ 3 | Component of the game. Actually it is a card with two main attributes such as attack and health. 4 | Creatures can fight each other, dealing their Attack damage, thereby reducing opponent's health. 5 | Some cards could be a Joker, this can somehow help in the game. 6 | """ 7 | def __init__(self, name: str, attack: int, health: int, is_joker: bool = False) -> None: 8 | self.name = name 9 | self.attack = attack 10 | self.health = health 11 | self.is_joker = is_joker 12 | 13 | def __str__(self) -> str: 14 | return f"{self.name} creature" 15 | -------------------------------------------------------------------------------- /behavioral/template_method/impl/__init__.py: -------------------------------------------------------------------------------- 1 | from behavioral.template_method.impl.temporary_damage_card_game import TemporaryDamageCardGame 2 | from behavioral.template_method.impl.permanent_damage_card_game import PermanentDamageCardGame 3 | -------------------------------------------------------------------------------- /behavioral/template_method/impl/permanent_damage_card_game.py: -------------------------------------------------------------------------------- 1 | from behavioral.template_method.card_game import CardGame 2 | from behavioral.template_method.creature import Creature 3 | 4 | 5 | class PermanentDamageCardGame(CardGame): 6 | """ 7 | This is a type of game (e.g. Hearthstone) where health damage persists. 8 | So, in order to kill some creature, you can take several shots. 9 | Also in this type of Game Jokers card have some additional effects. 10 | """ 11 | 12 | def _hit(self, attacker: Creature, defender: Creature) -> None: 13 | defender.health -= attacker.attack 14 | 15 | @property 16 | def _is_game_with_jokers(self) -> bool: 17 | return True 18 | 19 | def _joker_do(self, creature1: Creature, creature2: Creature) -> None: 20 | if creature1.is_joker and not creature2.is_joker: 21 | creature2.health -= 2 22 | print(f"Log: {creature2.name} health in decreased. {creature1.name} is Joker") 23 | elif creature2.is_joker and not creature1.is_joker: 24 | creature1.health -= 2 25 | print(f"Log: {creature1.name} health in decreased. {creature1.name} is Joker") 26 | elif creature1.is_joker and creature2.is_joker: 27 | creature1.health += 2 28 | creature2.health += 2 29 | print(f"Log: {creature1.name} and {creature1.name} health in increased. They are Jokers") 30 | -------------------------------------------------------------------------------- /behavioral/template_method/impl/temporary_damage_card_game.py: -------------------------------------------------------------------------------- 1 | from behavioral.template_method.card_game import CardGame 2 | from behavioral.template_method.creature import Creature 3 | 4 | 5 | class TemporaryDamageCardGame(CardGame): 6 | """ 7 | This is type of game (e.g. Magic: the Gathering), unless the creature has been killed, 8 | its health returns to the original value at the end of the combat. 9 | So, any creature can be killed only by one shot. 10 | """ 11 | def _hit(self, attacker: Creature, defender: Creature) -> None: 12 | if defender.health - attacker.attack <= 0: 13 | defender.health -= attacker.attack 14 | -------------------------------------------------------------------------------- /behavioral/visitor/README.md: -------------------------------------------------------------------------------- 1 | # Visitor pattern using Python 2 | Implementation of Visitor Design Pattern according to the task from 'Design Patterns in Python' course 3 | https://www.udemy.com/course/design-patterns-python/ 4 | 5 | The task is to implement two Visitors - the first prints the complicated expressions, the second evaluates the expressions. 6 | 7 | As components, we use different types of expressions: single-value, addition, subtraction, multiplication. They are built in a Composite way (one expression can contain other expressions, single-value is a leaf). 8 | Addition and subtraction are grouped in the parentheses for printing. 9 | 10 | 11 | This task was implemented in two different ways: 12 | * Using the classic approach described in https://refactoring.guru/design-patterns/composite 13 | * Using the alternative approach described in the lesson by using decorators with a class name as parameters. Library code is taken from https://tavianator.com/the-visitor-pattern-in-python/ 14 | -------------------------------------------------------------------------------- /behavioral/visitor/alternative_python/client_app.py: -------------------------------------------------------------------------------- 1 | from behavioral.visitor.alternative_python.visitor_impl.expr_printer import ExpressionPrinter 2 | from behavioral.visitor.alternative_python.visitor_impl.expr_evaluator import ExpressionEvaluator 3 | from behavioral.visitor.alternative_python.value import Value 4 | from behavioral.visitor.alternative_python.expressions import AdditionExpression, SubtractionExpression, \ 5 | MultiplicationExpression 6 | 7 | 8 | def main() -> None: 9 | """ 10 | Client app 11 | """ 12 | evaluator = ExpressionEvaluator() 13 | printer = ExpressionPrinter() 14 | 15 | expr = AdditionExpression(Value(2), Value(3)) 16 | printer.visit(expr) 17 | print(f"PrinterVisitor: (2+3) = {printer} -> {'(2+3)' == str(printer)}") 18 | evaluator.visit(expr) 19 | print(f"EvaluatorVisitor: {printer} = {evaluator} -> {eval('(2+3)') == evaluator.value}\n") 20 | printer.reset() 21 | expr = MultiplicationExpression( 22 | AdditionExpression(Value(2), Value(3)), 23 | Value(4) 24 | ) 25 | printer.visit(expr) 26 | print(f"PrinterVisitor: (2+3)*4 = {printer} -> {'(2+3)*4' == str(printer)}") 27 | evaluator.visit(expr) 28 | print(f"EvaluatorVisitor: {printer} = {evaluator} -> {eval('(2+3)*4') == evaluator.value}\n") 29 | 30 | printer.reset() 31 | expr = MultiplicationExpression( 32 | AdditionExpression( 33 | SubtractionExpression(Value(7), Value(3)), 34 | Value(6) 35 | ), 36 | SubtractionExpression(Value(-2), Value(3)) 37 | ) 38 | printer.visit(expr) 39 | print(f"PrinterVisitor: ((7-3)+6)*(-2-3) = {printer} -> {'((7-3)+6)*(-2-3)' == str(printer)}") 40 | evaluator.visit(expr) 41 | print(f"EvaluatorVisitor: {printer} = {evaluator} -> {eval('((7-3)+6)*(-2-3)') == evaluator.value}\n") 42 | 43 | 44 | if __name__ == '__main__': 45 | main() 46 | -------------------------------------------------------------------------------- /behavioral/visitor/alternative_python/expressions/__init__.py: -------------------------------------------------------------------------------- 1 | from behavioral.visitor.alternative_python.expressions.addition_expr import AdditionExpression 2 | from behavioral.visitor.alternative_python.expressions.subtraction_expr import SubtractionExpression 3 | from behavioral.visitor.alternative_python.expressions.multiplication_expr import MultiplicationExpression 4 | -------------------------------------------------------------------------------- /behavioral/visitor/alternative_python/expressions/addition_expr.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import TYPE_CHECKING, Union 3 | 4 | if TYPE_CHECKING: 5 | from behavioral.visitor.alternative_python.value import Value 6 | from behavioral.visitor.alternative_python.expressions import SubtractionExpression, MultiplicationExpression 7 | 8 | 9 | class AdditionExpression: 10 | """ 11 | Demonstrates addition expression. Expression is not supposed to be executed in this class directly in any way. 12 | """ 13 | def __init__(self, 14 | left: Union[Value, AdditionExpression, SubtractionExpression, MultiplicationExpression], 15 | right: Union[Value, AdditionExpression, SubtractionExpression, MultiplicationExpression] 16 | ) -> None: 17 | self.right = right 18 | self.left = left 19 | self.sign = '+' 20 | -------------------------------------------------------------------------------- /behavioral/visitor/alternative_python/expressions/multiplication_expr.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import TYPE_CHECKING, Union 3 | 4 | if TYPE_CHECKING: 5 | from behavioral.visitor.alternative_python.value import Value 6 | from behavioral.visitor.alternative_python.expressions import AdditionExpression, SubtractionExpression 7 | 8 | 9 | class MultiplicationExpression: 10 | """ 11 | Demonstrates multiplication expression. Expression is not supposed to be executed in this class directly in any way. 12 | """ 13 | def __init__(self, 14 | left: Union[Value, AdditionExpression, SubtractionExpression, MultiplicationExpression], 15 | right: Union[Value, AdditionExpression, SubtractionExpression, MultiplicationExpression] 16 | ) -> None: 17 | self.right = right 18 | self.left = left 19 | self.sign = '*' 20 | -------------------------------------------------------------------------------- /behavioral/visitor/alternative_python/expressions/subtraction_expr.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import TYPE_CHECKING, Union 3 | 4 | if TYPE_CHECKING: 5 | from behavioral.visitor.alternative_python.value import Value 6 | from behavioral.visitor.alternative_python.expressions import AdditionExpression, MultiplicationExpression 7 | 8 | 9 | class SubtractionExpression: 10 | """ 11 | Demonstrates subtraction expression. Expression is not supposed to be executed in this class directly in any way. 12 | """ 13 | def __init__(self, 14 | left: Union[Value, AdditionExpression, SubtractionExpression, MultiplicationExpression], 15 | right: Union[Value, AdditionExpression, SubtractionExpression, MultiplicationExpression] 16 | ) -> None: 17 | self.right = right 18 | self.left = left 19 | self.sign = '-' 20 | -------------------------------------------------------------------------------- /behavioral/visitor/alternative_python/value.py: -------------------------------------------------------------------------------- 1 | class Value: 2 | """ 3 | Demonstrates a single value that actually can be treated as expression. 4 | """ 5 | def __init__(self, value: int) -> None: 6 | self.value = value 7 | -------------------------------------------------------------------------------- /behavioral/visitor/alternative_python/visitor.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is LIBRARY CODE taken from https://tavianator.com/the-visitor-pattern-in-python/ 3 | """ 4 | 5 | 6 | def _qualname(obj): 7 | """Get the fully-qualified name of an object (including module).""" 8 | return obj.__module__ + '.' + obj.__qualname__ 9 | 10 | 11 | def _declaring_class(obj): 12 | """Get the name of the class that declared an object.""" 13 | name = _qualname(obj) 14 | return name[:name.rfind('.')] 15 | 16 | 17 | # Stores the actual visitor methods 18 | _methods = {} 19 | 20 | 21 | # Delegating visitor implementation 22 | def _visitor_impl(self, arg): 23 | """Actual visitor method implementation.""" 24 | key = (_qualname(type(self)), type(arg)) 25 | if not key in _methods: 26 | raise Exception('Key % not found' % key) 27 | method = _methods[key] 28 | return method(self, arg) 29 | 30 | 31 | # The actual @visitor decorator 32 | def visitor(arg_type): 33 | """Decorator that creates a visitor method.""" 34 | 35 | def decorator(fn): 36 | declaring_class = _declaring_class(fn) 37 | _methods[(declaring_class, arg_type)] = fn 38 | 39 | # Replace all decorated methods with _visitor_impl 40 | return _visitor_impl 41 | 42 | return decorator 43 | 44 | # ↑↑↑ LIBRARY CODE ↑↑↑ 45 | -------------------------------------------------------------------------------- /behavioral/visitor/alternative_python/visitor_impl/expr_evaluator.py: -------------------------------------------------------------------------------- 1 | from behavioral.visitor.alternative_python.visitor import visitor 2 | from behavioral.visitor.alternative_python.value import Value 3 | from behavioral.visitor.alternative_python.expressions import AdditionExpression, SubtractionExpression, \ 4 | MultiplicationExpression 5 | 6 | 7 | class ExpressionEvaluator: 8 | """ 9 | ExpressionEvaluator Visitor. Supports the execution of the expression. 10 | """ 11 | def __init__(self) -> None: 12 | self.value = None 13 | 14 | @visitor(Value) 15 | def visit(self, val: Value) -> None: 16 | self.value = val.value 17 | 18 | @visitor(AdditionExpression) 19 | def visit(self, ae: AdditionExpression) -> None: 20 | self.visit(ae.left) 21 | temp = self.value 22 | self.visit(ae.right) 23 | self.value += temp 24 | 25 | @visitor(SubtractionExpression) 26 | def visit(self, se: SubtractionExpression) -> None: 27 | self.visit(se.right) 28 | temp = self.value 29 | self.visit(se.left) 30 | self.value -= temp 31 | 32 | @visitor(MultiplicationExpression) 33 | def visit(self, me: MultiplicationExpression) -> None: 34 | self.visit(me.left) 35 | temp = self.value 36 | self.visit(me.right) 37 | self.value *= temp 38 | 39 | def __str__(self) -> str: 40 | return str(self.value) 41 | -------------------------------------------------------------------------------- /behavioral/visitor/alternative_python/visitor_impl/expr_printer.py: -------------------------------------------------------------------------------- 1 | from behavioral.visitor.alternative_python.visitor import visitor 2 | from behavioral.visitor.alternative_python.value import Value 3 | from behavioral.visitor.alternative_python.expressions import AdditionExpression, SubtractionExpression, \ 4 | MultiplicationExpression 5 | 6 | 7 | class ExpressionPrinter: 8 | """ 9 | ExpressionPrinter Visitor. Supports the printing of the expression. 10 | For addition and subtraction, parentheses are added. 11 | """ 12 | def __init__(self) -> None: 13 | self.buffer = [] 14 | 15 | @visitor(Value) 16 | def visit(self, elem: Value) -> None: 17 | self.buffer.append(str(elem.value)) 18 | 19 | @visitor(AdditionExpression) 20 | def visit(self, elem: AdditionExpression) -> None: 21 | self.buffer.append("(") 22 | self.visit(elem.left) 23 | self.buffer.append(elem.sign) 24 | self.visit(elem.right) 25 | self.buffer.append(")") 26 | 27 | @visitor(MultiplicationExpression) 28 | def visit(self, elem: MultiplicationExpression) -> None: 29 | self.visit(elem.left) 30 | self.buffer.append(elem.sign) 31 | self.visit(elem.right) 32 | 33 | @visitor(SubtractionExpression) 34 | def visit(self, elem: SubtractionExpression) -> None: 35 | self.buffer.append("(") 36 | self.visit(elem.left) 37 | self.buffer.append(elem.sign) 38 | self.visit(elem.right) 39 | self.buffer.append(")") 40 | 41 | def __str__(self) -> str: 42 | return "".join(self.buffer) 43 | 44 | def reset(self) -> None: 45 | self.buffer = [] 46 | -------------------------------------------------------------------------------- /behavioral/visitor/classic/client_app.py: -------------------------------------------------------------------------------- 1 | from behavioral.visitor.classic.visitor_impl.expr_printer import ExpressionPrinter 2 | from behavioral.visitor.classic.visitor_impl.expr_evaluator import ExpressionEvaluator 3 | from behavioral.visitor.classic.expressions import ValueExpression, AdditionExpression, SubtractionExpression, \ 4 | MultiplicationExpression 5 | 6 | 7 | def main() -> None: 8 | """ 9 | Client app 10 | """ 11 | evaluator = ExpressionEvaluator() 12 | printer = ExpressionPrinter() 13 | 14 | expr = AdditionExpression(ValueExpression(2), ValueExpression(3)) 15 | expr.accept(printer) # instead of `printer.visit(expr)` 16 | print(f"PrinterVisitor: (2+3) = {printer} -> {'(2+3)' == str(printer)}") 17 | expr.accept(evaluator) # instead of `evaluator.visit(expr)` 18 | print(f"EvaluatorVisitor: {printer} = {evaluator} -> {eval('(2+3)') == evaluator.value}\n") 19 | printer.reset() 20 | 21 | expr = MultiplicationExpression( 22 | AdditionExpression(ValueExpression(2), ValueExpression(3)), 23 | ValueExpression(4) 24 | ) 25 | expr.accept(printer) 26 | print(f"PrinterVisitor: (2+3)*4 = {printer} -> {'(2+3)*4' == str(printer)}") 27 | expr.accept(evaluator) 28 | print(f"EvaluatorVisitor: {printer} = {evaluator} -> {eval('(2+3)*4') == evaluator.value}\n") 29 | printer.reset() 30 | 31 | expr = MultiplicationExpression( 32 | AdditionExpression( 33 | SubtractionExpression(ValueExpression(7), ValueExpression(3)), 34 | ValueExpression(6) 35 | ), 36 | SubtractionExpression(ValueExpression(-2), ValueExpression(3)) 37 | ) 38 | expr.accept(printer) 39 | print(f"PrinterVisitor: ((7-3)+6)*(-2-3) = {printer} -> {'((7-3)+6)*(-2-3)' == str(printer)}") 40 | expr.accept(evaluator) 41 | print(f"EvaluatorVisitor: {printer} = {evaluator} -> {eval('((7-3)+6)*(-2-3)') == evaluator.value}\n") 42 | 43 | 44 | if __name__ == '__main__': 45 | main() 46 | -------------------------------------------------------------------------------- /behavioral/visitor/classic/component_expr.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from abc import ABC, abstractmethod 3 | from typing import TYPE_CHECKING 4 | 5 | if TYPE_CHECKING: 6 | from behavioral.visitor.classic.visitor import Visitor 7 | 8 | 9 | class ComponentExpression(ABC): 10 | """ 11 | The Component interface declares an `accept` method that should take the base visitor interface as an argument. 12 | """ 13 | 14 | @abstractmethod 15 | def accept(self, visitor: Visitor) -> None: 16 | pass 17 | -------------------------------------------------------------------------------- /behavioral/visitor/classic/expressions/__init__.py: -------------------------------------------------------------------------------- 1 | from behavioral.visitor.classic.expressions.value_expr import ValueExpression 2 | from behavioral.visitor.classic.expressions.addition_expr import AdditionExpression 3 | from behavioral.visitor.classic.expressions.subtraction_expr import SubtractionExpression 4 | from behavioral.visitor.classic.expressions.multiplication_expr import MultiplicationExpression 5 | -------------------------------------------------------------------------------- /behavioral/visitor/classic/expressions/addition_expr.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import TYPE_CHECKING 3 | 4 | from behavioral.visitor.classic.component_expr import ComponentExpression 5 | 6 | if TYPE_CHECKING: 7 | from behavioral.visitor.classic.visitor import Visitor 8 | 9 | 10 | class AdditionExpression(ComponentExpression): 11 | """ 12 | Demonstrates addition expression. Expression is not supposed to be executed in this class directly in any way. 13 | """ 14 | def __init__(self, left: ComponentExpression, right: ComponentExpression) -> None: 15 | self.right = right 16 | self.left = left 17 | self.sign = '+' 18 | 19 | def accept(self, visitor: Visitor) -> None: 20 | visitor.visit_addition_expr(self) 21 | -------------------------------------------------------------------------------- /behavioral/visitor/classic/expressions/multiplication_expr.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import TYPE_CHECKING 3 | 4 | from behavioral.visitor.classic.component_expr import ComponentExpression 5 | 6 | if TYPE_CHECKING: 7 | from behavioral.visitor.classic.visitor import Visitor 8 | 9 | 10 | class MultiplicationExpression(ComponentExpression): 11 | """ 12 | Demonstrates multiplication expression. Expression is not supposed to be executed in this class directly in any way. 13 | """ 14 | def __init__(self, left: ComponentExpression, right: ComponentExpression) -> None: 15 | self.right = right 16 | self.left = left 17 | self.sign = '*' 18 | 19 | def accept(self, visitor: Visitor) -> None: 20 | visitor.visit_multiplication_expr(self) 21 | -------------------------------------------------------------------------------- /behavioral/visitor/classic/expressions/subtraction_expr.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import TYPE_CHECKING 3 | 4 | from behavioral.visitor.classic.component_expr import ComponentExpression 5 | 6 | if TYPE_CHECKING: 7 | from behavioral.visitor.classic.visitor import Visitor 8 | 9 | 10 | class SubtractionExpression(ComponentExpression): 11 | """ 12 | Demonstrates subtraction expression. Expression is not supposed to be executed in this class directly in any way. 13 | """ 14 | def __init__(self, left: ComponentExpression, right: ComponentExpression) -> None: 15 | self.right = right 16 | self.left = left 17 | self.sign = '-' 18 | 19 | def accept(self, visitor: Visitor) -> None: 20 | visitor.visit_subtraction_expr(self) 21 | -------------------------------------------------------------------------------- /behavioral/visitor/classic/expressions/value_expr.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import TYPE_CHECKING 3 | 4 | from behavioral.visitor.classic.component_expr import ComponentExpression 5 | 6 | if TYPE_CHECKING: 7 | from behavioral.visitor.classic.visitor import Visitor 8 | 9 | 10 | class ValueExpression(ComponentExpression): 11 | """ 12 | Demonstrates a single value that actually can be treated as expression. 13 | """ 14 | def __init__(self, value: int) -> None: 15 | self.value = value 16 | 17 | def accept(self, visitor: Visitor) -> None: 18 | visitor.visit_value(self) 19 | -------------------------------------------------------------------------------- /behavioral/visitor/classic/visitor.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from abc import ABC, abstractmethod 3 | from typing import TYPE_CHECKING 4 | 5 | if TYPE_CHECKING: 6 | from behavioral.visitor.classic.expressions import ValueExpression, AdditionExpression, SubtractionExpression, \ 7 | MultiplicationExpression 8 | 9 | 10 | class Visitor(ABC): 11 | """ 12 | The Visitor Interface declares a set of visiting methods that correspond to component classes. 13 | The signature of a visiting method allows the visitor to identify the exact class of the component 14 | that it's dealing with. 15 | """ 16 | 17 | @abstractmethod 18 | def visit_value(self, element: ValueExpression) -> None: 19 | pass 20 | 21 | @abstractmethod 22 | def visit_addition_expr(self, element: AdditionExpression) -> None: 23 | pass 24 | 25 | @abstractmethod 26 | def visit_subtraction_expr(self, element: SubtractionExpression) -> None: 27 | pass 28 | 29 | @abstractmethod 30 | def visit_multiplication_expr(self, element: MultiplicationExpression) -> None: 31 | pass 32 | -------------------------------------------------------------------------------- /behavioral/visitor/classic/visitor_impl/expr_evaluator.py: -------------------------------------------------------------------------------- 1 | from behavioral.visitor.classic.visitor import Visitor 2 | from behavioral.visitor.classic.expressions.value_expr import ValueExpression 3 | from behavioral.visitor.classic.expressions import AdditionExpression, SubtractionExpression, \ 4 | MultiplicationExpression 5 | 6 | 7 | class ExpressionEvaluator(Visitor): 8 | """ 9 | ExpressionEvaluator Visitor. Supports the execution of the expression. 10 | """ 11 | def __init__(self) -> None: 12 | self.value = None 13 | 14 | def visit_value(self, element: ValueExpression) -> None: 15 | self.value = element.value 16 | 17 | def visit_addition_expr(self, element: AdditionExpression) -> None: 18 | element.left.accept(self) # instead of `self.visit(element.left)` 19 | temp = self.value 20 | element.right.accept(self) # instead of `self.visit(element.right)` 21 | self.value += temp 22 | 23 | def visit_subtraction_expr(self, element: SubtractionExpression) -> None: 24 | element.right.accept(self) 25 | temp = self.value 26 | element.left.accept(self) 27 | self.value -= temp 28 | 29 | def visit_multiplication_expr(self, element: MultiplicationExpression) -> None: 30 | element.left.accept(self) 31 | temp = self.value 32 | element.right.accept(self) 33 | self.value *= temp 34 | 35 | def __str__(self) -> str: 36 | return str(self.value) 37 | -------------------------------------------------------------------------------- /behavioral/visitor/classic/visitor_impl/expr_printer.py: -------------------------------------------------------------------------------- 1 | from behavioral.visitor.classic.visitor import Visitor 2 | from behavioral.visitor.classic.expressions.value_expr import ValueExpression 3 | from behavioral.visitor.classic.expressions import AdditionExpression, SubtractionExpression, \ 4 | MultiplicationExpression 5 | 6 | 7 | class ExpressionPrinter(Visitor): 8 | """ 9 | ExpressionPrinter Visitor. Supports the printing of the expression. 10 | For addition and subtraction, parentheses are added. 11 | """ 12 | def __init__(self) -> None: 13 | self.buffer = [] 14 | 15 | def visit_value(self, element: ValueExpression) -> None: 16 | self.buffer.append(str(element.value)) 17 | 18 | def visit_addition_expr(self, element: AdditionExpression) -> None: 19 | self.buffer.append("(") 20 | element.left.accept(self) # instead of `self.visit(element.left)` 21 | self.buffer.append(element.sign) 22 | element.right.accept(self) # instead of `self.visit(element.right)` 23 | self.buffer.append(")") 24 | 25 | def visit_subtraction_expr(self, element: SubtractionExpression) -> None: 26 | self.buffer.append("(") 27 | element.left.accept(self) 28 | self.buffer.append(element.sign) 29 | element.right.accept(self) 30 | self.buffer.append(")") 31 | 32 | def visit_multiplication_expr(self, element: MultiplicationExpression) -> None: 33 | element.left.accept(self) 34 | self.buffer.append(element.sign) 35 | element.right.accept(self) 36 | 37 | def __str__(self) -> str: 38 | return "".join(self.buffer) 39 | 40 | def reset(self) -> None: 41 | self.buffer = [] 42 | -------------------------------------------------------------------------------- /creational/builder/README.md: -------------------------------------------------------------------------------- 1 | # Builder pattern using Python 2 | Implementation of Builder Design Pattern according to the task from 'Design Patterns in Python' course 3 | https://www.udemy.com/course/design-patterns-python/ 4 | -------------------------------------------------------------------------------- /creational/builder/builders/code_builder.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from creational.builder.class_type import Class 4 | from creational.builder.field_type import Field 5 | 6 | 7 | class CodeBuilder: 8 | """ 9 | The CodeBuilder is the builder to create python class with optional fields 10 | """ 11 | def __init__(self, root_name: str): 12 | self.__class = Class(root_name) 13 | 14 | def add_field(self, name: str, value: str) -> CodeBuilder: 15 | self.__class.fields.append(Field(name, value)) 16 | return self 17 | 18 | def __str__(self): 19 | return self.__class.__str__() 20 | 21 | -------------------------------------------------------------------------------- /creational/builder/class_type.py: -------------------------------------------------------------------------------- 1 | class Class: 2 | """ 3 | Contains some of the important components of the class that are required 4 | in order to construct the class object (could be extended to add e.g. methods). 5 | """ 6 | def __init__(self, name: str) -> None: 7 | self.name = name 8 | self.fields = [] 9 | 10 | def __str__(self) -> str: 11 | lines = list() 12 | lines.append(f"class {self.name}:") 13 | if not self.fields: 14 | lines.append(f"{' '*2}pass") 15 | else: 16 | lines.append(f"{' '*2}def __init__(self):") 17 | for field in self.fields: 18 | lines.append(f"{' '*4}{field}") 19 | 20 | return '\n'.join(lines) 21 | -------------------------------------------------------------------------------- /creational/builder/client_app.py: -------------------------------------------------------------------------------- 1 | from builders.code_builder import CodeBuilder 2 | 3 | 4 | def main() -> None: 5 | """ 6 | Client app 7 | """ 8 | cb_with_fields = CodeBuilder('Person')\ 9 | .add_field('name', '""') \ 10 | .add_field('age', '0') 11 | 12 | print(cb_with_fields) 13 | print("\n") 14 | 15 | cb_just_class = CodeBuilder('Car') 16 | 17 | print(cb_just_class) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /creational/builder/field_type.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | 4 | class Field: 5 | """ 6 | Contains some of the important components that are required 7 | in order to construct the field object. 8 | """ 9 | def __init__(self, name: str, value: Union[str, int]) -> None: 10 | self.value = value 11 | self.name = name 12 | 13 | def __str__(self) -> str: 14 | return f'self.{self.name} = {self.value}' 15 | -------------------------------------------------------------------------------- /creational/factory/README.md: -------------------------------------------------------------------------------- 1 | # Factory pattern using Python 2 | Implementation of Factory Design Pattern according to the task from 'Design Patterns in Python' course 3 | https://www.udemy.com/course/design-patterns-python/ 4 | -------------------------------------------------------------------------------- /creational/factory/client_app.py: -------------------------------------------------------------------------------- 1 | from creational.factory.factories.person_factory import PersonFactory 2 | 3 | 4 | def main() -> None: 5 | """ 6 | Client app 7 | """ 8 | persons = list() 9 | pf = PersonFactory() 10 | persons.append(pf.create_person("Oleh")) 11 | persons.append(pf.create_person("Serhiy")) 12 | persons.append(pf.create_person("Yurii")) 13 | persons.append(pf.create_person("Taras")) 14 | persons.append(pf.create_person("Andrii")) 15 | 16 | for person in persons: 17 | print(person) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /creational/factory/factories/person_factory.py: -------------------------------------------------------------------------------- 1 | from creational.factory.person import Person 2 | 3 | 4 | class PersonFactory: 5 | """ 6 | Simple Factory is used to create a specific type of product (object). 7 | """ 8 | id_iterator = 0 9 | 10 | @staticmethod 11 | def create_person(name: str) -> Person: 12 | person = Person(PersonFactory.id_iterator, name) 13 | PersonFactory.id_iterator += 1 14 | return person 15 | -------------------------------------------------------------------------------- /creational/factory/person.py: -------------------------------------------------------------------------------- 1 | class Person: 2 | """ 3 | Contains some of the important components of the Person that are required 4 | in order to construct the person object. 5 | """ 6 | def __init__(self, _id: int, name: str) -> None: 7 | self.id = _id 8 | self.name = name 9 | 10 | def __str__(self) -> str: 11 | return f"Name: {self.name} (id={self.id})" 12 | -------------------------------------------------------------------------------- /creational/hw_pizza_station/README.md: -------------------------------------------------------------------------------- 1 | # Pizza Studio implementation with creational design patterns using Python 2 | Requirements: 3 | * Create Pizza: prepare, bake, cut, box. 4 | * Pizza components: dough (thick or thin crust), sauce (Marinara, Plum Tomato or Pesto …), toppings (…). 5 | * Pizza types: Cheese, Veggie, Clam, Pepperoni. 6 | * Bakeries: Lviv, Kyiv, Dnipro (pizza is baked on its own recipe in each city). 7 | 8 | Implementation: 9 | * Using 'Builder' pattern (alternative version) to build a raw pizza - just collects all necessary components like dough, sauce and different toppings depending on the recipe 10 | * Using 'Abstract Factory' pattern to implement bakeries logic, as different bakeries can cook different types of pizza, bake the in their own way, cut on the different numbers of slices, and box them differently. -------------------------------------------------------------------------------- /creational/hw_pizza_station/abstract_factory/__init__.py: -------------------------------------------------------------------------------- 1 | from .bakery_factory import BakeryFactory 2 | from .impl.lviv_bakery import LvivBakery 3 | from .impl.kyiv_bakery import KyivBakery 4 | from .impl.dnipro_bakery import DniproBakery 5 | -------------------------------------------------------------------------------- /creational/hw_pizza_station/abstract_factory/bakery_factory.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Dict, Type 3 | 4 | from creational.hw_pizza_station.builders.pizza_builder import PizzaBuilder 5 | from creational.hw_pizza_station.pizza import Pizza 6 | from creational.hw_pizza_station.enums.pizza_type import PizzaType 7 | from creational.hw_pizza_station.builders import CheesePizzaBuilder, VeggiePizzaBuilder, MeatPizzaBuilder,\ 8 | PepperoniPizzaBuilder 9 | 10 | 11 | class BakeryFactory(ABC): 12 | """ 13 | Abstract Factory is used to cook the pizza in different bakeries of Pizza Studio franchise. 14 | """ 15 | PIZZA_TYPES: Dict[PizzaType, Type[PizzaBuilder]] = { 16 | PizzaType.CHEESE: CheesePizzaBuilder, 17 | PizzaType.VEGGIE: VeggiePizzaBuilder, 18 | PizzaType.MEAT: MeatPizzaBuilder, 19 | PizzaType.PEPPERONI: PepperoniPizzaBuilder, 20 | } 21 | PIZZA_STATION = "" 22 | 23 | def prepare(self, pizza_type: PizzaType) -> Pizza: 24 | pizza_builder: PizzaBuilder = self.PIZZA_TYPES.get(pizza_type)(pizza_type) 25 | 26 | pizza = pizza_builder\ 27 | .choose_dough()\ 28 | .choose_sauce()\ 29 | .add_topping()\ 30 | .get_pizza() 31 | pizza.show_components() 32 | self.bake(pizza) 33 | self.cut(pizza) 34 | self.box(pizza) 35 | 36 | return pizza 37 | 38 | @abstractmethod 39 | def bake(self, pizza: Pizza) -> None: 40 | pass 41 | 42 | @abstractmethod 43 | def cut(self, pizza: Pizza) -> None: 44 | pass 45 | 46 | @abstractmethod 47 | def box(self, pizza: Pizza) -> None: 48 | pass 49 | 50 | def suggest_choosing_pizza(self) -> PizzaType: 51 | self.__welcome() 52 | self.__show_available_pizza_types() 53 | try: 54 | pizza_key = str(input("\n-> Select pizza: ")).upper() 55 | if pizza_key == "B": 56 | pizza_type = None 57 | elif int(pizza_key) not in [p_type.value for p_type in self.PIZZA_TYPES]: 58 | raise ValueError 59 | else: 60 | pizza_type = PizzaType(int(pizza_key)) 61 | return pizza_type 62 | 63 | except ValueError: 64 | print("Invalid key. Try again!") 65 | self.suggest_choosing_pizza() 66 | 67 | def __welcome(self) -> None: 68 | print("\n=========================================================") 69 | print(f"Welcome in {self.PIZZA_STATION} Pizza Station") 70 | print("=========================================================") 71 | 72 | def __show_available_pizza_types(self) -> None: 73 | print("Menu:") 74 | for pizza_type in self.PIZZA_TYPES: 75 | print(f"{pizza_type.value} - {pizza_type.name}") 76 | print("B - Back to the main menu") 77 | -------------------------------------------------------------------------------- /creational/hw_pizza_station/abstract_factory/impl/dnipro_bakery.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Type 2 | 3 | from ..bakery_factory import BakeryFactory 4 | from creational.hw_pizza_station.pizza import Pizza 5 | 6 | from creational.hw_pizza_station.builders.pizza_builder import PizzaBuilder 7 | from creational.hw_pizza_station.enums.pizza_type import PizzaType 8 | from creational.hw_pizza_station.builders import CheesePizzaBuilder, VeggiePizzaBuilder 9 | 10 | 11 | class DniproBakery(BakeryFactory): 12 | """ 13 | Specific Bakery which determines own rules of baking, cutting, and boxing a pizza. 14 | Specific bakery may cook limited pizza types. 15 | """ 16 | PIZZA_TYPES: Dict[PizzaType, Type[PizzaBuilder]] = { 17 | PizzaType.CHEESE: CheesePizzaBuilder, 18 | PizzaType.VEGGIE: VeggiePizzaBuilder, 19 | } 20 | PIZZA_STATION = "Dnipro" 21 | 22 | def bake(self, pizza: Pizza) -> None: 23 | print(f"Bake {pizza.pizza_type} pizza 20 minutes in an oven preheated to 190°C ") 24 | 25 | def cut(self, pizza: Pizza) -> None: 26 | print(f"Cut {pizza.pizza_type} pizza into 6 pieces") 27 | 28 | def box(self, pizza: Pizza) -> None: 29 | print(f"Put {pizza.pizza_type} pizza in a square wooden box") 30 | -------------------------------------------------------------------------------- /creational/hw_pizza_station/abstract_factory/impl/kyiv_bakery.py: -------------------------------------------------------------------------------- 1 | from ..bakery_factory import BakeryFactory 2 | from creational.hw_pizza_station.pizza import Pizza 3 | 4 | 5 | class KyivBakery(BakeryFactory): 6 | """ 7 | Specific Bakery which determines own rules of baking, cutting, and boxing a pizza. 8 | Specific bakery may cook limited pizza types. 9 | """ 10 | PIZZA_STATION = "Kyiv" 11 | 12 | def bake(self, pizza: Pizza) -> None: 13 | print(f"Bake {pizza.pizza_type} pizza 25 minutes in an oven preheated to 180°C ") 14 | 15 | def cut(self, pizza: Pizza) -> None: 16 | print(f"Cut {pizza.pizza_type} pizza into 8 pieces") 17 | 18 | def box(self, pizza: Pizza) -> None: 19 | print(f"Put {pizza.pizza_type} pizza in a round cardboard box") 20 | -------------------------------------------------------------------------------- /creational/hw_pizza_station/abstract_factory/impl/lviv_bakery.py: -------------------------------------------------------------------------------- 1 | from ..bakery_factory import BakeryFactory 2 | from creational.hw_pizza_station.pizza import Pizza 3 | 4 | 5 | class LvivBakery(BakeryFactory): 6 | """ 7 | Specific Bakery which determines own rules of baking, cutting, and boxing a pizza. 8 | Specific bakery may cook limited pizza types. 9 | """ 10 | PIZZA_STATION = "Lviv" 11 | 12 | def bake(self, pizza: Pizza) -> None: 13 | print(f"Bake {pizza.pizza_type} pizza 15 minutes in an oven preheated to 200°C ") 14 | 15 | def cut(self, pizza: Pizza) -> None: 16 | print(f"Cut {pizza.pizza_type} pizza into 6 pieces") 17 | 18 | def box(self, pizza: Pizza) -> None: 19 | print(f"Put {pizza.pizza_type} pizza in a square cardboard box") 20 | -------------------------------------------------------------------------------- /creational/hw_pizza_station/builders/__init__.py: -------------------------------------------------------------------------------- 1 | from .pizza_builder import PizzaBuilder 2 | 3 | from .impl.cheese_pizza_builder import CheesePizzaBuilder 4 | from .impl.veggie_pizza_builder import VeggiePizzaBuilder 5 | from .impl.meat_pizza_builder import MeatPizzaBuilder 6 | from .impl.pepperoni_pizza_builder import PepperoniPizzaBuilder 7 | -------------------------------------------------------------------------------- /creational/hw_pizza_station/builders/impl/cheese_pizza_builder.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Tuple 3 | 4 | from creational.hw_pizza_station.enums.pizza_components import PizzaComponents 5 | 6 | from .. import PizzaBuilder 7 | 8 | 9 | class CheesePizzaBuilder(PizzaBuilder): 10 | """ 11 | Creates specific pizza type. 12 | """ 13 | DEFAULT_DOUGH = PizzaComponents.Dough.THICK 14 | DEFAULT_SAUCE = PizzaComponents.Sauce.TOMATO 15 | DEFAULT_TOPPINGS = ( 16 | PizzaComponents.Topping.TOMATO, PizzaComponents.Topping.DOUBLE_CHEESE, PizzaComponents.Topping.BASIL 17 | ) 18 | 19 | def __init__(self, pizza_type): 20 | super().__init__(pizza_type) 21 | 22 | def choose_dough(self, dough_type: str = DEFAULT_DOUGH) -> CheesePizzaBuilder: 23 | self._pizza.dough = dough_type 24 | return self 25 | 26 | def choose_sauce(self, sauce_type: str = DEFAULT_SAUCE) -> CheesePizzaBuilder: 27 | self._pizza.sauce = sauce_type 28 | return self 29 | 30 | def add_topping(self, toppings: Tuple[str] = DEFAULT_TOPPINGS) -> CheesePizzaBuilder: 31 | for topping in toppings: 32 | self._pizza.add_topping(topping) 33 | return self 34 | -------------------------------------------------------------------------------- /creational/hw_pizza_station/builders/impl/meat_pizza_builder.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Tuple 3 | 4 | from creational.hw_pizza_station.enums.pizza_components import PizzaComponents 5 | 6 | from .. import PizzaBuilder 7 | 8 | 9 | class MeatPizzaBuilder(PizzaBuilder): 10 | """ 11 | Creates specific pizza type. 12 | """ 13 | DEFAULT_DOUGH = PizzaComponents.Dough.THICK 14 | DEFAULT_SAUCE = PizzaComponents.Sauce.MARINARA 15 | DEFAULT_TOPPINGS = ( 16 | PizzaComponents.Topping.CHEESE, PizzaComponents.Topping.CHICKEN, PizzaComponents.Topping.PEPPERONI, 17 | PizzaComponents.Topping.BACON, PizzaComponents.Topping.TOMATO, PizzaComponents.Topping.MUSHROOMS, 18 | ) 19 | 20 | def choose_dough(self, dough_type: str = DEFAULT_DOUGH) -> MeatPizzaBuilder: 21 | self._pizza.dough = dough_type 22 | return self 23 | 24 | def choose_sauce(self, sauce_type: str = DEFAULT_SAUCE) -> MeatPizzaBuilder: 25 | self._pizza.sauce = sauce_type 26 | return self 27 | 28 | def add_topping(self, toppings: Tuple[str] = DEFAULT_TOPPINGS) -> MeatPizzaBuilder: 29 | for topping in toppings: 30 | self._pizza.add_topping(topping) 31 | return self 32 | -------------------------------------------------------------------------------- /creational/hw_pizza_station/builders/impl/pepperoni_pizza_builder.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Tuple 3 | 4 | from creational.hw_pizza_station.enums.pizza_components import PizzaComponents 5 | 6 | from .. import PizzaBuilder 7 | 8 | 9 | class PepperoniPizzaBuilder(PizzaBuilder): 10 | """ 11 | Creates specific pizza type. 12 | """ 13 | DEFAULT_DOUGH = PizzaComponents.Dough.THIN 14 | DEFAULT_SAUCE = PizzaComponents.Sauce.TOMATO 15 | DEFAULT_TOPPINGS = ( 16 | PizzaComponents.Topping.DOUBLE_CHEESE, PizzaComponents.Topping.PEPPERONI, 17 | PizzaComponents.Topping.TOMATO, PizzaComponents.Topping.BASIL, 18 | ) 19 | 20 | def choose_dough(self, dough_type: str = DEFAULT_DOUGH) -> PepperoniPizzaBuilder: 21 | self._pizza.dough = dough_type 22 | return self 23 | 24 | def choose_sauce(self, sauce_type: str = DEFAULT_SAUCE) -> PepperoniPizzaBuilder: 25 | self._pizza.sauce = sauce_type 26 | return self 27 | 28 | def add_topping(self, toppings: Tuple[str] = DEFAULT_TOPPINGS) -> PepperoniPizzaBuilder: 29 | for topping in toppings: 30 | self._pizza.add_topping(topping) 31 | return self 32 | -------------------------------------------------------------------------------- /creational/hw_pizza_station/builders/impl/veggie_pizza_builder.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Tuple 3 | 4 | from creational.hw_pizza_station.enums.pizza_components import PizzaComponents 5 | 6 | from .. import PizzaBuilder 7 | 8 | 9 | class VeggiePizzaBuilder(PizzaBuilder): 10 | """ 11 | Creates specific pizza type. 12 | """ 13 | DEFAULT_DOUGH = PizzaComponents.Dough.THIN 14 | DEFAULT_SAUCE = PizzaComponents.Sauce.PESTO 15 | DEFAULT_TOPPINGS = ( 16 | PizzaComponents.Topping.CHEESE, PizzaComponents.Topping.MUSHROOMS, PizzaComponents.Topping.OLIVE, 17 | PizzaComponents.Topping.CORN, PizzaComponents.Topping.ONION, PizzaComponents.Topping.TOMATO, 18 | ) 19 | 20 | def choose_dough(self, dough_type: str = DEFAULT_DOUGH) -> VeggiePizzaBuilder: 21 | self._pizza.dough = dough_type 22 | return self 23 | 24 | def choose_sauce(self, sauce_type: str = DEFAULT_SAUCE) -> VeggiePizzaBuilder: 25 | self._pizza.sauce = sauce_type 26 | return self 27 | 28 | def add_topping(self, toppings: Tuple[str] = DEFAULT_TOPPINGS) -> VeggiePizzaBuilder: 29 | for topping in toppings: 30 | self._pizza.add_topping(topping) 31 | return self 32 | -------------------------------------------------------------------------------- /creational/hw_pizza_station/builders/pizza_builder.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from abc import ABC, abstractmethod 3 | from typing import Final 4 | 5 | from creational.hw_pizza_station.pizza import Pizza 6 | from creational.hw_pizza_station.enums.pizza_type import PizzaType 7 | 8 | 9 | class PizzaBuilder(ABC): 10 | """ 11 | PizzaBuilder is the builders interface contains set of common methods used 12 | in order to build the pizza object and its components. 13 | """ 14 | 15 | def __init__(self, pizza_type: PizzaType) -> None: 16 | self._pizza: Final[Pizza] = Pizza(pizza_type) 17 | 18 | @abstractmethod 19 | def choose_dough(self) -> PizzaBuilder: 20 | """You MUST TO override me, I'm a placeholder.""" 21 | 22 | @abstractmethod 23 | def choose_sauce(self) -> PizzaBuilder: 24 | """You MUST TO override me, I'm a placeholder.""" 25 | 26 | @abstractmethod 27 | def add_topping(self) -> PizzaBuilder: 28 | """You MUST TO override me, I'm a placeholder.""" 29 | 30 | def get_pizza(self) -> Pizza: 31 | return self._pizza 32 | -------------------------------------------------------------------------------- /creational/hw_pizza_station/client_app.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | 3 | from creational.hw_pizza_station.abstract_factory import BakeryFactory, LvivBakery, KyivBakery, DniproBakery 4 | 5 | menu_str: Dict[str, str] = dict() 6 | menu_str["1"] = "Lviv" 7 | menu_str["2"] = "Kyiv" 8 | menu_str["3"] = "Dnipro" 9 | menu_str["Q"] = "Quit" 10 | 11 | bakery_dict: Dict[str, BakeryFactory] = dict() 12 | bakery_dict["1"] = LvivBakery() 13 | bakery_dict["2"] = KyivBakery() 14 | bakery_dict["3"] = DniproBakery() 15 | 16 | 17 | def main() -> None: 18 | """ 19 | Client app 20 | """ 21 | bakery_key: str = "" 22 | while bakery_key != "Q": 23 | print("\n\nWelcome to Pizza Station") 24 | for key in menu_str: 25 | print(f"{key} - {menu_str.get(key)}") 26 | print("\n-> Select your location: ", end="") 27 | bakery_key = str(input()).upper() 28 | if bakery_key in bakery_dict: 29 | bakery = bakery_dict[bakery_key] 30 | pizza_type = bakery.suggest_choosing_pizza() 31 | if pizza_type is None: 32 | continue 33 | pizza = bakery.prepare(pizza_type) 34 | print("\n=========================================================") 35 | print(f"Bon Appetit, {pizza.pizza_type} pizza is ready. " 36 | f"See you next time in {bakery.PIZZA_STATION} Pizza Station.") 37 | print("=========================================================") 38 | 39 | 40 | if __name__ == '__main__': 41 | main() 42 | -------------------------------------------------------------------------------- /creational/hw_pizza_station/enums/pizza_bakery.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | 4 | class PizzaBakery(Enum): 5 | """ 6 | Collects different bakeries with Pizza Studio franchise. 7 | """ 8 | LVIV = auto() 9 | KYIV = auto() 10 | DNIPRO = auto() 11 | -------------------------------------------------------------------------------- /creational/hw_pizza_station/enums/pizza_components.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | 4 | class PizzaComponents: 5 | """ 6 | Collects different pizza components, like dough, sauce, and toppings. 7 | """ 8 | class Dough(Enum): 9 | """ 10 | Collects different dough types for pizza. 11 | """ 12 | THICK = auto() 13 | THIN = auto() 14 | 15 | class Sauce(Enum): 16 | """ 17 | Collects different sauces for pizza. 18 | """ 19 | MARINARA = auto() 20 | PLUM = auto() 21 | TOMATO = auto() 22 | PESTO = auto() 23 | 24 | class Topping(Enum): 25 | """ 26 | Collects different toppings for pizza. 27 | """ 28 | CHEESE = auto() 29 | DOUBLE_CHEESE = auto() 30 | CHICKEN = auto() 31 | PEPPERONI = auto() 32 | BACON = auto() 33 | MUSHROOMS = auto() 34 | OLIVE = auto() 35 | CORN = auto() 36 | ONION = auto() 37 | TOMATO = auto() 38 | BASIL = auto() 39 | -------------------------------------------------------------------------------- /creational/hw_pizza_station/enums/pizza_type.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | 4 | class PizzaType(Enum): 5 | """ 6 | Collects different pizza types. 7 | """ 8 | CHEESE = auto() 9 | VEGGIE = auto() 10 | MEAT = auto() 11 | PEPPERONI = auto() 12 | CUSTOM = auto() 13 | -------------------------------------------------------------------------------- /creational/hw_pizza_station/pizza.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import List, Optional 3 | from creational.hw_pizza_station.enums.pizza_type import PizzaType 4 | from creational.hw_pizza_station.enums.pizza_components import PizzaComponents 5 | 6 | 7 | class Pizza: 8 | """ 9 | Class for Pizza object. 10 | """ 11 | def __init__(self, pizza_type: PizzaType): 12 | self.__dough: Optional[str] = None 13 | self.__sauce: Optional[str] = None 14 | self.__toppings: Optional[List[str]] = [] 15 | self.__pizza_type: str = pizza_type.name 16 | 17 | @property 18 | def pizza_type(self) -> str: 19 | return self.__pizza_type 20 | 21 | @pizza_type.setter 22 | def pizza_type(self, value: PizzaType) -> None: 23 | self.__pizza_type = value.name 24 | 25 | @property 26 | def dough(self) -> str: 27 | return self.__dough 28 | 29 | @dough.setter 30 | def dough(self, value: PizzaComponents.Dough) -> None: 31 | self.__dough = value.name 32 | 33 | @property 34 | def sauce(self) -> str: 35 | return self.__sauce 36 | 37 | @sauce.setter 38 | def sauce(self, value: PizzaComponents.Sauce) -> None: 39 | self.__sauce = value.name 40 | 41 | @property 42 | def toppings(self) -> List[str]: 43 | return self.__toppings 44 | 45 | def add_topping(self, value: PizzaComponents.Topping) -> None: 46 | self.__toppings.append(value.name) 47 | 48 | def __str__(self) -> str: 49 | return f"Pizza '{self.__pizza_type}'" 50 | 51 | def show_components(self) -> None: 52 | pizza_str = f"\n----------------------{self} components----------------------\n" 53 | pizza_str += f"Dough: {self.__dough}\n" if self.__dough is not None else "" 54 | pizza_str += f"Sauce: {self.__sauce}\n" if self.__sauce is not None else "" 55 | if self.__toppings is not None: 56 | pizza_str += f"Toppings: {', '.join(sorted(self.__toppings))}" 57 | pizza_str += f"\n------------------------------------------------------------------\n" 58 | print(pizza_str) 59 | -------------------------------------------------------------------------------- /creational/prototype/README.md: -------------------------------------------------------------------------------- 1 | # Prototype pattern using Python 2 | Implementation of Prototype Design Pattern according to the task from 'Design Patterns in Python' course 3 | https://www.udemy.com/course/design-patterns-python/ 4 | -------------------------------------------------------------------------------- /creational/prototype/client_app.py: -------------------------------------------------------------------------------- 1 | from creational.prototype.point import Point 2 | from creational.prototype.line import Line 3 | 4 | 5 | def main() -> None: 6 | """ 7 | Client app 8 | """ 9 | start_point = Point(1, 2) 10 | end_point_a = Point(5, 2) 11 | end_point_b = Point(1, 10) 12 | 13 | horizontal_line = Line(start_point, end_point_a) 14 | vertical_line = horizontal_line.deep_copy() 15 | vertical_line.end = end_point_b 16 | 17 | print(horizontal_line, vertical_line, sep="\n") 18 | 19 | diagonal_line = Line() 20 | diagonal_line.end = Point(-5, -5) 21 | opposite_diagonal_line = diagonal_line.deep_copy() 22 | opposite_diagonal_line.end = Point(5, 5) 23 | print(diagonal_line, opposite_diagonal_line, sep="\n") 24 | 25 | 26 | if __name__ == '__main__': 27 | main() 28 | -------------------------------------------------------------------------------- /creational/prototype/line.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | 3 | from point import Point 4 | 5 | 6 | class Line: 7 | """ 8 | Contains some of the important components of the Line that are required 9 | in order to construct the line object. 10 | """ 11 | def __init__(self, start: Point = Point(), end: Point = Point()) -> None: 12 | self.start = start 13 | self.end = end 14 | 15 | def deep_copy(self): 16 | return deepcopy(self) 17 | 18 | def __str__(self) -> str: 19 | return f"Line starts at {self.start}, ends at {self.end}" 20 | -------------------------------------------------------------------------------- /creational/prototype/point.py: -------------------------------------------------------------------------------- 1 | class Point: 2 | """ 3 | Contains some of the important components of the Point that are required 4 | in order to construct the point object. 5 | """ 6 | def __init__(self, x: int = 0, y: int = 0) -> None: 7 | self.x = x 8 | self.y = y 9 | 10 | def __str__(self) -> str: 11 | return f"Point(x={self.x}, y={self.y})" 12 | -------------------------------------------------------------------------------- /creational/singleton/README.md: -------------------------------------------------------------------------------- 1 | # Singleton pattern using Python 2 | Implementation of Singleton Design Pattern according to the task from 'Design Patterns in Python' course 3 | https://www.udemy.com/course/design-patterns-python/ 4 | -------------------------------------------------------------------------------- /creational/singleton/client_app.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Callable 4 | 5 | from subjects.connection import Connection 6 | 7 | 8 | def is_singleton(class_name: Callable) -> bool: 9 | """ 10 | Function to check if given class is type of Singleton 11 | """ 12 | obj1 = class_name() 13 | obj2 = class_name() 14 | 15 | return obj1 == obj2 and obj1 is obj2 16 | 17 | 18 | def main() -> None: 19 | """ 20 | Client app 21 | """ 22 | first_connection = Connection("elonmask777", 'tesla999') 23 | print(first_connection) 24 | second_connection = Connection("buterin", 'ethereum') 25 | print(second_connection) 26 | print(is_singleton(Connection)) 27 | second_connection.connect() 28 | 29 | 30 | if __name__ == '__main__': 31 | main() 32 | -------------------------------------------------------------------------------- /creational/singleton/subjects/connection.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .singleton import Singleton 4 | 5 | 6 | class Connection(metaclass=Singleton): 7 | """ 8 | Connection is Singleton class 9 | """ 10 | def __init__(self, login: str = 'admin', password: str = '12345'): 11 | self.__login = login 12 | self.__password = password 13 | 14 | def connect(self): 15 | print(f"{self.__login} connected successfully") 16 | 17 | def __str__(self): 18 | return f"Login: {self.__login}, Password: " 19 | 20 | 21 | -------------------------------------------------------------------------------- /creational/singleton/subjects/singleton.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | 4 | class Singleton(type): 5 | """ 6 | Metaclass that creates a Singleton base type when called. 7 | """ 8 | __instances = {} 9 | 10 | def __call__(cls, *args, **kwargs): 11 | if cls not in cls.__instances: 12 | cls.__instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) 13 | return cls.__instances[cls] 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /structural/adapter/README.md: -------------------------------------------------------------------------------- 1 | # Adapter pattern using Python 2 | Implementation of Adapter Design Pattern according to the task from 'Design Patterns in Python' course 3 | https://www.udemy.com/course/design-patterns-python/ 4 | -------------------------------------------------------------------------------- /structural/adapter/adapters/square_to_rectangle_adapter.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from structural.adapter.square import Square 4 | 5 | 6 | class SquareToRectangleAdapter: 7 | """ 8 | Adapter to work with Square as with a Rectangle 9 | """ 10 | 11 | def __init__(self, square: Square) -> None: 12 | self.square = square 13 | 14 | @property 15 | def width(self) -> int: 16 | return self.square.side 17 | 18 | @property 19 | def height(self) -> int: 20 | return self.square.side 21 | -------------------------------------------------------------------------------- /structural/adapter/client_app.py: -------------------------------------------------------------------------------- 1 | from structural.adapter.square import Square 2 | from structural.adapter.rectangle import Rectangle 3 | from structural.adapter.adapters.square_to_rectangle_adapter import SquareToRectangleAdapter 4 | 5 | 6 | def main() -> None: 7 | """ 8 | Client app 9 | """ 10 | sq = Square(11) 11 | try: 12 | print(Rectangle.calculate_area(sq)) # AttributeError: 'Square' object has no attribute 'width' 13 | except AttributeError as err: 14 | print(err) 15 | 16 | adapter = SquareToRectangleAdapter(sq) 17 | print(f"Area of Square(11x11)={Rectangle.calculate_area(adapter)}") 18 | 19 | sq.side = 10 20 | print(f"Area of Square(10x10)={Rectangle.calculate_area(adapter)}") 21 | 22 | 23 | if __name__ == '__main__': 24 | main() 25 | -------------------------------------------------------------------------------- /structural/adapter/rectangle.py: -------------------------------------------------------------------------------- 1 | class Rectangle: 2 | """ 3 | External Rectangle class. We can not inherit from it, only use it public API 4 | """ 5 | def __init__(self, width: int = 0, height: int = 0) -> None: 6 | self.width = width 7 | self.height = height 8 | 9 | def __str__(self) -> str: 10 | return f'Rectangle: width = {self.width}, height = {self.height}' 11 | 12 | @staticmethod 13 | def calculate_area(rc) -> int: 14 | return rc.width * rc.height 15 | -------------------------------------------------------------------------------- /structural/adapter/square.py: -------------------------------------------------------------------------------- 1 | class Square: 2 | """ 3 | Defines Square class 4 | """ 5 | def __init__(self, side: int = 0) -> None: 6 | self.side = side 7 | 8 | def __str__(self) -> str: 9 | return f'Square with side length = {self.side}' 10 | -------------------------------------------------------------------------------- /structural/bridge/README.md: -------------------------------------------------------------------------------- 1 | # Bridge pattern using Python 2 | Implementation of Bridge Design Pattern according to the task from 'Design Patterns in Python' course 3 | https://www.udemy.com/course/design-patterns-python/ 4 | -------------------------------------------------------------------------------- /structural/bridge/abstractions/__init__.py: -------------------------------------------------------------------------------- 1 | from structural.bridge.abstractions.shape_abstraction import ShapeAbstraction 2 | from structural.bridge.abstractions.square_abstraction import SquareAbstraction 3 | from structural.bridge.abstractions.triangle_abstraction import TriangleAbstraction 4 | -------------------------------------------------------------------------------- /structural/bridge/abstractions/shape_abstraction.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from structural.bridge.impl.renderer_impl import RendererImplementation 4 | 5 | 6 | class ShapeAbstraction: 7 | """ 8 | ShapeAbstraction defines the interface for the "control"(Renderer) part of the 9 | class hierarchies. 10 | """ 11 | 12 | def __init__(self, renderer: RendererImplementation) -> None: 13 | self.renderer = renderer 14 | self.name = None 15 | 16 | def __str__(self): 17 | return self.renderer.what_to_render_as(self.name) 18 | -------------------------------------------------------------------------------- /structural/bridge/abstractions/square_abstraction.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from structural.bridge.abstractions.shape_abstraction import ShapeAbstraction 4 | from structural.bridge.impl.renderer_impl import RendererImplementation 5 | 6 | 7 | class SquareAbstraction(ShapeAbstraction): 8 | """ 9 | SquareAbstraction extend the Abstraction without changing the Implementation classes. 10 | """ 11 | 12 | def __init__(self, renderer: RendererImplementation) -> None: 13 | super().__init__(renderer) 14 | self.name = 'Square' 15 | 16 | -------------------------------------------------------------------------------- /structural/bridge/abstractions/triangle_abstraction.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from structural.bridge.abstractions.shape_abstraction import ShapeAbstraction 4 | from structural.bridge.impl.renderer_impl import RendererImplementation 5 | 6 | 7 | class TriangleAbstraction(ShapeAbstraction): 8 | """ 9 | TriangleAbstraction extend the Abstraction without changing the Implementation classes. 10 | """ 11 | 12 | def __init__(self, renderer: RendererImplementation) -> None: 13 | super().__init__(renderer) 14 | self.name = 'Triangle' 15 | 16 | -------------------------------------------------------------------------------- /structural/bridge/client_app.py: -------------------------------------------------------------------------------- 1 | 2 | from structural.bridge.abstractions import ShapeAbstraction, SquareAbstraction, TriangleAbstraction 3 | from structural.bridge.impl import RendererImplementation, RasterRendererImplementation, VectorRendererImplementation 4 | 5 | 6 | def main() -> None: 7 | """ 8 | Client app 9 | """ 10 | 11 | raster_impl: RendererImplementation = RasterRendererImplementation() 12 | abstraction: ShapeAbstraction = SquareAbstraction(raster_impl) 13 | print(abstraction) 14 | 15 | vector_impl: RendererImplementation = VectorRendererImplementation() 16 | abstraction: ShapeAbstraction = TriangleAbstraction(vector_impl) 17 | print(abstraction) 18 | 19 | abstraction: ShapeAbstraction = SquareAbstraction(vector_impl) 20 | print(abstraction) 21 | 22 | 23 | if __name__ == '__main__': 24 | main() 25 | -------------------------------------------------------------------------------- /structural/bridge/impl/__init__.py: -------------------------------------------------------------------------------- 1 | from structural.bridge.impl.renderer_impl import RendererImplementation 2 | from structural.bridge.impl.raster_renderer_impl import RasterRendererImplementation 3 | from structural.bridge.impl.vector_renderer_impl import VectorRendererImplementation 4 | -------------------------------------------------------------------------------- /structural/bridge/impl/raster_renderer_impl.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from structural.bridge.impl.renderer_impl import RendererImplementation 4 | 5 | 6 | class RasterRendererImplementation(RendererImplementation): 7 | """ 8 | RasterRendererImplementation corresponds to a Raster rendering type and implements 9 | the RendererImplementation interface. 10 | """ 11 | 12 | def what_to_render_as(self, name: str) -> str: 13 | return 'Drawing {0} as lines'.format(name) 14 | -------------------------------------------------------------------------------- /structural/bridge/impl/renderer_impl.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from abc import ABC, abstractmethod 4 | 5 | 6 | class RendererImplementation(ABC): 7 | """ 8 | RendererImplementation defines the interface for all implementation classes 9 | """ 10 | 11 | @abstractmethod 12 | def what_to_render_as(self, name: str) -> str: 13 | pass 14 | -------------------------------------------------------------------------------- /structural/bridge/impl/vector_renderer_impl.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from structural.bridge.impl.renderer_impl import RendererImplementation 4 | 5 | 6 | class VectorRendererImplementation(RendererImplementation): 7 | """ 8 | VectorRendererImplementation corresponds to a Vector rendering type and implements 9 | the VectorRendererImplementation interface. 10 | """ 11 | 12 | def what_to_render_as(self, name: str) -> str: 13 | return 'Drawing {0} as pixels'.format(name) 14 | -------------------------------------------------------------------------------- /structural/composite/README.md: -------------------------------------------------------------------------------- 1 | # Composite pattern using Python 2 | Implementation of Composite Design Pattern according to the task from 'Design Patterns in Python' course 3 | https://www.udemy.com/course/design-patterns-python/ 4 | -------------------------------------------------------------------------------- /structural/composite/client_app.py: -------------------------------------------------------------------------------- 1 | 2 | from structural.composite.components import SingleValue, ManyValues 3 | 4 | 5 | def main() -> None: 6 | """ 7 | Client app 8 | """ 9 | 10 | single_value = SingleValue(11) 11 | other_values = ManyValues() 12 | other_values.add(22) 13 | other_values.add(33) 14 | 15 | all_values = ManyValues() 16 | all_values.add(single_value) 17 | all_values.add(other_values) 18 | 19 | print(all_values.sum()) 20 | 21 | 22 | if __name__ == '__main__': 23 | main() 24 | -------------------------------------------------------------------------------- /structural/composite/components/__init__.py: -------------------------------------------------------------------------------- 1 | from structural.composite.components.value import Value 2 | from structural.composite.components.single_value import SingleValue 3 | from structural.composite.components.many_values import ManyValues 4 | -------------------------------------------------------------------------------- /structural/composite/components/many_values.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import List, Union 3 | 4 | from ..components import Value, SingleValue 5 | 6 | 7 | class ManyValues(Value): 8 | """ 9 | The ManyValues class represents multiple object that may have 10 | several values or even include other ManyValues objects. ManyValues(Composite) objects 11 | delegate the actual work to their children and then "sum-up" the result. 12 | """ 13 | 14 | def __init__(self) -> None: 15 | self._values: List[Value] = [] 16 | 17 | def add(self, value: Union[Value, int]) -> None: 18 | if isinstance(value, int): 19 | value = SingleValue(value) 20 | self._values.append(value) 21 | 22 | def remove(self, value: Value) -> None: 23 | self._values.remove(value) 24 | 25 | def is_composite(self) -> bool: 26 | return True 27 | 28 | def sum(self) -> int: 29 | result = 0 30 | for value in self._values: 31 | result += value.sum() 32 | return result 33 | -------------------------------------------------------------------------------- /structural/composite/components/single_value.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from ..components.value import Value 4 | 5 | 6 | class SingleValue(Value): 7 | """ 8 | The SingleValue class represents single value of a composition. It can't 9 | have multiple values. 10 | """ 11 | def __init__(self, value: int) -> None: 12 | self.value = value 13 | 14 | def sum(self) -> int: 15 | return self.value 16 | -------------------------------------------------------------------------------- /structural/composite/components/value.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from abc import ABC, abstractmethod 3 | 4 | 5 | class Value(ABC): 6 | """ 7 | The base Value class declares common operations for both simple and 8 | complex values of a composition. 9 | """ 10 | 11 | def add(self, value: Value) -> None: 12 | pass 13 | 14 | def remove(self, value: Value) -> None: 15 | pass 16 | 17 | def is_composite(self) -> bool: 18 | return False 19 | 20 | @abstractmethod 21 | def sum(self) -> int: 22 | pass 23 | -------------------------------------------------------------------------------- /structural/decorator/README.md: -------------------------------------------------------------------------------- 1 | # Decorator pattern using Python 2 | Implementation of Decorator Design Pattern according to the task from 'Design Patterns in Python' course 3 | https://www.udemy.com/course/design-patterns-python/ 4 | -------------------------------------------------------------------------------- /structural/decorator/client_app.py: -------------------------------------------------------------------------------- 1 | from structural.decorator.components import Circle, Square 2 | from structural.decorator.decorators import ColoredShape 3 | 4 | 5 | def main() -> None: 6 | """ 7 | Client app 8 | """ 9 | 10 | circle = ColoredShape(Circle(5), 'red') 11 | print(circle) 12 | print("Try to perform resizing...") 13 | circle.resize(2) 14 | print(circle) 15 | square = Square(4) 16 | print(square) 17 | square = ColoredShape(square, 'blue') 18 | print(square) 19 | print("Try to perform resizing...") 20 | square.resize(2) 21 | print(square) 22 | 23 | 24 | if __name__ == '__main__': 25 | main() 26 | -------------------------------------------------------------------------------- /structural/decorator/components/__init__.py: -------------------------------------------------------------------------------- 1 | from structural.decorator.components.shape import Shape 2 | from structural.decorator.components.circle import Circle 3 | from structural.decorator.components.square import Square 4 | -------------------------------------------------------------------------------- /structural/decorator/components/circle.py: -------------------------------------------------------------------------------- 1 | from .shape import Shape 2 | 3 | 4 | class Circle(Shape): 5 | """ 6 | The concrete Circle class provide default implementations of the operation. 7 | """ 8 | 9 | def __init__(self, radius: int) -> None: 10 | self.radius = radius 11 | 12 | def resize(self, factor: int) -> None: 13 | """ 14 | This method is intently not included in the base class. It should work only in Circle class. 15 | """ 16 | self.radius *= factor 17 | 18 | def __str__(self) -> str: 19 | return f'A circle of radius {self.radius}' 20 | -------------------------------------------------------------------------------- /structural/decorator/components/shape.py: -------------------------------------------------------------------------------- 1 | from abc import ABC 2 | 3 | 4 | class Shape(ABC): 5 | """ 6 | The base Shape interface defines operations that can be altered by decorators. 7 | """ 8 | 9 | def __str__(self) -> str: 10 | return '' 11 | -------------------------------------------------------------------------------- /structural/decorator/components/square.py: -------------------------------------------------------------------------------- 1 | from .shape import Shape 2 | 3 | 4 | class Square(Shape): 5 | """ 6 | The concrete Square class provide default implementations of the operation. 7 | """ 8 | 9 | def __init__(self, side: int) -> None: 10 | self.side = side 11 | 12 | def __str__(self) -> str: 13 | return f'A square with side {self.side}' 14 | -------------------------------------------------------------------------------- /structural/decorator/decorators/__init__.py: -------------------------------------------------------------------------------- 1 | from structural.decorator.decorators.decorator import Decorator 2 | from structural.decorator.decorators.colored_shape import ColoredShape 3 | -------------------------------------------------------------------------------- /structural/decorator/decorators/colored_shape.py: -------------------------------------------------------------------------------- 1 | from ..components.shape import Shape 2 | from .decorator import Decorator 3 | 4 | 5 | class ColoredShape(Decorator): 6 | """ 7 | The concrete wrapping code. ColoredShape execute his behavior after the call to a wrapped object. 8 | """ 9 | 10 | def __init__(self, shape: Shape, color: str) -> None: 11 | super().__init__(shape) 12 | self.color = color 13 | 14 | def __str__(self) -> str: 15 | return f'{self._shape} has the color {self.color}' 16 | -------------------------------------------------------------------------------- /structural/decorator/decorators/decorator.py: -------------------------------------------------------------------------------- 1 | from ..components.shape import Shape 2 | 3 | 4 | class Decorator(Shape): 5 | """ 6 | The base Decorator wrapping code. 7 | """ 8 | _shape: Shape = None 9 | 10 | def __init__(self, shape: Shape) -> None: 11 | self._shape = shape 12 | 13 | @property 14 | def shape(self) -> Shape: 15 | """ 16 | The Decorator delegates all work to the wrapped component. 17 | """ 18 | 19 | return self._shape 20 | 21 | def resize(self, factor: int) -> None: 22 | """ 23 | This method wasn't included in the base class. 24 | We want to call resize only for the concrete component classes that have it implemented. Otherwise, ignore it. 25 | """ 26 | resize = getattr(self._shape, "resize", None) # if concrete shape has 'resize' method we can call it 27 | if callable(resize): 28 | self.shape.resize(factor) 29 | 30 | def __str__(self) -> str: 31 | return str(self._shape) 32 | -------------------------------------------------------------------------------- /structural/facade/README.md: -------------------------------------------------------------------------------- 1 | # Façade pattern using Python 2 | Implementation of Façade Design Pattern according to the task from 'Design Patterns in Python' course 3 | https://www.udemy.com/course/design-patterns-python/ 4 | -------------------------------------------------------------------------------- /structural/facade/client_app.py: -------------------------------------------------------------------------------- 1 | from structural.facade.magic_square_generator import MagicSquareGenerator 2 | 3 | 4 | def main() -> None: 5 | """ 6 | Client app 7 | """ 8 | 9 | gen = MagicSquareGenerator() 10 | 11 | print("Try first time:") 12 | square = gen.generate(3) 13 | gen.print_matrix(square) 14 | print(f"Found magic square at {gen.checked_combinations} combination\n") 15 | 16 | print("Try second time:") 17 | square = gen.generate_another(3) 18 | gen.print_matrix(square) 19 | print(f"Found magic square at {gen.checked_combinations} combination") 20 | 21 | 22 | if __name__ == '__main__': 23 | main() 24 | -------------------------------------------------------------------------------- /structural/facade/magic_square_generator.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from .subsystems import Generator, Splitter, Verifier 4 | 5 | 6 | class MagicSquareGenerator: 7 | """ 8 | This class generates a magic square of a given size. 9 | Magic square wiki: https://en.wikipedia.org/wiki/Magic_square 10 | """ 11 | 12 | def __init__(self) -> None: 13 | self.g = Generator() 14 | self.s = Splitter() 15 | self.v = Verifier() 16 | self.checked_combinations = 0 17 | 18 | def generate(self, size: int) -> List[List[int]]: 19 | square = self._fill_matrix(size) 20 | self.checked_combinations = 1 21 | 22 | while self.v.verify(self.s.split(square)) is False: 23 | square = self._fill_matrix(size) 24 | self.checked_combinations += 1 25 | 26 | return square 27 | 28 | def generate_another(self, size: int) -> List[List[int]]: 29 | self.checked_combinations = 0 30 | while self.v.verify(self.s.split((square := self._fill_matrix(size)))) is False: 31 | self.checked_combinations += 1 32 | 33 | return square 34 | 35 | def _fill_matrix(self, size: int) -> List[List[int]]: 36 | square = [self.g.generate(size) for _ in range(size)] 37 | 38 | return square 39 | 40 | @staticmethod 41 | def print_matrix(square: List[List[int]]) -> None: 42 | for row in square: 43 | print(row) 44 | -------------------------------------------------------------------------------- /structural/facade/subsystems/__init__.py: -------------------------------------------------------------------------------- 1 | from structural.facade.subsystems.splitter import Splitter 2 | from structural.facade.subsystems.generator import Generator 3 | from structural.facade.subsystems.verifier import Verifier 4 | -------------------------------------------------------------------------------- /structural/facade/subsystems/generator.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | from typing import List 3 | 4 | 5 | class Generator: 6 | """ 7 | This class generates a 1-dimensional list of random digits in range 1 to 9 8 | """ 9 | @staticmethod 10 | def generate(count: int) -> List[int]: 11 | return [randint(1, 9) for _ in range(count)] 12 | -------------------------------------------------------------------------------- /structural/facade/subsystems/splitter.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | class Splitter: 5 | """ 6 | This class takes a 2D list and splits it into all possible arrangements of 1D lists. 7 | It gives the columns, the rows and the two diagonals. 8 | """ 9 | @staticmethod 10 | def split(array: List[List[int]]) -> List[List[int]]: 11 | result = [] 12 | 13 | row_count = len(array) 14 | col_count = len(array[0]) 15 | 16 | for r in range(row_count): 17 | the_row = [] 18 | for c in range(col_count): 19 | the_row.append(array[r][c]) 20 | result.append(the_row) 21 | 22 | for c in range(col_count): 23 | the_col = [] 24 | for r in range(row_count): 25 | the_col.append(array[r][c]) 26 | result.append(the_col) 27 | 28 | diag1 = [] 29 | diag2 = [] 30 | 31 | for c in range(col_count): 32 | for r in range(row_count): 33 | if c == r: 34 | diag1.append(array[r][c]) 35 | r2 = row_count - r - 1 36 | if c == r2: 37 | diag2.append(array[r][c]) 38 | 39 | result.append(diag1) 40 | result.append(diag2) 41 | 42 | return result 43 | -------------------------------------------------------------------------------- /structural/facade/subsystems/verifier.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | class Verifier: 5 | """ 6 | This class takes a 2D list and verifies that the sum of elements in every sublist is the same. 7 | """ 8 | @staticmethod 9 | def verify(arrays: List[List[int]]) -> bool: 10 | first = sum(arrays[0]) 11 | 12 | for i in range(1, len(arrays)): 13 | if sum(arrays[i]) != first: 14 | return False 15 | 16 | return True 17 | -------------------------------------------------------------------------------- /structural/flyweight/README.md: -------------------------------------------------------------------------------- 1 | # Flyweight pattern using Python 2 | Implementation of Flyweight Design Pattern according to the task from 'Design Patterns in Python' course 3 | https://www.udemy.com/course/design-patterns-python/ 4 | -------------------------------------------------------------------------------- /structural/flyweight/client_app.py: -------------------------------------------------------------------------------- 1 | from structural.flyweight.sentence import Sentence 2 | 3 | 4 | def main() -> None: 5 | """ 6 | Client app 7 | """ 8 | 9 | sentence = Sentence('Hello, world.') 10 | print(sentence) 11 | 12 | sentence[0].bold = True 13 | sentence[1].bold = True 14 | print(sentence) 15 | 16 | sentence[0].capitalized = True 17 | sentence[1].italic = True 18 | print(sentence) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /structural/flyweight/sentence.py: -------------------------------------------------------------------------------- 1 | from .word_formatting import WordFormatting 2 | 3 | 4 | class Sentence: 5 | """ 6 | This class provides an interface such that the indexer returns a flyweight 7 | that can be used to capitalize or make in bold/italic a particular word in the sentence 8 | """ 9 | def __init__(self, plain_text: str) -> None: 10 | self.words = plain_text.split(' ') 11 | self.tokens = {} 12 | 13 | def __getitem__(self, item: int) -> WordFormatting: 14 | wt = WordFormatting() 15 | self.tokens[item] = wt 16 | return self.tokens[item] 17 | 18 | def __str__(self) -> str: 19 | result = [] 20 | for index in range(len(self.words)): 21 | word = self.words[index] 22 | if index in self.tokens and self.tokens[index].capitalized: 23 | word = word.upper() 24 | if index in self.tokens and self.tokens[index].bold: 25 | word = f"\033[1m{word}\033[0m" 26 | if index in self.tokens and self.tokens[index].capitalized: 27 | word = f"\x1B[3m{word}\x1B[3m" 28 | result.append(word) 29 | return ' '.join(result) 30 | 31 | -------------------------------------------------------------------------------- /structural/flyweight/word_formatting.py: -------------------------------------------------------------------------------- 1 | class WordFormatting: 2 | """ 3 | Flyweight class that provides formatting for any word 4 | """ 5 | def __init__(self, capitalized: bool = False, bold: bool = False, italic: bool = False) -> None: 6 | self.capitalized = capitalized 7 | self.bold = bold 8 | self.italic = italic 9 | -------------------------------------------------------------------------------- /structural/proxy/README.md: -------------------------------------------------------------------------------- 1 | # Proxy pattern using Python 2 | Implementation of Proxy Design Pattern according to the task from 'Design Patterns in Python' course 3 | https://www.udemy.com/course/design-patterns-python/ 4 | -------------------------------------------------------------------------------- /structural/proxy/client_app.py: -------------------------------------------------------------------------------- 1 | from structural.proxy.impl import RealPerson, ProxyPerson 2 | 3 | 4 | def main() -> None: 5 | """ 6 | Client app 7 | """ 8 | 9 | age = 10 10 | young_person = RealPerson('Ivan', age) 11 | young_responsible_person = ProxyPerson('Sergio', age) 12 | 13 | print("Young person do:") 14 | print(young_person.drive()) 15 | print(young_person.drink()) 16 | print(young_person.drink_and_drive()) 17 | 18 | print("\nYoung responsible person do:") 19 | print(young_responsible_person.drive()) 20 | print(young_responsible_person.drink()) 21 | print(young_responsible_person.drink_and_drive()) 22 | 23 | age = 20 24 | person = RealPerson('Andrii', age) 25 | responsible_person = ProxyPerson('Yurii', age) 26 | 27 | print("\nPerson do:") 28 | print(person.drive()) 29 | print(person.drink()) 30 | print(person.drink_and_drive()) 31 | 32 | print("\nResponsible person do:") 33 | print(responsible_person.drive()) 34 | print(responsible_person.drink()) 35 | print(responsible_person.drink_and_drive()) 36 | 37 | 38 | if __name__ == '__main__': 39 | main() 40 | -------------------------------------------------------------------------------- /structural/proxy/impl/__init__.py: -------------------------------------------------------------------------------- 1 | from .person import Person 2 | from .real_person import RealPerson 3 | from .proxy_person import ProxyPerson 4 | -------------------------------------------------------------------------------- /structural/proxy/impl/person.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from abc import ABC, abstractmethod 3 | 4 | 5 | class Person(ABC): 6 | """ 7 | The Person interface declares common operations for both RealPerson and 8 | the ProxyPerson. As long as the client works with RealPerson using this 9 | interface, you'll be able to pass it a proxy person instead of a real person. 10 | """ 11 | @abstractmethod 12 | def drink(self) -> str: 13 | pass 14 | 15 | @abstractmethod 16 | def drive(self) -> str: 17 | pass 18 | 19 | @abstractmethod 20 | def drink_and_drive(self) -> str: 21 | pass 22 | -------------------------------------------------------------------------------- /structural/proxy/impl/proxy_person.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from .person import Person 3 | from .real_person import RealPerson 4 | 5 | 6 | class ProxyPerson(Person): 7 | """ 8 | The ProxyPerson has an interface identical to the RealPerson. 9 | But it can change the behavior of RealPerson without changing its code. 10 | """ 11 | 12 | def __init__(self, name: str, age: int) -> None: 13 | self.name = name 14 | self.age = age 15 | self._person = RealPerson(name, age) 16 | 17 | def drink(self) -> str: 18 | return f'{self.name} is too young to drink.' if self.age < 18 else self._person.drink() 19 | 20 | def drive(self) -> str: 21 | return f'{self.name} is too young to drive.' if self.age < 16 else self._person.drive() 22 | 23 | def drink_and_drive(self) -> str: 24 | return 'Drunk driving is too dangerous!' 25 | -------------------------------------------------------------------------------- /structural/proxy/impl/real_person.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from .person import Person 3 | 4 | 5 | class RealPerson(Person): 6 | """ 7 | The RealPerson contains some core business logic. 8 | """ 9 | def __init__(self, name: str, age: int) -> None: 10 | self.name = name 11 | self.age = age 12 | 13 | def drink(self) -> str: 14 | return f'{self.name} is drinking.' 15 | 16 | def drive(self) -> str: 17 | return f'{self.name} is driving.' 18 | 19 | def drink_and_drive(self) -> str: 20 | return f'{self.name} is drunk driving.' 21 | --------------------------------------------------------------------------------