├── Pipfile ├── advanced_python_topics ├── multiple_inherance.py ├── decorator_as_annotation.py ├── decorator.py └── inheritance.py ├── creational ├── prototype │ ├── udemy.py │ └── refactoring-guru.py ├── factory │ ├── udemy.py │ └── refactoring-guru.py ├── borg │ └── udemy.py ├── singleton │ ├── python-patterns.py │ ├── udemy.py │ └── refactoring-guru.py ├── abstract_factory │ ├── udemy.py │ └── refactoring-guru.py └── builder │ ├── udemy.py │ └── refactoring-guru.py ├── structural ├── proxy │ ├── udemy-2.py │ ├── udemy-1.py │ └── refactoring-guru.py ├── facade │ ├── udemy.py │ └── refactoring-guru.py ├── decorator │ ├── udemy.py │ └── refactoring-guru.py ├── adapter │ ├── udemy.py │ └── refactoring-guru.py ├── flyweight │ └── refactoring-guru.py ├── composite │ └── refactoring-guru.py └── bridge │ └── refactoring-guru.py ├── LICENSE ├── behavioral ├── memento │ ├── udemy.py │ └── refactoring-guru.py ├── template_method │ ├── udemy.py │ └── refactoring-guru.py ├── state │ ├── udemy.py │ └── refactoring-guru.py ├── chain_of_responsability │ ├── udemy.py │ └── refactoring-guru.py ├── strategy │ ├── udemy.py │ └── refactoring-guru.py ├── observer │ ├── udemy.py │ └── refactoring-guru.py ├── command │ ├── udemy.py │ └── refactoring-guru.py ├── mediator │ └── refactoring-guru.py ├── iterator │ ├── refactoring-guru-1.py │ └── refactoring-guru-2.py ├── interpreter │ └── udemy.py └── visitor │ └── refactoring-guru.py ├── .gitignore ├── Pipfile.lock └── README.md /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | pylint = "*" 8 | 9 | [packages] 10 | 11 | [requires] 12 | python_version = "3.7" 13 | -------------------------------------------------------------------------------- /advanced_python_topics/multiple_inherance.py: -------------------------------------------------------------------------------- 1 | 2 | class A: 3 | def __init__(self): 4 | print('A') 5 | 6 | @staticmethod 7 | def bar(): 8 | print('bar') 9 | 10 | 11 | class B: 12 | def __init__(self): 13 | print('B') 14 | 15 | @staticmethod 16 | def foo(): 17 | print('foo') 18 | 19 | 20 | class C(A, B): 21 | def foobar(self): 22 | self.foo() 23 | self.bar() 24 | 25 | 26 | c = C() # "A" 27 | C.__mro__ # (__main__.C, __main__.A, __main__.B, object) 28 | c.foobar() # foo \n bar -------------------------------------------------------------------------------- /advanced_python_topics/decorator_as_annotation.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | 4 | def timing_function(some_function): 5 | def wrapper(): 6 | t1 = time.time() 7 | some_function() 8 | t2 = time.time() 9 | print('Time is took to run the function: ' + str(t2 - t1) + '\n') 10 | return wrapper 11 | 12 | 13 | @timing_function 14 | def my_function(): 15 | num_list = [] 16 | for num in range(0, 10000): 17 | num_list.append(num) 18 | print('Sum of all the numbers: ' + str(sum(num_list)) + '\n') 19 | 20 | 21 | my_function() 22 | -------------------------------------------------------------------------------- /advanced_python_topics/decorator.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | 4 | def timing_function(some_function): 5 | def wrapper(): 6 | t1 = time.time() 7 | some_function() 8 | t2 = time.time() 9 | print('Time is took to run the function: ' + str(t2 - t1) + '\n') 10 | return wrapper 11 | 12 | 13 | def my_function(): 14 | num_list = [] 15 | for num in range(0, 10000): 16 | num_list.append(num) 17 | print('Sum of all the numbers: ' + str(sum(num_list)) + '\n') 18 | 19 | 20 | wrapped_function = timing_function(my_function) 21 | wrapped_function() 22 | -------------------------------------------------------------------------------- /creational/prototype/udemy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Specify the kinds of objects to use a prototypical instance, 3 | and create new objects by copying this prototype. 4 | """ 5 | 6 | from copy import deepcopy 7 | 8 | 9 | class Point: 10 | 11 | def __init__(self, x, y): 12 | self.x = x 13 | self.y = y 14 | 15 | def move(self, x, y): 16 | self.x += x 17 | self.y += y 18 | 19 | def clone(self): 20 | return deepcopy(self) 21 | 22 | def __str__(self): 23 | return "{}, {}".format(self.x, self.y) 24 | 25 | 26 | # client 27 | point = Point(0, 0) 28 | cloned_point = point.clone() 29 | 30 | point.move(1, 1) 31 | print(point) 32 | 33 | cloned_point.move(2, 2) 34 | print(cloned_point) 35 | -------------------------------------------------------------------------------- /structural/proxy/udemy-2.py: -------------------------------------------------------------------------------- 1 | """ 2 | A proxy provides a surrogate or place holder to provide 3 | access to an object. 4 | 5 | Ex2: 6 | Add a wrapper and delegation to protect the real component 7 | from undue complexity 8 | """ 9 | 10 | class Blog: 11 | 12 | def read(self): 13 | print('Read the blog') 14 | 15 | def write(self): 16 | print('Write the blog') 17 | 18 | 19 | class GenericProxy: 20 | 21 | def __init__(self, target): 22 | self.target = target 23 | 24 | def __getattr__(self, attr): 25 | return getattr(self.target, attr) 26 | 27 | 28 | class AnonUserBlogProxy(GenericProxy): 29 | 30 | def __init__(self, blog): 31 | super().__init__(blog) 32 | 33 | def write(self): 34 | print('Only authorized users can write blog posts.') 35 | 36 | 37 | blog = Blog() 38 | blog.write() 39 | 40 | proxy = AnonUserBlogProxy(blog) 41 | proxy.write() 42 | -------------------------------------------------------------------------------- /creational/factory/udemy.py: -------------------------------------------------------------------------------- 1 | """ 2 | A factory pattern defines a interface for creating an 3 | object, but defer object instantiation to run time. 4 | """ 5 | 6 | # abstract class (Interface) 7 | class ShapeInterface: 8 | 9 | def draw(self): 10 | raise NotImplementedError() 11 | 12 | 13 | # concrete classes 14 | class Circle(ShapeInterface): 15 | 16 | def draw(self): 17 | print('Circle.draw') 18 | 19 | 20 | class Square(ShapeInterface): 21 | 22 | def draw(self): 23 | print('Square.draw') 24 | 25 | 26 | # factory 27 | class ShapeFactory: 28 | 29 | @staticmethod 30 | def get_shape(shape_type): 31 | if shape_type == 'circle': 32 | return Circle() 33 | elif shape_type == 'square': 34 | return Square() 35 | raise Exception('Could not find shape ' + shape_type) 36 | 37 | # run 38 | ShapeFactory.get_shape('circle') 39 | ShapeFactory.get_shape('square') 40 | ShapeFactory.get_shape('triange') -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Rafael Cassau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /creational/borg/udemy.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Borg Idiom (a.k.a monostate pattern) lets a class have as many 3 | instances as one likes, but ensures that they all share the same state. 4 | 5 | __init__ doesn't return anything; it's only responsible for initializing 6 | the instance after it's beem created. 7 | 8 | 9 | Pros: 10 | Derivatives of monostate classes can also be monostate. 11 | Access to monostate objects does not have to be through pointers of references. 12 | 13 | Cons: 14 | No instantiation policy can exist for Monostate classes. 15 | Monostate instances may be allocated and deallocated many times. 16 | """ 17 | 18 | class Borg: 19 | __shared_state = {} 20 | 21 | def __init__(self): 22 | print(self.__shared_state) 23 | self.__dict__ = self.__shared_state 24 | 25 | 26 | class Client: 27 | 28 | def run(self): 29 | b = Borg() 30 | c = Borg() 31 | 32 | print('b == c is {}'.format(b == c)) 33 | print('b is c is {}'.format(b is c)) 34 | 35 | b.val = 'milkshake' 36 | 37 | print('c.val is {} shared state'.format(c.val)) 38 | 39 | 40 | client = Client() 41 | client.run() 42 | 43 | -------------------------------------------------------------------------------- /creational/singleton/python-patterns.py: -------------------------------------------------------------------------------- 1 | """ 2 | Singleton is a creational design pattern that lets you 3 | ensure that a class has only one instance and provide 4 | a globla access point to this instance. 5 | """ 6 | 7 | class SingletonMetaClass(type): 8 | 9 | def __init__(cls, name, bases, dict): 10 | super().__init__(name, bases, dict) 11 | original_new = cls.__new__ 12 | 13 | def my_new(cls, *args, **kwargs): 14 | if cls.instance == None: 15 | cls.instance = original_new(cls) 16 | return cls.instance 17 | 18 | cls.instance = None 19 | cls.__new__ = staticmethod(my_new) 20 | 21 | 22 | class Bar(metaclass=SingletonMetaClass): 23 | 24 | def __init__(self, val): 25 | self.val = val 26 | 27 | def __str__(self): 28 | return 'self {}'.format(self.val) 29 | 30 | 31 | class Client: 32 | 33 | def run(self): 34 | x = Bar('sausage') 35 | y = Bar('eggs') 36 | z = Bar('spam') 37 | 38 | print('x: {}'.format(x)) 39 | print('y: {}'.format(y)) 40 | print('z: {}'.format(z)) 41 | 42 | print(x is y is z) 43 | 44 | 45 | client = Client() 46 | client.run() 47 | -------------------------------------------------------------------------------- /advanced_python_topics/inheritance.py: -------------------------------------------------------------------------------- 1 | 2 | class Pet: 3 | """ Base class for all pets """ 4 | def __init__(self, name, species): 5 | self.name = name 6 | self.species = species 7 | 8 | def get_name(self): 9 | return self.name 10 | 11 | def get_species(self): 12 | return self.species 13 | 14 | def __str__(self): 15 | return '{} is a {}'.format(self.name, self.species) 16 | 17 | 18 | class Dog(Pet): 19 | 20 | def __init__(self, name, chases_cats): 21 | """ 22 | This is a overloading 23 | Same method with custom parameters 24 | """ 25 | super().__init__(name, 'Dog') 26 | self.chases_cats = chases_cats 27 | 28 | def chases_cats(self): 29 | return self.chases_cats 30 | 31 | def __str__(self): 32 | """ 33 | This is a override 34 | Same method and same attributes 35 | """ 36 | additional_info = '' 37 | if self.chases_cats: 38 | additional_info = ' who chases cats' 39 | return super().__str__() + additional_info 40 | 41 | 42 | p = Pet('Polly', 'Parrot') 43 | p.__str__() 44 | Pet.__subclasses__() 45 | 46 | d = Dog('Fred', True) 47 | d.__str__() 48 | Dog.__bases__ -------------------------------------------------------------------------------- /structural/proxy/udemy-1.py: -------------------------------------------------------------------------------- 1 | """ 2 | A proxy provides a surrogate or place holder to provide 3 | access to an object. 4 | 5 | Ex1: 6 | Use an extra level of indirection to support distributed, 7 | controlled, or conditional access. 8 | """ 9 | 10 | class SubjectInterface: 11 | """ 12 | Define the common interface for RealSubject and Proxy so that a 13 | Proxy can be used anywhere a RealSubject is expected. 14 | """ 15 | def request(self): 16 | raise NotImplementedError() 17 | 18 | 19 | class Proxy(SubjectInterface): 20 | """ 21 | Maintain a reference that lets the proxy access the real subject. 22 | Provide an interface identical to Subject's. 23 | """ 24 | def __init__(self, real_subject): 25 | self.real_subject = real_subject 26 | 27 | def request(self): 28 | print('Proxy may be doing something, like controlling request access.') 29 | self.real_subject.request() 30 | 31 | 32 | class RealSubject(SubjectInterface): 33 | """ 34 | Define the real object that the proxy represents. 35 | """ 36 | def request(self): 37 | print('The real thing is dealing with the request') 38 | 39 | 40 | real_subject = RealSubject() 41 | real_subject.request() 42 | 43 | proxy = Proxy(real_subject) 44 | proxy.request() -------------------------------------------------------------------------------- /creational/singleton/udemy.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Singleton pattern ensures that a class has only 3 | one instance, and provide a global point of access to it, 4 | for example, a logging class. 5 | 6 | __new__ is the first step of instance creation; it's called 7 | before __init__, and is responsible for returning a new instance 8 | of your class. 9 | 10 | 11 | Pros: 12 | Singletons are allocated once and only once. 13 | Policies can be added to the method that provides access to the singleton pointer. 14 | 15 | Cons: 16 | Derivatives of Singletons are not automatically Singletons. 17 | Singletons must always be accessed through a pointer of reference (obtaining this has overhead). 18 | """ 19 | 20 | class Singleton: 21 | __instance = None 22 | 23 | def __new__(cls, val=None): 24 | if cls.__instance is None: 25 | cls.__instance = object.__new__(cls) 26 | 27 | cls.__instance.val = val 28 | 29 | return cls.__instance 30 | 31 | 32 | class Client: 33 | 34 | def run(self): 35 | x = Singleton() 36 | x.val = 'burger' 37 | print(x.val) 38 | 39 | y = Singleton() 40 | y.val = 'chips' 41 | print(y.val) 42 | 43 | print(x.val) 44 | print('x == y is {}'.format(x == y)) 45 | print('x is y is {}'.format(x is y)) 46 | 47 | 48 | client = Client() 49 | client.run() 50 | -------------------------------------------------------------------------------- /behavioral/memento/udemy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Capture and externalize an object's internal state 3 | so that the object can be returned to this state 4 | later. 5 | """ 6 | import copy 7 | from typing import List 8 | 9 | 10 | class Memento: 11 | 12 | def __init__(self, data) -> None: 13 | """ make a deep copy of every variable in the given class. """ 14 | for attribute in vars(data): 15 | setattr(self, attribute, copy.deepcopy(getattr(data, attribute))) 16 | 17 | 18 | class Undoable: 19 | 20 | def __init__(self) -> None: 21 | """ 22 | each instance keeps the latest saved copy so that there is only 23 | one copy of each in memory 24 | """ 25 | self._last: Memento = None 26 | 27 | def save(self) -> None: 28 | self._last = Memento(self) 29 | 30 | def undo(self) -> None: 31 | for attibute in vars(self): 32 | setattr(self, attibute, copy.deepcopy(getattr(self._last, attibute))) 33 | 34 | 35 | class Data(Undoable): 36 | 37 | def __init__(self) -> None: 38 | super().__init__() 39 | self.numbers: List[int] = [] 40 | 41 | 42 | data: Undoable = Data() 43 | 44 | # foward 45 | for i in range(10): 46 | data.save() 47 | data.numbers.append(i) 48 | 49 | data.save() 50 | print(data.numbers) 51 | 52 | #backward 53 | for i in range(10): 54 | data.undo() 55 | print(data.numbers) 56 | -------------------------------------------------------------------------------- /structural/facade/udemy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Facade is structural design pattern that provides a simplified 3 | (but limited) interface to a library, a framework, or any other 4 | complex set of classes. 5 | """ 6 | 7 | 8 | class Engine: 9 | 10 | def __init__(self): 11 | # how much the motor is spinning in revs per minute 12 | self.spin = 0 13 | 14 | def start(self, spin): 15 | self.spin = min(spin, 3000) 16 | 17 | 18 | class StarterMotor: 19 | 20 | def __init__(self): 21 | # how much the starter motor is spinning in revs per minute 22 | self.spin = 0 23 | 24 | def start(self, charge): 25 | # if there is enough power then spin fast 26 | if charge > 50: 27 | self.spin = 2500 28 | 29 | 30 | class Battery: 31 | 32 | def __init__(self): 33 | # % charged, starts flat 34 | self.charge = 0 35 | 36 | 37 | class CarFacade: 38 | # the facade object that deals with the battery, engine and starter motor. 39 | 40 | def __init__(self): 41 | self.battery = Battery() 42 | self.starter = StarterMotor() 43 | self.engine = Engine() 44 | 45 | def turn_key(self): 46 | self.starter.start(self.battery.charge) 47 | self.engine.start(self.starter.spin) 48 | 49 | if self.engine.spin > 0: 50 | print('Engine started') 51 | else: 52 | print('Engine not started') 53 | 54 | def jump(self): 55 | self.battery.charge = 100 56 | print('Jumped') 57 | 58 | 59 | car = CarFacade() 60 | 61 | car.turn_key() 62 | car.jump() 63 | car.turn_key() 64 | -------------------------------------------------------------------------------- /creational/singleton/refactoring-guru.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | 4 | class Singleton: 5 | """ 6 | The Singleton class defines the `getInstance` method that lets clients 7 | access the unique singleton instance. 8 | """ 9 | 10 | _instance: Optional = None 11 | 12 | def __init__(self) -> None: 13 | if Singleton._instance is not None: 14 | raise ReferenceError("Cannot instantiate a singleton class.") 15 | else: 16 | Singleton._instance = self 17 | 18 | @staticmethod 19 | def get_instance() -> Singleton: 20 | """ 21 | The static method that controls the access to the singleton instance. 22 | 23 | This implementation let you subclass the Singleton class while 24 | keeping just one instance of each subclass around. 25 | """ 26 | 27 | if not Singleton._instance: 28 | Singleton() 29 | 30 | return Singleton._instance 31 | 32 | def some_business_logic(self): 33 | """ 34 | Finally, any singleton should define some business logic, which can 35 | be executed on its instance. 36 | """ 37 | pass 38 | 39 | 40 | class Demo: 41 | # The client code. 42 | 43 | def run(self) -> None: 44 | s1 = Singleton.get_instance() 45 | s2 = Singleton.get_instance() 46 | 47 | if id(s1) == id(s2): 48 | print("Singleton works, both variables contain the same instance.") 49 | else: 50 | print("Singleton failed, variables contain different instances.") 51 | 52 | 53 | demo: Demo = Demo() 54 | demo.run() -------------------------------------------------------------------------------- /behavioral/template_method/udemy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Define the skeleton of an algorithm, 3 | deferring some steps to client 4 | subclasses. Let subclasses redefine 5 | certain steps of an algorithm without 6 | changing the algorithm's structure. 7 | """ 8 | 9 | 10 | class MakeMeal: 11 | 12 | def __init__(self) -> None: 13 | self._cost: float = 0 14 | 15 | def buy_ingredients(self, money: float) -> None: 16 | if money < self._cost: 17 | assert 0, "Not enough money to buy ingredients!" 18 | 19 | def prepare(self) -> None: 20 | raise NotImplementedError() 21 | 22 | def cook(self) -> None: 23 | raise NotImplementedError() 24 | 25 | def go(self, money: float) -> None: 26 | self.buy_ingredients(money) 27 | self.prepare() 28 | self.cook() 29 | 30 | 31 | class MakePizza(MakeMeal): 32 | 33 | def __init__(self) -> None: 34 | self._cost: float = 3 35 | 36 | def prepare(self) -> None: 37 | print('Prepare Pizza - make a dough and add toppings.') 38 | 39 | def cook(self) -> None: 40 | print('Cook Pizza - cook in the oven on gas mark 8 for 10 minutes.') 41 | 42 | 43 | class MakeCake(MakeMeal): 44 | 45 | def __init__(self) -> None: 46 | self._cost: float = 2 47 | 48 | def prepare(self) -> None: 49 | print('Prepare Cake - mix ingredients together and pour into a cake tin.') 50 | 51 | def cook(self) -> None: 52 | print('Cook Cake - bake in the oven on gas mark 6 to 20 minutes.') 53 | 54 | 55 | pizza_maker: MakePizza = MakePizza() 56 | pizza_maker.go(5) 57 | 58 | cake_maker: MakeCake = MakeCake() 59 | cake_maker.go(5) 60 | -------------------------------------------------------------------------------- /.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 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | #vscode 104 | .vscode/ 105 | -------------------------------------------------------------------------------- /structural/decorator/udemy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Attach additional responsabilities to an object dynamically. 3 | Decorators provide a flexible alternative to subclassing for 4 | extending functionality. 5 | """ 6 | 7 | 8 | class WindowsInterface: 9 | 10 | def build(self) -> None: 11 | raise NotImplementedError() 12 | 13 | 14 | class Window(WindowsInterface): 15 | 16 | def build(self) -> None: 17 | print('Building a window') 18 | 19 | 20 | class AbstractWindowDecorator(WindowsInterface): 21 | """ 22 | Maintain a reference to a Window object and define 23 | an interface that conforms to Window's interface. 24 | """ 25 | 26 | def __init__(self, window: WindowsInterface): 27 | self._window = window 28 | 29 | def build(self) -> None: 30 | raise NotImplementedError() 31 | 32 | 33 | class BorderDecorator(AbstractWindowDecorator): 34 | 35 | def add_border(self) -> None: 36 | print('Adding border') 37 | 38 | def build(self) -> None: 39 | self.add_border() 40 | self._window.build() 41 | 42 | 43 | class VerticalSBDecorator(AbstractWindowDecorator): 44 | 45 | def add_vertical_scroll_bar(self) -> None: 46 | print('Adding vertical scroll bar') 47 | 48 | def build(self) -> None: 49 | self.add_vertical_scroll_bar() 50 | self._window.build() 51 | 52 | 53 | class HorizontalSBDecorator(AbstractWindowDecorator): 54 | 55 | def add_horizontal_scroll_bar(self) -> None: 56 | print('Adding horizontal scroll bar') 57 | 58 | def build(self) -> None: 59 | self.add_horizontal_scroll_bar() 60 | self._window.build() 61 | 62 | 63 | Window().build() 64 | 65 | BorderDecorator(Window()).build() 66 | 67 | VerticalSBDecorator(Window()).build() 68 | 69 | HorizontalSBDecorator(Window()).build() 70 | 71 | #dynamically wrappers 72 | 73 | HorizontalSBDecorator(VerticalSBDecorator(BorderDecorator(Window()))).build() -------------------------------------------------------------------------------- /behavioral/state/udemy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Allow an object to alter its behavior when 3 | its internal state changes. 4 | """ 5 | from typing import List 6 | 7 | 8 | class ComputerState: 9 | """ 10 | Base state class 11 | """ 12 | _name = 'state' 13 | _allowed: List[str] = [] 14 | 15 | def switch(self, state: "ComputerState") -> None: 16 | """ Switch to new state """ 17 | if state._name in self._allowed: 18 | print(f'Current: {self} -> switching to new state: {state._name}.') 19 | self.__class__ = state 20 | else: 21 | print(f'Current: {self} -> switching to: {state._name}, not possible.') 22 | 23 | def __str__(self) -> str: 24 | return self._name 25 | 26 | 27 | class Off(ComputerState): 28 | """ 29 | Off concrete state 30 | """ 31 | _name = 'off' 32 | _allowed = ['on'] 33 | 34 | 35 | class On(ComputerState): 36 | """ 37 | On concrete state 38 | """ 39 | _name = 'on' 40 | _allowed = ['off', 'suspend', 'hibernate'] 41 | 42 | 43 | class Suspend(ComputerState): 44 | """ 45 | Suspend concrete state 46 | """ 47 | _name = 'suspend' 48 | _allowed = ['on'] 49 | 50 | 51 | class Hibernate(ComputerState): 52 | """ 53 | Hibernate concrete state 54 | """ 55 | _name = 'hibernate' 56 | _allowed = ['on'] 57 | 58 | 59 | class Computer: 60 | 61 | def __init__(self) -> None: 62 | self._current_state: ComputerState = Off() 63 | 64 | def change(self, new_state: ComputerState) -> None: 65 | self._current_state.switch(new_state) 66 | 67 | 68 | computer = Computer() 69 | print(computer._current_state) 70 | 71 | # Off -> On (True) 72 | computer.change(On) 73 | 74 | # On -> Suspend (True) 75 | computer.change(Suspend) 76 | 77 | # Suspend -> Hibernate (False) 78 | computer.change(Hibernate) 79 | 80 | # Suspend -> On (True) 81 | computer.change(On) 82 | 83 | # On -> Off (True) 84 | computer.change(Off) 85 | -------------------------------------------------------------------------------- /creational/abstract_factory/udemy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Provide an interface for creating 3 | families of related objects without 4 | specifying their concrete classes. 5 | """ 6 | 7 | # abstract classes (interfaces) 8 | class Shape2DInterface: 9 | 10 | def draw(self): 11 | raise NotImplementedError() 12 | 13 | 14 | class Shape3DInterface: 15 | 16 | def build(self): 17 | raise NotImplementedError() 18 | 19 | 20 | # concrete 2D classes 21 | class Circle(Shape2DInterface): 22 | 23 | def draw(self): 24 | print('Circle.draw') 25 | 26 | 27 | class Square(Shape2DInterface): 28 | 29 | def draw(self): 30 | print('Square.draw') 31 | 32 | 33 | # concrete 3D classes 34 | class Sphere(Shape3DInterface): 35 | 36 | def build(self): 37 | print('Sphere.build') 38 | 39 | 40 | class Cube(Shape3DInterface): 41 | 42 | def build(self): 43 | print('Cube.build') 44 | 45 | 46 | # abstract shape factory 47 | class ShapeFactoryInterface: 48 | 49 | def get_shape(sides): 50 | raise NotImplementedError() 51 | 52 | 53 | # concrete shape factories 54 | class Shape2DFactory(ShapeFactoryInterface): 55 | 56 | @staticmethod 57 | def get_shape(sides): 58 | if sides == 1: 59 | return Circle() 60 | elif sides == 4: 61 | return Square() 62 | raise Exception('Bad 2D shape creation: shape not defined for ' + sides + ' sides') 63 | 64 | 65 | class Shape3DFactory(ShapeFactoryInterface): 66 | 67 | @staticmethod 68 | def get_shape(sides): 69 | """ here, sides refers to the numbers of faces """ 70 | if sides == 1: 71 | return Sphere() 72 | elif sides == 6: 73 | return Cube() 74 | raise Exception('Bad 3D shape creation: shape not defined for ' + sides + ' faces') 75 | 76 | 77 | # run 78 | shape_2 = Shape2DFactory.get_shape(1) 79 | shape_2.draw() # circle 80 | shape_2 = Shape2DFactory.get_shape(4) 81 | shape_2.draw() # square 82 | 83 | shape_3 = Shape3DFactory.get_shape(1) 84 | shape_3.build() # sphere 85 | shape_3 = Shape3DFactory.get_shape(6) 86 | shape_3.build() # cube 87 | -------------------------------------------------------------------------------- /behavioral/chain_of_responsability/udemy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Avoids coupling the sender of a request to the 3 | receiver by giving more than one object a chance 4 | to handle the request. 5 | """ 6 | 7 | 8 | class Car: 9 | 10 | def __init__(self, name: str, water: int, fuel: int, oil: int) -> None: 11 | self._name = name 12 | self._water = water 13 | self._fuel = fuel 14 | self._oil = oil 15 | 16 | def is_fine(self) -> bool: 17 | if self._water >= 20 and self._fuel >= 5 and self._oil >= 10: 18 | print('Car is good to go') 19 | return True 20 | else: 21 | return False 22 | 23 | 24 | class BaseHandler: 25 | 26 | def __init__(self, successor: "BaseHandler" = None): 27 | self._sucessor = successor 28 | 29 | def handle_request(self, car: Car) -> None: 30 | if not car.is_fine() and self._sucessor is not None: 31 | self._sucessor.handle_request(car) 32 | 33 | 34 | class WaterHandler(BaseHandler): 35 | 36 | def handle_request(self, car): 37 | if car._water < 20: 38 | car._water = 100 39 | print('Added water') 40 | 41 | super().handle_request(car) 42 | 43 | 44 | class FuelHandler(BaseHandler): 45 | 46 | def handle_request(self, car): 47 | if car._fuel < 5: 48 | car._fuel = 100 49 | print('Added fuel') 50 | 51 | super().handle_request(car) 52 | 53 | 54 | class OilHandler(BaseHandler): 55 | 56 | def handle_request(self, car): 57 | if car._oil < 10: 58 | car._oil = 100 59 | print('Added oil') 60 | 61 | super().handle_request(car) 62 | 63 | 64 | garage_handler: BaseHandler = OilHandler(FuelHandler(WaterHandler())) 65 | car: Car = Car(name='my car', water=1, fuel=1, oil=1) 66 | garage_handler.handle_request(car) 67 | 68 | 69 | car: Car = Car(name='my car', water=5, fuel=5, oil=5) 70 | garage_handler.handle_request(car) 71 | 72 | car: Car = Car(name='my car', water=10, fuel=10, oil=10) 73 | garage_handler.handle_request(car) 74 | 75 | car: Car = Car(name='my car', water=20, fuel=20, oil=20) 76 | garage_handler.handle_request(car) -------------------------------------------------------------------------------- /behavioral/strategy/udemy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Define a family of algorithms, 3 | encapsulate each one, and make them 4 | interchangeable. Strategy lets the 5 | algorithm vary independently from 6 | the clients that use it. 7 | """ 8 | from typing import List, Tuple 9 | 10 | 11 | class PrimeFinder: 12 | 13 | def __init__(self) -> None: 14 | self._primes: List[int] = [] 15 | 16 | def calculate(self, limit: int) -> None: 17 | """ Will calculate all the primes below limit. """ 18 | raise NotImplementedError() 19 | 20 | def out(self) -> None: 21 | """ Prints the list of primes prefixed with which algorithm made it """ 22 | print(self.__class__.__name__) 23 | for prime in self._primes: 24 | print(prime) 25 | 26 | 27 | class HardcodedPrimeFinder(PrimeFinder): 28 | 29 | def calculate(self, limit: int) -> None: 30 | _hardcoded_primes: Tuple[int] = (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47) 31 | for prime in _hardcoded_primes: 32 | if prime < limit: 33 | self._primes.append(prime) 34 | 35 | 36 | class StandardPrimeFinder(PrimeFinder): 37 | 38 | def calculate(self, limit: int) -> None: 39 | self._primes: List[int] = [2] 40 | # check only odd numbers 41 | for _number in range(3, limit, 2): 42 | _is_prime: bool = True 43 | 44 | for _prime in self._primes: 45 | if _number % _prime == 0: 46 | _is_prime = False 47 | break 48 | 49 | if _is_prime: 50 | self._primes.append(_number) 51 | 52 | 53 | class PrimeFinderClient: 54 | 55 | def __init__(self, limit: int) -> None: 56 | self._limit = limit 57 | if limit <= 50: 58 | self._finder = HardcodedPrimeFinder() 59 | else: 60 | self._finder = StandardPrimeFinder() 61 | 62 | def get_primes(self) -> None: 63 | self._finder.calculate(self._limit) 64 | self._finder.out() 65 | 66 | 67 | prime_finder_client: PrimeFinderClient = PrimeFinderClient(50) 68 | prime_finder_client.get_primes() 69 | 70 | prime_finder_client: PrimeFinderClient = PrimeFinderClient(100) 71 | prime_finder_client.get_primes() 72 | -------------------------------------------------------------------------------- /behavioral/observer/udemy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Define a one-to-many dependency between objects 3 | so that when on object changes state, all its 4 | dependents are notified and updated automatically. 5 | """ 6 | from typing import List 7 | 8 | 9 | class ObserverInterface: 10 | """ 11 | ObserverInterface defines a update method that will receiver 12 | all notifications when a state of observable object is updated. 13 | """ 14 | def update(self, *args, **kwargs) -> None: 15 | raise NotImplementedError() 16 | 17 | 18 | class Observable: 19 | """ 20 | Observable define some methods to register observers that will be notified 21 | when any change in this object occur. 22 | """ 23 | def __init__(self) -> None: 24 | self._observers: List[ObserverInterface] = [] 25 | 26 | def register(self, observer: ObserverInterface) -> None: 27 | self._observers.append(observer) 28 | 29 | def unregister(self, observer: ObserverInterface) -> None: 30 | if observer in self._observers: 31 | self._observers.remove(observer) 32 | 33 | def unregister_all(self) -> None: 34 | self._observers.clear() 35 | 36 | def update_observers(self, *args, **kwargs) -> None: 37 | for observer in self._observers: 38 | observer.update(*args, **kwargs) 39 | 40 | 41 | class AmericanStockMarket(ObserverInterface): 42 | """ 43 | Concrete observer will be notified when any change occur in the 44 | observable object. 45 | """ 46 | def update(self, *args, **kwargs) -> None: 47 | print(f'American stock market received: {args}') 48 | print(f'{kwargs}') 49 | 50 | 51 | class EuropeanStockMarket(ObserverInterface): 52 | """ 53 | Concrete observer will be notified when any change occur in the 54 | observable object. 55 | """ 56 | def update(self, *args, **kwargs) -> None: 57 | print(f'European stock market received: {args}') 58 | print(f'{kwargs}') 59 | 60 | 61 | really_big_company: Observable = Observable() 62 | 63 | american_observer: AmericanStockMarket = AmericanStockMarket() 64 | european_observer: EuropeanStockMarket = EuropeanStockMarket() 65 | 66 | really_big_company.register(american_observer) 67 | really_big_company.register(european_observer) 68 | 69 | really_big_company.update_observers('important update', msg='CEO unexpectedly resigns') -------------------------------------------------------------------------------- /structural/adapter/udemy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Convert the interface of a class into another 3 | interface clients expect. Adapter lets classes 4 | work together that couldn't otherwise because of 5 | incompatible interfaces 6 | """ 7 | 8 | class EuropeanSocketInterface: 9 | """ 10 | Adaptee (source) interface 11 | """ 12 | def voltage(self) -> int: 13 | raise NotImplementedError() 14 | 15 | def live(self) -> int: 16 | raise NotImplementedError() 17 | 18 | def neutral(self) -> int: 19 | raise NotImplementedError() 20 | 21 | def earth(self) -> int: 22 | raise NotImplementedError() 23 | 24 | 25 | class USASocketInterface: 26 | """ 27 | Target interface 28 | """ 29 | def voltage(self) -> int: 30 | raise NotImplementedError() 31 | 32 | def live(self) -> int: 33 | raise NotImplementedError() 34 | 35 | def neutral(self) -> int: 36 | raise NotImplementedError() 37 | 38 | 39 | class EuropeanSocket(EuropeanSocketInterface): 40 | """ 41 | Adaptee 42 | """ 43 | def voltage(self) -> int: 44 | return 230 45 | 46 | def live(self) -> int: 47 | return 1 48 | 49 | def neutral(self) -> int: 50 | return -1 51 | 52 | 53 | class AmericanKettle: 54 | """ 55 | Client 56 | """ 57 | __power : EuropeanSocketInterface = None 58 | 59 | def __init__(self, power: EuropeanSocketInterface) -> None: 60 | self.__power = power 61 | 62 | def boil(self) -> None: 63 | if self.__power.voltage() > 110: 64 | print('Kettle on fire!') 65 | else: 66 | if self.__power.live() == 1 and self.__power.neutral() == -1: 67 | print('Coffe time!') 68 | else: 69 | print('No power.') 70 | 71 | 72 | class Adapter(USASocketInterface): 73 | __socket = None 74 | 75 | def __init__(self, socket) -> None: 76 | self.__socket = socket 77 | 78 | def voltage(self) -> int: 79 | return 110 80 | 81 | def live(self) -> int: 82 | return self.__socket.live() 83 | 84 | def neutral(self) -> int: 85 | return self.__socket.neutral() 86 | 87 | 88 | socket = EuropeanSocket() 89 | kettle = AmericanKettle(socket) 90 | kettle.boil() 91 | 92 | socket = EuropeanSocket() 93 | adapter = Adapter(socket) 94 | kettle = AmericanKettle(adapter) 95 | kettle.boil() -------------------------------------------------------------------------------- /creational/factory/refactoring-guru.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | 4 | # buttons 5 | 6 | class ButtonInterface: 7 | """ 8 | The Factory Method pattern is applicable only when there is a products hierarchy. 9 | """ 10 | 11 | def render(self): 12 | raise NotImplementedError 13 | 14 | def on_click(self): 15 | raise NotImplementedError 16 | 17 | 18 | class WindowsButton(ButtonInterface): 19 | 20 | def render(self): 21 | print("TKInter (Test Button)") 22 | self.on_click() 23 | 24 | def on_click(self): 25 | print("WINDOWS -> Button says - 'Hello World!'") 26 | 27 | 28 | class HTMLButton(ButtonInterface): 29 | 30 | def render(self): 31 | print("") 32 | self.on_click() 33 | 34 | def on_click(self): 35 | print("HTML -> Click! Button says - 'Hello World!'") 36 | 37 | 38 | # dialogs factory methods 39 | 40 | class AbstractDialogFactoryMethod: 41 | """ 42 | Base factory class. Note that the "factory" is merely a role 43 | for the class. It should have some core business logic which 44 | needs different products to be created. 45 | """ 46 | def render_window(self): 47 | # Render other window controls. 48 | ok_button = self.create_button() 49 | ok_button.render() 50 | 51 | def create_button(self): 52 | # Therefore we extract all product creation code to a 53 | # special factory method. 54 | raise NotImplementedError 55 | 56 | 57 | class WindowsDialogFactoryMethod(AbstractDialogFactoryMethod): 58 | """ 59 | Concrete factories extend that method to produce different 60 | kinds of products. 61 | """ 62 | def create_button(self): 63 | return WindowsButton() 64 | 65 | 66 | class WebDialogFactoryMethod(AbstractDialogFactoryMethod): 67 | """ 68 | Concrete factories extend that method to produce different 69 | kinds of products. 70 | """ 71 | def create_button(self): 72 | return HTMLButton() 73 | 74 | 75 | # client application 76 | 77 | class ClientApplication: 78 | 79 | def __init__(self): 80 | self.configure() 81 | 82 | def configure(self): 83 | number = randint(1, 10) 84 | if number % 2 == 0: 85 | self.dialog = WindowsDialogFactoryMethod() 86 | else: 87 | self.dialog = WebDialogFactoryMethod() 88 | 89 | def run_business_logic(self): 90 | self.dialog.render_window() 91 | 92 | 93 | # run 94 | ClientApplication().run_business_logic() 95 | -------------------------------------------------------------------------------- /structural/facade/refactoring-guru.py: -------------------------------------------------------------------------------- 1 | """ 2 | Facade is structural design pattern that provides a simplified 3 | (but limited) interface to a library, a framework, or any other 4 | complex set of classes. 5 | """ 6 | import tempfile 7 | 8 | 9 | class VideoFile: 10 | 11 | def __init__(self, name: str): 12 | self.name = name 13 | self.codec_type = name.split('.')[0] 14 | 15 | def get_codec_type(self) -> str: 16 | return self.codec_type 17 | 18 | def get_name(self) -> str: 19 | return self.name 20 | 21 | 22 | class CodecInterface: 23 | 24 | def get_type(self): 25 | raise NotImplementedError() 26 | 27 | 28 | class MPEG4CompressionCodec(CodecInterface): 29 | 30 | def get_type(self) -> str: 31 | return 'mp4' 32 | 33 | 34 | class OggCompressionCodec(CodecInterface): 35 | 36 | def get_type(self) -> str: 37 | return 'ogg' 38 | 39 | 40 | class CodecFactory: 41 | 42 | @staticmethod 43 | def extract(file: VideoFile) -> CodecInterface: 44 | type = file.get_codec_type() 45 | if type == 'mp4': 46 | print('CodecFactory: extracting mpeg audio...') 47 | return MPEG4CompressionCodec() 48 | else: 49 | print('CodecFactory: extracting ogg audio...') 50 | return OggCompressionCodec() 51 | 52 | 53 | class BitrateReader: 54 | 55 | @staticmethod 56 | def read(file: VideoFile, codec: CodecInterface) -> VideoFile: 57 | print('BitrateReader: reading file...') 58 | return file 59 | 60 | @staticmethod 61 | def convert(buffer: VideoFile, codec: CodecInterface) -> VideoFile: 62 | print('BitrateReader: writing file...') 63 | return buffer 64 | 65 | 66 | class AudioMixer: 67 | 68 | def fix(self, result: VideoFile): 69 | print('AudioMixer: fixing audio...') 70 | temp = tempfile.TemporaryFile() 71 | return temp 72 | 73 | 74 | class VideoConversionFacade: 75 | 76 | def convert_video(self, file_name: str, format: str): 77 | print('VideoConversionFacade: conversion started.') 78 | file = VideoFile(file_name) 79 | source_codec = CodecFactory.extract(file) 80 | 81 | if format == 'mp4': 82 | destination_codec = MPEG4CompressionCodec() 83 | else: 84 | destination_codec = OggCompressionCodec() 85 | 86 | buffer = BitrateReader.read(file, source_codec) 87 | intermediate_result = BitrateReader.convert(buffer, destination_codec) 88 | result = AudioMixer().fix(intermediate_result) 89 | print('VideoConversionFacade: conversion completed.') 90 | 91 | return result 92 | 93 | 94 | converter = VideoConversionFacade() 95 | mp4_video = converter.convert_video('youtubevideo.ogg', 'mp4') 96 | -------------------------------------------------------------------------------- /creational/abstract_factory/refactoring-guru.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | """ 4 | Abstract Factory is a creational design pattern 5 | that lets you produce families of related objects 6 | without specifying their concrete classes 7 | """ 8 | 9 | # buttons 10 | 11 | class ButtonInterface: 12 | """ 13 | This is the common interface for buttons family. 14 | """ 15 | 16 | def paint(self): 17 | raise NotImplementedError 18 | 19 | 20 | class MacOSButton(ButtonInterface): 21 | 22 | def paint(self): 23 | print('You have created MacOSButton.') 24 | 25 | 26 | class WindowsButton(ButtonInterface): 27 | 28 | def paint(self): 29 | print('You have created WindowsButton.') 30 | 31 | 32 | # checkboxes 33 | 34 | class CheckboxInterface: 35 | """ 36 | This is the common interface for checkbox family. 37 | """ 38 | 39 | def paint(self): 40 | raise NotImplementedError 41 | 42 | 43 | class MacOSCheckbox(CheckboxInterface): 44 | 45 | def paint(self): 46 | print('You have created MacOSCheckbox.') 47 | 48 | 49 | class WindowsCheckbox(CheckboxInterface): 50 | 51 | def paint(self): 52 | print('You have created WindowsCheckbox.') 53 | 54 | 55 | # factories 56 | 57 | class GUIFactoryInterface: 58 | """ 59 | Abstract factory knows aboult all (abstract) product types. 60 | """ 61 | 62 | def create_button(self): 63 | raise NotImplementedError 64 | 65 | def create_checkbox(self): 66 | raise NotImplementedError 67 | 68 | 69 | class MacOSFactory(GUIFactoryInterface): 70 | 71 | def create_button(self): 72 | return MacOSButton() 73 | 74 | def create_checkbox(self): 75 | return MacOSCheckbox() 76 | 77 | 78 | class WindowsFactory(GUIFactoryInterface): 79 | 80 | def create_button(self): 81 | return WindowsButton() 82 | 83 | def create_checkbox(self): 84 | return WindowsCheckbox() 85 | 86 | 87 | # application class 88 | 89 | class Application: 90 | 91 | def __init__(self, factory): 92 | self.button = factory.create_button() 93 | self.checkbox = factory.create_checkbox() 94 | 95 | def paint(self): 96 | self.button.paint() 97 | self.checkbox.paint() 98 | 99 | 100 | # client 101 | 102 | class Demo: 103 | 104 | def configure_application(self): 105 | number = randint(1, 10) 106 | if number % 2 == 0: 107 | factory = MacOSFactory() 108 | app = Application(factory) 109 | else: 110 | factory = WindowsFactory() 111 | app = Application(factory) 112 | 113 | return app 114 | 115 | def run(self): 116 | application = self.configure_application() 117 | application.paint() 118 | 119 | 120 | # run 121 | Demo().run() 122 | -------------------------------------------------------------------------------- /creational/builder/udemy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Separate the construction of a complex object 3 | from its representation so that the same construction 4 | process can create different representations 5 | """ 6 | 7 | # car 8 | class Car: 9 | 10 | def __init__(self): 11 | self._wheels = [] 12 | self._engine = None 13 | self._body = None 14 | 15 | def set_body(self, body): 16 | self._body = body 17 | 18 | def attach_wheel(self, wheel): 19 | self._wheels.append(wheel) 20 | 21 | def set_engine(self, engine): 22 | self._engine = engine 23 | 24 | def specification(self): 25 | print('body: {}'.format(self._body.shape)) 26 | print('engine horsepower: {}'.format(self._engine.horsepower)) 27 | print('tire size: {}'.format(self._wheels[0].size)) 28 | 29 | 30 | # car parts 31 | class Wheel: 32 | size = None 33 | 34 | 35 | class Engine: 36 | horsepower = None 37 | 38 | 39 | class Body: 40 | shape = None 41 | 42 | 43 | # builder 44 | class BuilderInterface: 45 | 46 | def get_body(self): 47 | raise NotImplementedError 48 | 49 | def get_engine(self): 50 | raise NotImplementedError 51 | 52 | def get_wheel(self): 53 | raise NotImplementedError 54 | 55 | 56 | class JeepBuilder(BuilderInterface): 57 | 58 | def get_body(self): 59 | body = Body() 60 | body.shape = "SUV" 61 | return body 62 | 63 | def get_engine(self): 64 | engine = Engine() 65 | engine.horsepower = 400 66 | return engine 67 | 68 | def get_wheel(self): 69 | wheel = Wheel() 70 | wheel.size = 22 71 | return wheel 72 | 73 | 74 | class NissanBuilder(BuilderInterface): 75 | 76 | def get_body(self): 77 | body = Body() 78 | body.shape = "hatchback" 79 | return body 80 | 81 | def get_engine(self): 82 | engine = Engine() 83 | engine.horsepower = 100 84 | return engine 85 | 86 | def get_wheel(self): 87 | wheel = Wheel() 88 | wheel.size = 16 89 | return wheel 90 | 91 | 92 | # client builder 93 | class Director: 94 | _builder = None 95 | 96 | def set_builder(self, builder): 97 | self._builder = builder 98 | 99 | # The algorithm for assembling a car 100 | def get_car(self): 101 | car = Car() 102 | 103 | body = self._builder.get_body() 104 | car.set_body(body) 105 | 106 | # Then engine 107 | engine = self._builder.get_engine() 108 | car.set_engine(engine) 109 | 110 | # And four wheels 111 | for i in range(4): 112 | wheel = self._builder.get_wheel() 113 | car.attach_wheel(wheel) 114 | 115 | return car 116 | 117 | 118 | # run 119 | director = Director() 120 | 121 | # jeep 122 | director.set_builder(JeepBuilder()) 123 | jeep = director.get_car() 124 | jeep.specification() 125 | 126 | # nissan 127 | director.set_builder(NissanBuilder()) 128 | nissan = director.get_car() 129 | nissan.specification() 130 | -------------------------------------------------------------------------------- /behavioral/command/udemy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Decouple the object that invokes the operation from 3 | the one that knows how to perform it. 4 | """ 5 | from typing import List 6 | 7 | 8 | class Screen: 9 | 10 | def __init__(self, text: str = '') -> None: 11 | self._text = text 12 | self._clip_board: str = '' 13 | 14 | def cut(self, start: int = 0, end: int =0) -> None: 15 | self._clip_board = self._text[start:end] 16 | self._text = self._text[:start] + self._text[end:] 17 | 18 | def paste(self, offset: int = 0) -> None: 19 | self._text = self._text[:offset] + self._clip_board + self._text[offset:] 20 | 21 | def clear_clipboard(self): 22 | self._clip_board = '' 23 | 24 | def length(self): 25 | return len(self._text) 26 | 27 | def __str__(self): 28 | return self._text 29 | 30 | 31 | class ScreenCommandInterface: 32 | """ 33 | Screen command interface 34 | """ 35 | def __init__(self, screen: Screen) -> None: 36 | self._screen = screen 37 | self._previous_state: str = screen._text 38 | 39 | def execute(self) -> None: 40 | raise NotImplementedError() 41 | 42 | def undo(self): 43 | raise NotImplementedError() 44 | 45 | 46 | class CutCommand(ScreenCommandInterface): 47 | 48 | def __init__(self, screen: Screen, start: int = 0, end: int = 0) -> None: 49 | super().__init__(screen) 50 | self._start = start 51 | self._end = end 52 | 53 | def execute(self) -> None: 54 | self._screen.cut(start=self._start, end=self._end) 55 | 56 | def undo(self): 57 | self._screen.clear_clipboard() 58 | self._screen._text = self._previous_state 59 | 60 | 61 | class PasteCommand(ScreenCommandInterface): 62 | 63 | def __init__(self, screen: Screen, offset: int = 0) -> None: 64 | super().__init__(screen) 65 | self._offset = offset 66 | 67 | def execute(self) -> None: 68 | self._screen.paste(offset=self._offset) 69 | 70 | def undo(self) -> None: 71 | self._screen.clear_clipboard() 72 | self._screen._text = self._previous_state 73 | 74 | 75 | class ScreenInvoker: 76 | 77 | def __init__(self) -> None: 78 | self._history: List[ScreenCommandInterface] = [] 79 | 80 | def store_and_execute(self, command: ScreenCommandInterface) -> None: 81 | command.execute() 82 | self._history.append(command) 83 | 84 | def undo_last(self) -> None: 85 | if self._history: 86 | _last_commit: ScreenCommandInterface = self._history.pop() 87 | _last_commit.undo() 88 | 89 | 90 | invokers: ScreenInvoker = ScreenInvoker() 91 | 92 | screen: Screen = Screen(text='Hello world') 93 | print(screen) 94 | 95 | cut_command: CutCommand = CutCommand(screen, start=5, end=11) 96 | invokers.store_and_execute(cut_command) 97 | print(screen) 98 | 99 | paste_command: PasteCommand = PasteCommand(screen, offset=0) 100 | invokers.store_and_execute(paste_command) 101 | print(screen) 102 | 103 | invokers.undo_last() 104 | print(screen) 105 | 106 | invokers.undo_last() 107 | print(screen) 108 | -------------------------------------------------------------------------------- /behavioral/mediator/refactoring-guru.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mediator is a behavioral design pattern that 3 | reduces coupling between components of a program 4 | by making them communicate indirectly, through 5 | a special mediator object. 6 | 7 | The Mediator makes it easy to modify, extend 8 | and reuse individual components because they're 9 | no longer dependent on the dozens of other classes. 10 | """ 11 | 12 | 13 | class MediatorInterface: 14 | """ 15 | The Mediator interface declares a method used by components 16 | to notify the mediator about various events. The mediator 17 | may react to these events and pass the execution to other 18 | components. 19 | """ 20 | 21 | def notify(self, sender: object, event: str) -> None: 22 | raise NotImplementedError() 23 | 24 | 25 | class ConcreteMediator(MediatorInterface): 26 | 27 | def __init__(self, component_1: 'Component1', component_2: 'Component2') -> None: 28 | self._component_1 = component_1 29 | self._component_2 = component_2 30 | 31 | self._component_1.set_mediator(self) 32 | self._component_2.set_mediator(self) 33 | 34 | def notify(self, sender: object, event: str) -> None: 35 | if event == 'A': 36 | print('Mediator reacts on A and triggers following operations:') 37 | self._component_2.do_c() 38 | elif event == 'D': 39 | print('Mediator reacts on D and triggers following operations:') 40 | self._component_1.do_b() 41 | self._component_2.do_c() 42 | 43 | 44 | 45 | class BaseComponent: 46 | """ 47 | The Base Component provides the basic functionality of storing a 48 | mediator's instance inside component objects. 49 | """ 50 | 51 | def __init__(self, mediator: MediatorInterface = None) -> None: 52 | self._mediator = mediator 53 | 54 | def set_mediator(self, mediator: MediatorInterface) -> None: 55 | self._mediator = mediator 56 | 57 | 58 | class Component1(BaseComponent): 59 | """ 60 | Concrete Components implement various functionality. They don't 61 | depend on other components. They also don't depend on any 62 | concrete mediator classes. 63 | """ 64 | def do_a(self) -> None: 65 | print('Component 1 does A.') 66 | self._mediator.notify(self, 'A') 67 | 68 | def do_b(self) -> None: 69 | print('Component 1 does B.') 70 | self._mediator.notify(self, 'B') 71 | 72 | 73 | class Component2(BaseComponent): 74 | 75 | def do_c(self) -> None: 76 | print('Component 2 does C.') 77 | self._mediator.notify(self, 'C') 78 | 79 | def do_d(self) -> None: 80 | print('Component 2 does D.') 81 | self._mediator.notify(self, 'D') 82 | 83 | 84 | class Demo: 85 | 86 | def run(self) -> None: 87 | _component1 = Component1() 88 | _component2 = Component2() 89 | 90 | _mediator: MediatorInterface = ConcreteMediator(_component1, _component2) 91 | 92 | print("Client triggers operation A.") 93 | _component1.do_a() 94 | 95 | print('\n', end="") 96 | 97 | print("Client triggers operation D.") 98 | _component2.do_d() 99 | 100 | 101 | demo: Demo = Demo() 102 | demo.run() 103 | -------------------------------------------------------------------------------- /structural/adapter/refactoring-guru.py: -------------------------------------------------------------------------------- 1 | """ 2 | Adapter is a structural design pattern, which allows 3 | imcompatible objects to collaborate. 4 | 5 | Adapter acts as a wrapper between two objects, 6 | it catches calls for one object and transforms them 7 | to format and interface recognizable by the second 8 | object. 9 | """ 10 | import math 11 | 12 | 13 | class RoundPeg: 14 | """ 15 | RoundPegs are compatible with RoundRoles. 16 | """ 17 | __radius: float = None 18 | 19 | def __init__(self, radius: float) -> None: 20 | self.__radius = radius 21 | 22 | def get_radius(self) -> float: 23 | return self.__radius 24 | 25 | 26 | class RoundHole: 27 | """ 28 | RoundHoles are compatible with RoundPegs. 29 | """ 30 | __radius: float = None 31 | 32 | def __init__(self, radius: float) -> None: 33 | self.__radius = radius 34 | 35 | def get_radius(self) -> float: 36 | return self.__radius 37 | 38 | def fits(self, round_peg: RoundPeg) -> bool: 39 | return self.get_radius() >= round_peg.get_radius() 40 | 41 | 42 | class SquarePeg: 43 | """ 44 | SquarePegs are not compatible with RoundHoles (they were 45 | implemented by previous development team). But we have to 46 | integrate them into our program. 47 | """ 48 | __width: float = None 49 | 50 | def __init__(self, width: float) -> None: 51 | self.__width = width 52 | 53 | def get_width(self) -> float: 54 | return self.__width 55 | 56 | def get_square(self) -> float: 57 | return self.__width ** 2 58 | 59 | 60 | class SquarePegAdapter(RoundPeg): 61 | """ 62 | Adapter allows fitting square pegs into round holes. 63 | """ 64 | __square_peg : SquarePeg = None 65 | 66 | def __init__(self, square_peg: SquarePeg) -> None: 67 | self.__square_peg = square_peg 68 | 69 | def get_radius(self) -> float: 70 | """ 71 | Calculate a minimum circle radius, which can fit this peg. 72 | """ 73 | result: float = self.__square_peg.get_width() / 2 74 | result = math.pow(result, 2) * 2 75 | result = math.sqrt(result) 76 | return result 77 | 78 | 79 | class Demo: 80 | """ 81 | Somewhere in client code... 82 | """ 83 | def run(self): 84 | """ 85 | Round fits round, no surpise. 86 | """ 87 | round_hole: RoundHole = RoundHole(5) 88 | round_peg: RoundPeg = RoundPeg(5) 89 | 90 | if round_hole.fits(round_peg): 91 | print('Round peg r5 fits round hole r5.') 92 | 93 | small_square_peg: SquarePeg = SquarePeg(2) 94 | large_square_peg: SquarePeg = SquarePeg(20) 95 | # round_role.fits(small_square_peg) Won't compile. 96 | 97 | """ 98 | Adapter solves the problem. 99 | """ 100 | small_square_peg_adaper: SquarePegAdapter = SquarePegAdapter(small_square_peg) 101 | large_square_peg_adapter: SquarePegAdapter = SquarePegAdapter(large_square_peg) 102 | 103 | if round_hole.fits(small_square_peg_adaper): 104 | print('Square peg w2 fits round hole r5.') 105 | 106 | if not round_hole.fits(large_square_peg_adapter): 107 | print('Square peg w20 does not fit into round hole r5.') 108 | 109 | 110 | demo = Demo() 111 | demo.run() -------------------------------------------------------------------------------- /behavioral/iterator/refactoring-guru-1.py: -------------------------------------------------------------------------------- 1 | """ 2 | Iterator is a behavioral design pattern that 3 | allows sequential traversal through a complex 4 | data structure without exposing its internal 5 | details. 6 | 7 | Thanks to the Iterator, clients go over elements 8 | of different collections in a similar fashion 9 | using a single iterator interface. 10 | """ 11 | from __future__ import annotations 12 | from collections.abc import Iterable, Iterator 13 | from typing import List, Any 14 | 15 | 16 | """ 17 | To create an iterator in Python, there are two abstract classes from the 18 | built-in `collections` module - Iterable, Iterator. We need to implement the 19 | `__iter__()` method in the iterated object (collection), and the `__next__()` 20 | method in the iterator. 21 | """ 22 | class AlphabeticalOrderIterator(Iterator): 23 | """ 24 | Concrete Iterators implement various traversal algorithms. These classes 25 | store the current traversal position at all times. 26 | 27 | `_position` attribute stores the current traversal position. An iterator 28 | may have a lot of other fields for storing iteration state, especially 29 | when it is supposed to work with a particular kind of collection. 30 | """ 31 | _position: int = None 32 | 33 | """ 34 | This attribute indicates the traversal direction. 35 | """ 36 | _reverse: bool = False 37 | 38 | def __init__(self, collection: "WordsCollection", reverse: bool = False) -> None: 39 | self._collection = collection 40 | self._reverse = reverse 41 | self._position = -1 if reverse else 0 42 | 43 | def __next__(self): 44 | """ 45 | The `__next__()` method must return the next item in the sequence. On 46 | reaching the end, and in subsequent calls, it must raise StopIteration. 47 | """ 48 | try: 49 | value = self._collection[self._position] 50 | self._position += -1 if self._reverse else 1 51 | except IndexError: 52 | raise StopIteration() 53 | 54 | return value 55 | 56 | 57 | class WordsCollection(Iterable): 58 | """ 59 | Concrete Collections provide one or several methods for retrieving fresh 60 | iterator instances, compatible with the collection class. 61 | """ 62 | 63 | def __init__(self, collection: List[Any] = []) -> None: 64 | self._collection = collection 65 | 66 | def __iter__(self) -> AlphabeticalOrderIterator: 67 | """ 68 | The `__iter__()` method returns the iterator object itself, by default 69 | we return the iterator in ascending order. 70 | """ 71 | return AlphabeticalOrderIterator(self._collection) 72 | 73 | def get_reverse_iterator(self) -> AlphabeticalOrderIterator: 74 | return AlphabeticalOrderIterator(self._collection, True) 75 | 76 | def add_item(self, item: Any) -> None: 77 | self._collection.append(item) 78 | 79 | 80 | 81 | # The client code may or may not know about the Concrete Iterator or 82 | # Collection classes, depending on the level of indirection you want to 83 | # keep in your program. 84 | collection = WordsCollection() 85 | 86 | collection.add_item("First") 87 | collection.add_item("Second") 88 | collection.add_item("Third") 89 | 90 | print("Straight traversal: ") 91 | print('\n'.join(collection)) 92 | 93 | print('\n') 94 | 95 | print("Reverse traversal: ") 96 | print('\n'.join(collection.get_reverse_iterator()), end="") 97 | -------------------------------------------------------------------------------- /behavioral/template_method/refactoring-guru.py: -------------------------------------------------------------------------------- 1 | """ 2 | Template Method is a behavioral design pattern 3 | that allows you to defines a skeleton of an 4 | algorithm in a base class and let subclasses 5 | override the steps without changing the overall 6 | algorithm's structure. 7 | """ 8 | from time import sleep 9 | 10 | 11 | class AbstractNetwork: 12 | """ 13 | Base class of social network. 14 | """ 15 | def __init__(self, username: str, password: str) -> None: 16 | self._username = username 17 | self._password = password 18 | 19 | def post(self, message: str) -> bool: 20 | """ 21 | Publish the data to whatever network. 22 | 23 | Authenticate before posting. Every network uses a different 24 | authentication method. 25 | """ 26 | if self.login(self._username, self._password): 27 | # Send the post data. 28 | result: bool = self.send_data(message) 29 | self.logout() 30 | return result 31 | 32 | return False 33 | 34 | def login(self, username: str, password: str) -> bool: 35 | raise NotImplementedError() 36 | 37 | def send_data(self, data: str) -> bool: 38 | raise NotImplementedError() 39 | 40 | def logout(self) -> None: 41 | raise NotImplementedError() 42 | 43 | 44 | class Facebook(AbstractNetwork): 45 | 46 | def login(self, username: str, password: str) -> bool: 47 | print("Checking user's parameters.") 48 | print(f"Name: {username}") 49 | print(f"Password: {'*' * len(password)}") 50 | 51 | self.simulate_network_latency() 52 | 53 | print("Login success on Facebook.") 54 | return True 55 | 56 | def send_data(self, data: str) -> bool: 57 | message_posted: bool = True 58 | if message_posted: 59 | print(f"Message: {data} was posted on Facebook.") 60 | return True 61 | else: 62 | return False 63 | 64 | def logout(self) -> None: 65 | print(f"User: {self._username} was logged out from Facebook.") 66 | 67 | def simulate_network_latency(self) -> None: 68 | count: int = 0 69 | while(count < 10): 70 | sleep(0.5) 71 | print(".", end="") 72 | count += 1 73 | 74 | 75 | class Twitter(AbstractNetwork): 76 | 77 | def login(self, username: str, password: str) -> bool: 78 | print("Checking user's parameters.") 79 | print(f"Name: {username}") 80 | print(f"Password: {'*' * len(password)}") 81 | 82 | self.simulate_network_latency() 83 | 84 | print("Login success on Twitter.") 85 | return True 86 | 87 | def send_data(self, data: str) -> bool: 88 | message_posted: bool = True 89 | if message_posted: 90 | print(f"Message: {data} was posted on Twitter.") 91 | return True 92 | else: 93 | return False 94 | 95 | def logout(self) -> None: 96 | print(f"User: {self._username} was logged out from Twitter.") 97 | 98 | def simulate_network_latency(self) -> None: 99 | count: int = 0 100 | while(count < 10): 101 | sleep(0.5) 102 | print(".", end="") 103 | count += 1 104 | 105 | 106 | class Demo: 107 | 108 | def run(self): 109 | username: str = input("Input username: ") 110 | password: str = input("Input password: ") 111 | message: str = input("Input message: ") 112 | 113 | print("Choose social network for posting message.") 114 | print("1 - Facebook") 115 | print("2 - Twitter") 116 | 117 | choice: int = int(input("Input your choice: ")) 118 | if choice == 1: 119 | network: AbstractNetwork = Facebook(username, password) 120 | elif choice == 2: 121 | network: AbstractNetwork = Twitter(username, password) 122 | 123 | network.post(message) 124 | 125 | 126 | demo: Demo = Demo() 127 | demo.run() -------------------------------------------------------------------------------- /structural/flyweight/refactoring-guru.py: -------------------------------------------------------------------------------- 1 | """ 2 | Flyweight is a structural design pattern that 3 | allows programs to support vast quantities of 4 | objects by keeping their memory consumpition 5 | low. 6 | 7 | Pattern achieves it by sharing parts of object 8 | state between multiple objects. In other words, 9 | the Flyweight saves RAM by caching the same data 10 | used by different objects. 11 | """ 12 | import json 13 | from typing import Dict 14 | 15 | 16 | class Flyweight(): 17 | """ 18 | The Flyweight stores a common portion of the state (also called intrinsic 19 | state) that belongs to multiple real business entities. The Flyweight 20 | accepts the rest of the state (extrinsic state, unique for each entity) 21 | via its method parameters. 22 | """ 23 | 24 | def __init__(self, shared_state: str) -> None: 25 | self._shared_state = shared_state 26 | 27 | def operation(self, unique_state: str) -> None: 28 | s = json.dumps(self._shared_state) 29 | u = json.dumps(unique_state) 30 | print(f"Flyweight: Displaying shared ({s}) and unique ({u}) state.", end="") 31 | 32 | 33 | class FlyweightFactory(): 34 | """ 35 | The Flyweight Factory creates and manages the Flyweight objects. It 36 | ensures that flyweights are shared correctly. When the client requests a 37 | flyweight, the factory either returns an existing instance or creates a 38 | new one, if it doesn't exist yet. 39 | """ 40 | 41 | _flyweights: Dict[str, Flyweight] = {} 42 | 43 | def __init__(self, initial_flyweights: Dict) -> None: 44 | for state in initial_flyweights: 45 | self._flyweights[self.get_key(state)] = Flyweight(state) 46 | 47 | def get_key(self, state: Dict) -> str: 48 | """ 49 | Returns a Flyweight's string hash for a given state. 50 | """ 51 | 52 | return "_".join(sorted(state)) 53 | 54 | def get_flyweight(self, shared_state: Dict) -> Flyweight: 55 | """ 56 | Returns an existing Flyweight with a given state or creates a new 57 | one. 58 | """ 59 | key = self.get_key(shared_state) 60 | 61 | if not self._flyweights.get(key): 62 | print("FlyweightFactory: Can't find a flyweight, creating new one.") 63 | self._flyweights[key] = Flyweight(shared_state) 64 | else: 65 | print("FlyweightFactory: Reusing existing flyweight.") 66 | 67 | return self._flyweights[key] 68 | 69 | def list_flyweights(self) -> None: 70 | count = len(self._flyweights) 71 | print(f"FlyweightFactory: I have {count} flyweights:") 72 | print("\n".join(map(str, self._flyweights.keys())), end="") 73 | 74 | 75 | def add_car_to_police_database( 76 | factory: FlyweightFactory, plates: str, owner: str, 77 | brand: str, model: str, color: str 78 | ) -> None: 79 | print("\n\nClient: Adding a car to database.") 80 | flyweight = factory.get_flyweight([brand, model, color]) 81 | # The client code either stores or calculates extrinsic state and passes it 82 | # to the flyweight's methods. 83 | flyweight.operation([plates, owner]) 84 | 85 | 86 | class Demo: 87 | 88 | def run(self) -> None: 89 | """ 90 | The client code usually creates a bunch of pre-populated flyweights in 91 | the initialization stage of the application. 92 | """ 93 | 94 | factory = FlyweightFactory([ 95 | ["Chevrolet", "Camaro2018", "pink"], 96 | ["Mercedes Benz", "C300", "black"], 97 | ["Mercedes Benz", "C500", "red"], 98 | ["BMW", "M5", "red"], 99 | ["BMW", "X6", "white"], 100 | ]) 101 | 102 | factory.list_flyweights() 103 | 104 | add_car_to_police_database( 105 | factory, "CL234IR", "James Doe", "BMW", "M5", "red") 106 | 107 | add_car_to_police_database( 108 | factory, "CL234IR", "James Doe", "BMW", "X1", "red") 109 | 110 | print("\n") 111 | 112 | factory.list_flyweights() 113 | 114 | 115 | demo: Demo = Demo() 116 | demo.run() 117 | -------------------------------------------------------------------------------- /structural/decorator/refactoring-guru.py: -------------------------------------------------------------------------------- 1 | """ 2 | Decorator is a structural pattern that allows 3 | adding new behaviors to objects dynamically by 4 | placing them inside special wrapper objects. 5 | 6 | Using decorator you can wrap objects countless 7 | number of times since both target objects and 8 | decorators follow the same interface. The resulting 9 | object will get a stacking behavior of all wrappers. 10 | """ 11 | import os 12 | import zlib 13 | import base64 14 | import binascii 15 | from pathlib import Path 16 | 17 | 18 | class DataSourceInterface: 19 | 20 | def write_data(self, data: str) -> None: 21 | raise NotImplementedError() 22 | 23 | def read_data(self) -> str: 24 | raise NotImplementedError() 25 | 26 | 27 | class DataSourceFile(DataSourceInterface): 28 | 29 | def __init__(self, name: str): 30 | self._name = name 31 | 32 | def write_data(self, data: str) -> None: 33 | f = open(self._name, 'w') 34 | f.write(data) 35 | f.close() 36 | 37 | def read_data(self) -> str: 38 | f = open(self._name, 'rb') 39 | data = f.read() 40 | f.close() 41 | return data 42 | 43 | 44 | class DataSourceAbstractDecorator(DataSourceInterface): 45 | 46 | def __init__(self, wrappee: DataSourceInterface): 47 | self._wrappee = wrappee 48 | 49 | def write_data(self, data: str) -> None: 50 | self._wrappee.write_data(data) 51 | 52 | def read_data(self) -> str: 53 | data = self._wrappee.read_data() 54 | return data 55 | 56 | 57 | class EncryptionDecorator(DataSourceAbstractDecorator): 58 | 59 | def write_data(self, data: str) -> None: 60 | data = self.encode(data) 61 | self._wrappee.write_data(data) 62 | 63 | def read_data(self) -> str: 64 | data = self._wrappee.read_data() 65 | return self.decode(data) 66 | 67 | def encode(self, data: str) -> str: 68 | encoded = base64.b64encode(bytes(data, 'utf8')) 69 | return str(encoded, encoding='utf8') 70 | 71 | def decode(self, data: str) -> str: 72 | decoded = base64.b64decode(data) 73 | return str(decoded, encoding='utf8') 74 | 75 | 76 | class CompressionDecorator(DataSourceAbstractDecorator): 77 | 78 | def __init__(self, wrappee: DataSourceInterface): 79 | super().__init__(wrappee) 80 | self._level = 6 81 | 82 | def write_data(self, data: str) -> None: 83 | data = self.compress(data) 84 | self._wrappee.write_data(data) 85 | 86 | def read_data(self) -> str: 87 | data = self._wrappee.read_data() 88 | data = self.decompress(data) 89 | return data 90 | 91 | def set_compress_level(self, level: int) -> None: 92 | self.level = level 93 | 94 | def get_compress_level(self) -> int: 95 | return self._level 96 | 97 | def compress(self, data: str) -> str: 98 | compressed = zlib.compress(bytes(data, 'utf8'), self._level) 99 | compressed = base64.b64encode(compressed) 100 | return str(compressed, encoding='utf8') 101 | 102 | def decompress(self, data: str) -> str: 103 | decompressed = base64.b64decode(data) 104 | decompressed = zlib.decompress(decompressed) 105 | return str(decompressed, encoding='utf8') 106 | 107 | 108 | class Demo: 109 | 110 | def run(self) -> None: 111 | current_path = Path().resolve() 112 | file_path = f'{current_path}{os.sep}output_demo.txt' 113 | 114 | salary_records: str = "Name,Salary\nJonh Smith,100000\nSteven Jobs,912000" 115 | encoded: DataSourceAbstractDecorator = CompressionDecorator(EncryptionDecorator(DataSourceFile(file_path))) 116 | encoded.write_data(salary_records) 117 | 118 | plain: DataSourceFile = DataSourceFile(file_path) 119 | 120 | print('- Input -------------------') 121 | print(salary_records) 122 | print('- Encoded -----------------') 123 | print(plain.read_data()) 124 | print('- Decoded ------------------') 125 | print(encoded.read_data()) 126 | 127 | 128 | demo: Demo = Demo() 129 | demo.run() -------------------------------------------------------------------------------- /behavioral/interpreter/udemy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Denife a grammatical representation for a language 3 | and an interpreter to interpret the gramar. 4 | 5 | Client: 6 | Build the tree of expressions, the Interpret 7 | method of the top item in the tree is then called 8 | 9 | Context (optional): 10 | Used to store any information that needs to be 11 | available to all expression objects 12 | 13 | ExpressionBase: 14 | Base class defining the interpret method 15 | 16 | TerminalExpression: 17 | Can be interpreted in a single object 18 | 19 | NonTerminalExpression: 20 | Aggregates containing one or more further expressions, 21 | each of which may be terminal or no-terminal 22 | """ 23 | 24 | 25 | class ExpressionInterface: 26 | """ 27 | Base class defining the interpret method 28 | """ 29 | def interpret(self, text: str) -> bool: 30 | raise NotImplementedError() 31 | 32 | 33 | class TerminalExpression(ExpressionInterface): 34 | """ 35 | TerminalExpression 36 | 37 | Can be interpreted in a single object 38 | """ 39 | def __init__(self, word: str) -> None: 40 | self._word = word 41 | 42 | def interpret(self, text: str) -> bool: 43 | if self._word in text: 44 | return True 45 | else: 46 | return False 47 | 48 | 49 | class OrExpression(ExpressionInterface): 50 | """ 51 | NonTerminalExpression 52 | 53 | Aggregates containing one or more further expressions, 54 | each of which may be terminal or no-terminal 55 | """ 56 | def __init__(self, exp1: ExpressionInterface, exp2: ExpressionInterface) -> None: 57 | self._exp1 = exp1 58 | self._exp2 = exp2 59 | 60 | def interpret(self, text) -> bool: 61 | return self._exp1.interpret(text) or self._exp2.interpret(text) 62 | 63 | 64 | class AndExpression(ExpressionInterface): 65 | """ 66 | NonTerminalExpression 67 | 68 | Aggregates containing one or more further expressions, 69 | each of which may be terminal or no-terminal 70 | """ 71 | def __init__(self, exp1: ExpressionInterface, exp2: ExpressionInterface) -> None: 72 | self._exp1 = exp1 73 | self._exp2 = exp2 74 | 75 | def interpret(self, text) -> bool: 76 | return self._exp1.interpret(text) and self._exp2.interpret(text) 77 | 78 | 79 | jonh = TerminalExpression('Jonh') 80 | henry = TerminalExpression('Henry') 81 | mary = TerminalExpression('Mary') 82 | sarah = TerminalExpression('Sarah') 83 | 84 | # rules 85 | 86 | rule1 = AndExpression(jonh, henry) 87 | print(rule1.interpret('Jonh')) # should contains ("Jonh" and "Henry") -> False 88 | print(rule1.interpret('Henry')) # should contains ("Jonh" and "Henry") -> False 89 | print(rule1.interpret('Jonh + Henry')) # should contains ("Jonh" and "Henry") -> True 90 | 91 | rule2 = OrExpression(mary, rule1) 92 | print(rule2.interpret('Jonh')) # should contains ("Mary" or ("Jonh" and "Henry")) -> False 93 | print(rule2.interpret('Henry')) # should contains ("Mary" or ("Jonh" and "Henry")) -> False 94 | print(rule2.interpret('Mary')) # should contains ("Mary" or ("Jonh" and "Henry")) -> True 95 | print(rule2.interpret('Jonh + Henry')) # should contains ("Mary" or ("Jonh" and "Henry")) -> True 96 | print(rule2.interpret('Jonh + Henry + Mary')) # should contains ("Mary" or ("Jonh" and "Henry")) -> True 97 | 98 | rule3 = AndExpression(sarah, rule2) 99 | print(rule3.interpret('Mary')) # should contains (("Mary" or ("Jonh" and "Henry")) and "Sarah") -> False 100 | print(rule3.interpret('Sarah')) # should contains (("Mary" or ("Jonh" and "Henry")) and "Sarah") -> False 101 | print(rule3.interpret('Jonh + Henry')) # should contains (("Mary" or ("Jonh" and "Henry")) and "Sarah") -> False 102 | print(rule3.interpret('Jonh + Henry + Mary')) # should contains (("Mary" or ("Jonh" and "Henry")) and "Sarah") -> False 103 | print(rule3.interpret('Mary + Sarah')) # should contains (("Mary" or ("Jonh" and "Henry")) and "Sarah") -> True 104 | print(rule3.interpret('Jonh + Henry + Sarah')) # should contains (("Mary" or ("Jonh" and "Henry")) and "Sarah") -> True 105 | 106 | print(rule3.interpret('Mary + Jonh + Henry + Sarah')) # should contains (("Mary" or ("Jonh" and "Henry")) and "Sarah") -> True 107 | -------------------------------------------------------------------------------- /behavioral/memento/refactoring-guru.py: -------------------------------------------------------------------------------- 1 | """ 2 | Memento is a behavioral design pattern that 3 | allows making snapshots of an object's state 4 | and restoring it in future. 5 | 6 | The Memento doesn't compromise the internal structure 7 | of the object it works with, as well as data kept 8 | inside the snapshots 9 | """ 10 | import string 11 | import random 12 | from datetime import datetime 13 | from typing import List 14 | 15 | 16 | class MementoInterface: 17 | """ 18 | The Memento interface provides a way to retrieve the memento's metadata, 19 | such as creation date or name. However, it doesn't expose the Originator's state. 20 | """ 21 | def get_name(self) -> str: raise NotImplementedError() 22 | def get_date(self) -> datetime: raise NotImplementedError() 23 | 24 | 25 | class ConcreteMemento(MementoInterface): 26 | """ 27 | The Concrete Memento contains the infrastructure for storing the Originator's state. 28 | """ 29 | def __init__(self, state: str) -> None: 30 | self._state = state 31 | self._date = datetime.now() 32 | 33 | def get_state(self) -> str: 34 | """ The Originator uses this method when restoring its state. """ 35 | return self._state 36 | 37 | # The rest of the methods are used by caretaker to display metadata. 38 | 39 | def get_name(self) -> str: 40 | return f"{self._date}/{self._state[:9]}..." 41 | 42 | def get_date(self) -> datetime: 43 | return self._date 44 | 45 | 46 | 47 | class Originator: 48 | """ 49 | The Originator holds some important state that may change over time. 50 | It also defines a method for saving the state inside a memento and 51 | another method for restoring the state from it. 52 | """ 53 | def __init__(self, state: str) -> None: 54 | self._state = state 55 | print(f"Originator: My initial state is: {self._state}") 56 | 57 | def do_something(self) -> None: 58 | print("Originator: I'm doing something important.") 59 | self._state = self.generate_random_string() 60 | print(f"Originator: And my state has changed to {self._state}") 61 | 62 | def generate_random_string(self) -> str: 63 | random_string: str = ''.join(random.choices(string.ascii_uppercase + string.digits, k=30)) 64 | return random_string 65 | 66 | def save(self) -> MementoInterface: 67 | """ Saves the current state inside a memento. """ 68 | return ConcreteMemento(self._state) 69 | 70 | def restore(self, memento: MementoInterface) -> None: 71 | """ Restores the Originator's state from a memento object. """ 72 | self._state = memento.get_state() 73 | print(f"Originator: My state has changed to: {self._state}") 74 | 75 | 76 | class Caretaker: 77 | """ 78 | The Caretaker doesn't depend on the Concrete Memento class. 79 | Therefore, it doesn't have access to the originator's state, 80 | stored inside the memento. It works with all mementos via 81 | the base Memento interface. 82 | """ 83 | def __init__(self, originator: Originator) -> None: 84 | self._originator = originator 85 | self._mementos: List[MementoInterface] = [] 86 | 87 | def backup(self) -> None: 88 | print("Caretaker: Saving Originator's state...") 89 | self._mementos.append(self._originator.save()) 90 | 91 | def undo(self) -> None: 92 | if not self._mementos: 93 | return 94 | 95 | _memento = self._mementos.pop() 96 | print(f"Caretaker: Restoring state to: {_memento.get_name()}") 97 | 98 | self._originator.restore(_memento) 99 | 100 | def show_history(self) -> None: 101 | print("Caretaker: Here's the list of mementos:") 102 | for _memento in self._mementos: 103 | print(_memento.get_name()) 104 | 105 | 106 | class Demo: 107 | 108 | def run(self) -> None: 109 | originator: Originator = Originator("Super-duper-super-puper-super.") 110 | caretaker: Caretaker = Caretaker(originator) 111 | 112 | caretaker.backup() 113 | originator.do_something() 114 | 115 | caretaker.backup() 116 | originator.do_something() 117 | 118 | caretaker.backup() 119 | originator.do_something() 120 | 121 | print("Show history") 122 | caretaker.show_history() 123 | 124 | print("Client: Now, let's rollback!") 125 | caretaker.undo() 126 | 127 | print("Client: Once more!") 128 | caretaker.undo() 129 | 130 | 131 | demo: Demo = Demo() 132 | demo.run() 133 | -------------------------------------------------------------------------------- /creational/prototype/refactoring-guru.py: -------------------------------------------------------------------------------- 1 | """ 2 | Prototype is a creational design pattern that lets you 3 | produce new objects by copying existing ones without 4 | compromising their internals. 5 | """ 6 | 7 | class AbstractShape: 8 | x = 0 9 | y = 0 10 | color = 0 11 | 12 | def __init__(self, target=None): 13 | if target: 14 | self.x = target.x 15 | self.y = target.y 16 | self.color = target.color 17 | 18 | def __eq__(self, other): 19 | if not isinstance(other, AbstractShape): 20 | return False 21 | 22 | return other.x == self.x and other.y == self.y and other.color == self.color 23 | 24 | def clone(self): 25 | raise NotImplementedError 26 | 27 | 28 | # concrete shapes 29 | 30 | class Circle(AbstractShape): 31 | radius = 0 32 | 33 | def __init__(self, target=None): 34 | super().__init__(target=target) 35 | if target: 36 | self.radius = target.radius 37 | 38 | def __eq__(self, other): 39 | if not isinstance(other, Circle): 40 | return False 41 | 42 | return super().__eq__(other) and other.radius == self.radius 43 | 44 | def clone(self): 45 | return Circle(target=self) 46 | 47 | 48 | class Rectangle(AbstractShape): 49 | width = 0 50 | height = 0 51 | 52 | def __init__(self, target=None): 53 | super().__init__(target=target) 54 | if target: 55 | self.width = target.width 56 | self.height = target.height 57 | 58 | def __eq__(self, other): 59 | if not isinstance(other, Rectangle): 60 | return False 61 | 62 | return super().__eq__(other) and other.width == self.width and other.height == self.height 63 | 64 | def clone(self): 65 | return Rectangle(target=self) 66 | 67 | 68 | # client 69 | 70 | class Client: 71 | 72 | def run(self): 73 | shapes = [] 74 | 75 | # circle 76 | circle = Circle() 77 | circle.x = 10 78 | circle.y = 20 79 | circle.radius = 15 80 | 81 | shapes.append(circle) 82 | 83 | another_circle = circle.clone() 84 | shapes.append(another_circle) 85 | 86 | # rectangle 87 | rectangle = Rectangle() 88 | rectangle.width = 10 89 | rectangle.height = 20 90 | shapes.append(rectangle) 91 | 92 | self.clone_and_compare(shapes) 93 | 94 | def clone_and_compare(self, shapes): 95 | shapes_copy = [] 96 | 97 | for shape in shapes: 98 | shapes_copy.append(shape.clone()) 99 | 100 | for index, (shape, shape_copy) in enumerate(zip(shapes, shapes_copy)): 101 | shape, shape_copy = (shape, shape_copy) 102 | 103 | if shape is not shape_copy: 104 | print('{}: Shapes are different objects (yay!)'.format(index)) 105 | if shape == shape_copy: 106 | print('{}: And they are identical (yay)'.format(index)) 107 | else: 108 | print('{}: But they are not identical (booo!)'.format(index)) 109 | else: 110 | print('{}: Shape objects are the same (booo!)'.format(index)) 111 | 112 | 113 | #run 114 | 115 | client = Client() 116 | client.run() 117 | 118 | 119 | # Registered Prototype 120 | 121 | class BundledShapeCache: 122 | cache = {} 123 | 124 | def __init__(self): 125 | circle = Circle() 126 | circle.x = 5 127 | circle.y = 7 128 | circle.radius = 45 129 | circle.color = "Green" 130 | 131 | rectangle = Rectangle() 132 | rectangle.x = 6 133 | rectangle.y = 9 134 | rectangle.width = 8 135 | rectangle.height = 10 136 | rectangle.color = "Blue" 137 | 138 | self.cache['Big green circle'] = circle 139 | self.cache['Medium blue rectangle'] = rectangle 140 | 141 | def put(self, key, shape): 142 | self.cache[key] = shape 143 | return shape 144 | 145 | def get(self, key): 146 | return self.cache[key].clone() 147 | 148 | 149 | 150 | class RegisteredClient: 151 | 152 | def run(self): 153 | bundled_shape_cache = BundledShapeCache() 154 | 155 | shape1 = bundled_shape_cache.get('Big green circle') 156 | shape2 = bundled_shape_cache.get('Medium blue rectangle') 157 | shape3 = bundled_shape_cache.get('Medium blue rectangle') 158 | 159 | if shape1 is not shape2 and not shape1 == shape2: 160 | print('Big green circle != Medium blue rectangle (yay!)') 161 | else: 162 | print('Big green circle == Medium blue rectangle (booo!)') 163 | 164 | if shape2 is not shape3: 165 | print('Medium blue rectangles are two different objects (yay!)') 166 | 167 | if shape2 == shape3: 168 | print('And they are identical (yay!)') 169 | else: 170 | print('But they are not identical (booo!)') 171 | else: 172 | print('Rectangle objects are the same (booo!)') 173 | 174 | 175 | registered_client = RegisteredClient() 176 | registered_client.run() 177 | -------------------------------------------------------------------------------- /behavioral/observer/refactoring-guru.py: -------------------------------------------------------------------------------- 1 | """ 2 | Observer is a behavioral design pattern that 3 | allows one objects to notify other objects about 4 | changes in their state. 5 | 6 | The Observer pattern provides a way to subscribe 7 | and unsubscribe to and from these events for any 8 | object that implements a subscriber interface. 9 | """ 10 | from typing import List, Dict 11 | 12 | 13 | class File: 14 | """ 15 | File, this class just simulate a file object 16 | """ 17 | def __init__(self, file_path: str) -> None: 18 | self._file_path = file_path 19 | self._name = file_path.rsplit('/')[-1] 20 | 21 | def get_name(self) -> str: 22 | return self._name 23 | 24 | 25 | class EventListenerInterface: 26 | """ 27 | EventListenerInterface, this interface will be implemented for all subscribers 28 | that want to be notified about any update in the Observable object. 29 | """ 30 | def update(self, event_type: str, file: File) -> None: 31 | raise NotImplementedError() 32 | 33 | 34 | class EventManager: 35 | """ 36 | EventManager, this helper class acts as a Publisher component. 37 | this class contains all necessary methods to register, unregister 38 | and notify all subscribers registered here. 39 | """ 40 | def __init__(self, *operations: List[str]) -> None: 41 | """ listeners are composed by event_type and a list of subscribers """ 42 | self._listeners: Dict[str, List[EventListenerInterface]] = {} 43 | 44 | for operation in operations: 45 | self._listeners[operation]: List[EventListenerInterface] = [] 46 | 47 | def subscribe(self, event_type: str, listener: EventListenerInterface) -> None: 48 | """ register subscribers grouped by event_type (operations) """ 49 | users: List[EventListenerInterface] = self._listeners[event_type] 50 | users.append(listener) 51 | 52 | def unsubscribe(self, event_type: str, listener: EventListenerInterface) -> None: 53 | """ unregister subscribers grouped by event_type (operations) """ 54 | users: List[EventListenerInterface] = self._listeners[event_type] 55 | if listener in users: 56 | users.remove(listener) 57 | 58 | def notify(self, event_type: str, file: File): 59 | """ notify all registered subscribers by event_type """ 60 | users: List[EventListenerInterface] = self._listeners[event_type] 61 | for listener in users: 62 | listener.update(event_type, file) 63 | 64 | 65 | class EmailNotificationListener(EventListenerInterface): 66 | """ 67 | Concrete Observer class 68 | """ 69 | def __init__(self, email: str) -> None: 70 | self._email = email 71 | 72 | def update(self, event_type: str, file: File) -> None: 73 | message: str = f""" 74 | Email to {self._email}: Someone has performed {event_type} 75 | operation with the following file: {file.get_name()} 76 | """ 77 | print(message) 78 | 79 | 80 | class LogOpenListener(EventListenerInterface): 81 | """ 82 | Concrete Observer class 83 | """ 84 | def __init__(self, filename: str) -> None: 85 | self._log = File(filename) 86 | 87 | def update(self, event_type: str, file: File) -> None: 88 | message: str = f""" 89 | Save to log {self._log.get_name()}: Someone has performed {event_type} 90 | operation with the following file: {file.get_name()} 91 | """ 92 | print(message) 93 | 94 | 95 | class Editor: 96 | """ 97 | Editor, class that acts as a text editor, 98 | when some operation are executed a custom event is triggered 99 | """ 100 | def __init__(self) -> None: 101 | self._events: EventManager = EventManager('open', 'save') 102 | self._file: File = None 103 | 104 | def open_file(self, file_path: str) -> None: 105 | """ 106 | dispach open event 107 | """ 108 | self._file = File(file_path) 109 | self._events.notify('open', self._file) 110 | 111 | def save_file(self) -> None: 112 | """ 113 | dispach save event 114 | """ 115 | if self._file: 116 | self._events.notify('save', self._file) 117 | else: 118 | raise Exception('Please open a file first.') 119 | 120 | 121 | class Demo: 122 | 123 | def run(self): 124 | """ 125 | Instantiate a editor object and register some subscribers to acts as observers. 126 | When a file is opened or saved, then all subscribers below will be notified and 127 | a action will be executed for each subscriber. 128 | """ 129 | _editor: Editor = Editor() 130 | _editor._events.subscribe('open', LogOpenListener('/path/to/log/file.text')) 131 | _editor._events.subscribe('save', EmailNotificationListener('admin@example.com')) 132 | 133 | try: 134 | _editor.open_file('text.txt') 135 | _editor.save_file() 136 | except Exception as err: 137 | print(f'An error occured when try to open file, error: {err}') 138 | 139 | 140 | demo: Demo = Demo() 141 | demo.run() 142 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "ed63b31f068d51bac7a2fd34e605f75dbafcc8c96209103c96edcd09c836d424" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.7" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": {}, 19 | "develop": { 20 | "astroid": { 21 | "hashes": [ 22 | "sha256:35b032003d6a863f5dcd7ec11abd5cd5893428beaa31ab164982403bcb311f22", 23 | "sha256:6a5d668d7dc69110de01cdf7aeec69a679ef486862a0850cc0fd5571505b6b7e" 24 | ], 25 | "version": "==2.1.0" 26 | }, 27 | "isort": { 28 | "hashes": [ 29 | "sha256:1153601da39a25b14ddc54955dbbacbb6b2d19135386699e2ad58517953b34af", 30 | "sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8", 31 | "sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497" 32 | ], 33 | "version": "==4.3.4" 34 | }, 35 | "lazy-object-proxy": { 36 | "hashes": [ 37 | "sha256:0ce34342b419bd8f018e6666bfef729aec3edf62345a53b537a4dcc115746a33", 38 | "sha256:1b668120716eb7ee21d8a38815e5eb3bb8211117d9a90b0f8e21722c0758cc39", 39 | "sha256:209615b0fe4624d79e50220ce3310ca1a9445fd8e6d3572a896e7f9146bbf019", 40 | "sha256:27bf62cb2b1a2068d443ff7097ee33393f8483b570b475db8ebf7e1cba64f088", 41 | "sha256:27ea6fd1c02dcc78172a82fc37fcc0992a94e4cecf53cb6d73f11749825bd98b", 42 | "sha256:2c1b21b44ac9beb0fc848d3993924147ba45c4ebc24be19825e57aabbe74a99e", 43 | "sha256:2df72ab12046a3496a92476020a1a0abf78b2a7db9ff4dc2036b8dd980203ae6", 44 | "sha256:320ffd3de9699d3892048baee45ebfbbf9388a7d65d832d7e580243ade426d2b", 45 | "sha256:50e3b9a464d5d08cc5227413db0d1c4707b6172e4d4d915c1c70e4de0bbff1f5", 46 | "sha256:5276db7ff62bb7b52f77f1f51ed58850e315154249aceb42e7f4c611f0f847ff", 47 | "sha256:61a6cf00dcb1a7f0c773ed4acc509cb636af2d6337a08f362413c76b2b47a8dd", 48 | "sha256:6ae6c4cb59f199d8827c5a07546b2ab7e85d262acaccaacd49b62f53f7c456f7", 49 | "sha256:7661d401d60d8bf15bb5da39e4dd72f5d764c5aff5a86ef52a042506e3e970ff", 50 | "sha256:7bd527f36a605c914efca5d3d014170b2cb184723e423d26b1fb2fd9108e264d", 51 | "sha256:7cb54db3535c8686ea12e9535eb087d32421184eacc6939ef15ef50f83a5e7e2", 52 | "sha256:7f3a2d740291f7f2c111d86a1c4851b70fb000a6c8883a59660d95ad57b9df35", 53 | "sha256:81304b7d8e9c824d058087dcb89144842c8e0dea6d281c031f59f0acf66963d4", 54 | "sha256:933947e8b4fbe617a51528b09851685138b49d511af0b6c0da2539115d6d4514", 55 | "sha256:94223d7f060301b3a8c09c9b3bc3294b56b2188e7d8179c762a1cda72c979252", 56 | "sha256:ab3ca49afcb47058393b0122428358d2fbe0408cf99f1b58b295cfeb4ed39109", 57 | "sha256:bd6292f565ca46dee4e737ebcc20742e3b5be2b01556dafe169f6c65d088875f", 58 | "sha256:cb924aa3e4a3fb644d0c463cad5bc2572649a6a3f68a7f8e4fbe44aaa6d77e4c", 59 | "sha256:d0fc7a286feac9077ec52a927fc9fe8fe2fabab95426722be4c953c9a8bede92", 60 | "sha256:ddc34786490a6e4ec0a855d401034cbd1242ef186c20d79d2166d6a4bd449577", 61 | "sha256:e34b155e36fa9da7e1b7c738ed7767fc9491a62ec6af70fe9da4a057759edc2d", 62 | "sha256:e5b9e8f6bda48460b7b143c3821b21b452cb3a835e6bbd5dd33aa0c8d3f5137d", 63 | "sha256:e81ebf6c5ee9684be8f2c87563880f93eedd56dd2b6146d8a725b50b7e5adb0f", 64 | "sha256:eb91be369f945f10d3a49f5f9be8b3d0b93a4c2be8f8a5b83b0571b8123e0a7a", 65 | "sha256:f460d1ceb0e4a5dcb2a652db0904224f367c9b3c1470d5a7683c0480e582468b" 66 | ], 67 | "version": "==1.3.1" 68 | }, 69 | "mccabe": { 70 | "hashes": [ 71 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", 72 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" 73 | ], 74 | "version": "==0.6.1" 75 | }, 76 | "pylint": { 77 | "hashes": [ 78 | "sha256:689de29ae747642ab230c6d37be2b969bf75663176658851f456619aacf27492", 79 | "sha256:771467c434d0d9f081741fec1d64dfb011ed26e65e12a28fe06ca2f61c4d556c" 80 | ], 81 | "index": "pypi", 82 | "version": "==2.2.2" 83 | }, 84 | "six": { 85 | "hashes": [ 86 | "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", 87 | "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" 88 | ], 89 | "version": "==1.12.0" 90 | }, 91 | "wrapt": { 92 | "hashes": [ 93 | "sha256:d4d560d479f2c21e1b5443bbd15fe7ec4b37fe7e53d335d3b9b0a7b1226fe3c6" 94 | ], 95 | "version": "==1.10.11" 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /structural/proxy/refactoring-guru.py: -------------------------------------------------------------------------------- 1 | """ 2 | Proxy is a structural design pattern that provides an object 3 | that acts as a substitute for a real service object used by a 4 | client. Proxy receives client requests, does some work 5 | (access control, caching, etc.) and then passes request to a 6 | service object. 7 | """ 8 | 9 | import time 10 | import random 11 | 12 | 13 | class Video: 14 | 15 | def __init__(self, id, title): 16 | self.id = id 17 | self.title = title 18 | self.data = 'Random video.' 19 | 20 | 21 | class ThirdPartyYoutubeLibInterface: 22 | 23 | def popular_videos(self) -> dict: 24 | raise NotImplementedError() 25 | 26 | def get_video(self, video_id: str) -> Video: 27 | raise NotImplementedError() 28 | 29 | 30 | class ThirdPartyYoutubeClass(ThirdPartyYoutubeLibInterface): 31 | 32 | def popular_videos(self) -> dict: 33 | self._conect_to_server('https://youtube.com') 34 | return self._get_random_videos() 35 | 36 | def get_video(self, video_id: str) -> Video: 37 | self._conect_to_server('https://youtube.com/{}/'.format(video_id)) 38 | return self._get_some_video(video_id) 39 | 40 | # Fake methods to simulate network activity. They are slow as a real life. 41 | 42 | def _experience_network_latency(self): 43 | random_latency = random.randint(5, 10) 44 | time.sleep(random_latency) 45 | 46 | def _conect_to_server(self, server: str): 47 | print(f'Connecting to {server} ...') 48 | self._experience_network_latency() 49 | print('Connected! \n') 50 | 51 | def _get_random_videos(self) -> dict: 52 | print('Downloading populars...') 53 | 54 | self._experience_network_latency() 55 | 56 | videos = {} 57 | videos['catzzzzzzzzz'] = Video('sadgahasgdas', 'Catzzzz.avi') 58 | videos['mkafksangasj'] = Video('mkafksangasj', 'Dog play with ball.mp4') 59 | videos['dancesvideoo'] = Video('asdfas3ffasd', 'Dancing video.mpq') 60 | videos['dlsdk5jfslaf'] = Video('dlsdk5jfslaf', 'Barcelona vs RealM.mov') 61 | videos['3sdfgsd1j333'] = Video('3sdfgsd1j333', 'Programing lesson#1.avi') 62 | 63 | print('Done! \n') 64 | return videos 65 | 66 | def _get_some_video(self, video_id: str) -> Video: 67 | print('Downloading video...') 68 | 69 | self._experience_network_latency() 70 | video = Video(video_id, 'Some video title') 71 | 72 | print('Done! \n') 73 | return video 74 | 75 | 76 | class YoutubeCacheProxy(ThirdPartyYoutubeLibInterface): 77 | 78 | def __init__(self): 79 | self.youtube_service = ThirdPartyYoutubeClass() 80 | self.cache_popular = {} 81 | self.cache_all = {} 82 | 83 | def popular_videos(self) -> dict: 84 | if not self.cache_popular: 85 | self.cache_popular = self.youtube_service.popular_videos() 86 | else: 87 | print('Retrieved list from cache.') 88 | 89 | return self.cache_popular 90 | 91 | def get_video(self, video_id: str) -> Video: 92 | video = self.cache_all.get(video_id) 93 | if not video: 94 | video = self.youtube_service.get_video(video_id) 95 | self.cache_all[video_id] = video 96 | else: 97 | print(f'Retrieved video {video_id} from cache.') 98 | 99 | return video 100 | 101 | def reset(self): 102 | self.cache_all.clear() 103 | self.cache_popular.clear() 104 | 105 | 106 | class YoutubeDownloader: 107 | 108 | def __init__(self, api: ThirdPartyYoutubeLibInterface): 109 | self.api = api 110 | 111 | def render_video_page(self, video_id: str): 112 | video = self.api.get_video(video_id) 113 | print('\n-------------------------------') 114 | print('Video page (imagine fancy HTML)') 115 | print(f'ID: {video_id}') 116 | print(f'Title: {video.title}') 117 | print(f'VIdeo: {video.data}') 118 | print('-------------------------------\n') 119 | 120 | def render_popular_videos(self): 121 | videos = self.api.popular_videos() 122 | print('\n-------------------------------') 123 | print('Most popular videos on Youtube (imagine fancy HTML)') 124 | for video in videos.values(): 125 | print(f'ID: {video.id} / Title: {video.title}') 126 | print('-------------------------------\n') 127 | 128 | 129 | # Demo (Client Code) 130 | 131 | class Demo: 132 | 133 | def run(self): 134 | naive_downloader = YoutubeDownloader(ThirdPartyYoutubeClass()) 135 | smart_downloader = YoutubeDownloader(YoutubeCacheProxy()) 136 | 137 | naive = self.test(naive_downloader) 138 | smart = self.test(smart_downloader) 139 | result = naive - smart 140 | print(f'Time saved by caching proxy: {result} ms.') 141 | 142 | def test(self, downloader: YoutubeDownloader) -> int: 143 | start_time = time.time() 144 | 145 | # User behavior in our app 146 | downloader.render_popular_videos() 147 | downloader.render_video_page('catzzzzzzzzz') 148 | downloader.render_popular_videos() 149 | downloader.render_video_page('dancesvideoo') 150 | # Users might visit the same page quite often. 151 | downloader.render_video_page('catzzzzzzzzz') 152 | downloader.render_video_page('someothervid') 153 | 154 | end_time = time.time() 155 | estimated_time = end_time - start_time 156 | print(f'Time elapsed {estimated_time} ms. \n') 157 | 158 | return estimated_time 159 | 160 | 161 | demo_client = Demo() 162 | demo_client.run() -------------------------------------------------------------------------------- /structural/composite/refactoring-guru.py: -------------------------------------------------------------------------------- 1 | """ 2 | Composite is a structural design pattern that 3 | allows composing objects into a tree-like 4 | structure and work with the it as if it was 5 | a singular object. 6 | 7 | Composite became a pretty popular solution for 8 | the most problems that require building a tree 9 | structure. Composite's great feature is the 10 | ability to run methods recursively over the whole 11 | tree structure and sun up the results. 12 | """ 13 | from typing import List 14 | 15 | 16 | class ComponentInterface: 17 | """ 18 | The base Component class declares common operations for both simple and 19 | complex objects of a composition. 20 | """ 21 | 22 | def get_parent(self) -> "ComponentInterface": 23 | return self._parent 24 | 25 | def set_parent(self, parent: "ComponentInterface"): 26 | """ 27 | Optionally, the base Component can declare an interface for setting 28 | and accessing a parent of the component in a tree structure. It can 29 | also provide some default implementation for these methods. 30 | """ 31 | self._parent = parent 32 | 33 | """ 34 | In some cases, it would be beneficial to define the child-management 35 | operations right in the base Component class. This way, you won't need to 36 | expose any concrete component classes to the client code, even during the 37 | object tree assembly. The downside is that these methods will be empty 38 | for the leaf-level components. 39 | """ 40 | 41 | def add(self, component: "ComponentInterface") -> None: 42 | raise NotImplementedError() 43 | 44 | def remove(self, component: "ComponentInterface") -> None: 45 | raise NotImplementedError() 46 | 47 | def is_composite(self) -> bool: 48 | """ 49 | You can provide a method that lets the client code figure out whether 50 | a component can bear children. 51 | """ 52 | return False 53 | 54 | def operation(self) -> str: 55 | """ 56 | The base Component may implement some default behavior or leave it to 57 | concrete classes (by declaring the method containing the behavior as 58 | "abstract"). 59 | """ 60 | pass 61 | 62 | 63 | class Leaf(ComponentInterface): 64 | """ 65 | The Leaf class represents the end objects of a composition. A leaf can't 66 | have any children. 67 | 68 | Usually, it's the Leaf objects that do the actual work, whereas Composite 69 | objects only delegate to their sub-components. 70 | """ 71 | 72 | def operation(self) -> str: 73 | return "Leaf" 74 | 75 | 76 | class Composite(ComponentInterface): 77 | """ 78 | The Composite class represents the complex components that may have 79 | children. Usually, the Composite objects delegate the actual work to 80 | their children and then "sum-up" the result. 81 | """ 82 | 83 | def __init__(self) -> None: 84 | self._children: List[ComponentInterface] = [] 85 | 86 | """ 87 | A composite object can add or remove other components (both simple or 88 | complex) to or from its child list. 89 | """ 90 | 91 | def add(self, component: ComponentInterface) -> None: 92 | self._children.append(component) 93 | component.set_parent(self) 94 | 95 | def remove(self, component: ComponentInterface) -> None: 96 | self._children.remove(component) 97 | component.set_parent(None) 98 | 99 | def is_composite(self) -> bool: 100 | return True 101 | 102 | def operation(self) -> str: 103 | """ 104 | The Composite executes its primary logic in a particular way. It 105 | traverses recursively through all its children, collecting and 106 | summing their results. Since the composite's children pass these 107 | calls to their children and so forth, the whole object tree is 108 | traversed as a result. 109 | """ 110 | 111 | results = [] 112 | for child in self._children: 113 | results.append(child.operation()) 114 | return f"Branch({'+'.join(results)})" 115 | 116 | 117 | def client_code(component: ComponentInterface) -> None: 118 | """ 119 | The client code works with all of the components via the base interface. 120 | """ 121 | 122 | print(f"RESULT: {component.operation()}", end="") 123 | 124 | 125 | def client_code2(component1: ComponentInterface, component2: ComponentInterface) -> None: 126 | """ 127 | Thanks to the fact that the child-management operations are declared in 128 | the base Component class, the client code can work with any component, 129 | simple or complex, without depending on their concrete classes. 130 | """ 131 | 132 | if component1.is_composite(): 133 | component1.add(component2) 134 | 135 | print(f'Result: {component1.operation()}', end='') 136 | 137 | 138 | class Demo: 139 | 140 | def run(self) -> None: 141 | # This way the client code can support the simple leaf components... 142 | simple = Leaf() 143 | print("Client: I've got a simple component:") 144 | client_code(simple) 145 | print("\n") 146 | 147 | # ...as well as the complex composites. 148 | tree = Composite() 149 | 150 | branch1 = Composite() 151 | branch1.add(Leaf()) 152 | branch1.add(Leaf()) 153 | 154 | branch2 = Composite() 155 | branch2.add(Leaf()) 156 | 157 | tree.add(branch1) 158 | tree.add(branch2) 159 | 160 | print("Client: Now I've got a composite tree:") 161 | client_code(tree) 162 | print("\n") 163 | 164 | print("Client: I don't need to check the components classes even when managing the tree:") 165 | client_code2(tree, simple) 166 | 167 | 168 | demo: Demo = Demo() 169 | demo.run() 170 | -------------------------------------------------------------------------------- /behavioral/chain_of_responsability/refactoring-guru.py: -------------------------------------------------------------------------------- 1 | """ 2 | Chain of Responsability is a behavioral design 3 | pattern that allows passing request along the 4 | chain of potential handlers until one of them 5 | handles request. 6 | 7 | The pattern allows multiple objects to handle the request without 8 | coupling sender class to the concrete classes of the receivers. The chain 9 | can be composed dynamically at runtime with any handler that follows 10 | a standard handler interface. 11 | """ 12 | import time 13 | from typing import Dict 14 | 15 | 16 | class Server: 17 | """ 18 | Server class. 19 | """ 20 | def __init__(self) -> None: 21 | self._users: Dict[str] = {} 22 | self._middleware: "AbstractMiddleware" = None 23 | 24 | def set_middleware(self, middleware: "AbstractMiddleware") -> None: 25 | """ 26 | Client passes a chain of object to server. This improves flexibility and 27 | makes testing the server class easier. 28 | """ 29 | self._middleware = middleware 30 | 31 | def login(self, email: str, password: str) -> bool: 32 | if self._middleware.check(email, password): 33 | print('Authorization have been successful!') 34 | 35 | """ Do something useful here for authorized users. """ 36 | return True 37 | else: 38 | return False 39 | 40 | def register(self, email: str, password: str) -> None: 41 | self._users[email] = password 42 | 43 | def has_email(self, email: str) -> bool: 44 | return email in self._users.keys() 45 | 46 | def is_valid_password(self, email: str, password: str) -> bool: 47 | return self._users.get(email) == password 48 | 49 | 50 | class AbstractMiddleware: 51 | 52 | def link_with(self, next: "AbstractMiddleware") -> "AbstractMiddleware": 53 | """ Builds chains of middleware objects. """ 54 | self._next = next 55 | return next 56 | 57 | def check(self, email: str, password: str) -> bool: 58 | """ Subclasses will implement this method with concrete checks. """ 59 | raise NotImplementedError() 60 | 61 | def next_check(self, email: str, password: str) -> bool: 62 | """ 63 | Runs check on the next object in a chain or ends traversing if we're in 64 | last object in chain. 65 | """ 66 | if not self._next: 67 | return True 68 | 69 | return self._next.check(email, password) 70 | 71 | 72 | class ThrottlingMiddleware(AbstractMiddleware): 73 | """ 74 | ConcreteHandler. Checks whether there are too many failed login requests. 75 | """ 76 | def __init__(self, request_per_minute: int) -> None: 77 | self._request_per_minute: int = request_per_minute 78 | 79 | self._current_time: int = int(time.time()) 80 | self._request: int = 0 81 | 82 | def check(self, email: str, password: str) -> bool: 83 | """ 84 | Please, note that check_next() call can be inserted both in the beginning 85 | of this method and in the end. 86 | 87 | This gives much more flexibility than a simple loop over all middleware 88 | objects. For instance, an element of a chain can change the order of 89 | checks by running its check after all other checks. 90 | """ 91 | if int(time.time()) > self._current_time + 60_000: 92 | self._request = 0 93 | self._current_time = int(time.time()) 94 | 95 | self._request += 1 96 | 97 | if self._request > self._request_per_minute: 98 | print('Request limit excedeed!') 99 | return False 100 | 101 | return self.next_check(email, password) 102 | 103 | 104 | class UserExistsMiddleware(AbstractMiddleware): 105 | """ 106 | ConcreteHandler. Checks whether a user with the given credentials exists. 107 | """ 108 | def __init__(self, server: Server = None): 109 | self._server = server 110 | 111 | def check(self, email: str, password: str) -> bool: 112 | if not self._server.has_email(email): 113 | print('This email is not registered!') 114 | return False 115 | 116 | if not self._server.is_valid_password(email, password): 117 | print('Wrong password!') 118 | return False 119 | 120 | return self.next_check(email, password) 121 | 122 | 123 | class RoleCheckMiddleware(AbstractMiddleware): 124 | """ 125 | ConcreteHandler. Checks a user's role. 126 | """ 127 | 128 | def check(self, email: str, password: str) -> bool: 129 | if email == 'admin@example.com': 130 | print('Hello, admin!') 131 | return True 132 | else: 133 | print('Hello, user!') 134 | 135 | return self.next_check(email, password) 136 | 137 | 138 | class Demo: 139 | """ 140 | Demo class. Everything comes together here. 141 | """ 142 | 143 | def run(self): 144 | _server: Server = Server() 145 | 146 | _server.register('admin@example.com', 'admin_pass') 147 | _server.register('user@example.com', 'user_pass') 148 | 149 | """ 150 | All checks are linked. Client can build various chains 151 | using the same components. 152 | """ 153 | _middleware: ThrottlingMiddleware = ThrottlingMiddleware(request_per_minute=2) 154 | 155 | """ This is the pattern """ 156 | _middleware.link_with( 157 | UserExistsMiddleware(_server) 158 | ).link_with( 159 | RoleCheckMiddleware() 160 | ) 161 | 162 | """ Server gets a chain from client code. """ 163 | _server.set_middleware(_middleware) 164 | 165 | _success: bool = False 166 | while not _success: 167 | _email: str = input('Enter a email: ') 168 | _password: str = input('Enter a password: ') 169 | 170 | _success = _server.login(_email, _password) 171 | 172 | 173 | demo: Demo = Demo() 174 | demo.run() -------------------------------------------------------------------------------- /structural/bridge/refactoring-guru.py: -------------------------------------------------------------------------------- 1 | """ 2 | Bridge is a structural design pattern that 3 | divides business logic or huge class into 4 | separate class hierarchies that can be developed 5 | independently. 6 | 7 | One of these hierarchies (often called the Abstraction) 8 | will get a reference to an object of the second hierarchy 9 | (Implementation). The abstraction will be able to delegate 10 | some (sometimes, most) of its calls to the implementations 11 | object. Since all implementations will have a common interface, 12 | they'd be interchangeable inside the abstraction. 13 | """ 14 | 15 | class DeviceInterface: 16 | 17 | def is_enabled(self) -> bool: 18 | raise NotImplementedError() 19 | 20 | def enable(self) -> None: 21 | raise NotImplementedError() 22 | 23 | def disable(self) -> None: 24 | raise NotImplementedError() 25 | 26 | def get_volume(self) -> int: 27 | raise NotImplementedError() 28 | 29 | def set_volume(self, volume: int) -> None: 30 | raise NotImplementedError() 31 | 32 | def get_channel(self) -> int: 33 | raise NotImplementedError() 34 | 35 | def set_channel(self, channel) -> None: 36 | raise NotImplementedError() 37 | 38 | def print_status(self) -> None: 39 | raise NotImplementedError() 40 | 41 | 42 | class Radio(DeviceInterface): 43 | _on: bool = False 44 | _volume: int = 30 45 | _channel: int = 1 46 | 47 | def is_enabled(self) -> bool: 48 | return self._on 49 | 50 | def enable(self) -> None: 51 | self._on = True 52 | 53 | def disable(self) -> None: 54 | self._on = False 55 | 56 | def get_volume(self) -> int: 57 | return self._volume 58 | 59 | def set_volume(self, volume: int) -> None: 60 | if volume > 100: 61 | self._volume = 100 62 | elif volume < 0: 63 | self._volume = 0 64 | else: 65 | self._volume = volume 66 | 67 | def get_channel(self) -> int: 68 | return self._channel 69 | 70 | def set_channel(self, channel: int) -> None: 71 | self._channel = channel 72 | 73 | def print_status(self) -> None: 74 | print("--------------------------") 75 | print("| I'm radio.") 76 | print("| I'm enable" if self._on else "I'm disable") 77 | print(f"| Current volume is {self._volume}%") 78 | print(f"| Current channel is {self._channel}") 79 | print("--------------------------") 80 | 81 | 82 | class Tv(DeviceInterface): 83 | _on: bool = False 84 | _volume: int = 30 85 | _channel: int = 1 86 | 87 | def is_enabled(self) -> bool: 88 | return self._on 89 | 90 | def enable(self) -> None: 91 | self._on = True 92 | 93 | def disable(self) -> None: 94 | self._on = False 95 | 96 | def get_volume(self) -> int: 97 | return self._volume 98 | 99 | def set_volume(self, volume: int) -> None: 100 | if volume > 100: 101 | self._volume = 100 102 | elif volume < 0: 103 | self._volume = 0 104 | else: 105 | self._volume = volume 106 | 107 | def get_channel(self) -> int: 108 | return self._channel 109 | 110 | def set_channel(self, channel: int) -> None: 111 | self._channel = channel 112 | 113 | def print_status(self) -> None: 114 | print("--------------------------") 115 | print("| I'm TV set.") 116 | print("| I'm enabled" if self._on else "I'm disabled") 117 | print(f"| Current volume is {self._volume}%") 118 | print(f"| Current channel is {self._channel}") 119 | print("--------------------------") 120 | 121 | 122 | class RemoteInterface: 123 | 124 | def power(self) -> None: 125 | raise NotImplementedError() 126 | 127 | def volume_down(self) -> None: 128 | raise NotImplementedError() 129 | 130 | def volume_up(self) -> None: 131 | raise NotImplementedError() 132 | 133 | def channel_down(self) -> None: 134 | raise NotImplementedError() 135 | 136 | def channel_up(self) -> None: 137 | raise NotImplementedError() 138 | 139 | 140 | class BasicRemote(RemoteInterface): 141 | 142 | def __init__(self, device: DeviceInterface) -> None: 143 | self._device = device 144 | 145 | def power(self) -> None: 146 | print('Remote: power toggle') 147 | if self._device.is_enabled(): 148 | self._device.disable() 149 | else: 150 | self._device.enable() 151 | 152 | def volume_down(self) -> None: 153 | print('Remote: volume down') 154 | current_volume = self._device.get_volume() 155 | new_volume = current_volume - 10 156 | self._device.set_volume(new_volume) 157 | 158 | def volume_up(self) -> None: 159 | print('Remote: volume up') 160 | current_volume = self._device.get_volume() 161 | new_volume = current_volume + 10 162 | self._device.set_volume(new_volume) 163 | 164 | def channel_down(self) -> None: 165 | print('Remote: channel down') 166 | current_channel = self._device.get_channel() 167 | new_channel = current_channel - 1 168 | self._device.set_channel(new_channel) 169 | 170 | def channel_up(self) -> None: 171 | print('Remote: channel up') 172 | current_channel = self._device.get_channel() 173 | new_channel = current_channel + 1 174 | self._device.set_channel(new_channel) 175 | 176 | 177 | class AdvancedRemote(BasicRemote): 178 | 179 | def __init__(self, device: DeviceInterface): 180 | self._device = device 181 | 182 | def mute(self): 183 | print('Remote: mute') 184 | self._device.set_volume(0) 185 | 186 | 187 | # TV + BasicRemote 188 | tv = Tv() 189 | basic_remote = BasicRemote(tv) 190 | basic_remote.power() 191 | tv.print_status() 192 | 193 | # TV + AdvancedRemote 194 | tv = Tv() 195 | advanced_remote = AdvancedRemote(tv) 196 | advanced_remote.power() 197 | advanced_remote.mute() 198 | tv.print_status() 199 | 200 | # Radio + BasicRemote 201 | radio = Radio() 202 | basic_remote = BasicRemote(radio) 203 | basic_remote.power() 204 | radio.print_status() 205 | 206 | # Radio + AdvancedRemote 207 | radio = Radio() 208 | advanced_remote = AdvancedRemote(radio) 209 | advanced_remote.power() 210 | advanced_remote.mute() 211 | radio.print_status() 212 | -------------------------------------------------------------------------------- /behavioral/strategy/refactoring-guru.py: -------------------------------------------------------------------------------- 1 | """ 2 | Strategy is a behavioral design pattern that 3 | turns a set of behaviors into objects and makes 4 | them interchangeable inside original context object. 5 | 6 | The original object, called context, holds a reference 7 | to a strategy object and delegates it executing the 8 | behavior. In order to change the way the context performs 9 | its work, other objects may replace currently linked 10 | strategy object with another one. 11 | """ 12 | from typing import Dict 13 | 14 | 15 | class CreditCard: 16 | 17 | def __init__(self, number: int, date: str, cvv: str) -> None: 18 | self._amount: int = 100_000 19 | self._number = number 20 | self._date = date 21 | self._cvv = cvv 22 | 23 | def get_amount(self) -> int: 24 | return self._amount 25 | 26 | def set_amount(self, amount: int) -> None: 27 | self._amount = amount 28 | 29 | 30 | class Order: 31 | """ 32 | Order class. Doesn't know the concrete payment method (strategy) user has 33 | picked. It users common strategy interface to delegate collecting payment data 34 | to strategy object. It can be used to save order to database. 35 | """ 36 | def __init__(self): 37 | self._total_cost: int = 0 38 | self._is_closed: bool = False 39 | 40 | def process_order(self, strategy: "PayStrategy") -> None: 41 | strategy.collect_payment_details() 42 | # Here we could collect and store payment data from the strategy. 43 | 44 | def set_total_cost(self, cost: int) -> None: 45 | self._total_cost += cost 46 | 47 | def get_total_cost(self) -> int: 48 | return self._total_cost 49 | 50 | def set_closed(self) -> None: 51 | self._is_closed = True 52 | 53 | def is_closed(self) -> bool: 54 | return self._is_closed 55 | 56 | 57 | class PayStrategy: 58 | """ Common interface for all strategies. """ 59 | 60 | def collect_payment_details(self) -> None: 61 | raise NotImplementedError() 62 | 63 | def pay(self, payment_amount: int) -> bool: 64 | raise NotImplementedError() 65 | 66 | 67 | class PayByPayPal(PayStrategy): 68 | """ Concrete strategy. Implements PayPal payment method. """ 69 | 70 | def __init__(self) -> None: 71 | self._email: str = '' 72 | self._password: str = '' 73 | self._signed_in: bool = False 74 | 75 | self.DATABASES: Dict[str, str] = { 76 | 'amanda1985': 'amanda@ya.com', 77 | 'qwerty': 'jonh@amazon.eu' 78 | } 79 | 80 | def collect_payment_details(self) -> None: 81 | """ Collect customer's data """ 82 | 83 | while not self._signed_in: 84 | self._email: str = input("Enter the user's email: ") 85 | self._password: str = input("Enter the password: ") 86 | if self.verify(): 87 | print("Data verification has been successful.") 88 | else: 89 | print("Wrong email or password!") 90 | 91 | def pay(self, payment_amount: int) -> bool: 92 | """ Save customer data for future shopping attempts. """ 93 | 94 | if self._signed_in: 95 | print(f'Paying {payment_amount} using PayPal.') 96 | return True 97 | else: 98 | return False 99 | 100 | def verify(self) -> bool: 101 | _verified: bool = self._email == self.DATABASES.get(self._password) 102 | self.set_signed_in(_verified) 103 | return self._signed_in 104 | 105 | def set_signed_in(self, signed_in: bool) -> None: 106 | self._signed_in = signed_in 107 | 108 | 109 | class PayByCreditCard(PayStrategy): 110 | """ Concrete strategy. Implements credit card payment method. """ 111 | 112 | def __init__(self): 113 | self._card: CreditCard = None 114 | 115 | def collect_payment_details(self) -> None: 116 | """ Collect credit card data. """ 117 | 118 | _number: int = int(input("Enter the card number: ")) 119 | _date: str = input("Enter the card expiration date 'mm/yy': ") 120 | _cvv: str = input("Enter the cvv code: ") 121 | 122 | self._card = CreditCard(_number, _date, _cvv) 123 | 124 | def pay(self, payment_amount: int) -> bool: 125 | """ After card validation we can charge customer's credit card """ 126 | 127 | if self.card_is_present(): 128 | print(f'Paying {payment_amount} using Credit Card.') 129 | _balance: int = self._card.get_amount() - payment_amount 130 | self._card.set_amount(_balance) 131 | return True 132 | else: 133 | return False 134 | 135 | def card_is_present(self) -> bool: 136 | return self._card != None 137 | 138 | 139 | class Demo: 140 | """ World first console e-commerce application """ 141 | 142 | def __init__(self): 143 | self._PRICES_ON_PRODUCTS: Dict[int, int] = {1: 2200, 2: 1850, 3: 1100, 4: 890,} 144 | self._order: Order = Order() 145 | self._strategy: PayStrategy = None 146 | 147 | def run(self): 148 | while not self._order.is_closed(): 149 | _cost: int = 0 150 | _count: int = 0 151 | _continue_choice_shopping: str = 'C' 152 | _continue_choice_products: str = 'Y' 153 | _payment_method_choice: int = 0 154 | 155 | while _continue_choice_products == 'Y': 156 | print(_continue_choice_products) 157 | print("Please, select a product: ") 158 | print("1 - Mother board") 159 | print("2 - CPU") 160 | print("3 - HDD") 161 | print("4 - Memory") 162 | 163 | _choice: int = int(input('Enter your choice here: ')) 164 | _cost = self._PRICES_ON_PRODUCTS.get(_choice) 165 | _count: int = int(input("Count: ")) 166 | 167 | self._order.set_total_cost(_count * _cost) 168 | _continue_choice_products: str = input("Do you wish to continue selecting products? Y/N: ") 169 | 170 | print("Please, select the payment method: ") 171 | print("1 - PayPal") 172 | print("2 - Credit Card") 173 | _payment_method_choice: int = int(input('Enter your choice here: ')) 174 | 175 | # Client creates different strategies based on input from user, 176 | # application configuration, etc. 177 | if _payment_method_choice == 1: 178 | self._strategy = PayByPayPal() 179 | else: 180 | self._strategy = PayByCreditCard() 181 | 182 | # Order object delegates gathering payment data to strategy 183 | # object, since only strategies know what data they need to 184 | # process a payment. 185 | self._order.process_order(self._strategy) 186 | 187 | _continue_choice_shopping: str = input(f"Pay {self._order.get_total_cost()} units or Continue shopping? P/C: ") 188 | if _continue_choice_shopping == 'P': 189 | # Finally, strategy handles the payment. 190 | if self._strategy.pay(self._order.get_total_cost()): 191 | print('Payment has been successful.') 192 | else: 193 | print('FAIL! Please, check your data.') 194 | 195 | self._order.set_closed() 196 | 197 | 198 | demo: Demo = Demo() 199 | demo.run() -------------------------------------------------------------------------------- /behavioral/visitor/refactoring-guru.py: -------------------------------------------------------------------------------- 1 | """ 2 | Visitor is behavioral design pattern that 3 | allows adding new behaviors to existing class 4 | hierarchy without altering any existing code. 5 | """ 6 | import typing 7 | 8 | 9 | class Shape: 10 | 11 | def move(self, x: int, y: int) -> None: raise NotImplementedError() 12 | def draw(self) -> None: raise NotImplementedError() 13 | def accept(self, visitor: "Visitor") -> str: raise NotImplementedError() 14 | 15 | 16 | class Dot(Shape): 17 | 18 | def __init__(self, id_: int, x: int, y: int) -> None: 19 | self._id = id_ 20 | self._x = x 21 | self._y = y 22 | 23 | def accept(self, visitor: "Visitor"): 24 | # double dispach 25 | return visitor.visit_dot(self) 26 | 27 | def move(self, x: int, y: int) -> None: 28 | print('Dot moved!') 29 | 30 | def draw(self) -> None: 31 | print('Dot drawn!') 32 | 33 | def get_id(self) -> None: 34 | return self._id 35 | 36 | def get_x(self) -> int: 37 | return self._x 38 | 39 | def get_y(self) -> int: 40 | return self._y 41 | 42 | 43 | class Circle(Shape): 44 | 45 | def __init__(self, id_: int, x: int, y: int, radius: int) -> None: 46 | self._id = id_ 47 | self._x = x 48 | self._y = y 49 | self._radius = radius 50 | 51 | def accept(self, visitor: "Visitor") -> str: 52 | # double dispach 53 | return visitor.visit_circle(self) 54 | 55 | def move(self, x: int, y: int) -> None: 56 | print('Circle moved!') 57 | 58 | def draw(self) -> None: 59 | print('Circle drawn!') 60 | 61 | def get_id(self) -> None: 62 | return self._id 63 | 64 | def get_x(self) -> int: 65 | return self._x 66 | 67 | def get_y(self) -> int: 68 | return self._y 69 | 70 | def get_radius(self) -> int: 71 | return self._radius 72 | 73 | 74 | class Rectangle(Shape): 75 | 76 | def __init__(self, id_: int, x: int, y: int, width: int, height: int) -> None: 77 | self._id = id_ 78 | self._x = x 79 | self._y = y 80 | self._width = width 81 | self._height = height 82 | 83 | def accept(self, visitor: "Visitor") -> str: 84 | # double dispach 85 | return visitor.visit_rectangle(self) 86 | 87 | def move(self, x: int, y: int) -> None: 88 | print('Rectangle moved!') 89 | 90 | def draw(self) -> None: 91 | print('Rectangle drawn!') 92 | 93 | def get_id(self) -> None: 94 | return self._id 95 | 96 | def get_x(self) -> int: 97 | return self._x 98 | 99 | def get_y(self) -> int: 100 | return self._y 101 | 102 | def get_width(self) -> int: 103 | return self._width 104 | 105 | def get_height(self) -> int: 106 | return self._height 107 | 108 | 109 | class CompoundShape(Shape): 110 | 111 | def __init__(self, _id: int) -> None: 112 | self._id = _id 113 | self._children: typing.List[Shape] = [] 114 | 115 | def accept(self, visitor: "Visitor") -> str: 116 | # double dispach 117 | return visitor.visit_compound_shape(self) 118 | 119 | def move(self, x: int, y: int) -> None: 120 | print('CompoundShape moved!') 121 | 122 | def draw(self) -> None: 123 | print('CompoundShape drawn!') 124 | 125 | def get_id(self) -> None: 126 | return self._id 127 | 128 | def add(self, shape: Shape) -> None: 129 | self._children.append(shape) 130 | 131 | def get_children(self) -> typing.List[Shape]: 132 | return self._children 133 | 134 | 135 | class VisitorInterface: 136 | def visit_dot(self, dot: Dot) -> str: raise NotImplementedError() 137 | def visit_circle(self, circle: Circle) -> str: raise NotImplementedError() 138 | def visit_rectangle(self, rectangle: Rectangle) -> str: raise NotImplementedError() 139 | def visit_compound_shape(self, compound_shape: CompoundShape) -> str: raise NotImplementedError() 140 | 141 | 142 | class ExportXMLVisitor(VisitorInterface): 143 | 144 | def export(self, *shapes: Shape) -> None: 145 | _xml: str = '' 146 | for shape in shapes: 147 | _xml += '" + "\n' 148 | _xml += shape.accept(self) + '\n' 149 | return _xml 150 | 151 | def visit_dot(self, dot: Dot) -> str: 152 | return '\n' + \ 153 | ' ' + str(dot.get_id()) + '\n' + \ 154 | ' ' + str(dot.get_x()) + '\n' + \ 155 | ' ' + str(dot.get_y()) + '\n' + \ 156 | '' 157 | 158 | def visit_circle(self, circle: Circle) -> str: 159 | return '\n' + \ 160 | ' ' + str(circle.get_id()) + '\n' + \ 161 | ' ' + str(circle.get_x()) + '\n' + \ 162 | ' ' + str(circle.get_y()) + '\n' + \ 163 | ' ' + str(circle.get_y()) + '\n' + \ 164 | '' 165 | 166 | def visit_rectangle(self, rectange: Rectangle) -> str: 167 | return '\n' + \ 168 | ' ' + str(rectange.get_id()) + '\n' + \ 169 | ' ' + str(rectange.get_x()) + '\n' + \ 170 | ' ' + str(rectange.get_y()) + '\n' + \ 171 | ' ' + str(rectange.get_width()) + '\n' + \ 172 | ' ' + str(rectange.get_height()) + '\n' + \ 173 | '' 174 | 175 | def visit_compound_shape(self, compound_shape: CompoundShape) -> str: 176 | return '\n' + \ 177 | ' ' + str(compound_shape.get_id()) + '\n' + \ 178 | self._visit_compound_shape(compound_shape) + \ 179 | '' 180 | 181 | def _visit_compound_shape(self, compound_shape: CompoundShape) -> str: 182 | _xml: str = '' 183 | for child in compound_shape.get_children(): 184 | _child_xml: str = child.accept(self) 185 | # Proper identation for sub-objects. 186 | _child_xml = ' ' + _child_xml.replace('\n', '\n ') + '\n' 187 | _xml += _child_xml 188 | return _xml 189 | 190 | 191 | class Demo: 192 | 193 | def run(self) -> None: 194 | # create base shapes 195 | _dot: Dot = Dot(1, 10, 55) 196 | _circle: Circle = Circle(2, 23, 15, 10) 197 | _rectangle: Rectangle = Rectangle(3, 10, 17, 20, 30) 198 | 199 | # create a compound shape and add base shapes inside there 200 | _first_compound_shape: CompoundShape = CompoundShape(4) 201 | _first_compound_shape.add(_dot) 202 | _first_compound_shape.add(_circle) 203 | _first_compound_shape.add(_rectangle) 204 | 205 | # create a second compound shape and add dot inside there 206 | _second_compound_shape: CompoundShape = CompoundShape(5) 207 | _second_compound_shape.add(_dot) 208 | 209 | # add the second compound shape inside the first compound shape 210 | _first_compound_shape.add(_second_compound_shape) 211 | 212 | _exporter: ExportXMLVisitor = ExportXMLVisitor() 213 | _exported_shapes_as_xml = _exporter.export(_circle, _first_compound_shape) 214 | 215 | print(_exported_shapes_as_xml) 216 | 217 | 218 | demo: Demo = Demo() 219 | demo.run() 220 | -------------------------------------------------------------------------------- /behavioral/state/refactoring-guru.py: -------------------------------------------------------------------------------- 1 | """ 2 | State is a behavioral design pattern that allows 3 | an object to change the behavior when its internal 4 | state changes. 5 | 6 | The pattern extracts state-related behaviors into 7 | separate state classes and forces original object 8 | to delegate the work to an instance of these classes, 9 | instead of acting on its own. 10 | """ 11 | from typing import List 12 | 13 | 14 | class Player: 15 | 16 | def __init__(self): 17 | self._playing: bool = False 18 | self._playlist: List[str] = [] 19 | self._current_track: int = 0 20 | 21 | self._state = ReadyState(self) 22 | self.set_playing(True) 23 | for i in range(1, 13): 24 | self._playlist.append(f'Track {i}') 25 | 26 | def change_state(self, state: "StateInterface") -> None: 27 | self._state = state 28 | 29 | def get_state(self) -> "StateInterface": 30 | return self._state 31 | 32 | def set_playing(self, playing: bool) -> None: 33 | self._playing = playing 34 | 35 | def is_playing(self) -> bool: 36 | return self._playing 37 | 38 | def start_playback(self) -> str: 39 | return f'Playing {self._playlist[self._current_track]}' 40 | 41 | def next_track(self) -> None: 42 | self._current_track += 1 43 | 44 | if self._current_track >= len(self._playlist): 45 | self._current_track = 0 46 | 47 | print(f'Playing {self._playlist[self._current_track]}') 48 | 49 | def previous_track(self) -> None: 50 | self._current_track -= 1 51 | 52 | if self._current_track < 0: 53 | self._current_track = 1 54 | 55 | print(f'Playing {self._playlist[self._current_track]}') 56 | 57 | def set_current_track_after_stop(self) -> None: 58 | self._current_track = 0 59 | 60 | 61 | class StateInterface: 62 | """ 63 | Common interface for all states 64 | """ 65 | def __init__(self, player: Player) -> None: 66 | """ 67 | Player acts a context class (i.e main class) 68 | 69 | Context passes itself through the state contructor. 70 | This may help a state to fetch some useful context 71 | data if needed. 72 | """ 73 | self._player = player 74 | 75 | def on_lock(self) -> str: 76 | raise NotImplementedError() 77 | 78 | def on_play(self) -> str: 79 | raise NotImplementedError() 80 | 81 | def on_next(self) -> str: 82 | raise NotImplementedError() 83 | 84 | def on_previous(self) -> str: 85 | raise NotImplementedError() 86 | 87 | 88 | class LockedState(StateInterface): 89 | """ 90 | Concrete states provide the special implementation for all 91 | interface methods. 92 | """ 93 | def __init__(self, player: Player) -> None: 94 | super().__init__(player) 95 | self._player.set_playing(False) 96 | 97 | def on_lock(self) -> str: 98 | if self._player.is_playing(): 99 | self._player.change_state(ReadyState(self._player)) 100 | return 'Stop playing' 101 | else: 102 | return 'Locked...' 103 | 104 | def on_play(self) -> str: 105 | self._player.change_state(ReadyState(self._player)) 106 | return 'Ready' 107 | 108 | def on_next(self) -> str: 109 | return 'Locked...' 110 | 111 | def on_previous(self) -> str: 112 | return 'Locked...' 113 | 114 | 115 | class ReadyState(StateInterface): 116 | """ 117 | They can also trigger state transitions in the context. 118 | """ 119 | def __init__(self, player: Player) -> None: 120 | super().__init__(player) 121 | 122 | def on_lock(self) -> str: 123 | self._player.change_state(LockedState(self._player)) 124 | return 'Locked...' 125 | 126 | def on_play(self) -> str: 127 | _action: str = self._player.start_playback() 128 | self._player.change_state(PlayingState(self._player)) 129 | return _action 130 | 131 | def on_next(self) -> str: 132 | return 'Locked...' 133 | 134 | def on_previous(self) -> str: 135 | return 'Locked...' 136 | 137 | 138 | class PlayingState(StateInterface): 139 | """ 140 | Concrete states provide the special implementation for all 141 | interface methods. 142 | """ 143 | def __init__(self, player: Player) -> None: 144 | super().__init__(player) 145 | 146 | def on_lock(self) -> str: 147 | self._player.change_state(LockedState(self._player)) 148 | self._player.set_current_track_after_stop() 149 | return 'Stop playing' 150 | 151 | def on_play(self) -> str: 152 | self._player.change_state(ReadyState(self._player)) 153 | return 'Paused...' 154 | 155 | def on_next(self) -> str: 156 | return self._player.next_track() 157 | 158 | def on_previous(self) -> str: 159 | return self._player.previous_track() 160 | 161 | 162 | class Demo: 163 | 164 | def run(self): 165 | _player: Player = Player() 166 | 167 | # play, initial state (PlayingState) 168 | print(_player.get_state().on_play()) 169 | 170 | # play all tracks until start again, state remains the same (PlayingState) 171 | _player.get_state().on_next() # foward 172 | _player.get_state().on_next() # foward 173 | _player.get_state().on_next() # foward 174 | _player.get_state().on_next() # foward 175 | _player.get_state().on_next() # foward 176 | _player.get_state().on_next() # foward 177 | _player.get_state().on_next() # foward 178 | _player.get_state().on_next() # foward 179 | _player.get_state().on_next() # foward 180 | _player.get_state().on_next() # foward 181 | _player.get_state().on_next() # foward 182 | 183 | # change the state object from (PlayingState) to (ReadyState) 184 | print(_player.get_state().on_play()) 185 | 186 | _player.get_state().on_next() # locked... 187 | _player.get_state().on_previous() # locked... 188 | 189 | # change the state object from (ReadyState) to (PlayingState) again 190 | print(_player.get_state().on_play()) 191 | 192 | _player.get_state().on_previous() # backward 193 | _player.get_state().on_previous() # backward 194 | _player.get_state().on_previous() # backward 195 | _player.get_state().on_previous() # backward 196 | _player.get_state().on_previous() # backward 197 | _player.get_state().on_previous() # backward 198 | _player.get_state().on_previous() # backward 199 | _player.get_state().on_previous() # backward 200 | _player.get_state().on_previous() # backward 201 | _player.get_state().on_previous() # backward 202 | _player.get_state().on_previous() # backward 203 | 204 | # change the state object from (PlayingState) to (LockedState) 205 | print(_player.get_state().on_lock()) 206 | 207 | _player.get_state().on_next() # locked... 208 | _player.get_state().on_previous() # locked... 209 | 210 | # state remains the same (LockedState) 211 | print(_player.get_state().on_lock()) 212 | 213 | # change the state object from (LockedState) to (ReadyState) 214 | print(_player.get_state().on_play()) 215 | # change the state object from (ReadyState) to (PlayingState) 216 | print(_player.get_state().on_play()) 217 | # change the state object from (PlayingState) to (LockedState) 218 | print(_player.get_state().on_lock()) 219 | 220 | 221 | demo: Demo = Demo() 222 | demo.run() 223 | -------------------------------------------------------------------------------- /behavioral/command/refactoring-guru.py: -------------------------------------------------------------------------------- 1 | """ 2 | Command is a behavioral design pattern that 3 | converts requests or simple operations into 4 | objects. 5 | 6 | The conversion allows deferred or remote execution 7 | of commands, storing command history, etc. 8 | """ 9 | from typing import List 10 | 11 | 12 | class TextField: 13 | """ 14 | TextField works a GUI text component, is this context it's acts as (receiver) 15 | 16 | Receiver -> is the object that perform the concrete action triggered on commands 17 | """ 18 | def __init__(self) -> None: 19 | # text 20 | self._text: str = '' 21 | self._selected_text: str = '' 22 | # positions 23 | self._offset: int = 0 24 | self._start: int = 0 25 | self._end: int = 0 26 | 27 | # text methods 28 | 29 | def set_text(self, text: str) -> None: 30 | self._text = text 31 | 32 | def get_text(self) -> str: 33 | return self._text 34 | 35 | def select_text(self, start: int = 0, end: int = 0) -> None: 36 | self._selected_text = self._text[start:end] 37 | 38 | def get_selected_text(self) -> str: 39 | return self._selected_text 40 | 41 | def insert_text(self, clipboard: str, offset: int = 0) -> None: 42 | self._text = self._text[:offset] + clipboard + self._text[offset:] 43 | self._offset = offset 44 | 45 | # positions 46 | 47 | def set_caret_position(self, offset: int) -> None: 48 | self._offset = offset 49 | 50 | def get_caret_position(self) -> int: 51 | return self._offset 52 | 53 | def get_selection_start(self) -> int: 54 | return self._start 55 | 56 | def get_selection_end(self) -> int: 57 | return self._end 58 | 59 | 60 | class Editor: 61 | """ 62 | Editor works as GUI rich editor, in this context it's acts as a (sender) 63 | 64 | Sender -> is the object that create and trigger all commands 65 | """ 66 | def __init__(self) -> None: 67 | self._text_field: TextField = TextField() 68 | self._clipboard: str = '' 69 | self._history: CommandHistory = CommandHistory() 70 | 71 | def _execute_command(self, command: "AbstractCommand") -> None: 72 | if command.execute(): 73 | self._history.push(command) 74 | 75 | def _undo(self) -> None: 76 | # import pdb; pdb.set_trace() 77 | if self._history.is_empty(): 78 | return False 79 | 80 | _command: AbstractCommand = self._history.pop() 81 | if _command: 82 | _command.undo() 83 | 84 | # simulate actions on text editor 85 | 86 | def typing_text(self, text: str) -> None: 87 | self._text_field.set_text(text) 88 | 89 | def ctrl_c(self, start: int, end: int) -> None: 90 | self._text_field.select_text(start, end) 91 | self._execute_command(CopyCommand(self)) 92 | 93 | def ctrl_v(self, offset: int) -> None: 94 | self._text_field.set_caret_position(offset) 95 | self._execute_command(PasteCommand(self)) 96 | 97 | def ctrl_x(self, start: int, end: int) -> None: 98 | self._text_field.select_text(start, end) 99 | self._execute_command(CutCommand(self)) 100 | 101 | def ctrl_z(self) -> None: 102 | self._undo() 103 | 104 | 105 | class AbstractCommand: 106 | """ 107 | Abstract base command 108 | """ 109 | def __init__(self, editor: Editor) -> None: 110 | self._editor = editor 111 | self._backup: str = '' 112 | 113 | def backup(self) -> None: 114 | self._backup = self._editor._text_field.get_text() 115 | 116 | def undo(self) -> None: 117 | self._editor._text_field.set_text(self._backup) 118 | 119 | def execute(self) -> bool: 120 | """ 121 | execute perform a operation and return True if current state was changed. 122 | """ 123 | raise NotImplementedError() 124 | 125 | 126 | class CopyCommand(AbstractCommand): 127 | """ 128 | CopyCommand class, implement the copy operation from receiver 129 | 130 | copy selected text to clipboard 131 | """ 132 | def execute(self) -> bool: 133 | _selected_text: str = self._editor._text_field.get_selected_text() 134 | self._editor._clipboard = _selected_text 135 | return False 136 | 137 | 138 | class PasteCommand(AbstractCommand): 139 | """ 140 | PasteCommand class, implement the paste operation from receiver 141 | 142 | paste text from clipboard 143 | """ 144 | def execute(self) -> bool: 145 | if not self._editor._clipboard: 146 | return False 147 | 148 | self.backup() 149 | 150 | _clipboard = self._editor._clipboard 151 | _offset = self._editor._text_field.get_caret_position() 152 | self._editor._text_field.insert_text(_clipboard, offset=_offset) 153 | 154 | return True 155 | 156 | 157 | class CutCommand(AbstractCommand): 158 | """ 159 | CutCommand class, implement the cut operation from receiver 160 | 161 | cut text from clipboard 162 | """ 163 | def execute(self) -> bool: 164 | if not self._editor._text_field.get_selected_text(): 165 | return False 166 | 167 | self.backup() 168 | 169 | _current_text: str = self._editor._text_field.get_text() 170 | self._editor._clipboard = self._editor._text_field.get_selected_text() 171 | 172 | _cuttted_text: str = self._cut_text(_current_text) 173 | self._editor._text_field.set_text(_cuttted_text) 174 | 175 | def _cut_text(self, text: str) -> str: 176 | _start = self._editor._text_field.get_selection_start() 177 | _end = self._editor._text_field.get_selection_end() 178 | 179 | _cutted_text: str = text[_start:] + text[:_end] 180 | 181 | return _cutted_text 182 | 183 | 184 | class CommandHistory: 185 | """ 186 | CommandHistory class, save history of all command 187 | that has was change editor state. 188 | """ 189 | def __init__(self) -> None: 190 | self._history: List[AbstractCommand] = [] 191 | 192 | def push(self, command: AbstractCommand) -> None: 193 | self._history.append(command) 194 | 195 | def pop(self) -> AbstractCommand: 196 | return self._history.pop() 197 | 198 | def is_empty(self) -> bool: 199 | return not self._history 200 | 201 | 202 | class Demo: 203 | 204 | def run(self) -> None: 205 | editor: Editor = Editor() 206 | editor.typing_text('Hi my name is Rafael Cassau!') 207 | 208 | # simule Ctrl+C Ctrl+V action 209 | editor.ctrl_c(13, 28) 210 | editor.ctrl_v(28) 211 | print('Ctrl+C Ctrl+V:') 212 | print('-------------------') 213 | print('Text copied: {}'.format(editor._text_field.get_selected_text())) 214 | print('Text pasted, new text: {}'.format(editor._text_field.get_text())) 215 | print('-------------------') 216 | 217 | # simulate Ctrl+Z action 218 | editor.ctrl_z() 219 | print('Ctrl+Z:') 220 | print('-------------------') 221 | print('undo, new text: {}'.format(editor._text_field.get_text())) 222 | print('-------------------') 223 | 224 | # simulate Ctrl+X Ctrl+V action 225 | editor.ctrl_x(13, 28) 226 | editor.ctrl_v(0) 227 | print('Ctrl+X + Ctrl+V:') 228 | print('-------------------') 229 | print('Text cutted: {}'.format(editor._text_field.get_selected_text())) 230 | print('Text pasted, new text: {}'.format(editor._text_field.get_text())) 231 | print('-------------------') 232 | 233 | editor.ctrl_z() 234 | print('Ctrl+Z:') 235 | print('-------------------') 236 | print('undo, new text: {}'.format(editor._text_field.get_text())) 237 | print('-------------------') 238 | 239 | 240 | demo: Demo = Demo() 241 | demo.run() 242 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # design-patterns 2 | Play design patterns 3 | 4 | 5 | * Design pattern book https://sourcemaking.com/design_patterns/ 6 | * Udemy course https://www.udemy.com/python-design-patterns/ 7 | * Refactoring-guru 8 | https://refactoring.guru/design-patterns/ 9 | * Python examples https://python-3-patterns-idioms-test.readthedocs.io 10 | 11 | 12 | Creational Design Patterns 13 | These patterns provide various object creation mechanisms, which 14 | increase flexibility and reuse of existing code. 15 | 16 | Factory 17 | is a creational design pattern that provides an interface 18 | for creating objects in superclass, but allow subclasses to alter 19 | the type of objects that will be created. 20 | 21 | (https://refactoring.guru/design-patterns/factory-method) 22 | 23 | Abstract Factory 24 | Is a creational design pattern that lets you produce 25 | families of related objects without specifying their concrete classes. 26 | 27 | (https://refactoring.guru/design-patterns/abstract-factory) 28 | 29 | Builder 30 | Is a creational design pattern that lets you produce different 31 | types and representations of an object using the same building process. 32 | Builder allows constructing complex objects step by step. 33 | 34 | (https://refactoring.guru/design-patterns/builder) 35 | 36 | Prototype 37 | Is a creational design pattern that lets you produce new objects 38 | by copying existing ones without compromising their internals. 39 | 40 | (https://refactoring.guru/design-patterns/prototype) 41 | 42 | Singleton 43 | Is a creational design pattern that lets you ensure that a class has 44 | only one instance and provide a global access point to this instance. 45 | 46 | (https://refactoring.guru/design-patterns/singleton) 47 | 48 | Borg 49 | The Borg Idiom (a.k.a monostate pattern) lets a class have as many 50 | instances as one likes, but ensures that they all share the same state. 51 | 52 | 53 | Structural Patterns 54 | These patterns explain how to assemble objects and classes into 55 | larger structures, while keeping this structures flexible and efficient. 56 | 57 | Facade 58 | Is a structural design pattern that provides a simplified interface 59 | to a library, a framework, or any complex set of classes. 60 | 61 | (https://refactoring.guru/design-patterns/facade) 62 | 63 | Proxy 64 | Is a structural design pattern that lets you provide a subsitute or 65 | placeholder for another object. A proxy controls access to the original 66 | object, allowing you to perform something either before or after the 67 | request gets through to the original object. 68 | 69 | (https://refactoring.guru/design-patterns/proxy) 70 | 71 | Decorator 72 | Is a structural design pattern that lets you attach new behaviors to 73 | objects by placing these objects inside special wrapper objects that 74 | contain the behaviors. 75 | 76 | (https://refactoring.guru/design-patterns/decorator) 77 | 78 | Adapter 79 | Is a structural design pattern that allows objects with incompatible 80 | interfaces to collaborate. 81 | 82 | (https://refactoring.guru/design-patterns/adapter) 83 | 84 | Bridge 85 | Is a structural design pattern that lets you split a large class or a set 86 | of closely related classes into two separate hierarchies - abstraction and 87 | implementation - which can be developed independently of each other. 88 | 89 | https://refactoring.guru/design-patterns/bridge 90 | 91 | Composite 92 | Is a structural design pattern that lets you compose objects into tree 93 | structures and when work with these structures as if they were individual 94 | objects. 95 | 96 | (https://refactoring.guru/design-patterns/composite) 97 | 98 | Flyweight 99 | Is a structural design pattern that lets you fit more objects into the 100 | available amount of RAM by sharing common parts of state between multiple 101 | objects instead of keeping all of the data in each object. 102 | 103 | (https://refactoring.guru/design-patterns/flyweight) 104 | 105 | 106 | Behavioral 107 | These patterns are concerned with algorithms and the assignment 108 | of responsabilities between objects. 109 | 110 | Command 111 | Is a behavioral design pattern that turns a request into stand-alone 112 | object that contains all information about the request. This transformation 113 | lets you parametrize methods with different requests, delay or queue a 114 | request's execution, and support undoable operations. 115 | 116 | (https://refactoring.guru/design-patterns/command) 117 | 118 | Interpreter 119 | Is a behavioral design pattern that denife a grammatical representation 120 | for a language and an interpreter to interpret the gramar. 121 | 122 | State 123 | Is a behavioral design pattern that lets an object alter its behavior 124 | when its internal state changes. It appears as if the object changed 125 | its class. 126 | 127 | (https://refactoring.guru/design-patterns/state) 128 | 129 | Chain of responsability 130 | Is a behavioral design pattern that lets you pass requests along 131 | a chain of handlers. Upon receiving a request, each handler decides 132 | either to process the request or to pass it to the next handler in 133 | the chain. 134 | 135 | (https://refactoring.guru/design-patterns/chain-of-responsibility) 136 | 137 | Observer 138 | Is a behavioral design pattern that lets you define a subscription 139 | mechanism to notify multiple objects about any events that happen to 140 | the object they're observing. 141 | 142 | (https://refactoring.guru/design-patterns/observer) 143 | 144 | Strategy 145 | Is a behavioral design pattern that lets you define a family of algorithms, 146 | put each of them into a separate class, and make their objects interhangeable. 147 | 148 | (https://refactoring.guru/design-patterns/strategy) 149 | 150 | Memento 151 | Is a behavioral design pattern that lets you save and restore the previous 152 | state of an object without revealing the details of its implementation. 153 | 154 | (https://refactoring.guru/design-patterns/memento) 155 | 156 | Template Method 157 | Is a behavioral design pattern that defines the skeleton of an algorithm in 158 | the superclass but lets subclasses override specific steps of the algorithm 159 | without changing its structure. 160 | 161 | (https://refactoring.guru/design-patterns/template-method) 162 | 163 | Visitor 164 | Is a behavioral design pattern that lets you separate algorithms 165 | from the objects on which they operate. 166 | 167 | (https://refactoring.guru/design-patterns/visitor) 168 | 169 | Iterator 170 | Is a behavioral design pattern that lets you traverse elements of a collection 171 | without exposing its underlying representation (list, stack, tree, etc.). 172 | 173 | (https://refactoring.guru/design-patterns/iterator) 174 | 175 | 176 | Mediator 177 | Is a behavioral design pattern that lets you reduce chaotic dependencies between 178 | objects. The pattern restricts direct communications between the objects and forces 179 | them to collaborate only via a mediator object. 180 | 181 | (https://refactoring.guru/design-patterns/mediator) 182 | 183 | Python buitin patterns 184 | 185 | Iterables and Iterators 186 | The iterator pattern aims to provide a way to access 187 | the elements of an aggregate object sequentially without 188 | exposing its underlying representation 189 | 190 | List comprehension 191 | A list comprehension is a tool for transforming any iterable into a new list. 192 | Elements of the iterable can be conditionally included and transformed as required. 193 | 194 | Wrapper functions (or decorators) 195 | A decorator is a function that takes another function and extends the behaviour of 196 | the second function without explicitly modifying it 197 | Can be reused across multiple functions (but does no apply to classes) 198 | Functions are first-class objects, which means they can be defined in a 199 | returned by other functions. -------------------------------------------------------------------------------- /creational/builder/refactoring-guru.py: -------------------------------------------------------------------------------- 1 | """ 2 | Builder is a creational design pattern that lets 3 | you produce different types and representations 4 | of an object using the same process. Builder allows 5 | constructing complex objects step by step. 6 | """ 7 | 8 | from enum import Enum 9 | 10 | 11 | class BuilderInterface: 12 | """ 13 | Builder interface defines all possible ways to configure a product. 14 | """ 15 | 16 | def set_type(self, type): 17 | raise NotImplementedError 18 | 19 | def set_seats(self, seats): 20 | raise NotImplementedError 21 | 22 | def set_engine(self, engine): 23 | raise NotImplementedError 24 | 25 | def set_transmission(self, transmission): 26 | raise NotImplementedError 27 | 28 | def set_trip_computer(self, trip_computer): 29 | raise NotImplementedError 30 | 31 | def set_gps_navigator(self, gps_navigator): 32 | raise NotImplementedError 33 | 34 | 35 | class CarBuilder(BuilderInterface): 36 | """ 37 | Concrete builders implement steps defined in the common interface. 38 | """ 39 | 40 | def set_type(self, type): 41 | self.type = type 42 | 43 | def set_seats(self, seats): 44 | self.seats = seats 45 | 46 | def set_engine(self, engine): 47 | self.engine = engine 48 | 49 | def set_transmission(self, transmission): 50 | self.transmission = transmission 51 | 52 | def set_trip_computer(self, trip_computer): 53 | self.trip_computer = trip_computer 54 | 55 | def set_gps_navigator(self, gps_navigator): 56 | self.gps_navigator = gps_navigator 57 | 58 | def get_result(self): 59 | return Car( 60 | type=self.type, 61 | seats=self.seats, 62 | engine=self.engine, 63 | transmission=self.transmission, 64 | trip_computer=self.trip_computer, 65 | gps_navigator=self.gps_navigator, 66 | ) 67 | 68 | 69 | class CarManualBuilder(BuilderInterface): 70 | """ 71 | Unlike other creational patterns, Builder can construct unrelated products, 72 | which don't have the common interface. 73 | 74 | In this case we build a user manual for a car, using the same steps as we 75 | built a car. This allows to produce manuals for specific car models, 76 | configured with different features. 77 | """ 78 | 79 | def set_type(self, type): 80 | self.type = type 81 | 82 | def set_seats(self, seats): 83 | self.seats = seats 84 | 85 | def set_engine(self, engine): 86 | self.engine = engine 87 | 88 | def set_transmission(self, tranmission): 89 | self.tranmission = tranmission 90 | 91 | def set_trip_computer(self, trip_computer): 92 | self.trip_computer = trip_computer 93 | 94 | def set_gps_navigator(self, gps_navigator): 95 | self.gps_navigator = gps_navigator 96 | 97 | def get_result(self): 98 | return Manual( 99 | type=self.type, 100 | seats=self.seats, 101 | engine=self.engine, 102 | transmission=self.tranmission, 103 | trip_computer=self.trip_computer, 104 | gps_navigator=self.gps_navigator 105 | ) 106 | 107 | 108 | class Car: 109 | """ 110 | Car is a product class. 111 | """ 112 | fuel = 0 113 | 114 | def __init__(self, type, seats, engine, transmission, trip_computer, gps_navigator): 115 | self.type = type 116 | self.seats = seats 117 | self.engine = engine 118 | self.transmission = transmission 119 | self.trip_computer = trip_computer 120 | 121 | # complexity instantiation process is incapsulated 122 | self.trip_computer.set_car(self) 123 | 124 | self.gps_navigator = gps_navigator 125 | 126 | # get custom attribute 127 | def get_fuel(self): 128 | return self.fuel 129 | 130 | # set custom attribute 131 | def set_fuel(self, fuel): 132 | self.fuel = fuel 133 | 134 | def get_type(self): 135 | return self.type 136 | 137 | def get_seats(self): 138 | return self.seats 139 | 140 | def get_engine(self): 141 | return self.engine 142 | 143 | def get_transmission(self): 144 | return self.transmission 145 | 146 | def get_trip_computer(self): 147 | return self.trip_computer 148 | 149 | def get_gps_navigator(self): 150 | return self.gps_navigator 151 | 152 | 153 | class Manual: 154 | 155 | def __init__(self, type, seats, engine, transmission, trip_computer, gps_navigator): 156 | self.type = type 157 | self.seats = seats 158 | self.engine = engine 159 | self.transmission = transmission 160 | self.trip_computer = trip_computer 161 | self.gps_navigator = gps_navigator 162 | 163 | def print(self): 164 | info = '' 165 | info += 'Type of car: {} \n'.format(self.type) 166 | info += 'Count of seats: {} \n'.format(self.seats) 167 | info += 'Engine: volume - {}; mileage - {} \n'.format( 168 | self.engine.get_volume(), 169 | self.engine.get_mileage() 170 | ) 171 | info += 'Transmission: {}'.format(self.transmission) 172 | 173 | if self.trip_computer: 174 | info += 'Trip Computer: Functional \n' 175 | else: 176 | info += 'Trip Computer: N/A \n' 177 | 178 | if self.gps_navigator: 179 | info += 'GPS Navigator: Functional \n' 180 | else: 181 | info += 'GPS Navigator: N/A \n' 182 | 183 | return info 184 | 185 | 186 | class Type(Enum): 187 | CITY_CAR = 1 188 | SPORT_CAR = 2 189 | SUV = 3 190 | 191 | 192 | # components 193 | 194 | class Engine: 195 | """ 196 | Just another feature of a car. 197 | """ 198 | 199 | def __init__(self, volume, mileage): 200 | self.volume = volume 201 | self.mileage = mileage 202 | self.started = None 203 | 204 | def on(self): 205 | self.started = True 206 | 207 | def off(self): 208 | self.started = False 209 | 210 | def is_started(self): 211 | return self.is_started 212 | 213 | def go(self, mileage): 214 | if self.started: 215 | self.mileage += mileage 216 | else: 217 | print('Cannot go(), you must start engine first!') 218 | 219 | def get_volume(self): 220 | return self.volume 221 | 222 | def get_mileage(self): 223 | return self.mileage 224 | 225 | 226 | class GPSNavigator: 227 | """ 228 | Just another feature of a car. 229 | """ 230 | 231 | def __init__(self, manual_route=None): 232 | if manual_route: 233 | self.route = manual_route 234 | else: 235 | self.route = '221b, Baker Street, London to Scoltand Yard, 8-10 Broadway, London' 236 | 237 | def get_route(self): 238 | return self.route 239 | 240 | 241 | class Transmission(Enum): 242 | """ 243 | Just another feature of a car. 244 | """ 245 | SINGLE_SPEED = 1 246 | MANUAL = 2 247 | AUTOMATIC = 3 248 | SEMI_AUTOMATIC = 4 249 | 250 | 251 | class TripComputer: 252 | """ 253 | Just another feature of a car. 254 | """ 255 | 256 | def set_car(self, car): 257 | self.car = car 258 | 259 | def show_fuel_level(self): 260 | print('Fuel level: {}'.format(car.get_fuel())) 261 | 262 | def show_status(self): 263 | if self.car.get_engine().is_started(): 264 | print("Car is started") 265 | else: 266 | print("Car isn't started") 267 | 268 | 269 | # class that will handle objects and it's builders 270 | 271 | class Director: 272 | """ 273 | Director defines the order of building steps. 274 | It works with a builder object through common Builder interface. 275 | Therefore it may not know what product is being built. 276 | """ 277 | 278 | def construct_sports_car(self, builder): 279 | builder.set_type(Type.SPORT_CAR) 280 | builder.set_seats(2) 281 | builder.set_engine(Engine(volume=3.0, mileage=0)) 282 | builder.set_transmission(Transmission.SEMI_AUTOMATIC) 283 | builder.set_trip_computer(TripComputer()) 284 | builder.set_gps_navigator(GPSNavigator()) 285 | 286 | def construct_city_car(self, builder): 287 | builder.set_type(type.CITY_CAR) 288 | builder.set_seats(2) 289 | builder.set_engine(volume=1.2, mileage=2) 290 | builder.set_transmission(Transmission.AUTOMATIC) 291 | builder.set_trip_computer(TripComputer()) 292 | builder.set_gps_navigator(GPSNavigator()) 293 | 294 | def constructSUV(self, builder): 295 | builder.set_type(Type.SUV) 296 | builder.set_seats(4) 297 | builder.set_engine(Engine(volume=2.5, mileage=0)) 298 | builder.set_transmission(Transmission.MANUAL) 299 | builder.set_gps_navigator(GPSNavigator()) 300 | 301 | 302 | # client code 303 | 304 | class Client: 305 | 306 | def run(self): 307 | """ 308 | Client class. Everything comes together here. 309 | """ 310 | director = Director() 311 | 312 | """ 313 | Director gets the concrete builder object from the client 314 | (application code). That's because application knows better which 315 | builder to use to get a specific product. 316 | """ 317 | car_builder = CarBuilder() 318 | director.construct_sports_car(car_builder) 319 | """ 320 | The final product is often retrieved from a builder object, since 321 | Director is not aware and not dependent on concrete builders and products. 322 | """ 323 | car = car_builder.get_result() 324 | print('Car built: {}'.format(car.get_type())) 325 | 326 | car_manual_builder = CarManualBuilder() 327 | 328 | """ 329 | Director may know several building recipes. 330 | """ 331 | director.construct_sports_car(car_manual_builder) 332 | car_manual = car_manual_builder.get_result() 333 | print('Car manual built: {} \n'.format(car_manual.print())) 334 | 335 | 336 | client = Client() 337 | client.run() 338 | -------------------------------------------------------------------------------- /behavioral/iterator/refactoring-guru-2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Iterator is a behavioral design pattern that 3 | allows sequential traversal through a complex 4 | data structure without exposing its internal 5 | details. 6 | 7 | Thanks for the Iterator, clients go over elements 8 | of different collections in a similar fashion 9 | using a single iterator interface. 10 | """ 11 | from time import sleep 12 | from typing import List, Dict 13 | 14 | 15 | class ProfileIteratorInterface: 16 | """ Defines profile interface """ 17 | 18 | def has_next(self) -> bool: raise NotImplementedError() 19 | def get_next(self) -> "Profile": raise NotImplementedError() 20 | def reset(self) -> None: raise NotImplementedError() 21 | 22 | 23 | class FacebookIterator(ProfileIteratorInterface): 24 | """ Implements iteration over Facebook profiles """ 25 | 26 | def __init__(self, facebook: "Facebook", type_: str, email: str) -> None: 27 | self._facebook = facebook 28 | self._type = type_ 29 | self._email = email 30 | 31 | self._current_position: int = 0 32 | self._emails: List[str] = [] 33 | self._profiles: List[Profile] = [] 34 | 35 | def lazy_load(self) -> None: 36 | if not self._emails: 37 | _emails_from_facebook: List[str] = self._facebook.request_profile_email_friends(self._email, self._type) 38 | for email in _emails_from_facebook: 39 | self._emails.append(email) 40 | self._profiles.append(None) 41 | 42 | def has_next(self) -> bool: 43 | self.lazy_load() 44 | return self._current_position < len(self._emails) 45 | 46 | def get_next(self) -> "Profile": 47 | if not self.has_next(): 48 | return None 49 | 50 | _friend_email: str = self._emails[self._current_position] 51 | _friend_profile: Profile = self._profiles[self._current_position] 52 | 53 | if not _friend_profile: 54 | _friend_profile = self._facebook.request_profile(_friend_email) 55 | self._profiles[self._current_position] = _friend_profile 56 | 57 | self._current_position += 1 58 | return _friend_profile 59 | 60 | def reset(self) -> None: 61 | self._current_position = 0 62 | 63 | 64 | class LinkedInIterator(ProfileIteratorInterface): 65 | """ Implements iteration over LinkedIn profiles """ 66 | 67 | def __init__(self, linkedin: "LinkedIn", type_: str, email: str) -> None: 68 | self._linkedin = linkedin 69 | self._type = type_ 70 | self._email = email 71 | 72 | self._current_position: int = 0 73 | self._emails: List[str] = [] 74 | self._profiles: List[Profile] = [] 75 | 76 | def lazy_load(self) -> None: 77 | if not self._emails: 78 | _emails_from_linkedin: List[str] = self._linkedin.request_related_email_contacts(self._email, self._type) 79 | for email in _emails_from_linkedin: 80 | self._emails.append(email) 81 | self._profiles.append(None) 82 | 83 | def has_next(self) -> bool: 84 | self.lazy_load() 85 | return self._current_position < len(self._emails) 86 | 87 | def get_next(self) -> "Profile": 88 | if not self.has_next(): 89 | return None 90 | 91 | _contact_email: str = self._emails[self._current_position] 92 | _contact_profile = self._profiles[self._current_position] 93 | 94 | if not _contact_profile: 95 | _contact_profile: Profile = self._linkedin.request_contact(_contact_email) 96 | self._profiles[self._current_position] = _contact_profile 97 | 98 | self._current_position += 1 99 | return _contact_profile 100 | 101 | def reset(self) -> None: 102 | self._current_position = 0 103 | 104 | 105 | class SocialNetworkInterface: 106 | """ Defines common social network interface """ 107 | 108 | def create_friends_iterator(self, profile_email: str) -> ProfileIteratorInterface: raise NotImplementedError() 109 | def create_coworkers_iterator(self, profile_email: str) -> ProfileIteratorInterface: raise NotImplementedError() 110 | 111 | 112 | class Facebook(SocialNetworkInterface): 113 | 114 | def __init__(self, cache: List["Profile"]) -> None: 115 | if cache: 116 | self._profiles: List[Profile] = cache 117 | else: 118 | self._profiles: List[Profile] = [] 119 | 120 | 121 | def request_profile(self, friend_email: str) -> "Profile": 122 | """ 123 | Here would be a POST request to one of the Facebook API endpoints. 124 | Instead, we emulates long network connection, which you would expect 125 | in real life... 126 | """ 127 | self.simulate_network_latency() 128 | print(f'Facebook: Loading profile {friend_email} over the network...') 129 | 130 | return self.find_profile(friend_email) 131 | 132 | def request_profile_email_friends(self, profile_email: str, contact_type: str) -> List[str]: 133 | """ 134 | Here we would be a POST request to one of the Facebook API endpoints. 135 | Instead, we emulate network latency connection, which you would expect 136 | in real life... 137 | """ 138 | self.simulate_network_latency() 139 | print(f'Facebook: Loading "{contact_type}" list of "{profile_email}" over the network...') 140 | 141 | _profile: Profile = self.find_profile(profile_email) 142 | if _profile: 143 | return _profile.get_contacts(contact_type) 144 | return None 145 | 146 | def find_profile(self, friend_email: str) -> "Profile": 147 | for profile in self._profiles: 148 | if profile.get_email() == friend_email: 149 | return profile 150 | return None 151 | 152 | def simulate_network_latency(self): 153 | sleep(2.5) 154 | 155 | def create_friends_iterator(self, profile_email: str) -> ProfileIteratorInterface: 156 | return FacebookIterator(self, 'friends', profile_email) 157 | 158 | def create_coworkers_iterator(self, profile_email: str) -> ProfileIteratorInterface: 159 | return FacebookIterator(self, 'coworkers', profile_email) 160 | 161 | 162 | class LinkedIn(SocialNetworkInterface): 163 | 164 | def __init__(self, cache: List["Profile"]) -> None: 165 | if cache: 166 | self._contacts: List[Profile] = cache 167 | else: 168 | self._contacts: List[Profile] = [] 169 | 170 | def request_contact(self, contact_email: str) -> "Profile": 171 | """ 172 | Here we would be a POST request to one of the LinkedIn API endpoints. 173 | Instead, we emulates long network connection, which you would expect 174 | in the real life... 175 | """ 176 | self.simulate_network_latency() 177 | print(f'LinkedIn: Loading profile "{contact_email}" over the network...') 178 | 179 | return self.find_contact(contact_email) 180 | 181 | def request_related_email_contacts(self, contact_email: str, contact_type: str) -> List[str]: 182 | """ 183 | Here we would be a POST request to one of the LinkedIn API endpoints. 184 | Instead, we emulates long network connection, which you would expect 185 | in the real life... 186 | """ 187 | self.simulate_network_latency() 188 | print(f'LinkedIn: Loading "{contact_type}" list of {contact_email} over the network...') 189 | 190 | _profile: Profile = self.find_contact(contact_email) 191 | if _profile: 192 | return _profile.get_contacts(contact_type) 193 | return None 194 | 195 | def find_contact(self, contact_email: str) -> "Profile": 196 | for profile in self._contacts: 197 | if profile.get_email() == contact_email: 198 | return profile 199 | return None 200 | 201 | def simulate_network_latency(self) -> None: 202 | sleep(2.5) 203 | 204 | def create_friends_iterator(self, profile_email: str) -> ProfileIteratorInterface: 205 | return LinkedInIterator(self, 'friends', profile_email) 206 | 207 | def create_coworkers_iterator(self, profile_email: str) -> ProfileIteratorInterface: 208 | return LinkedInIterator(self, 'coworkers', profile_email) 209 | 210 | 211 | class Profile: 212 | 213 | def __init__(self, email: str, name: str, *contacts: str) -> None: 214 | self._email = email 215 | self._name = name 216 | self._contacts: Dict[str, List[str]] = {} 217 | 218 | # Parse contact list from a set of "friend:email@gmail.com" pairs. 219 | for contact in contacts: 220 | _parts: str = contact.split(':') 221 | _contact_type: str = 'friends' 222 | 223 | if len(_parts) == 1: 224 | _contact_email = _parts[0] 225 | else: 226 | _contact_type = _parts[0] 227 | _contact_email = _parts[1] 228 | 229 | if _contact_type not in self._contacts: 230 | self._contacts[_contact_type] = [] 231 | 232 | self._contacts[_contact_type].append(_contact_email) 233 | 234 | def get_email(self) -> str: 235 | return self._email 236 | 237 | def get_contacts(self, contact_type: str) -> List[str]: 238 | if contact_type not in self._contacts: 239 | self._contacts[contact_type] = [] 240 | 241 | return self._contacts[contact_type] 242 | 243 | 244 | class SocialSpammer: 245 | """ 246 | Message sending app. 247 | """ 248 | 249 | def __init__(self, network: SocialNetworkInterface) -> None: 250 | self._network = network 251 | self._profile_iterator: ProfileIteratorInterface = None 252 | 253 | def send_span_to_friends(self, profile_email: str, message: str) -> None: 254 | print('\n Iterating over friends...\n') 255 | self._profile_iterator = self._network.create_friends_iterator(profile_email) 256 | while self._profile_iterator.has_next(): 257 | _profile: Profile = self._profile_iterator.get_next() 258 | self.send_message(_profile.get_email(), message) 259 | 260 | def send_span_to_coworkers(self, profile_email: str, message: str) -> None: 261 | print('\n Iterating over coworkers...\n') 262 | self._profile_iterator = self._network.create_coworkers_iterator(profile_email) 263 | while self._profile_iterator.has_next(): 264 | _profile: Profile = self._profile_iterator.get_next() 265 | self.send_message(_profile.get_email(), message) 266 | 267 | 268 | def send_message(self, email: str, message: str) -> None: 269 | print(f'Sent message to: {email}. Message body: {message}') 270 | 271 | 272 | class Demo: 273 | """ Demo class. Everything comes together here. """ 274 | 275 | def run(self) -> None: 276 | print('Please specify social network to target spam tool (default:Facebook)') 277 | print('1 - Facebook') 278 | print('2 - LinkedIn') 279 | _choice: int = int(input('Please make your choice: ')) 280 | if _choice == 2: 281 | _network: SocialNetworkInterface = LinkedIn(self.create_test_profiles()) 282 | else: 283 | _network: SocialNetworkInterface = Facebook(self.create_test_profiles()) 284 | 285 | _spammer = SocialSpammer(_network) 286 | 287 | _spammer.send_span_to_friends( 288 | "anna.smith@bing.com", 289 | "Hey! This is Anna's friend Josh. Can you do me a favor and like this post [link]?" 290 | ) 291 | _spammer.send_span_to_coworkers( 292 | "anna.smith@bing.com", 293 | "Hey! This is Anna's boss Jason. Anna told me you would be interested in [link]." 294 | ) 295 | 296 | def create_test_profiles(self) -> List[Profile]: 297 | _profile_data: List[Profile] = [] 298 | _profile_data.append(Profile( 299 | 'anna.smith@bing.com', 300 | 'Anna Smith', 301 | 'friends:mad_max@ya.com', 302 | 'friends:catwoman@yahoo.com', 303 | 'coworkers:sam@amazon.com', 304 | )) 305 | _profile_data.append(Profile( 306 | 'mad_max@ya.com', 307 | 'Maximilian', 308 | 'friends:anna.smith@bing.com', 309 | 'coworkers:sam@amazon.com', 310 | )) 311 | _profile_data.append(Profile( 312 | 'bill@microsoft.eu', 313 | 'Billie', 314 | 'coworkers:avanger@ukr.net', 315 | )) 316 | _profile_data.append(Profile( 317 | 'avanger@ukr.net', 318 | 'Jonh Day', 319 | 'coworkers:bill@microsoft.eu', 320 | )) 321 | _profile_data.append(Profile( 322 | 'sam@amazon.com', 323 | 'Sam Kitting', 324 | 'coworkers:anna.smith@bing.com', 325 | 'coworkers:mad_max@ya.com', 326 | 'friends:catwoman@yahoo.com', 327 | )) 328 | _profile_data.append(Profile( 329 | 'catwoman@yahoo.com', 330 | 'Liza', 331 | 'friends:anna.smith@bing.com', 332 | 'friends:sam@amazon.com', 333 | )) 334 | 335 | return _profile_data 336 | 337 | 338 | demo: Demo = Demo() 339 | demo.run() --------------------------------------------------------------------------------