Skip to content

Commit 8869164

Browse files
Python Patterns v0.2.0
1 parent 19c0414 commit 8869164

32 files changed

+1730
-5
lines changed

.gitignore

+2-4
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,8 @@ ipython_config.py
106106
#pdm.lock
107107
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108108
# in version control.
109-
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
109+
# https://pdm.fming.dev/#use-with-ide
110110
.pdm.toml
111-
.pdm-python
112-
.pdm-build/
113111

114112
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
115113
__pypackages__/
@@ -159,4 +157,4 @@ cython_debug/
159157
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
160158
# and can be added to the global gitignore or merged into this file. For a more nuclear
161159
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
162-
.idea/
160+
.idea/

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
BSD 3-Clause License
22

3-
Copyright (c) 2024, A.A. Suvorov
3+
Copyright (c) 2021-2024, A.A. Suvorov
44

55
Redistribution and use in source and binary forms, with or without
66
modification, are permitted provided that the following conditions are met:

README.md

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Python Patterns
2+
3+
A collection of design patterns and idioms in Python.
4+
5+
***
6+
7+
## Current Patterns
8+
9+
__Creational Patterns__:
10+
11+
| Pattern | Description |
12+
|:-------:| ----------- |
13+
| [Abstract Factory](patterns/creational/abstract_factory.py) | use a generic function with specific factories |
14+
| [Builder](patterns/creational/builder.py) | instead of using multiple constructors, builder receives parameters and returns constructed objects |
15+
| [Factory Method](patterns/creational/factory.py) | delegate a specialized function/method to create instances|
16+
| [Prototype](patterns/creational/prototype.py) | use a factory and clones of a prototype for new instances (if instantiation is expensive) |
17+
| [Singleton](patterns/creational/singleton.py) | Ensures that the class has only one instance, and provides a global access point to it. |
18+
19+
__Structural Patterns__:
20+
21+
| Pattern | Description |
22+
|:-------:| ----------- |
23+
| [Adapter](patterns/structural/adapter.py) | converts the interface of one class to the interface of another that clients expect. |
24+
| [Bridge](patterns/structural/bridge.py) | a client-provider middleman to soften interface changes |
25+
| [Composite](patterns/structural/composite.py) | lets clients treat individual objects and compositions uniformly |
26+
| [Decorator](patterns/structural/decorator.py) | wrap functionality with other functionality in order to affect outputs |
27+
| [Facade](patterns/structural/facade.py) | use one class as an API to a number of others |
28+
| [Flyweight](patterns/structural/flyweight.py) | transparently reuse existing instances of objects with similar/identical state |
29+
| [Proxy](patterns/structural/proxy.py) | an object funnels operations to something else |
30+
31+
__Behavior Patterns__:
32+
33+
| Pattern | Description |
34+
|:-------------------------------------------------------------------------:| ----------- |
35+
| [Blackboard](patterns/behavioral/blackboard.py) | architectural model, assemble different sub-system knowledge to build a solution, AI approach - non gang of four pattern. |
36+
| [Chain Of Responsibility](patterns/behavioral/chain_of_responsibility.py) | apply a chain of successive handlers to try and process the data. |
37+
| [Command](patterns/behavioral/command.py) | bundle a command and arguments to call later. |
38+
| [Interpreter](patterns/behavioral/interpreter.py) | a behavioral design pattern that solves a frequently encountered but subject to change problem. |
39+
| [Iterator](patterns/behavioral/iterator.py) | traverse a container and access the container's elements. |
40+
| [Mediator](patterns/behavioral/mediator.py) | an object that knows how to connect other objects and act as a proxy. |
41+
| [Memento](patterns/behavioral/memento.py) | generate an opaque token that can be used to go back to a previous state. |
42+
| [Observer](patterns/behavioral/observer.py) | provide a callback for notification of events/changes to data. |
43+
| [State](patterns/behavioral/state.py) | logic is organized into a discrete number of potential states and the next state that can be transitioned to. |
44+
| [Strategy](patterns/behavioral/strategy.py) | selectable operations over the same data. |
45+
| [Template Method](patterns/behavioral/template_method.py) |defines the basis of the algorithm and allows subclasses to override some of the steps in the algorithm, without changing its structure as a whole. |
46+
| [Visitor](patterns/behavioral/visitor.py) | invoke a callback for all items of a collection. |
47+
48+
***
49+
50+
## Disclaimer of liability:
51+
52+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
53+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
54+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
55+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
56+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
57+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
58+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
59+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
60+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
61+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
62+
63+
***
64+
65+
# --------------------------------------------------------
66+
# Licensed under the terms of the BSD 3-Clause License
67+
# (see LICENSE for details).
68+
# Copyright © 2018-2024, A.A Suvorov
69+
# All rights reserved.
70+
# --------------------------------------------------------

patterns/__init__.py

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# --------------------------------------------------------
2+
# Licensed under the terms of the BSD 3-Clause License
3+
# (see LICENSE for details).
4+
# Copyright © 2018-2024, A.A Suvorov
5+
# All rights reserved.
6+
# --------------------------------------------------------
7+
# https://github.com/smartlegionlab/
8+
# --------------------------------------------------------
9+
"""A collection of design patterns and idioms in Python"""

patterns/behavioral/__init__.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# --------------------------------------------------------
2+
# Licensed under the terms of the BSD 3-Clause License
3+
# (see LICENSE for details).
4+
# Copyright © 2018-2024, A.A Suvorov
5+
# All rights reserved.
6+
# --------------------------------------------------------
7+
# https://github.com/smartlegionlab/
8+
# --------------------------------------------------------

patterns/behavioral/blackboard.py

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# --------------------------------------------------------
2+
# Licensed under the terms of the BSD 3-Clause License
3+
# (see LICENSE for details).
4+
# Copyright © 2018-2024, A.A Suvorov
5+
# All rights reserved.
6+
# --------------------------------------------------------
7+
# https://github.com/smartlegionlab/
8+
# --------------------------------------------------------
9+
"""Black Board"""
10+
import abc
11+
import random
12+
13+
14+
class Blackboard:
15+
16+
def __init__(self):
17+
self.experts = []
18+
self.common_state = {
19+
'problems': 0,
20+
'suggestions': 0,
21+
'contributions': [],
22+
'progress': 0
23+
}
24+
25+
def add_expert(self, expert):
26+
self.experts.append(expert)
27+
28+
29+
class Controller:
30+
31+
def __init__(self, black_board):
32+
self.blackboard = black_board
33+
34+
def run_loop(self):
35+
while self.blackboard.common_state['progress'] < 100:
36+
for expert in self.blackboard.experts:
37+
if expert.is_eager_to_contribute:
38+
expert.contribute()
39+
return self.blackboard.common_state['contributions']
40+
41+
42+
class AbstractExpert:
43+
__metaclass__ = abc.ABCMeta
44+
45+
def __init__(self, black_board):
46+
self.blackboard = black_board
47+
48+
@property
49+
def is_eager_to_contribute(self):
50+
raise NotImplementedError('Must provide implementation in subclass.')
51+
52+
@abc.abstractmethod
53+
def contribute(self):
54+
raise NotImplementedError('Must provide implementation in subclass.')
55+
56+
57+
class Student(AbstractExpert):
58+
59+
@property
60+
def is_eager_to_contribute(self):
61+
return True
62+
63+
def contribute(self):
64+
self.blackboard.common_state['problems'] += random.randint(1, 10)
65+
self.blackboard.common_state['suggestions'] += random.randint(1, 10)
66+
self.blackboard.common_state['contributions'] += [self.__class__.__name__]
67+
self.blackboard.common_state['progress'] += random.randint(1, 2)
68+
69+
70+
class Scientist(AbstractExpert):
71+
72+
@property
73+
def is_eager_to_contribute(self):
74+
return random.randint(0, 1)
75+
76+
def contribute(self):
77+
self.blackboard.common_state['problems'] += random.randint(10, 20)
78+
self.blackboard.common_state['suggestions'] += random.randint(10, 20)
79+
self.blackboard.common_state['contributions'] += [self.__class__.__name__]
80+
self.blackboard.common_state['progress'] += random.randint(10, 30)
81+
82+
83+
class Professor(AbstractExpert):
84+
85+
@property
86+
def is_eager_to_contribute(self):
87+
return True if self.blackboard.common_state['problems'] > 100 else False
88+
89+
def contribute(self):
90+
self.blackboard.common_state['problems'] += random.randint(1, 2)
91+
self.blackboard.common_state['suggestions'] += random.randint(10, 20)
92+
self.blackboard.common_state['contributions'] += [self.__class__.__name__]
93+
self.blackboard.common_state['progress'] += random.randint(10, 100)
94+
95+
96+
def main():
97+
blackboard = Blackboard()
98+
blackboard.add_expert(Student(blackboard))
99+
blackboard.add_expert(Scientist(blackboard))
100+
blackboard.add_expert(Professor(blackboard))
101+
c = Controller(blackboard)
102+
contributions = c.run_loop()
103+
from pprint import pprint
104+
pprint(contributions)
105+
106+
107+
if __name__ == '__main__':
108+
# Output:
109+
# ['Student',
110+
# 'Student',
111+
# 'Scientist',
112+
# 'Student',
113+
# 'Scientist',
114+
# 'Student',
115+
# 'Scientist',
116+
# 'Student',
117+
# 'Scientist',
118+
# 'Student',
119+
# 'Scientist',
120+
# 'Professor']
121+
main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# --------------------------------------------------------
2+
# Licensed under the terms of the BSD 3-Clause License
3+
# (see LICENSE for details).
4+
# Copyright © 2018-2024, A.A Suvorov
5+
# All rights reserved.
6+
# --------------------------------------------------------
7+
# https://github.com/smartlegionlab/
8+
# --------------------------------------------------------
9+
"""Chain of responsibility"""
10+
11+
12+
class HttpHandler:
13+
def handle(self, code):
14+
raise NotImplementedError()
15+
16+
17+
class Http404Handler(HttpHandler):
18+
def handle(self, code):
19+
if code == 404:
20+
return 'Page not found'
21+
22+
23+
class Http500Handler(HttpHandler):
24+
def handle(self, code):
25+
if code == 500:
26+
return 'server error'
27+
28+
29+
class Client:
30+
def __init__(self):
31+
self._handlers = []
32+
33+
def add_handler(self, h):
34+
self._handlers.append(h)
35+
36+
def response(self, code):
37+
for h in self._handlers:
38+
msg = h.handle(code)
39+
if msg:
40+
print('Answer: %s' % msg)
41+
break
42+
else:
43+
print('Code not processed')
44+
45+
46+
def main():
47+
client = Client()
48+
client.add_handler(Http404Handler())
49+
client.add_handler(Http500Handler())
50+
client.response(400)
51+
client.response(404)
52+
client.response(500)
53+
54+
55+
if __name__ == '__main__':
56+
# Output:
57+
# Code not processed
58+
# Answer: Page not found
59+
# Answer: Server error
60+
main()

patterns/behavioral/command.py

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# --------------------------------------------------------
2+
# Licensed under the terms of the BSD 3-Clause License
3+
# (see LICENSE for details).
4+
# Copyright © 2018-2024, A.A Suvorov
5+
# All rights reserved.
6+
# --------------------------------------------------------
7+
# https://github.com/smartlegionlab/
8+
# --------------------------------------------------------
9+
"""Command"""
10+
from abc import ABC
11+
12+
13+
class Light:
14+
15+
@staticmethod
16+
def turn_on():
17+
print('To turn on the light')
18+
19+
@staticmethod
20+
def turn_off():
21+
print('Turn the lights off')
22+
23+
24+
class CommandBase:
25+
def execute(self):
26+
raise NotImplementedError()
27+
28+
29+
class LightCommandBase(CommandBase, ABC):
30+
def __init__(self, light_):
31+
self.light = light_
32+
33+
34+
class TurnOnLightCommand(LightCommandBase):
35+
def execute(self):
36+
self.light.turn_on()
37+
38+
39+
class TurnOffLightCommand(LightCommandBase):
40+
def execute(self):
41+
self.light.turn_off()
42+
43+
44+
class Switch:
45+
def __init__(self, on_cmd, off_cmd):
46+
self.on_cmd = on_cmd
47+
self.off_cmd = off_cmd
48+
49+
def on(self):
50+
self.on_cmd.execute()
51+
52+
def off(self):
53+
self.off_cmd.execute()
54+
55+
56+
def main():
57+
light = Light()
58+
switch = Switch(on_cmd=TurnOnLightCommand(light),
59+
off_cmd=TurnOffLightCommand(light))
60+
switch.on()
61+
switch.off()
62+
63+
64+
if __name__ == '__main__':
65+
# Output:
66+
# To turn on the light
67+
# Turn the lights off
68+
main()

0 commit comments

Comments
 (0)