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