├── 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()
--------------------------------------------------------------------------------