diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 6bf400e..e546f77 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,12 +1,12 @@ # These are supported funding model platforms -github: [Sean-Bradley] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: # Replace with a single Patreon username +github: [Sean-Bradley]# Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: seanwasere open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username +ko_fi: sean_bradley tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username -custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] +custom: ['https://sean-bradley.medium.com/membership']# Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.gitignore b/.gitignore index dfa2806..19c706f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .vscode/ -site/ \ No newline at end of file +site/ +__pycache__/ \ No newline at end of file diff --git a/LICENSE b/LICENSE index 359e1ea..e676d93 100644 --- a/LICENSE +++ b/LICENSE @@ -1,29 +1,12 @@ -BSD 3-Clause License - -Copyright (c) 2019, Sean Bradley -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +All code, images, text, videos and downloads from + +Repository : https://github.com/Sean-Bradley/Design-Patterns-In-Python +Website : https://sbcode.net/python +Book : https://www.amazon.com/dp/B08XLJ8Z2J : ASIN B08XLJ8Z2J +EBook : https://www.amazon.com/dp/B08Z282SBC : ASIN B08Z282SBC +YouTube : https://www.youtube.com/playlist?list=PLKWUX7aMnlEJzRvCXnwFEdk_WJDNjMDOo +Udemy : https://www.udemy.com/course/design-patterns-in-python/?referralCode=7493DBBBF97FF2B0D24D +Skillshare : https://skl.sh/34SM2Xg + +Copyright (c) 2019-2022, Sean Bradley +All rights reserved. \ No newline at end of file diff --git a/README.md b/README.md index 086261f..1e77797 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,92 @@ # Design Patterns In Python -> [Design Patterns In Python](https://designpatterns.seanwasere.com/) +This repository focuses on the 23 famous GoF (Gang of Four) Design Patterns implemented in Python. -This course is about common GOF (Gang Of Four) Design Patterns implemented in Python. +It is supplementary to my book titled **Design Patterns In Python** (ASIN : B08XLJ8Z2J) -A Design Pattern is a description or template that can be repeatedly applied to a commonly recurring problem in software design. + -You will find a familiarity with Design Patterns very useful when planning, discussing, developing, managing and documenting your applications from now on and into the future. +   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 -You will learn these Design Patterns +## Course Access -* Creational - * [Factory](factory) - * [Abstract Factory](abstract_factory) - * [Builder](builder) - * [Prototype](prototype) - * Singleton -* Structural - * [Decorator](decorator) - * [Adapter](adapter) - * [Facade](facade) - * [Bridge](bridge) - * [Composite](composite) - * Flyweight - * [Proxy](proxy) -* Behavioural - * [Command](command) - * [Chain of Responsibility](chain_of_responsibility) - * [Observer Pattern](observer) - * Interpreter - * [Iterator](iterator) - * [Mediator](mediator) - * Memento - * State - * Strategy - * Template - * Visitor +There are 3 possible ways to access the video content in this course, +1. Udemy : [https://www.udemy.com/course/design-patterns-in-python/?referralCode=7493DBBBF97FF2B0D24D](https://www.udemy.com/course/design-patterns-in-python/?referralCode=7493DBBBF97FF2B0D24D) + - Get **Udemy Discount Coupons** at [https://sbcode.net/coupons](https://sbcode.net/coupons) + - Certificate of Completion + - 30 Day Money Back Guarantee +2. YouTube Membership : [https://www.youtube.com/channel/UCmUILI2AWt2MSUgPlZwFdOg/join](https://www.youtube.com/channel/UCmUILI2AWt2MSUgPlZwFdOg/join) + - Cancel Membership Anytime +3. Book : [https://amzn.to/466lBN6](https://amzn.to/466lBN6) : ASIN B08XLJ8Z2J + - **Book** includes FREE Video Access Codes to view videos from the official documentation website at [https://sbcode.net/python/](https://sbcode.net/python/) -Register for the course at -- [Skillshare](https://skl.sh/34SM2Xg) **(Get Extra 2 Months Premium Membership Free)** -- [Udemy](https://www.udemy.com/course/design-patterns-in-python/?referralCode=7493DBBBF97FF2B0D24D) **(New Student Discount)** +All the code examples in the book can be found in these pages. +--- -## Introduction Video +**TIP** -[![Design Patterns in Python Introduction](https://img.youtube.com/vi/OOxyTUWsY7A/0.jpg)](https://youtu.be/OOxyTUWsY7A) +> [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/) +--- +**TIP** -To register for this course visit +> Get **Udemy Discount Coupons** at [https://sbcode.net/coupons](https://sbcode.net/coupons) - +--- -- Get 2 Months Free Premium Membership to 1000s of Courses -- Full Lifetime Access -- Subscription Model -- Cancel Any Time +## Overview - +A Design Pattern is a description or template that can be repeatedly applied to a commonly recurring problem in software design. -- One Time Payment -- Full Lifetime Access -- Certificate of Completion -- 30 Day Money-Back Guarantee +A familiarity of Design Patterns will be very useful when planning, discussing, managing and documenting your applications from now on and into the future. + +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. + +So, in this book, you will learn about these 23 Design Patterns, + +- Creational + - [Factory](factory) + - [Abstract Factory](abstract_factory) + - [Builder](builder) + - [Prototype](prototype) + - [Singleton](singleton) +- Structural + - [Decorator](decorator) + - [Adapter](adapter) + - [Facade](facade) + - [Bridge](bridge) + - [Composite](composite) + - [Flyweight](flyweight) + - [Proxy](proxy) +- Behavioral + - [Command](command) + - [Chain of Responsibility](chain_of_responsibility) + - [Observer Pattern](observer) + - [Interpreter](interpreter) + - [Iterator](iterator) + - [Mediator](mediator) + - [Memento](memento) + - [State](state) + - [Strategy](strategy) + - [Template](template) + - [Visitor](visitor) + +## Pattern Types + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Class Scope and Object Scope Patterns + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ diff --git a/abstract_factory/Abstract Factory Design Pattern.md b/abstract_factory/Abstract Factory Design Pattern.md deleted file mode 100644 index 343c538..0000000 --- a/abstract_factory/Abstract Factory Design Pattern.md +++ /dev/null @@ -1,59 +0,0 @@ -# Abstract Factory Design Pattern - -The Abstract Factory Pattern adds an abstract layer over multiple factory method implementations. - -The Abstract Factory contains or composites one or more than one factory method - -![Abstract Factory Overview](abstract_factory.png) - -Abstract Factory in the context of a Furniture factory -![Abstract Factory in context](abstract_factory_furniture.png) - - -Import existing factories -```python -from chair_factory import ChairFactory -from table_factory import TableFactory -``` - -Create an interface -```python -class IFurnitureFactory(metaclass=ABCMeta): - """Furniture Factory Interface""" - - @abstractstaticmethod - def get_furniture(furniture): - """The static funiture factory interface method""" -``` - -The factories abstract static method which delegates to the correct factory -```python - -class FurnitureFactory(IFurnitureFactory): - """The Furniture Factory Concrete Class""" - - @staticmethod - def get_furniture(furniture): - """Static get_furniture method""" - try: - if furniture in ["SmallChair", "MediumChair", "BigChair"]: - return ChairFactory().get_chair(furniture) - if furniture in ["SmallTable", "MediumTable", "BigTable"]: - return TableFactory().get_table(furniture) - raise AssertionError("No Furniture Factory Found") - except AssertionError as _e: - print(_e) - return None -``` - - -Requesting from the abstract factory at run time -```python -if __name__ == "__main__": - FURNITURE = FurnitureFactory.get_furniture("SmallChair") - print(f"{FURNITURE.__class__} : {FURNITURE.dimensions()}") - - FURNITURE = FurnitureFactory.get_furniture("MediumTable") - print(f"{FURNITURE.__class__} : {FURNITURE.dimensions()}") -``` - diff --git a/abstract_factory/Abstract Factory Design Pattern.pdf b/abstract_factory/Abstract Factory Design Pattern.pdf deleted file mode 100644 index 163fc79..0000000 Binary files a/abstract_factory/Abstract Factory Design Pattern.pdf and /dev/null differ diff --git a/abstract_factory/README.md b/abstract_factory/README.md index f9a4525..fe3f1c5 100644 --- a/abstract_factory/README.md +++ b/abstract_factory/README.md @@ -1,58 +1,120 @@ # Abstract Factory Design Pattern -The Abstract Factory Pattern adds an abstract layer over multiple factory method implementations. +## Videos -The Abstract Factory contains or composites one or more than one factory method +Section | Video Links +-|- +Abstract Factory Overview | Abstract Factory Overview Abstract Factory Overview Abstract Factory Overview +Abstract Factory Use Case | Abstract Factory Use Case Abstract Factory Use Case Abstract Factory Use Case +Exception Handling | Exception Handling Exception Handling Exception Handling -![Abstract Factory Overview](abstract_factory.png) +## Book -Abstract Factory in the context of a Furniture factory -![Abstract Factory in context](abstract_factory_furniture.png) +Cover | Links +-|- +![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    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 +## Overview -Import existing factories -```python -from chair_factory import ChairFactory -from table_factory import TableFactory +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Terminology + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Abstract Factory UML Diagram + +![Abstract Factory Overview](/img/abstract_factory_concept.svg) + +## Output + +``` bash +python ./abstract_factory/abstract_factory_concept.py + + +``` + +## Abstract Factory Example Use Case + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Abstract Factory Example UML Diagram + +See this UML diagram of an Abstract Furniture Factory implementation that returns chairs and tables. + +![Abstract Furniture Factory](/img/abstract_furniture_factory.svg) + +## Output + +``` bash +python ./abstract_factory/client.py + : {'width': 40, 'depth': 40, 'height': 40} + : {'width': 110, 'depth': 70, 'height': 60} ``` -Create an interface -```python -class IFurnitureFactory(metaclass=ABCMeta): - """Furniture Factory Interface""" +## New Coding Concepts - @abstractstaticmethod - def get_furniture(furniture): - """The static funiture factory interface method""" +### Exception Handling + +Your Python code may produce errors. It happens to everybody. It is hard to foresee all possible errors, but you can try to handle them in case anyway. + +Use the `Try`, `Except` and optional `finally` keywords to manage error handling. + +In the example code, if no chair or table is returned, an `Exception` error is raised, and it includes a text string that can be read and written to the console. + +Within your code you can use the `raise` keyword to trigger Python built in exceptions or even create your own. + +``` python +def get_furniture(furniture): + "Static get_factory method" + try: + if furniture in ['SmallChair', 'MediumChair', 'BigChair']: + return ChairFactory.get_chair(furniture) + if furniture in ['SmallTable', 'MediumTable', 'BigTable']: + return TableFactory.get_table(furniture) + raise Exception('No Factory Found') + except Exception as _e: + print(_e) + return None ``` -The factories abstract static method which delegates to the correct factory -```python - -class FurnitureFactory(IFurnitureFactory): - """The Furniture Factory Concrete Class""" - - @staticmethod - def get_furniture(furniture): - """Static get_furniture method""" - try: - if furniture in ["SmallChair", "MediumChair", "BigChair"]: - return ChairFactory().get_chair(furniture) - if furniture in ["SmallTable", "MediumTable", "BigTable"]: - return TableFactory().get_table(furniture) - raise AssertionError("No Furniture Factory Found") - except AssertionError as _e: - print(_e) - return None +If `WoodenTable` is requested from the factory, it will print `No Factory Found` + +You don't need to always raise an exception to make one happen. In that case you can handle the possibility of any type of error using just `try` and `except`, with the optional `finally` if you need it. + +``` python +try: + print(my_var) +except: + print("An unknown error Occurred") +finally: + print("This is optional and will get called even if there is no error") ``` +The above code produces the message `An Error Occurred` because `my_var` is not defined. -Requesting from the abstract factory at run time -```python -if __name__ == "__main__": - FURNITURE = FurnitureFactory.get_furniture("SmallChair") - print(f"{FURNITURE.__class__} : {FURNITURE.dimensions()}") +The `try/except` allows the program to continue running, as can be verified by the line printed in the `finally` statement. So, this has given you the opportunity to manage any unforeseen errors any way you wish. + +Alternatively, if your code didn't include the `try/except` and optional `finally` statements, the Python interpreter would return the error `NameError: name 'my_var' is not defined` and the program will crash at that line. + +Also note how the default Python inbuilt error starts with `NameError`. You can handle this specific error explicitly using an extra `except` keyword. + +``` python +try: + print(my_var) +except NameError: + print("There was a `NameError`") +except: + print("An unknown error Occurred") +finally: + print("This is optional and will get called even if there is no error") - FURNITURE = FurnitureFactory.get_furniture("MediumTable") - print(f"{FURNITURE.__class__} : {FURNITURE.dimensions()}") ``` + +You can add exception handling for as many types of errors as you wish. + +Python Errors and Exceptions : [https://docs.python.org/3/tutorial/errors.html](https://docs.python.org/3/tutorial/errors.html) + +## Summary + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ \ No newline at end of file diff --git a/abstract_factory/__pycache__/chair_factory.cpython-37.pyc b/abstract_factory/__pycache__/chair_factory.cpython-37.pyc deleted file mode 100644 index 9f82152..0000000 Binary files a/abstract_factory/__pycache__/chair_factory.cpython-37.pyc and /dev/null differ diff --git a/abstract_factory/__pycache__/table_factory.cpython-37.pyc b/abstract_factory/__pycache__/table_factory.cpython-37.pyc deleted file mode 100644 index 5337b4d..0000000 Binary files a/abstract_factory/__pycache__/table_factory.cpython-37.pyc and /dev/null differ diff --git a/abstract_factory/abstract_factory.png b/abstract_factory/abstract_factory.png deleted file mode 100644 index ba53bc3..0000000 Binary files a/abstract_factory/abstract_factory.png and /dev/null differ diff --git a/abstract_factory/abstract_factory_concept.py b/abstract_factory/abstract_factory_concept.py new file mode 100644 index 0000000..7b6a105 --- /dev/null +++ b/abstract_factory/abstract_factory_concept.py @@ -0,0 +1,39 @@ +# pylint: disable=too-few-public-methods +"Abstract Factory Concept Sample Code" +from abc import ABCMeta, abstractmethod +from factory_a import FactoryA +from factory_b import FactoryB + + +class IAbstractFactory(metaclass=ABCMeta): + "Abstract Factory Interface" + + @staticmethod + @abstractmethod + def create_object(factory): + "The static Abstract factory interface method" + + +class AbstractFactory(IAbstractFactory): + "The Abstract Factory Concrete Class" + + @staticmethod + def create_object(factory): + "Static get_factory method" + try: + if factory in ['aa', 'ab', 'ac']: + return FactoryA.create_object(factory[1]) + if factory in ['ba', 'bb', 'bc']: + return FactoryB.create_object(factory[1]) + raise Exception('No Factory Found') + except Exception as _e: + print(_e) + return None + + +# The Client +PRODUCT = AbstractFactory.create_object('ab') +print(f"{PRODUCT.__class__}") + +PRODUCT = AbstractFactory.create_object('bc') +print(f"{PRODUCT.__class__}") diff --git a/abstract_factory/abstract_factory_furniture.png b/abstract_factory/abstract_factory_furniture.png deleted file mode 100644 index 2ccfe0b..0000000 Binary files a/abstract_factory/abstract_factory_furniture.png and /dev/null differ diff --git a/abstract_factory/big_chair.py b/abstract_factory/big_chair.py new file mode 100644 index 0000000..f8821b1 --- /dev/null +++ b/abstract_factory/big_chair.py @@ -0,0 +1,18 @@ +"A Class of Chair" +from interface_chair import IChair + + +class BigChair(IChair): # pylint: disable=too-few-public-methods + "The Big Chair Concrete Class that implements the IChair interface" + + def __init__(self): + self._height = 80 + self._width = 80 + self._depth = 80 + + def get_dimensions(self): + return { + "width": self._width, + "depth": self._depth, + "height": self._height + } diff --git a/abstract_factory/big_table.py b/abstract_factory/big_table.py new file mode 100644 index 0000000..a56d636 --- /dev/null +++ b/abstract_factory/big_table.py @@ -0,0 +1,18 @@ +"A Class of Table" +from interface_table import ITable + + +class BigTable(ITable): # pylint: disable=too-few-public-methods + "The Big Chair Concrete Class implements the ITable interface" + + def __init__(self): + self._height = 60 + self._width = 120 + self._depth = 80 + + def get_dimensions(self): + return { + "width": self._width, + "depth": self._depth, + "height": self._height + } diff --git a/abstract_factory/chair_factory.py b/abstract_factory/chair_factory.py index 78e034e..87d96e2 100644 --- a/abstract_factory/chair_factory.py +++ b/abstract_factory/chair_factory.py @@ -1,75 +1,23 @@ -"""A Factory Pattern Example -The Factory Pattern Defines in Interface for creating an object -and defers instantation until runtime. -Used when you don't know how many or what type of objects will be needed until during runtime -""" - -from abc import ABCMeta, abstractstaticmethod - - -class IChair(metaclass=ABCMeta): # pylint: disable=too-few-public-methods - """The Chair Interface""" - - @abstractstaticmethod - def dimensions(): - """A static inteface method""" - - -class BigChair(IChair): # pylint: disable=too-few-public-methods - """The Big Chair Concrete Class which implements the IChair interface""" - - def __init__(self): - self._height = 80 - self._width = 80 - self._depth = 80 - - def dimensions(self): - return {"width": self._width, "depth": self._depth, "height": self._height} - - -class MediumChair(IChair): # pylint: disable=too-few-public-methods - """The Medium Chair Concrete Class which implements the IChair interface""" - - def __init__(self): - self._height = 60 - self._width = 60 - self._depth = 60 - - def dimensions(self): - return {"width": self._width, "depth": self._depth, "height": self._height} - - -class SmallChair(IChair): # pylint: disable=too-few-public-methods - """The Small Chair Concrete Class which implements the IChair interface""" - - def __init__(self): - self._height = 40 - self._width = 40 - self._depth = 40 - - def dimensions(self): - return {"width": self._width, "depth": self._depth, "height": self._height} +"The Factory Class" +from small_chair import SmallChair +from medium_chair import MediumChair +from big_chair import BigChair class ChairFactory: # pylint: disable=too-few-public-methods - """Tha Factory Class""" + "The Factory Class" @staticmethod def get_chair(chair): - """A static method to get a table""" + "A static method to get a chair" try: - if chair == "BigChair": + if chair == 'BigChair': return BigChair() - if chair == "MediumChair": + if chair == 'MediumChair': return MediumChair() - if chair == "SmallChair": + if chair == 'SmallChair': return SmallChair() - raise AssertionError("Chair Not Found") - except AssertionError as _e: + raise Exception('Chair Not Found') + except Exception as _e: print(_e) return None - - -if __name__ == "__main__": - CHAIR_FACTORY = ChairFactory().get_chair("SmallChair") - print(CHAIR_FACTORY.dimensions()) diff --git a/abstract_factory/classes_abstract_factory.dot b/abstract_factory/classes_abstract_factory.dot deleted file mode 100644 index a9abafb..0000000 --- a/abstract_factory/classes_abstract_factory.dot +++ /dev/null @@ -1,7 +0,0 @@ -digraph "classes_abstract_factory" { -charset="utf-8" -rankdir=BT -"0" [label="{FurnitureFactory|\l|get_furniture()\l}", shape="record"]; -"1" [label="{IFurnitureFactory|\l|get_furniture()\l}", shape="record"]; -"0" -> "1" [arrowhead="empty", arrowtail="none"]; -} diff --git a/abstract_factory/classes_abstract_factory2.dot b/abstract_factory/classes_abstract_factory2.dot deleted file mode 100644 index 29bb541..0000000 --- a/abstract_factory/classes_abstract_factory2.dot +++ /dev/null @@ -1,19 +0,0 @@ -digraph "classes_abstract_factory" { -charset="utf-8" -rankdir=BT -{rank=same; 0,1 } -"2" [label="{Factory1|\l|get_factory()\l}", shape="record"]; -"3" [label="{Factory2|\l|get_factory()\l}", shape="record"]; -"4" [label="{Factory3|\l|get_factory()\l}", shape="record"]; -"5" [label="{IFactory|\l|get_factory()\l}", shape="record"]; -"1" [label="{IAbstractFactory|\l|get_factory()\l}", shape="record"]; -"0" [label="{AbstractFactory|\l|get_factory()\l}", shape="record"]; - -"0" -> "1" [arrowhead="empty", arrowtail="none"]; -"0" -> "2" [arrowhead="open", arrowtail="none", style="dashed"]; -"0" -> "3" [arrowhead="open", arrowtail="none", style="dashed"]; -"0" -> "4" [arrowhead="open", arrowtail="none", style="dashed"]; -"2" -> "5" [arrowhead="empty", arrowtail="none"]; -"3" -> "5" [arrowhead="empty", arrowtail="none"]; -"4" -> "5" [arrowhead="empty", arrowtail="none"]; -} diff --git a/abstract_factory/client.py b/abstract_factory/client.py new file mode 100644 index 0000000..2eb75f6 --- /dev/null +++ b/abstract_factory/client.py @@ -0,0 +1,9 @@ +"Abstract Factory Use Case Example Code" +from furniture_factory import FurnitureFactory + + +FURNITURE = FurnitureFactory.get_furniture("SmallChair") +print(f"{FURNITURE.__class__} : {FURNITURE.get_dimensions()}") + +FURNITURE = FurnitureFactory.get_furniture("MediumTable") +print(f"{FURNITURE.__class__} : {FURNITURE.get_dimensions()}") diff --git a/abstract_factory/factory_a.py b/abstract_factory/factory_a.py new file mode 100644 index 0000000..731be96 --- /dev/null +++ b/abstract_factory/factory_a.py @@ -0,0 +1,61 @@ +# pylint: disable=too-few-public-methods +"FactoryA Sample Code" +from abc import ABCMeta, abstractmethod + + +class IProduct(metaclass=ABCMeta): + "A Hypothetical Class Interface (Product)" + + @staticmethod + @abstractmethod + def create_object(): + "An abstract interface method" + + +class ConcreteProductA(IProduct): + "A Concrete Class that implements the IProduct interface" + + def __init__(self): + self.name = "ConcreteProductA" + + def create_object(self): + return self + + +class ConcreteProductB(IProduct): + "A Concrete Class that implements the IProduct interface" + + def __init__(self): + self.name = "ConcreteProductB" + + def create_object(self): + return self + + +class ConcreteProductC(IProduct): + "A Concrete Class that implements the IProduct interface" + + def __init__(self): + self.name = "ConcreteProductC" + + def create_object(self): + return self + + +class FactoryA: + "The FactoryA Class" + + @staticmethod + def create_object(some_property): + "A static method to get a concrete product" + try: + if some_property == 'a': + return ConcreteProductA() + if some_property == 'b': + return ConcreteProductB() + if some_property == 'c': + return ConcreteProductC() + raise Exception('Class Not Found') + except Exception as _e: + print(_e) + return None diff --git a/abstract_factory/factory_b.py b/abstract_factory/factory_b.py new file mode 100644 index 0000000..9533cf1 --- /dev/null +++ b/abstract_factory/factory_b.py @@ -0,0 +1,61 @@ +# pylint: disable=too-few-public-methods +"FactoryB Sample Code" +from abc import ABCMeta, abstractmethod + + +class IProduct(metaclass=ABCMeta): + "A Hypothetical Class Interface (Product)" + + @staticmethod + @abstractmethod + def create_object(): + "An abstract interface method" + + +class ConcreteProductA(IProduct): + "A Concrete Class that implements the IProduct interface" + + def __init__(self): + self.name = "ConcreteProductA" + + def create_object(self): + return self + + +class ConcreteProductB(IProduct): + "A Concrete Class that implements the IProduct interface" + + def __init__(self): + self.name = "ConcreteProductB" + + def create_object(self): + return self + + +class ConcreteProductC(IProduct): + "A Concrete Class that implements the IProduct interface" + + def __init__(self): + self.name = "ConcreteProductC" + + def create_object(self): + return self + + +class FactoryB: + "The FactoryB Class" + + @staticmethod + def create_object(some_property): + "A static method to get a concrete product" + try: + if some_property == 'a': + return ConcreteProductA() + if some_property == 'b': + return ConcreteProductB() + if some_property == 'c': + return ConcreteProductC() + raise Exception('Class Not Found') + except Exception as _e: + print(_e) + return None diff --git a/abstract_factory/furniture_abstract_factory.py b/abstract_factory/furniture_abstract_factory.py deleted file mode 100644 index 83fa9da..0000000 --- a/abstract_factory/furniture_abstract_factory.py +++ /dev/null @@ -1,40 +0,0 @@ -"""An Abstract Factory Pattern Example -The Abstract Factory Pattern adds an abstract layer over multiple factory method implementations. -The Abstract Factory contains or composites one or more than one factory method -""" - -from abc import ABCMeta, abstractstaticmethod -from chair_factory import ChairFactory -from table_factory import TableFactory - - -class IFurnitureFactory(metaclass=ABCMeta): # pylint: disable=too-few-public-methods - """Furniture Factory Interface""" - - @abstractstaticmethod - def get_furniture(furniture): - """The static funiture factory inteface method""" - - -class FurnitureFactory(IFurnitureFactory): # pylint: disable=too-few-public-methods - """The Furniture Factory Concrete Class""" - - @staticmethod - def get_furniture(furniture): - """Static get_furniture method""" - try: - if furniture in ["SmallChair", "MediumChair", "BigChair"]: - return ChairFactory().get_chair(furniture) - if furniture in ["SmallTable", "MediumTable", "BigTable"]: - return TableFactory().get_table(furniture) - raise AssertionError("No Furniture Factory Found") - except AssertionError as _e: - print(_e) - return None - - -FURNITURE = FurnitureFactory.get_furniture("SmallChair") -print(f"{FURNITURE.__class__} : {FURNITURE.dimensions()}") - -FURNITURE = FurnitureFactory.get_furniture("MediumTable") -print(f"{FURNITURE.__class__} : {FURNITURE.dimensions()}") diff --git a/abstract_factory/furniture_factory.py b/abstract_factory/furniture_factory.py new file mode 100644 index 0000000..f52fdc9 --- /dev/null +++ b/abstract_factory/furniture_factory.py @@ -0,0 +1,22 @@ +# pylint: disable=too-few-public-methods +"Abstract Furniture Factory" +from interface_furniture_factory import IFurnitureFactory +from chair_factory import ChairFactory +from table_factory import TableFactory + + +class FurnitureFactory(IFurnitureFactory): + "The Abstract Factory Concrete Class" + + @staticmethod + def get_furniture(furniture): + "Static get_factory method" + try: + if furniture in ['SmallChair', 'MediumChair', 'BigChair']: + return ChairFactory.get_chair(furniture) + if furniture in ['SmallTable', 'MediumTable', 'BigTable']: + return TableFactory.get_table(furniture) + raise Exception('No Factory Found') + except Exception as _e: + print(_e) + return None diff --git a/abstract_factory/interface_chair.py b/abstract_factory/interface_chair.py new file mode 100644 index 0000000..978e020 --- /dev/null +++ b/abstract_factory/interface_chair.py @@ -0,0 +1,12 @@ +# pylint: disable=too-few-public-methods +"The Chair Interface" +from abc import ABCMeta, abstractmethod + + +class IChair(metaclass=ABCMeta): + "The Chair Interface (Product)" + + @staticmethod + @abstractmethod + def get_dimensions(): + "A static interface method" diff --git a/abstract_factory/interface_furniture_factory.py b/abstract_factory/interface_furniture_factory.py new file mode 100644 index 0000000..5dc5151 --- /dev/null +++ b/abstract_factory/interface_furniture_factory.py @@ -0,0 +1,12 @@ +# pylint: disable=too-few-public-methods +"The Abstract Factory Interface" +from abc import ABCMeta, abstractmethod + + +class IFurnitureFactory(metaclass=ABCMeta): + "Abstract Furniture Factory Interface" + + @staticmethod + @abstractmethod + def get_furniture(furniture): + "The static Abstract factory interface method" diff --git a/abstract_factory/interface_table.py b/abstract_factory/interface_table.py new file mode 100644 index 0000000..6847edc --- /dev/null +++ b/abstract_factory/interface_table.py @@ -0,0 +1,12 @@ +# pylint: disable=too-few-public-methods +"The Table Interface" +from abc import ABCMeta, abstractmethod + + +class ITable(metaclass=ABCMeta): + "The Table Interface (Product)" + + @staticmethod + @abstractmethod + def get_dimensions(): + "A static interface method" diff --git a/abstract_factory/medium_chair.py b/abstract_factory/medium_chair.py new file mode 100644 index 0000000..fbdf00d --- /dev/null +++ b/abstract_factory/medium_chair.py @@ -0,0 +1,18 @@ +"A Class of Chair" +from interface_chair import IChair + + +class MediumChair(IChair): # pylint: disable=too-few-public-methods + """The Medium Chair Concrete Class implements the IChair interface""" + + def __init__(self): + self._height = 60 + self._width = 60 + self._depth = 60 + + def get_dimensions(self): + return { + "width": self._width, + "depth": self._depth, + "height": self._height + } diff --git a/abstract_factory/medium_table.py b/abstract_factory/medium_table.py new file mode 100644 index 0000000..7c725f1 --- /dev/null +++ b/abstract_factory/medium_table.py @@ -0,0 +1,18 @@ +"A Class of Table" +from interface_table import ITable + + +class MediumTable(ITable): # pylint: disable=too-few-public-methods + "The Medium Table Concrete Class implements the ITable interface" + + def __init__(self): + self._height = 60 + self._width = 110 + self._depth = 70 + + def get_dimensions(self): + return { + "width": self._width, + "depth": self._depth, + "height": self._height + } diff --git a/abstract_factory/small_chair.py b/abstract_factory/small_chair.py new file mode 100644 index 0000000..79dbb7d --- /dev/null +++ b/abstract_factory/small_chair.py @@ -0,0 +1,18 @@ +"A Class of Chair" +from interface_chair import IChair + + +class SmallChair(IChair): # pylint: disable=too-few-public-methods + "The Small Chair Concrete Class implements the IChair interface" + + def __init__(self): + self._height = 40 + self._width = 40 + self._depth = 40 + + def get_dimensions(self): + return { + "width": self._width, + "depth": self._depth, + "height": self._height + } diff --git a/abstract_factory/small_table.py b/abstract_factory/small_table.py new file mode 100644 index 0000000..92dd904 --- /dev/null +++ b/abstract_factory/small_table.py @@ -0,0 +1,18 @@ +"A Class of Table" +from interface_table import ITable + + +class SmallTable(ITable): # pylint: disable=too-few-public-methods + "The Small Table Concrete Class implements the ITable interface" + + def __init__(self): + self._height = 60 + self._width = 100 + self._depth = 60 + + def get_dimensions(self): + return { + "width": self._width, + "depth": self._depth, + "height": self._height + } diff --git a/abstract_factory/table_factory.py b/abstract_factory/table_factory.py index fc0b6bf..74871c0 100644 --- a/abstract_factory/table_factory.py +++ b/abstract_factory/table_factory.py @@ -1,75 +1,23 @@ -"""A Factory Pattern Example -The Factory Pattern Defines in Interface for creating an object -and defers instantation until runtime. -Used when you don't know how many or what type of objects will be needed until during runtime -""" - -from abc import ABCMeta, abstractstaticmethod - - -class ITable(metaclass=ABCMeta): # pylint: disable=too-few-public-methods - """The Table Interface""" - - @abstractstaticmethod - def dimensions(): - """Get the table dimensions""" - - -class BigTable(ITable): # pylint: disable=too-few-public-methods - """The Big Table Concrete Class which implements the ITable interface""" - - def __init__(self): - self._height = 60 - self._width = 120 - self._depth = 80 - - def dimensions(self): - return {"width": self._width, "depth": self._depth, "height": self._height} - - -class MediumTable(ITable): # pylint: disable=too-few-public-methods - """The Medium Table Concrete Class which implements the ITable interface""" - - def __init__(self): - self._height = 60 - self._width = 110 - self._depth = 70 - - def dimensions(self): - return {"width": self._width, "depth": self._depth, "height": self._height} - - -class SmallTable(ITable): # pylint: disable=too-few-public-methods - """The Small Table Concrete Class which implements the ITable interface""" - - def __init__(self): - self._height = 60 - self._width = 100 - self._depth = 60 - - def dimensions(self): - return {"width": self._width, "depth": self._depth, "height": self._height} +"The Factory Class" +from small_table import SmallTable +from medium_table import MediumTable +from big_table import BigTable class TableFactory: # pylint: disable=too-few-public-methods - """Tha Factory Class""" + "The Factory Class" @staticmethod def get_table(table): - """A static method to get a table""" + "A static method to get a table" try: - if table == "BigTable": + if table == 'BigTable': return BigTable() - if table == "MediumTable": + if table == 'MediumTable': return MediumTable() - if table == "SmallTable": + if table == 'SmallTable': return SmallTable() - raise AssertionError("Table Not Found") - except AssertionError as _e: + raise Exception('Table Not Found') + except Exception as _e: print(_e) return None - - -if __name__ == "__main__": - TABLE = TableFactory().get_table("SmalTable") - print(TABLE) diff --git a/adapter/Adapter Design Pattern.md b/adapter/Adapter Design Pattern.md deleted file mode 100644 index 7184f92..0000000 --- a/adapter/Adapter Design Pattern.md +++ /dev/null @@ -1,17 +0,0 @@ -# Adapter Design Pattern - -The adapter design pattern solves these problems: - -- How can a class be reused that does not have an interface that a client requires? -- How can classes that have incompatible interfaces work together? -- How can an alternative interface be provided for a class? - -In this lecture, I have 2 classes, they don't share the same interface. The client requires it's objects to use an already standardised interface. - -So we need to create an adapter, that wraps the incompatible object, but implements the standardised interface. - -## Two Incompatible Classes -![Adapter Design Pattern](adapter_before.png) - -## After Creating an Adapter -![Adapter Design Pattern](adapter.png) diff --git a/adapter/Adapter Design Pattern.pdf b/adapter/Adapter Design Pattern.pdf deleted file mode 100644 index e7f3b02..0000000 Binary files a/adapter/Adapter Design Pattern.pdf and /dev/null differ diff --git a/adapter/README.md b/adapter/README.md index 7184f92..36a5311 100644 --- a/adapter/README.md +++ b/adapter/README.md @@ -1,17 +1,146 @@ # Adapter Design Pattern -The adapter design pattern solves these problems: +## Videos -- How can a class be reused that does not have an interface that a client requires? -- How can classes that have incompatible interfaces work together? -- How can an alternative interface be provided for a class? +Section | Video Links +-|- +Adapter Overview | Adapter Overview Adapter Overview Adapter Overview +Adapter Use Case | Adapter Use Case Adapter Use Case Adapter Use Case +Python **isinstance()** Function | Python isinstance() Function Python isinstance() Function Python isinstance() Function +Python **time** Module | Python time Module Python time Module Python time Module -In this lecture, I have 2 classes, they don't share the same interface. The client requires it's objects to use an already standardised interface. +## Book -So we need to create an adapter, that wraps the incompatible object, but implements the standardised interface. +Cover | Links +-|- +![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    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 -## Two Incompatible Classes -![Adapter Design Pattern](adapter_before.png) +## Overview -## After Creating an Adapter -![Adapter Design Pattern](adapter.png) +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Terminology + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Adapter UML Diagram + +![Adapter Pattern UML Diagram](/img/adapter_concept.svg) + +## Source Code + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Output + +``` bash +python ./adapter/adapter_concept.py +method A +method B +method A +method B +``` + +## Example Use Case + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Example UML Diagram + +![Adapter Pattern in Context](/img/adapter_example.svg) + +## Output + +``` bash +python ./adapter/client.py +Company A is busy, trying company B +Company B is busy, trying company A +Company A is busy, trying company B +Company B is busy, trying company A +Company A building Cube id:2968196317136, 2x3x7 +Company A is busy, trying company B +Company B building Cube id:2968196317136, 8x2x8 +Company A building Cube id:2968196317040, 4x6x4 +Company A is busy, trying company B +Company B is busy, trying company A +Company A building Cube id:2968196317136, 5x4x8 +Company A is busy, trying company B +Company B building Cube id:2968196317136, 2x2x9 +5 cubes have been manufactured +``` + +## New Coding Concepts + +### Python `isinstance()` Function + +Syntax: `isinstance(object, type)` + +Returns: `True` or `False` + +You can use the inbuilt function `isinstance()` to conditionally check the `type` of an object. + +``` python +>>> isinstance(1,int) +True +>>> isinstance(1,bool) +False +>>> isinstance(True,bool) +True +>>> isinstance("abc",str) +True +>>> isinstance("abc",(int,list,dict,tuple,set)) +False +>>> isinstance("abc",(int,list,dict,tuple,set,str)) +True +``` + +You can also test your custom classes. + +``` python +class my_class: + "nothing to see here" + +CLASS_A = my_class() +print(type(CLASS_A)) +print(isinstance(CLASS_A, bool)) +print(isinstance(CLASS_A, my_class)) +``` + +Outputs + +``` + + +False +True +``` + +You can use it in logical statements as I do in [/adapter/adapter_concept.py](/adapter/adapter_concept.py). + +### Python `time` Module + +The time module provides time related functions, most notably in my case, the current epoch (ticks) since `January 1, 1970, 00:00:00 (UTC)` . + +The `time` module provides many options that are outlined in more detail at [https://docs.python.org/3/library/time.html](https://docs.python.org/3/library/time.html) + +In [/adapter/cube_a.py](/adapter/cube_a.py), I check the `time.time()` at various intervals to compare how long a task took. + +``` python + now = int(time.time()) + if now > int(CubeA.last_time + 1): + CubeA.last_time = now + return True +``` + +I also use the `time` module to sleep for a second between loops to simulate a 1 second delay. See [/adapter/client.py](/adapter/client.py) + +``` python + # wait some time before manufacturing a new cube + time.sleep(1) +``` + +When executing [/adapter/cube_a.py](/adapter/cube_a.py) you will notice that the process will run for about 10 seconds outputting the gradual progress of the construction of each cube. + +## Summary + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ \ No newline at end of file diff --git a/adapter/adapter.dot b/adapter/adapter.dot deleted file mode 100644 index f8e95f2..0000000 --- a/adapter/adapter.dot +++ /dev/null @@ -1,13 +0,0 @@ -digraph "classes" { -charset="utf-8" -{rank=same;0,1;2} -"0" [label="{ClassA|\l|method_a()\l}", shape="record"]; -"1" [label="{ClassB|\l|method_b()\l}", shape="record"]; -"2" [label="{ClassBAdapter|class_b\l|method_a()\l}", shape="record"]; -"3" [label="{IA|\l|method_a()\l}", shape="record"]; -"4" [label="{IB|\l|method_b()\l}", shape="record"]; -"0" -> "3" [arrowhead="empty", arrowtail="none"]; -"1" -> "4" [arrowhead="empty", arrowtail="none"]; -"2" -> "3" [arrowhead="empty", arrowtail="none"]; -"1" -> "2" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="class_b", style="solid"]; -} diff --git a/adapter/adapter.png b/adapter/adapter.png deleted file mode 100644 index 762368e..0000000 Binary files a/adapter/adapter.png and /dev/null differ diff --git a/adapter/adapter.py b/adapter/adapter.py deleted file mode 100644 index a18db1c..0000000 --- a/adapter/adapter.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -Adapter Design Pattern -""" - -from abc import ABCMeta, abstractmethod - - -class IA(metaclass=ABCMeta): - @staticmethod - @abstractmethod - def method_a(): - """An abstract method A""" - - -class ClassA(IA): - def method_a(self): - print("method A") - - -class IB(metaclass=ABCMeta): - @staticmethod - @abstractmethod - def method_b(): - """An abstract method B""" - - -class ClassB(IB): - def method_b(self): - print("method B") - - -"""ClassB does not have a method_a, so we create an adapter""" - - -class ClassBAdapter(IA): - def __init__(self): - self.class_b = ClassB() - - def method_a(self): - """calls the class b method_b instead""" - self.class_b.method_b() - - -# client - -#ITEM = ClassA() -#ITEM = ClassB() # has no method_a -ITEM = ClassBAdapter() - -ITEM.method_a() diff --git a/adapter/adapter_before.dot b/adapter/adapter_before.dot deleted file mode 100644 index c45f322..0000000 --- a/adapter/adapter_before.dot +++ /dev/null @@ -1,10 +0,0 @@ -digraph "classes" { -charset="utf-8" -{rank=same;0,1} -"0" [label="{ClassA|\l|method_a()\l}", shape="record"]; -"1" [label="{ClassB|\l|method_b()\l}", shape="record"]; -"3" [label="{IA|\l|method_a()\l}", shape="record"]; -"4" [label="{IB|\l|method_b()\l}", shape="record"]; -"0" -> "3" [arrowhead="empty", arrowtail="none"]; -"1" -> "4" [arrowhead="empty", arrowtail="none"]; -} diff --git a/adapter/adapter_before.png b/adapter/adapter_before.png deleted file mode 100644 index 6a2416c..0000000 Binary files a/adapter/adapter_before.png and /dev/null differ diff --git a/adapter/adapter_concept.py b/adapter/adapter_concept.py new file mode 100644 index 0000000..ce43edf --- /dev/null +++ b/adapter/adapter_concept.py @@ -0,0 +1,62 @@ +# pylint: disable=too-few-public-methods +# pylint: disable=arguments-differ +"Adapter Concept Sample Code" +from abc import ABCMeta, abstractmethod + + +class IA(metaclass=ABCMeta): + "An interface for an object" + @staticmethod + @abstractmethod + def method_a(): + "An abstract method A" + + +class ClassA(IA): + "A Sample Class the implements IA" + + def method_a(self): + print("method A") + + +class IB(metaclass=ABCMeta): + "An interface for an object" + @staticmethod + @abstractmethod + def method_b(): + "An abstract method B" + + +class ClassB(IB): + "A Sample Class the implements IB" + + def method_b(self): + print("method B") + + +class ClassBAdapter(IA): + "ClassB does not have a method_a, so we can create an adapter" + + def __init__(self): + self.class_b = ClassB() + + def method_a(self): + "calls the class b method_b instead" + self.class_b.method_b() + + +# The Client +# Before the adapter I need to test the objects class to know which +# method to call. +ITEMS = [ClassA(), ClassB()] +for item in ITEMS: + if isinstance(item, ClassB): + item.method_b() + else: + item.method_a() + +# After creating an adapter for ClassB I can reuse the same method +# signature as ClassA (preferred) +ITEMS = [ClassA(), ClassBAdapter()] +for item in ITEMS: + item.method_a() diff --git a/adapter/client.py b/adapter/client.py new file mode 100644 index 0000000..bd0e46e --- /dev/null +++ b/adapter/client.py @@ -0,0 +1,38 @@ +"Adapter Example Use Case" + +import time +import random +from cube_a import CubeA +from cube_b_adapter import CubeBAdapter + + +# client +TOTALCUBES = 5 +COUNTER = 0 +while COUNTER < TOTALCUBES: + # produce 5 cubes from which ever supplier can manufacture it first + WIDTH = random.randint(1, 10) + HEIGHT = random.randint(1, 10) + DEPTH = random.randint(1, 10) + CUBE = CubeA() + SUCCESS = CUBE.manufacture(WIDTH, HEIGHT, DEPTH) + if SUCCESS: + print( + f"Company A building Cube id:{id(CUBE)}, " + f"{CUBE.width}x{CUBE.height}x{CUBE.depth}") + COUNTER = COUNTER + 1 + else: # try other manufacturer + print("Company A is busy, trying company B") + CUBE = CubeBAdapter() + SUCCESS = CUBE.manufacture(WIDTH, HEIGHT, DEPTH) + if SUCCESS: + print( + f"Company B building Cube id:{id(CUBE)}, " + f"{CUBE.width}x{CUBE.height}x{CUBE.depth}") + COUNTER = COUNTER + 1 + else: + print("Company B is busy, trying company A") + # wait some time before manufacturing a new cube + time.sleep(1) + +print(f"{TOTALCUBES} cubes have been manufactured") diff --git a/adapter/cube_a.py b/adapter/cube_a.py new file mode 100644 index 0000000..9c92ed7 --- /dev/null +++ b/adapter/cube_a.py @@ -0,0 +1,24 @@ +# pylint: disable=too-few-public-methods +"A Class of Cube from Company A" +import time +from interface_cube_a import ICubeA + + +class CubeA(ICubeA): + "A hypothetical Cube tool from company A" + # a static variable indicating the last time a cube was manufactured + last_time = int(time.time()) + + def __init__(self): + self.width = self.height = self.depth = 0 + + def manufacture(self, width, height, depth): + self.width = width + self.height = height + self.depth = depth + # if not busy, then manufacture a cube with dimensions + now = int(time.time()) + if now > int(CubeA.last_time + 1): + CubeA.last_time = now + return True + return False # busy diff --git a/adapter/cube_b.py b/adapter/cube_b.py new file mode 100644 index 0000000..dda45a9 --- /dev/null +++ b/adapter/cube_b.py @@ -0,0 +1,17 @@ +# pylint: disable=too-few-public-methods +"A Class of Cube from Company B" +import time +from interface_cube_b import ICubeB + + +class CubeB(ICubeB): + "A hypothetical Cube tool from company B" + # a static variable indicating the last time a cube was manufactured + last_time = int(time.time()) + + def create(self, top_left_front, bottom_right_back): + now = int(time.time()) + if now > int(CubeB.last_time + 2): + CubeB.last_time = now + return True + return False # busy diff --git a/adapter/cube_b_adapter.py b/adapter/cube_b_adapter.py new file mode 100644 index 0000000..034e4ae --- /dev/null +++ b/adapter/cube_b_adapter.py @@ -0,0 +1,23 @@ +# pylint: disable=too-few-public-methods +"An adapter for CubeB so that it can be used like Cube A" +from interface_cube_a import ICubeA +from cube_b import CubeB + + +class CubeBAdapter(ICubeA): + "Adapter for CubeB that implements ICubeA" + + def __init__(self): + self.cube = CubeB() + self.width = self.height = self.depth = 0 + + def manufacture(self, width, height, depth): + self.width = width + self.height = height + self.depth = depth + + success = self.cube.create( + [0-width/2, 0-height/2, 0-depth/2], + [0+width/2, 0+height/2, 0+depth/2] + ) + return success diff --git a/adapter/interface_cube_a.py b/adapter/interface_cube_a.py new file mode 100644 index 0000000..93d5d03 --- /dev/null +++ b/adapter/interface_cube_a.py @@ -0,0 +1,11 @@ +# pylint: disable=too-few-public-methods +"An interface to implement" +from abc import ABCMeta, abstractmethod + + +class ICubeA(metaclass=ABCMeta): + "An interface for an object" + @staticmethod + @abstractmethod + def manufacture(width, height, depth): + "manufactures a cube" diff --git a/adapter/interface_cube_b.py b/adapter/interface_cube_b.py new file mode 100644 index 0000000..aae97b1 --- /dev/null +++ b/adapter/interface_cube_b.py @@ -0,0 +1,11 @@ +# pylint: disable=too-few-public-methods +"An interface to implement" +from abc import ABCMeta, abstractmethod + + +class ICubeB(metaclass=ABCMeta): + "An interface for an object" + @staticmethod + @abstractmethod + def create(top_left_front, bottom_right_back): + "Manufactures a Cube with coords offset [0, 0, 0]" diff --git a/bridge/README.md b/bridge/README.md new file mode 100644 index 0000000..21be331 --- /dev/null +++ b/bridge/README.md @@ -0,0 +1,124 @@ +# Bridge Design Pattern + +## Videos + +| Section | Video Links | +|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Bridge Overview | Bridge Overview  Bridge Overview  Bridge Overview  | +| Bridge Use Case | Bridge Use Case  Bridge Use Case  Bridge Use Case  | +| Python **Tuple** | Python Tuple  Python Tuple  Python Tuple  | +| Python **\*args** | Python *args  Python *args  Python *args  | + +## Book + +Cover | Links +-|- +![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    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 + +## Overview + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Terminology + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Bridge UML Diagram + +![Bridge Pattern UML Diagram](/img/bridge_concept.svg) + +## Source Code + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Output + +``` bash +python ./bridge/bridge_concept.py +('a', 'b', 'c') +a +b +c +``` + +## Example Use Case + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Example UML Diagram + +![Bridge Pattern in Context](/img/bridge_example.svg) + +## Output + +``` bash +python ./bridge/client.py + ****** + ** ** + * * + +* * +* * + + * * + ** ** + ****** +************** + +* * +* * +* * +* * +* * +* * + +************** +``` + +## New Coding Concepts + +### The `*args` Argument. + +The `*args` argument takes all arguments that were sent to this method, and packs them into a [Tuple](#python-tuple). + +It is useful when you don't know how many arguments, or what types, will be sent to a method, and you want the method to support any number of arguments or types being sent to it. + +If you want your method to be strict about the types that it can accept, the set it specifically to accept [List](/builder#python-list), [Dictionary](/singleton#python-dictionary), [Set](/observer#python-set) or [Tuple](#python-tuple), and treat the argument as such within the method body, but the `*args` argument is another common option that you will see in source code throughout the internet. + +E.g., when using the `*args` in your method signature, you can call it with any number of arguments of any type. + +``` python +def my_method(*args): + for arg in args: + print(arg) + +my_method(1, 22, [3], {4}) +``` + +Outputs + +``` bash +1 +22 +[3] +{4} +``` + +### Python Tuple + +A Python **Tuple** is similar to a [List](/builder#python-list). Except that the items in the Tuple are ordered, unchangeable and allow duplicates. + +A Tuple can be instantiated using the round brackets `()` or `tuple()` , verses `[]` for a [List](/builder#python-list) and `{}` for a [Set](/observer#python-set) or [Dictionary](/singleton#python-dictionary). + +``` python +PS> python +>>> items = ("alpha", "bravo", "charlie", "alpha") +>>> print(items) +('alpha', 'bravo', 'charlie', 'alpha') +>>> print(len(items)) +4 +``` + +## Summary + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ \ No newline at end of file diff --git a/bridge/bridge.py b/bridge/bridge.py deleted file mode 100644 index e6946ef..0000000 --- a/bridge/bridge.py +++ /dev/null @@ -1,79 +0,0 @@ -""" -Bridge pattern example. -""" -from abc import ABCMeta, abstractmethod - - -NOT_IMPLEMENTED = "You should implement this." - - -class DrawingAPI: - __metaclass__ = ABCMeta - - @abstractmethod - def draw_circle(self, x, y, radius): - raise NotImplementedError(NOT_IMPLEMENTED) - - -class DrawingAPI1(DrawingAPI): - def draw_circle(self, x, y, radius): - return "API1.circle at {0}:{1} - radius: {2}".format(x, y, radius) - - -class DrawingAPI2(DrawingAPI): - def draw_circle(self, x, y, radius): - return "API2.circle at {0}:{1} - radius: {2}".format(x, y, radius) - -class DrawingAPI3(DrawingAPI): - def draw_circle(self, x, y, radius): - return "API3.circle at {0}:{1} - radius: {2}".format(x, y, radius) - - -class Shape: - __metaclass__ = ABCMeta - - drawing_api = None - def __init__(self, drawing_api): - self.drawing_api = drawing_api - - @abstractmethod - def draw(self): - raise NotImplementedError(NOT_IMPLEMENTED) - - @abstractmethod - def resize_by_percentage(self, percent): - raise NotImplementedError(NOT_IMPLEMENTED) - - -class CircleShape(Shape): - def __init__(self, x, y, radius, drawing_api): - self.x = x - self.y = y - self.radius = radius - super(CircleShape, self).__init__(drawing_api) - - - def draw(self): - return self.drawing_api.draw_circle( - self.x, self.y, self.radius - ) - - def resize_by_percentage(self, percent): - self.radius *= (1 + percent/100) - - -class BridgePattern(object): - @staticmethod - def test(): - shapes = [ - CircleShape(1.0, 2.0, 3.0, DrawingAPI1()), - CircleShape(5.0, 7.0, 11.0, DrawingAPI2()), - CircleShape(5.0, 4.0, 12.0, DrawingAPI3()) - ] - - for shape in shapes: - shape.resize_by_percentage(2.5) - print(shape.draw()) - - -BridgePattern.test() \ No newline at end of file diff --git a/bridge/bridge_concept.py b/bridge/bridge_concept.py new file mode 100644 index 0000000..3ba44b8 --- /dev/null +++ b/bridge/bridge_concept.py @@ -0,0 +1,60 @@ +# pylint: disable=too-few-public-methods +# pylint: disable=arguments-differ +"Bridge Pattern Concept Sample Code" +from abc import ABCMeta, abstractmethod + +class IAbstraction(metaclass=ABCMeta): + "The Abstraction Interface" + + @staticmethod + @abstractmethod + def method(*args): + "The method handle" + +class RefinedAbstractionA(IAbstraction): + "A Refined Abstraction" + + def __init__(self, implementer): + self.implementer = implementer() + + def method(self, *args): + self.implementer.method(*args) + +class RefinedAbstractionB(IAbstraction): + "A Refined Abstraction" + + def __init__(self, implementer): + self.implementer = implementer() + + def method(self, *args): + self.implementer.method(*args) + +class IImplementer(metaclass=ABCMeta): + "The Implementer Interface" + + @staticmethod + @abstractmethod + def method(*args: tuple) -> None: + "The method implementation" + +class ConcreteImplementerA(IImplementer): + "A Concrete Implementer" + + @staticmethod + def method(*args: tuple) -> None: + print(args) + +class ConcreteImplementerB(IImplementer): + "A Concrete Implementer" + + @staticmethod + def method(*args: tuple) -> None: + for arg in args: + print(arg) + +# The Client +REFINED_ABSTRACTION_A = RefinedAbstractionA(ConcreteImplementerA) +REFINED_ABSTRACTION_A.method('a', 'b', 'c') + +REFINED_ABSTRACTION_B = RefinedAbstractionB(ConcreteImplementerB) +REFINED_ABSTRACTION_B.method('a', 'b', 'c') diff --git a/bridge/circle.py b/bridge/circle.py new file mode 100644 index 0000000..a87d2cc --- /dev/null +++ b/bridge/circle.py @@ -0,0 +1,13 @@ +# pylint: disable=too-few-public-methods +"A Circle Abstraction" +from interface_shape import IShape + + +class Circle(IShape): + "The Circle is a Refined Abstraction" + + def __init__(self, implementer): + self.implementer = implementer() + + def draw(self): + self.implementer.draw_implementation() diff --git a/bridge/circle_implementer.py b/bridge/circle_implementer.py new file mode 100644 index 0000000..7f8b4fa --- /dev/null +++ b/bridge/circle_implementer.py @@ -0,0 +1,17 @@ +# pylint: disable=too-few-public-methods +"A Circle Implementer" +from interface_shape_implementer import IShapeImplementer + + +class CircleImplementer(IShapeImplementer): + "A Circle Implementer" + + def draw_implementation(self): + print(" ******") + print(" ** **") + print(" * *") + print("* *") + print("* *") + print(" * *") + print(" ** **") + print(" ******") diff --git a/bridge/classes.dot b/bridge/classes.dot deleted file mode 100644 index 9c9847a..0000000 --- a/bridge/classes.dot +++ /dev/null @@ -1,15 +0,0 @@ -digraph "classes" { -charset="utf-8" -rankdir=BT -"0" [label="{BridgePattern|\l|test()\l}", shape="record"]; -"1" [label="{CircleShape|radius\lx\ly\l|draw()\lresize_by_percentage()\l}", shape="record"]; -"2" [label="{DrawingAPI|\l|draw_circle()\l}", shape="record"]; -"3" [label="{DrawingAPI1|\l|draw_circle()\l}", shape="record"]; -"4" [label="{DrawingAPI2|\l|draw_circle()\l}", shape="record"]; -"5" [label="{DrawingAPI3|\l|draw_circle()\l}", shape="record"]; -"6" [label="{Shape|drawing_api\ldrawing_api : NoneType\l|draw()\lresize_by_percentage()\l}", shape="record"]; -"1" -> "6" [arrowhead="empty", arrowtail="none"]; -"3" -> "2" [arrowhead="empty", arrowtail="none"]; -"4" -> "2" [arrowhead="empty", arrowtail="none"]; -"5" -> "2" [arrowhead="empty", arrowtail="none"]; -} diff --git a/bridge/client.py b/bridge/client.py new file mode 100644 index 0000000..1673346 --- /dev/null +++ b/bridge/client.py @@ -0,0 +1,12 @@ +"Bridge Pattern Concept Sample Code" + +from circle_implementer import CircleImplementer +from square_implementer import SquareImplementer +from circle import Circle +from square import Square + +CIRCLE = Circle(CircleImplementer) +CIRCLE.draw() + +SQUARE = Square(SquareImplementer) +SQUARE.draw() diff --git a/bridge/interface_shape.py b/bridge/interface_shape.py new file mode 100644 index 0000000..17df0cf --- /dev/null +++ b/bridge/interface_shape.py @@ -0,0 +1,12 @@ +# pylint: disable=too-few-public-methods +"The Shape Abstraction Interface" +from abc import ABCMeta, abstractmethod + + +class IShape(metaclass=ABCMeta): + "The Shape Abstraction Interface" + + @staticmethod + @abstractmethod + def draw(): + "The method that will be handled at the shapes implementer" diff --git a/bridge/interface_shape_implementer.py b/bridge/interface_shape_implementer.py new file mode 100644 index 0000000..80b36f8 --- /dev/null +++ b/bridge/interface_shape_implementer.py @@ -0,0 +1,12 @@ +# pylint: disable=too-few-public-methods +"A Shape Implementor Interface" +from abc import ABCMeta, abstractmethod + + +class IShapeImplementer(metaclass=ABCMeta): + "Shape Implementer" + + @staticmethod + @abstractmethod + def draw_implementation(): + "The method that the refined abstractions will implement" diff --git a/bridge/square.py b/bridge/square.py new file mode 100644 index 0000000..9f174cb --- /dev/null +++ b/bridge/square.py @@ -0,0 +1,13 @@ +# pylint: disable=too-few-public-methods +"A Square Abstraction" +from interface_shape import IShape + + +class Square(IShape): + "The Square is a Refined Abstraction" + + def __init__(self, implementer): + self.implementer = implementer() + + def draw(self): + self.implementer.draw_implementation() diff --git a/bridge/square_implementer.py b/bridge/square_implementer.py new file mode 100644 index 0000000..efac3b1 --- /dev/null +++ b/bridge/square_implementer.py @@ -0,0 +1,17 @@ +# pylint: disable=too-few-public-methods +"A Square Implementer" +from interface_shape_implementer import IShapeImplementer + + +class SquareImplementer(IShapeImplementer): + "A Square Implementer" + + def draw_implementation(self): + print("**************") + print("* *") + print("* *") + print("* *") + print("* *") + print("* *") + print("* *") + print("**************") diff --git a/builder/Builder Design Pattern.md b/builder/Builder Design Pattern.md deleted file mode 100644 index 7e24bd7..0000000 --- a/builder/Builder Design Pattern.md +++ /dev/null @@ -1,22 +0,0 @@ -# Builder Design Pattern - -The Builder Pattern is a creational pattern whose intent is to separate the construction of a complex object from its representation so that you can use the same construction process to create different representations. - -The Builder Pattern tries to solve, -- How can a class create different representations of a complex object? -- How can a class that includes creating a complex object be simplified? - -The Builder and Factory patterns are very simiar in the fact they both instantiate new objects at run time. The difference is when the process of creating the object is more complex, so rather than the Factory returning a new instance of `ObjectA`, it could call the builders director construct method `ObjectA.construct()`. Both return an Object. - -Parts of the Builder Pattern -1. **Product** - The Product being built -2. **Concrete Builder** - Build the concrete product. Implements the IBuilder interface -3. **Builder Interface** - The Interface which the Concrete builder should implement -4. **Director** - Has a construct method which when called creates a customised product - -![Builder Pattern Overiew](builder.png) - -The Builder Pattern in the context of a House Builder. There are multiple directors creating there own complex objects - -![Builder Pattern in Context](house_builder.png). - diff --git a/builder/Builder Design Pattern.pdf b/builder/Builder Design Pattern.pdf deleted file mode 100644 index b68d742..0000000 Binary files a/builder/Builder Design Pattern.pdf and /dev/null differ diff --git a/builder/README.md b/builder/README.md index 05938d6..1016ae9 100644 --- a/builder/README.md +++ b/builder/README.md @@ -1,21 +1,116 @@ # Builder Design Pattern -The Builder Pattern is a creational pattern whose intent is to separate the construction of a complex object from its representation so that you can use the same construction process to create different representations. +## Videos -The Builder Pattern tries to solve, -- How can a class create different representations of a complex object? -- How can a class that includes creating a complex object be simplified? +Section | Video Links +-|- +Builder Overview | Builder Overview Builder Overview Builder Overview +Builder Use Case | Builder Use Case Builder Use Case Builder Use Case +Python **List** | Python List Python List Python List -The Builder and Factory patterns are very similar in the fact they both instantiate new objects at run time. The difference is when the process of creating the object is more complex, so rather than the Factory returning a new instance of `ObjectA`, it could call the builders director construct method `ObjectA.construct()`. Both return an Object. +## Book -Parts of the Builder Pattern -1. **Product** - The Product being built -2. **Concrete Builder** - Build the concrete product. Implements the IBuilder interface -3. **Builder Interface** - The Interface which the Concrete builder should implement -4. **Director** - Has a construct method which when called creates a customised product +Cover | Links +-|- +![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    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 -![Builder Pattern Overview](builder.png) +## Overview -The Builder Pattern in the context of a House Builder. There are multiple directors creating there own complex objects +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ -![Builder Pattern in Context](house_builder.png). +## Terminology + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Builder UML Diagram + +![Builder Pattern Overview](/img/builder_concept.svg) + +## Source Code + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Output + +``` bash +python ./builder/builder_concept.py +['a', 'b', 'c'] +``` + +## Example Use Case + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Example UML Diagram + +![Builder Pattern in Context](/img/builder_example.svg) + +## Output + +``` bash +python ./builder/client.py +This is a Ice Igloo with 1 door(s) and 0 window(s). +This is a Sandstone Castle with 100 door(s) and 200 window(s). +This is a Wood House Boat with 6 door(s) and 8 window(s). +``` + +## New Coding Concepts + +### Python List + +In the file [/builder/builder_concept.py](/builder/builder_concept.py) + +``` python linenums="47" + + def __init__(self): + self.parts = [] + +``` + +The `[]` is indicating a Python **List**. + +The list can store multiple items, they can be changed, they can have items added and removed, can be re-ordered, can be pre-filled with items when instantiated and is also very flexible. + +``` python +PS> python +>>> items = [] +>>> items.append("shouldn't've") +>>> items.append("y'aint") +>>> items.extend(["whomst", "superfluity"]) +>>> items +["shouldn't've", "y'aint", 'whomst', 'superfluity'] +>>> items.reverse() +>>> items +['superfluity', 'whomst', "y'aint", "shouldn't've"] +>>> items.remove("y'aint") +>>> items +['superfluity', 'whomst', "shouldn't've"] +>>> items.insert(1, "phoque") +>>> items +['superfluity', 'phoque', 'whomst', "shouldn't've"] +>>> items.append("whomst") +>>> items.count("whomst") +2 +>>> len(items) +5 +>>> items[2] = "bagnose" +>>> items +['superfluity', 'phoque', 'bagnose', "shouldn't've", 'whomst'] +>>> items[-2] +"shouldn't've" +``` + +Lists are used in almost every code example in this book. You will see all the many ways they can be used. + +In fact, a list was used in the [/abstract_factory/furniture_factory.py](/abstract_factory/furniture_factory.py) example, + +``` python +if furniture in ['SmallChair', 'MediumChair', 'BigChair']: + ... +``` + +This line, creates a list at runtime including the strings 'SmallChair', 'MediumChair' and 'BigChair'. If the value in `furniture` equals the same string as one of those items in the list, then the condition is true and the code within the if statement block will execute. + +## Summary + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ \ No newline at end of file diff --git a/builder/builder.png b/builder/builder.png deleted file mode 100644 index cf194b1..0000000 Binary files a/builder/builder.png and /dev/null differ diff --git a/builder/builder.py b/builder/builder.py deleted file mode 100644 index 27e99c5..0000000 --- a/builder/builder.py +++ /dev/null @@ -1,124 +0,0 @@ -"""The Builder Pattern in Python - -The intent of the Builder design pattern is to separate the -construction of a complex object from its representation. -By doing so the same construction process can create different -representations - -The Builder Pattern tries to solve, -- How can a class (the same construction process) create different -representations of a complex object? -- How can a class that includes creating a complex object be simplified? -""" -from abc import ABCMeta, abstractstaticmethod - - -class IHouseBuilder(metaclass=ABCMeta): - """The Builder Interface""" - - @abstractstaticmethod - def set_wall_material(value): - """Set the wall_material""" - - @abstractstaticmethod - def set_building_type(value): - """Set the building_type""" - - @abstractstaticmethod - def set_number_doors(value): - """Set the number of doors""" - - @abstractstaticmethod - def set_number_windows(value): - """Set the number of windows""" - - @abstractstaticmethod - def get_result(): - """Return the house""" - - -class HouseBuilder(IHouseBuilder): - """The Concrete Builder.""" - - def __init__(self): - self.house = House() - - def set_wall_material(self, value): - self.house.wall_material = value - return self - - def set_building_type(self, value): - self.house.building_type = value - return self - - def set_number_doors(self, value): - self.house.doors = value - return self - - def set_number_windows(self, value): - self.house.windows = value - return self - - def get_result(self): - return self.house - - -class House(): - """The Product""" - - def __init__(self, building_type="Apartment", doors=0, windows=0, wall_material="Brick"): - #brick, wood, straw, ice - self.wall_material = wall_material - # Apartment, Bungalow, Caravan, Hut, Castle, Duplex, HouseBoat, Igloo - self.building_type = building_type - self.doors = doors - self.windows = windows - - def __str__(self): - return "This is a {0} {1} with {2} door(s) and {3} window(s).".format( - self.wall_material, self.building_type, self.doors, self.windows - ) - - -class IglooDirector: - """The Director, building a different representation.""" - @staticmethod - def construct(): - return HouseBuilder()\ - .set_building_type("Igloo")\ - .set_wall_material("Ice")\ - .set_number_doors(1)\ - .set_number_windows(0)\ - .get_result() - - -class HouseBoatDirector: - """The Director, building a different representation.""" - @staticmethod - def construct(): - return HouseBuilder()\ - .set_building_type("House Boat")\ - .set_wall_material("Wooden")\ - .set_number_doors(6)\ - .set_number_windows(8)\ - .get_result() - - -class CastleDirector: - """The Director, building a different representation.""" - @staticmethod - def construct(): - return HouseBuilder()\ - .set_building_type("Castle")\ - .set_wall_material("Granite")\ - .set_number_doors(100)\ - .set_number_windows(200).get_result() - - -if __name__ == "__main__": - IGLOO = IglooDirector.construct() - HOUSE_BOAT = HouseBoatDirector.construct() - CASTLE = CastleDirector.construct() - print(IGLOO) - print(HOUSE_BOAT) - print(CASTLE) diff --git a/builder/builder_concept.py b/builder/builder_concept.py new file mode 100644 index 0000000..676f3ee --- /dev/null +++ b/builder/builder_concept.py @@ -0,0 +1,75 @@ +# pylint: disable=too-few-public-methods +# pylint: disable=arguments-differ +"Builder Concept Sample Code" +from abc import ABCMeta, abstractmethod + + +class IBuilder(metaclass=ABCMeta): + "The Builder Interface" + + @staticmethod + @abstractmethod + def build_part_a(): + "Build part a" + + @staticmethod + @abstractmethod + def build_part_b(): + "Build part b" + + @staticmethod + @abstractmethod + def build_part_c(): + "Build part c" + + @staticmethod + @abstractmethod + def get_result(): + "Return the final product" + + +class Builder(IBuilder): + "The Concrete Builder." + + def __init__(self): + self.product = Product() + + def build_part_a(self): + self.product.parts.append('a') + return self + + def build_part_b(self): + self.product.parts.append('b') + return self + + def build_part_c(self): + self.product.parts.append('c') + return self + + def get_result(self): + return self.product + + +class Product(): + "The Product" + + def __init__(self): + self.parts = [] + + +class Director: + "The Director, building a complex representation." + + @staticmethod + def construct(): + "Constructs and returns the final product" + return Builder()\ + .build_part_a()\ + .build_part_b()\ + .build_part_c()\ + .get_result() + + +# The Client +PRODUCT = Director.construct() +print(PRODUCT.parts) diff --git a/builder/castle_director.py b/builder/castle_director.py new file mode 100644 index 0000000..0f6a81d --- /dev/null +++ b/builder/castle_director.py @@ -0,0 +1,16 @@ +"A Director Class" +from house_builder import HouseBuilder + + +class CastleDirector: # pylint: disable=too-few-public-methods + "One of the Directors, that can build a complex representation." + + @staticmethod + def construct(): + "Constructs and returns the final product" + return HouseBuilder()\ + .set_building_type("Castle")\ + .set_wall_material("Sandstone")\ + .set_number_doors(100)\ + .set_number_windows(200)\ + .get_result() diff --git a/builder/classes_builder.dot b/builder/classes_builder.dot deleted file mode 100644 index 0099b22..0000000 --- a/builder/classes_builder.dot +++ /dev/null @@ -1,14 +0,0 @@ -digraph "classes_builder" { -charset="utf-8" -rankdir=BT -{rank=same; 0, 2} -"0" [label="{ConcreteBuilder|product\l|build_part_a()\lbuild_part_b()\lbuild_part_c()\l}", shape="record"]; -"2" [label="{IBuilder|\l|build_part_a()\lbuild_part_b()\lbuild_part_c()\l}", shape="record"]; -"3" [label="{Product|part_a : str\lpart_b : int\lpart_c : float\l|}", shape="record"]; -"1" [label="{Director|\l|construct()\l}", shape="record"]; -"0" -> "2" [arrowhead="empty", arrowtail="none"]; -"3" -> "0" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="product", style="solid"]; - -"2" -> "1" [arrowhead="odiamond", arrowtail="none"]; - -} diff --git a/builder/classes_house_builder.dot b/builder/classes_house_builder.dot deleted file mode 100644 index 12639c4..0000000 --- a/builder/classes_house_builder.dot +++ /dev/null @@ -1,21 +0,0 @@ -digraph "classes_house_builder" { -charset="utf-8" -rankdir=BT -{rank=same; HouseBuilder, IHouseBuilder} -"CastleDirector" [label="{CastleDirector|\l|construct()\l}", shape="record"]; -"House" [label="{House (Product)|building_type : str\ldoors : int\lwall_material : str\lwindows : int\l|}", shape="record"]; -"HouseBoatDirector" [label="{HouseBoatDirector|\l|construct()\l}", shape="record"]; -"HouseBuilder" [label="{HouseBuilder|house\l|get_result()\lset_building_type()\lset_number_doors()\lset_number_windows()\lset_wall_material()\l}", shape="record"]; -"IHouseBuilder" [label="{IHouseBuilder|\l|get_result()\lset_building_type()\lset_number_doors()\lset_number_windows()\lset_wall_material()\l}", shape="record"]; -"IglooDirector" [label="{IglooDirector|\l|construct()\l}", shape="record"]; - -"HouseBuilder" -> "IHouseBuilder" [arrowhead="empty", arrowtail="none"]; -"House" -> "HouseBuilder" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="house", style="solid"]; - - - -"IHouseBuilder" -> "HouseBoatDirector" [arrowhead="odiamond", arrowtail="none"]; -"IHouseBuilder" -> "IglooDirector" [arrowhead="odiamond", arrowtail="none"]; -"IHouseBuilder" -> "CastleDirector" [arrowhead="odiamond", arrowtail="none"]; - -} diff --git a/builder/client.py b/builder/client.py new file mode 100644 index 0000000..a596f97 --- /dev/null +++ b/builder/client.py @@ -0,0 +1,13 @@ +"House Builder Example Code" + +from igloo_director import IglooDirector +from castle_director import CastleDirector +from houseboat_director import HouseBoatDirector + +IGLOO = IglooDirector.construct() +CASTLE = CastleDirector.construct() +HOUSEBOAT = HouseBoatDirector.construct() + +print(IGLOO.construction()) +print(CASTLE.construction()) +print(HOUSEBOAT.construction()) diff --git a/builder/house.py b/builder/house.py new file mode 100644 index 0000000..56b4815 --- /dev/null +++ b/builder/house.py @@ -0,0 +1,21 @@ +"The Product" + + +class House(): # pylint: disable=too-few-public-methods + "The Product" + + def __init__(self, building_type="Apartment", doors=0, + windows=0, wall_material="Brick"): + # brick, wood, straw, ice + self.wall_material = wall_material + # Apartment, Bungalow, Caravan, Hut, Castle, Duplex, + # HouseBoat, Igloo + self.building_type = building_type + self.doors = doors + self.windows = windows + + def construction(self): + "Returns a string describing the construction" + return f"This is a {self.wall_material} "\ + f"{self.building_type} with {self.doors} "\ + f"door(s) and {self.windows} window(s)." diff --git a/builder/house_builder.png b/builder/house_builder.png deleted file mode 100644 index d672560..0000000 Binary files a/builder/house_builder.png and /dev/null differ diff --git a/builder/house_builder.py b/builder/house_builder.py new file mode 100644 index 0000000..f676019 --- /dev/null +++ b/builder/house_builder.py @@ -0,0 +1,29 @@ +"The Builder Class" +from interface_house_builder import IHouseBuilder +from house import House + + +class HouseBuilder(IHouseBuilder): + "The House Builder." + + def __init__(self): + self.house = House() + + def set_building_type(self, building_type): + self.house.building_type = building_type + return self + + def set_wall_material(self, wall_material): + self.house.wall_material = wall_material + return self + + def set_number_doors(self, number): + self.house.doors = number + return self + + def set_number_windows(self, number): + self.house.windows = number + return self + + def get_result(self): + return self.house diff --git a/builder/houseboat_director.py b/builder/houseboat_director.py new file mode 100644 index 0000000..68acae0 --- /dev/null +++ b/builder/houseboat_director.py @@ -0,0 +1,16 @@ +"A Director Class" +from house_builder import HouseBuilder + + +class HouseBoatDirector: # pylint: disable=too-few-public-methods + "One of the Directors, that can build a complex representation." + + @staticmethod + def construct(): + "Constructs and returns the final product" + return HouseBuilder()\ + .set_building_type("House Boat")\ + .set_wall_material("Wood")\ + .set_number_doors(6)\ + .set_number_windows(8)\ + .get_result() diff --git a/builder/igloo_director.py b/builder/igloo_director.py new file mode 100644 index 0000000..5dfd618 --- /dev/null +++ b/builder/igloo_director.py @@ -0,0 +1,18 @@ +"A Director Class" +from house_builder import HouseBuilder + + +class IglooDirector: # pylint: disable=too-few-public-methods + "One of the Directors, that can build a complex representation." + + @staticmethod + def construct(): + """Constructs and returns the final product + Note that in this IglooDirector, it has omitted the set_number_of + windows call since this Igloo will have no windows. + """ + return HouseBuilder()\ + .set_building_type("Igloo")\ + .set_wall_material("Ice")\ + .set_number_doors(1)\ + .get_result() diff --git a/builder/interface_house_builder.py b/builder/interface_house_builder.py new file mode 100644 index 0000000..cf18079 --- /dev/null +++ b/builder/interface_house_builder.py @@ -0,0 +1,31 @@ +"The Builder Interface" +from abc import ABCMeta, abstractmethod + + +class IHouseBuilder(metaclass=ABCMeta): + "The House Builder Interface" + + @staticmethod + @abstractmethod + def set_building_type(building_type): + "Build type" + + @staticmethod + @abstractmethod + def set_wall_material(wall_material): + "Build material" + + @staticmethod + @abstractmethod + def set_number_doors(number): + "Number of doors" + + @staticmethod + @abstractmethod + def set_number_windows(number): + "Number of windows" + + @staticmethod + @abstractmethod + def get_result(): + "Return the final product" diff --git a/chain_of_responsibility/Chain Of Responsibility Design Pattern.md b/chain_of_responsibility/Chain Of Responsibility Design Pattern.md deleted file mode 100644 index dfc2ad2..0000000 --- a/chain_of_responsibility/Chain Of Responsibility Design Pattern.md +++ /dev/null @@ -1,47 +0,0 @@ -# Chain of Responsibility Design Pattern - -Chain of responsibility pattern is a behavioural pattern used to achieve loose coupling -in software design. -In this example, a request from a client is passed to a chain of objects to process them. -The objects in the chain will decide how to process them and/or pas them to the next in the chain. -The objects can also modify the next in the chain if for example you wanted to run objects in a recursive manner. - - -## Chain of Responsibility Diagram -![Chain of Responsibility UML Diagram](chain_of_responsibility.png) - - -## Chain of Responsibility UML Diagram in the context of an ATM -![Chain of Responsibility UML Diagram in the context of an ATM](atm.png) - -In the ATM example, the chain is created to dispense an amount of £50, then £20s and then £10s in order. -The successor chain is hardcoded in the chain client. - -```python -def __init__(self): - # initialize the successor chain - self.chain1 = Dispenser50() - self.chain2 = Dispenser20() - self.chain3 = Dispenser10() - - # set the chain of responsibility - # The Client may compose chains once or - # the hadler can set them dynamically at - # handle time - self.chain1.set_successor(self.chain2) - self.chain2.set_successor(self.chain3) - -``` -You also have the option to set the next successor on logic at handle time. - -## Output -```bash -$ python atm.py -Enter amount to withdrawal -130 -Dispensing 2 £50 note -Dispensing 1 £20 note -Dispensing 1 £10 note -Go spoil yourself -``` - diff --git a/chain_of_responsibility/Chain Of Responsibility Design Pattern.pdf b/chain_of_responsibility/Chain Of Responsibility Design Pattern.pdf deleted file mode 100644 index b8e8485..0000000 Binary files a/chain_of_responsibility/Chain Of Responsibility Design Pattern.pdf and /dev/null differ diff --git a/chain_of_responsibility/README.md b/chain_of_responsibility/README.md index 4cfe5c7..23537c5 100644 --- a/chain_of_responsibility/README.md +++ b/chain_of_responsibility/README.md @@ -1,47 +1,114 @@ # Chain of Responsibility Design Pattern -Chain of responsibility pattern is a behavioural pattern used to achieve loose coupling -in software design. -In this example, a request from a client is passed to a chain of objects to process them. -The objects in the chain will decide how to process them and/or pass them to the next in the chain. -The objects can also modify the next in the chain if for example you wanted to run objects in a recursive manner. +## Videos +Section | Video Links +-|- +Chain of Responsibility | Chain of Responsibility Overview Chain of Responsibility Overview Chain of Responsibility Overview +Use Case | Chain of Responsibility Use Case Chain of Responsibility Use Case Chain of Responsibility Use Case +Python Floor Division | Python Floor Division Python Floor Division Python Floor Division +Accepting User Input | Accepting User Input Accepting User Input Accepting User Input -## Chain of Responsibility Diagram -![Chain of Responsibility UML Diagram](chain_of_responsibility.png) +## Book +Cover | Links +-|- +![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    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 -## Chain of Responsibility UML Diagram in the context of an ATM -![Chain of Responsibility UML Diagram in the context of an ATM](atm.png) +## Overview -In the ATM example, the chain is created to dispense an amount of £50, then £20s and then £10s in order. -The successor chain is hard coded in the chain client. +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ -```python -def __init__(self): - # initialize the successor chain - self.chain1 = Dispenser50() - self.chain2 = Dispenser20() - self.chain3 = Dispenser10() +## Terminology - # set the chain of responsibility - # The Client may compose chains once or - # the handler can set them dynamically at - # handle time - self.chain1.set_successor(self.chain2) - self.chain2.set_successor(self.chain3) +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ +## Chain of Responsibility UML Diagram + +![Chain of Responsibility Design Pattern](/img/chain_of_responsibility_concept.svg) + +## Source Code + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Output + +``` bash +python ./chain_of_responsibility/chain_of_responsibility_concept.py +Successor1 payload = 1 +Successor2 payload = -1 +Successor2 payload = -0.5 +Successor2 payload = -0.25 +Successor1 payload = -0.5 +Successor1 payload = 0.5 +Successor2 payload = -1.5 +Finished result = -1.5 ``` -You also have the option to set the next successor on logic at handle time. + +## Example Use Case + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Example UML Diagram + +![Chain of Responsibility Design Pattern](/img/chain_of_responsibility_example.svg) ## Output -```bash -$ python atm.py -Enter amount to withdrawal -130 -Dispensing 2 £50 note -Dispensing 1 £20 note + +``` bash +python ./chain_of_responsibility/client.py +Enter amount to withdrawal : 180 +Dispensing 3 £50 note(s) +Dispensing 1 £20 note(s) Dispensing 1 £10 note -Go spoil yourself +Now go spoil yourself +``` + +## New Coding Concepts + +### Floor Division + +Normally division uses a single **/** character and will return a float even if the numbers are integers or exactly divisible with no remainder, + +E.g., + +``` python +PS> python +>>> 9 / 3 +3.0 +``` + +Python Version 3 also has an option to return an integer version (floor) of the number by using the double **//** characters instead. + +``` python +PS> python +>>> 9 // 3 +3 +``` + +See PEP-0238 : [https://www.python.org/dev/peps/pep-0238/](https://www.python.org/dev/peps/pep-0238/) + +### Accepting User Input + +In the file [/chain_of_responsibility/client.py](/chain_of_responsibility/client.py) above, there is a command `input` . + +The `input` command allows your script to accept user input from the command prompt. + +In the ATM example, when you start it, it will ask the user to enter a number. + +Then when the user presses the `enter` key, the input is converted to an integer and the value tested if valid. + +``` python +AMOUNT = int(input("Enter amount to withdrawal : ")) +if AMOUNT < 10 or AMOUNT % 10 != 0: + ...continue + ``` +Note that in Python 2.x, use the `raw_input()` command instead of `input()` . + +See PEP-3111 : [https://www.python.org/dev/peps/pep-3111/](https://www.python.org/dev/peps/pep-3111/) + +## Summary + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ \ No newline at end of file diff --git a/chain_of_responsibility/atm.png b/chain_of_responsibility/atm.png deleted file mode 100644 index 7ffb880..0000000 Binary files a/chain_of_responsibility/atm.png and /dev/null differ diff --git a/chain_of_responsibility/atm.py b/chain_of_responsibility/atm.py deleted file mode 100644 index 2a6ecfb..0000000 --- a/chain_of_responsibility/atm.py +++ /dev/null @@ -1,126 +0,0 @@ -"""Chain of Responsibility Pattern -Chain of responsibility pattern is a behavioural pattern used to achieve loose coupling -in software design. -In this example, a request from a client is passed to a chain of objects to process them. -The objects in the chain will decide how to process them and/or pas them to the next in the chain. -The objects can also modify the next in the chain if for example you wanted to run objects in a recursive manner. -""" - -from abc import ABCMeta, abstractstaticmethod - - -class IHandler(metaclass=ABCMeta): - """The Interface for handling requests.""" - - @abstractstaticmethod - def set_successor(successor): - """Set the next handler in the chain""" - - @abstractstaticmethod - def handle(amount): - """Handle the event""" - - -class Dispenser50(IHandler): - """ConcreteHandler - Dispense £50 notes if applicable, - otherwise continue to successor - """ - - def __init__(self): - self._successor = None - - def set_successor(self, successor): - """Set the successor""" - self._successor = successor - - def handle(self, amount): - """Handle the dispensing of notes""" - if amount >= 50: - num = amount // 50 - remainder = amount % 50 - print(f"Dispensing {num} £50 note") - if remainder != 0: - self._successor.handle(remainder) - else: - self._successor.handle(amount) - - -class Dispenser20(IHandler): - """ConcreteHandler - Dispense £20 notes if applicable, - otherwise continue to successor - """ - - def __init__(self): - self._successor = None - - def set_successor(self, successor): - """Set the successor""" - self._successor = successor - - def handle(self, amount): - """Handle the dispensing of notes""" - if amount >= 20: - num = amount // 20 - remainder = amount % 20 - print(f"Dispensing {num} £20 note") - if remainder != 0: - self._successor.handle(remainder) - else: - self._successor.handle(amount) - - -class Dispenser10(IHandler): - """ConcreteHandler - Dispense £10 notes if applicable, - otherwise continue to successor - """ - - def __init__(self): - self._successor = None - - def set_successor(self, successor): - """Set the successor""" - self._successor = successor - - def handle(self, amount): - """Handle the dispensing of notes""" - if amount >= 10: - num = amount // 10 - remainder = amount % 10 - print(f"Dispensing {num} £10 note") - if remainder != 0: - self._successor.handle(remainder) - else: - self._successor.handle(amount) - - -class ATMDispenserChain: # pylint: disable=too-few-public-methods - """The Chain Client""" - - def __init__(self): - # initialize the successor chain - self.chain1 = Dispenser50() - self.chain2 = Dispenser20() - self.chain3 = Dispenser10() - - # set the chain of responsibility - # The Client may compose chains once or - # the hadler can set them dynamically at - # handle time - self.chain1.set_successor(self.chain2) - self.chain2.set_successor(self.chain3) - - -if __name__ == "__main__": - - ATM = ATMDispenserChain() - - AMOUNT = int(input("Enter amount to withdrawal : ")) - if AMOUNT < 10 or AMOUNT % 10 != 0: - print("Amount should be positive and in multiple of 10s.") - exit() - # process the request - ATM.chain1.handle(AMOUNT) - print("Now go spoil yourself") diff --git a/chain_of_responsibility/atm_dispenser_chain.py b/chain_of_responsibility/atm_dispenser_chain.py new file mode 100644 index 0000000..3c3ada0 --- /dev/null +++ b/chain_of_responsibility/atm_dispenser_chain.py @@ -0,0 +1,19 @@ +"The ATM Dispenser Chain" +from dispenser10 import Dispenser10 +from dispenser20 import Dispenser20 +from dispenser50 import Dispenser50 + + +class ATMDispenserChain: # pylint: disable=too-few-public-methods + "The Chain Client" + + def __init__(self): + # initializing the successors chain + self.chain1 = Dispenser50() + self.chain2 = Dispenser20() + self.chain3 = Dispenser10() + # Setting a default successor chain that will process the 50s first, + # the 20s second and the 10s last. The successor chain will be + # recalculated dynamically at runtime. + self.chain1.next_successor(self.chain2) + self.chain2.next_successor(self.chain3) diff --git a/chain_of_responsibility/chain_of_responsibility.png b/chain_of_responsibility/chain_of_responsibility.png deleted file mode 100644 index d6e01ed..0000000 Binary files a/chain_of_responsibility/chain_of_responsibility.png and /dev/null differ diff --git a/chain_of_responsibility/chain_of_responsibility_concept.py b/chain_of_responsibility/chain_of_responsibility_concept.py new file mode 100644 index 0000000..863983d --- /dev/null +++ b/chain_of_responsibility/chain_of_responsibility_concept.py @@ -0,0 +1,57 @@ +# pylint: disable=too-few-public-methods +"The Chain Of Responsibility Pattern Concept" +import random +from abc import ABCMeta, abstractmethod + + +class IHandler(metaclass=ABCMeta): + "The Handler Interface that the Successors should implement" + @staticmethod + @abstractmethod + def handle(payload): + "A method to implement" + + +class Successor1(IHandler): + "A Concrete Handler" + @staticmethod + def handle(payload): + print(f"Successor1 payload = {payload}") + test = random.randint(1, 2) + if test == 1: + payload = payload + 1 + payload = Successor1().handle(payload) + if test == 2: + payload = payload - 1 + payload = Successor2().handle(payload) + return payload + + +class Successor2(IHandler): + "A Concrete Handler" + @staticmethod + def handle(payload): + print(f"Successor2 payload = {payload}") + test = random.randint(1, 3) + if test == 1: + payload = payload * 2 + payload = Successor1().handle(payload) + if test == 2: + payload = payload / 2 + payload = Successor2().handle(payload) + return payload + + +class Chain(): + "A chain with a default first successor" + @staticmethod + def start(payload): + "Setting the first successor that will modify the payload" + return Successor1().handle(payload) + + +# The Client +CHAIN = Chain() +PAYLOAD = 1 +OUT = CHAIN.start(PAYLOAD) +print(f"Finished result = {OUT}") diff --git a/chain_of_responsibility/classes_atm.dot b/chain_of_responsibility/classes_atm.dot deleted file mode 100644 index c715a6d..0000000 --- a/chain_of_responsibility/classes_atm.dot +++ /dev/null @@ -1,15 +0,0 @@ -digraph "classes_atm" { -charset="utf-8" -rankdir=BT -"0" [label="{ATMDispenserChain|chain1\lchain2\lchain3\l|}", shape="record"]; -"1" [label="{Dispenser10|\l|handle()\lset_successor()\l}", shape="record"]; -"2" [label="{Dispenser20|\l|handle()\lset_successor()\l}", shape="record"]; -"3" [label="{Dispenser50|\l|handle()\lset_successor()\l}", shape="record"]; -"4" [label="{IHandler|\l|handle()\lset_successor()\l}", shape="record"]; -"1" -> "4" [arrowhead="empty", arrowtail="none"]; -"2" -> "4" [arrowhead="empty", arrowtail="none"]; -"3" -> "4" [arrowhead="empty", arrowtail="none"]; -"1" -> "0" [arrowhead="ediamond", arrowtail="none", fontcolor="green", label="chain3", style="solid"]; -"2" -> "0" [arrowhead="ediamond", arrowtail="none", fontcolor="green", label="chain2", style="solid"]; -"3" -> "0" [arrowhead="ediamond", arrowtail="none", fontcolor="green", label="chain1", style="solid"]; -} diff --git a/chain_of_responsibility/classes_chain_of_responsibility.dot b/chain_of_responsibility/classes_chain_of_responsibility.dot deleted file mode 100644 index 0dc12c5..0000000 --- a/chain_of_responsibility/classes_chain_of_responsibility.dot +++ /dev/null @@ -1,19 +0,0 @@ -digraph R { - - node [shape=record]; - - { rank=same Next1 Process1 } - { rank=same Next2 Process2 } - { rank=same Next3 Process3 } - - "Client"-> Next1; - - Next1 -> Process1; - Process1 -> Next2; - Next2 -> Process2; - Process2 -> Next3 - Next3 -> Process3; - Process3 -> Finished - "Finished" - -} \ No newline at end of file diff --git a/chain_of_responsibility/client.py b/chain_of_responsibility/client.py new file mode 100644 index 0000000..759bdcf --- /dev/null +++ b/chain_of_responsibility/client.py @@ -0,0 +1,12 @@ +"An ATM Dispenser that dispenses denominations of notes" +import sys +from atm_dispenser_chain import ATMDispenserChain + +ATM = ATMDispenserChain() +AMOUNT = int(input("Enter amount to withdrawal : ")) +if AMOUNT < 10 or AMOUNT % 10 != 0: + print("Amount should be positive and in multiple of 10s.") + sys.exit() +# process the request +ATM.chain1.handle(AMOUNT) +print("Now go spoil yourself") diff --git a/chain_of_responsibility/dispenser10.py b/chain_of_responsibility/dispenser10.py new file mode 100644 index 0000000..112ce8a --- /dev/null +++ b/chain_of_responsibility/dispenser10.py @@ -0,0 +1,24 @@ +"A dispenser of £10 notes" +from interface_dispenser import IDispenser + + +class Dispenser10(IDispenser): + "Dispenses £10s if applicable, otherwise continues to next successor" + + def __init__(self): + self._successor = None + + def next_successor(self, successor): + "Set the next successor" + self._successor = successor + + def handle(self, amount): + "Handle the dispensing of notes" + if amount >= 10: + num = amount // 10 + remainder = amount % 10 + print(f"Dispensing {num} £10 note") + if remainder != 0: + self._successor.handle(remainder) + else: + self._successor.handle(amount) diff --git a/chain_of_responsibility/dispenser20.py b/chain_of_responsibility/dispenser20.py new file mode 100644 index 0000000..2b1d691 --- /dev/null +++ b/chain_of_responsibility/dispenser20.py @@ -0,0 +1,24 @@ +"A dispenser of £20 notes" +from interface_dispenser import IDispenser + + +class Dispenser20(IDispenser): + "Dispenses £20s if applicable, otherwise continues to next successor" + + def __init__(self): + self._successor = None + + def next_successor(self, successor): + "Set the next successor" + self._successor = successor + + def handle(self, amount): + "Handle the dispensing of notes" + if amount >= 20: + num = amount // 20 + remainder = amount % 20 + print(f"Dispensing {num} £20 note(s)") + if remainder != 0: + self._successor.handle(remainder) + else: + self._successor.handle(amount) diff --git a/chain_of_responsibility/dispenser50.py b/chain_of_responsibility/dispenser50.py new file mode 100644 index 0000000..0592351 --- /dev/null +++ b/chain_of_responsibility/dispenser50.py @@ -0,0 +1,24 @@ +"A dispenser of £50 notes" +from interface_dispenser import IDispenser + + +class Dispenser50(IDispenser): + "Dispenses £50s if applicable, otherwise continues to next successor" + + def __init__(self): + self._successor = None + + def next_successor(self, successor): + "Set the next successor" + self._successor = successor + + def handle(self, amount): + "Handle the dispensing of notes" + if amount >= 50: + num = amount // 50 + remainder = amount % 50 + print(f"Dispensing {num} £50 note(s)") + if remainder != 0: + self._successor.handle(remainder) + else: + self._successor.handle(amount) diff --git a/chain_of_responsibility/interface_dispenser.py b/chain_of_responsibility/interface_dispenser.py new file mode 100644 index 0000000..998c368 --- /dev/null +++ b/chain_of_responsibility/interface_dispenser.py @@ -0,0 +1,15 @@ +"The ATM Notes Dispenser Interface" +from abc import ABCMeta, abstractmethod + + +class IDispenser(metaclass=ABCMeta): + "Methods to implement" + @staticmethod + @abstractmethod + def next_successor(successor): + """Set the next handler in the chain""" + + @staticmethod + @abstractmethod + def handle(amount): + """Handle the event""" diff --git a/coding-conventions.md b/coding-conventions.md new file mode 100644 index 0000000..5c8de04 --- /dev/null +++ b/coding-conventions.md @@ -0,0 +1,112 @@ +# Coding Conventions + +## Python Interactive Console Versus *.py Scripts + +You can execute Python code by writing your scripts into text files and commonly using the `.py` extension. Text files on most operating systems will be UTF-8 encoded by default. Python also reads UTF-8 encoded text files by default. + +Create a new text file called `example.py` and add the following text. + +``` python +print("Hello World!") +``` + +and then you can execute it using `python` or `python3` depending on your operating system and Python version. + +``` bash +PS> python ./example.py +Hello World! +``` + +You can also enter Python code directly into the Python Interactive Console by typing just `python` or `python3` from the command line and then press `Enter` . You then get a prompt like below. + +``` python +PS> python +Python 3.10.1 (tags/v3.10.1:2cd268a, Dec 6 2021, 19:10:37) [MSC ... +Type "help", "copyright", "credits" or "license" for more information. +>>> +``` + +You can now enter python commands directly. + +``` + +>>> print("Hello World!") +Hello World! +>>> +``` + +To exit the Python Interactive Console, you can usually type `quit()` or press `Ctrl-Z` then press `Enter` + +This repository will show examples of using both `*.py` scripts and the interactive console to execute Python. Look out for the `>>>` characters in the code blocks to indicate if I was using the Python Interactive Console or a `*.py` script. + +## PEP8 + +The code styling in this repository is formatted using mostly PEP8 styling recommendations. + +* **UPPER_CASE** : Constants will be defined using UPPER_CASE naming style. +* **PascalCase** : Class names will use PascalCase naming style +* **snake_case** : For variables names, method names and method arguments. +* **Docstrings** : Classes and methods contain extra documentation that is descriptive text enclosed in " or """ for multiline strings. +* **_leading_underscore** : Use a leading underscore to indicate variables that should be considered as private class variables. + +See PEP-0008 : [https://www.python.org/dev/peps/pep-0008/](https://www.python.org/dev/peps/pep-0008/) + +## Pylint + +I use the Pylint tool to check for code styling recommendations. + +On most operating systems you would generally install Pylint by using the `PIP` or `PIP3` installer. + +``` powershell +pip install pylint +``` + +If using VSCode, open the Command Palette (Ctrl+Shift+P), then set the + +`Python: Enable Linting` to `on` + +and + +`Python: Select Linter` to `Pylint` + +## Common Pylint Warning and Error Messages + +| ID | Message | Description | +|-|-|-| +| R0201 | Method could be a function (no-self-use) | Your method has an attribute that refers to `self` or `cls`, but it is not necessary since your are **NOT** using `self` or `cls` within the method body. You have the option of using the `@staticmethod` decorator on your methods instead, and to remove the `self` or `cls` from the method attributes. | +| R0903 | Too few public methods (1/2) (too-few-public-methods) | The error assumes that your class may be used for just storing data. You could use a dictionary instead. However the assumption is not always correct. You may be extending a class, or often in my case, I am trying to keep examples very small, readable and to the point. So you have the option to insert a pylint declaration at the top of the file, or at a particular method declaration to ignore this pylint error. `# pylint: disable=too-few-public-methods` | +| E0110 | Abstract class '*ClassName*' with abstract methods instantiated (abstract-class-instantiated) | The Class that inherits your abstract class is not implementing all the abstract methods as described in the interfaces signature.| +| W0221 | Parameters differ from overridden '*method*' method (arguments-differ) | The arguments in your abstract class don't match the arguments in your implementing class. Check spelling of arguments.| +| C0304 | Final newline missing (missing-final-newline) | Pylint preferes a file to end with a new line. When copying code from a webpage into a `.py` file, the copied code may not finish with a new line character. You can add one manually by pressing the `enter` key on your keyboard at the end of your code, or if you use VSCode, pressing the key combination of **SHIFT-ALT-F** will auto format your `*.py` file with a final newline when you have the Pylint linter, or other linter, enabled.| +| W0612 | Unused variable | You can remove the unused variable from your code. If you cannot remove the unused variable then use a `_` as the variable name. See the section [the-underscore-only-_variable](mediator#the-underscore-only-_-variable) in the [Mediator](mediator) pattern for more information. + +## Command Line Interfaces + +Command Line Interfaces (CLI) on different operating systems (Windows, Linux, MacOSX, RaspberryPI) vary in appearance quite a lot. + +You can use CMD, PowerShell or Git BASH on Windows, Bash on Linux or Terminal on MacOSX. + +``` text +-- Windows PowerShell -- +PS> python example.py +PS E:\python_design_patterns> python example.py + +-- Windows CMD -- +C:\> python example.py + +-- Git BASH +Username@hostname MINGW64 /e/python_design_patterns +$ python example.py + +-- Linux -- +user@domain:~# python3 example.py +user@domain:$/ python3 example.py +user@domain:/python-design-patterns# python3 example.py +$ python3 example.py +# python3 example.py + +-- MacOSX-- +hostname:~ username$ python3 example.py +``` + +Wikipedia - Command-line interface : [https://en.wikipedia.org/wiki/Command-line_interface](https://en.wikipedia.org/wiki/Command-line_interface) diff --git a/command/Command Design Pattern.md b/command/Command Design Pattern.md deleted file mode 100644 index c0876d0..0000000 --- a/command/Command Design Pattern.md +++ /dev/null @@ -1,39 +0,0 @@ -# Command Design Pattern - -The command pattern is a behavioural design pattern, in which an abstraction exists between an object that invokes a command, and the object that performs it. - -The components if the Command Design Pattern are, -1. **Receiver** - The Object that will receive and execute the command -2. **Invoker** - Which will send the command to the receiver -3. **Command Object** - Itself, which implements an execute, or action method, and contains all required information to execute it -4. **Client** - The application or component which is aware of the Receiver, Invoker and Commands - -Eg, a button, will call the Invoker, which will call a pre registered Commands execute method, which the Receiver will perform. - -A Concrete Class will delegate a request to a command object, instead of implementing the request directly. -Using a command design pattern allows you to seperate concerns a little easier and to solve problems of the concerns independently of each of the layers. -eg, logging the execution of a command and it's outcome. - -Uses: -GUI Buttons, menus -Macro recording -Multi level undo/redo -networking - send whole command objects across a network, even as a batch -parallel processing or thread pools, -transactional behaviour -Wizards - -Notes: -The receiver object should manages it's own state, not the command object -There can be one or more invokers which can execute the command at a later date. - -![The Command Pattern Overview](command_pattern.png) - -The Command Pattern in the context of a light switch - -![The Command Pattern Switch](switch_command_pattern.png) - -The Command Pattern in the contect of a slider for a heater, which also implements UNDO/REDO - -![The Command Pattern Slider](slider_command.png) - diff --git a/command/Command Design Pattern.pdf b/command/Command Design Pattern.pdf deleted file mode 100644 index 9f9c471..0000000 Binary files a/command/Command Design Pattern.pdf and /dev/null differ diff --git a/command/README.md b/command/README.md index bd6382d..9835591 100644 --- a/command/README.md +++ b/command/README.md @@ -1,40 +1,81 @@ # Command Design Pattern -The command pattern is a behavioural design pattern, in which an abstraction exists between an object that invokes a command, and the object that performs it. +## Videos -The components if the Command Design Pattern are, -1. **Receiver** - The Object that will receive and execute the command -2. **Invoker** - Which will send the command to the receiver -3. **Command Object** - Itself, which implements an execute, or action method, and contains all required information to execute it -4. **Client** - The application or component which is aware of the Receiver, Invoker and Commands +Section | Video Links +-|- +Command Overview | Command Overview Command Overview Command Overview +Command Use Case | Command Use Case Command Use Case Command Use Case +Single Leading Underscore | Single Leading Underscore Single Leading Underscore Single Leading Underscore -Eg, a button, will call the Invoker, which will call a pre registered Commands execute method, which the Receiver will perform. +## Book -A Concrete Class will delegate a request to a command object, instead of implementing the request directly. -Using a command design pattern allows you to separate concerns a little easier and to solve problems of the concerns independently of each of the layers. -eg, logging the execution of a command and it's outcome. +Cover | Links +-|- +![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    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 -Uses: -GUI Buttons, menus -Macro recording -Multi level undo/redo -networking - send whole command objects across a network, even as a batch -parallel processing or thread pools, -transactional behaviour -Wizards +## Overview -Notes: -The receiver object should manages it's own state, not the command object -There can be one or more invokers which can execute the command at a later date. +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ -![The Command Pattern Overview](command_pattern.png) +## Terminology -The Command Pattern in the context of a light switch +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ -![The Command Pattern Switch](switch_command_pattern.png) +## Command Pattern UML Diagram -The Command Pattern in the context of a slider for a heater, which also implements UNDO/REDO +![The Command Pattern UML Diagram](/img/command_concept.svg) -![The Command Pattern Slider](slider_command.png) +## Source Code +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ +## Output + +``` bash +python ./command/command_concept.py +Executing Command 1 +Executing Command 2 +Executing Command 1 +Executing Command 2 +``` + +## Example Use Case + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Example UML Diagram + +![The Command Pattern UML Diagram](/img/command_example.svg) + +## Output + +``` bash +python ./command/client.py +Light turned ON +Light turned OFF +Light turned ON +Light turned OFF +11:23:35 : ON +11:23:35 : OFF +11:23:35 : ON +11:23:35 : OFF +Light turned ON +Light turned OFF +``` + +## New Coding Concepts + +### _Single Leading Underscore + +The single leading underscore **`_variable`**, on your class variables is a useful indicator to other developers that this property should be considered private. + +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. + +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. + +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. + +## Summary + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ \ No newline at end of file diff --git a/command/classes_command.dot b/command/classes_command.dot deleted file mode 100644 index 6f4c4ae..0000000 --- a/command/classes_command.dot +++ /dev/null @@ -1,26 +0,0 @@ -digraph "classes_switch_command" { -charset="utf-8" -rankdir=BT -{rank=same; Client, Invoker, ICommand} -"Command1" [label="{Command1|\l|execute()\l}", shape="record"]; -"Command2" [label="{Command2|\l|execute()\l}", shape="record"]; -"Reciever" [label="{Reciever|\l|do_command1()\ldo_command2()\l}", shape="record"]; -"ICommand" [label="{ICommand|\l|execute()\l}", shape="record"]; -"Invoker" [label="{Invoker|\l|register()\lexecute()\l}", shape="record"]; -"Client" [label="{Client|\l|\l}", shape="record"]; - - -"Client" -> "Invoker" [arrowhead="open", arrowtail="none", style="dashed"]; - -"Invoker" -> "ICommand" [arrowhead="open", arrowtail="none", style="dashed"]; - -"Command1" -> "Reciever" [arrowhead="open", arrowtail="none", style="dashed"]; -"Command2" -> "Reciever" [arrowhead="open", arrowtail="none", style="dashed"]; - -"Command1" -> "ICommand" [arrowhead="empty", arrowtail="none"]; -"Command2" -> "ICommand" [arrowhead="empty", arrowtail="none"]; - - - - -} diff --git a/command/classes_slider.dot b/command/classes_slider.dot deleted file mode 100644 index d52c681..0000000 --- a/command/classes_slider.dot +++ /dev/null @@ -1,23 +0,0 @@ -digraph "classes_slider" { -charset="utf-8" -rankdir=BT -{rank=same; Slider, ICommand, Heater} -"IUndoRedo" [label="{{IUndoRedo (Invoker)}|\l|history()\lundo()\lredo()\l}", shape="record"]; -"Slider" [label="{Slider (Invoker)|history\l|execute()\lregister()\lredo()\lundo()\l}", shape="record"]; -//"Slider" [label="{Slider (Invoker)|\l|execute()\lregister()\l}", shape="record"]; -"ICommand" [label="{ICommand (Command)|\l|execute()\l}", shape="record"]; -"Heater" [label="{Heater (Receiver)|\l|set_to_max()\lset_to_percent()\lturn_off()\l}", shape="record"]; - -"3" [label="{SliderMaxCommand|\l|execute()\l}", shape="record"]; -"4" [label="{SliderOffCommand|\l|execute()\l}", shape="record"]; -"5" [label="{SliderPercentCommand|\l|execute()\l}", shape="record"]; -"3" -> "ICommand" [arrowhead="empty", arrowtail="none"]; -"4" -> "ICommand" [arrowhead="empty", arrowtail="none"]; -"5" -> "ICommand" [arrowhead="empty", arrowtail="none"]; - -"Slider" -> "ICommand" [arrowhead="open", arrowtail="none", style="dashed"]; -"ICommand" -> "Heater" [arrowhead="open", arrowtail="none", style="dashed"]; - -"Slider" -> "IUndoRedo" [arrowhead="empty", arrowtail="none"]; - -} diff --git a/command/classes_switch_command.dot b/command/classes_switch_command.dot deleted file mode 100644 index 644f05b..0000000 --- a/command/classes_switch_command.dot +++ /dev/null @@ -1,26 +0,0 @@ -digraph "classes_switch_command" { -charset="utf-8" -rankdir=BT -{rank=same; Client, Invoker, ICommand} -"Command1" [label="{Switch ON|\l|execute()\l}", shape="record"]; -"Command2" [label="{Switch OFF|\l|execute()\l}", shape="record"]; -"Reciever" [label="{Light|\l|turn_off()\lturn_on()\l}", shape="record"]; -"ICommand" [label="{ICommand|\l|execute()\l}", shape="record"]; -"Invoker" [label="{Switch|\l|register()\lexecute()\l}", shape="record"]; -"Client" [label="{APP.py|\l|\l}", shape="record"]; - - -"Client" -> "Invoker" [arrowhead="open", arrowtail="none", style="dashed"]; - -"Invoker" -> "ICommand" [arrowhead="open", arrowtail="none", style="dashed"]; - -"Command1" -> "Reciever" [arrowhead="open", arrowtail="none", style="dashed"]; -"Command2" -> "Reciever" [arrowhead="open", arrowtail="none", style="dashed"]; - -"Command1" -> "ICommand" [arrowhead="empty", arrowtail="none"]; -"Command2" -> "ICommand" [arrowhead="empty", arrowtail="none"]; - - - - -} diff --git a/command/client.py b/command/client.py new file mode 100644 index 0000000..c4de602 --- /dev/null +++ b/command/client.py @@ -0,0 +1,29 @@ +"The Command Pattern Use Case Example. A smart light Switch" +from light import Light +from switch import Switch +from switch_on_command import SwitchOnCommand +from switch_off_command import SwitchOffCommand + +# Create a receiver +LIGHT = Light() + +# Create Commands +SWITCH_ON = SwitchOnCommand(LIGHT) +SWITCH_OFF = SwitchOffCommand(LIGHT) + +# Register the commands with the invoker +SWITCH = Switch() +SWITCH.register("ON", SWITCH_ON) +SWITCH.register("OFF", SWITCH_OFF) + +# Execute the commands that are registered on the Invoker +SWITCH.execute("ON") +SWITCH.execute("OFF") +SWITCH.execute("ON") +SWITCH.execute("OFF") + +# show history +SWITCH.show_history() + +# replay last two executed commands +SWITCH.replay_last(2) diff --git a/command/command_concept.py b/command/command_concept.py new file mode 100644 index 0000000..e16231b --- /dev/null +++ b/command/command_concept.py @@ -0,0 +1,84 @@ +# pylint: disable=arguments-differ +"The Command Pattern Concept" +from abc import ABCMeta, abstractmethod + + +class ICommand(metaclass=ABCMeta): # pylint: disable=too-few-public-methods + "The command interface, that all commands will implement" + @staticmethod + @abstractmethod + def execute(): + "The required execute method that all command objects will use" + + +class Invoker: + "The Invoker Class" + + def __init__(self): + self._commands = {} + + def register(self, command_name, command): + "Register commands in the Invoker" + self._commands[command_name] = command + + def execute(self, command_name): + "Execute any registered commands" + if command_name in self._commands.keys(): + self._commands[command_name].execute() + else: + print(f"Command [{command_name}] not recognised") + + +class Receiver: + "The Receiver" + + @staticmethod + def run_command_1(): + "A set of instructions to run" + print("Executing Command 1") + + @staticmethod + def run_command_2(): + "A set of instructions to run" + print("Executing Command 2") + + +class Command1(ICommand): # pylint: disable=too-few-public-methods + """A Command object, that implements the ICommand interface and + runs the command on the designated receiver""" + + def __init__(self, receiver): + self._receiver = receiver + + def execute(self): + self._receiver.run_command_1() + + +class Command2(ICommand): # pylint: disable=too-few-public-methods + """A Command object, that implements the ICommand interface and + runs the command on the designated receiver""" + + def __init__(self, receiver): + self._receiver = receiver + + def execute(self): + self._receiver.run_command_2() + + +# Create a receiver +RECEIVER = Receiver() + +# Create Commands +COMMAND1 = Command1(RECEIVER) +COMMAND2 = Command2(RECEIVER) + +# Register the commands with the invoker +INVOKER = Invoker() +INVOKER.register("1", COMMAND1) +INVOKER.register("2", COMMAND2) + +# Execute the commands that are registered on the Invoker +INVOKER.execute("1") +INVOKER.execute("2") +INVOKER.execute("1") +INVOKER.execute("2") diff --git a/command/command_direct.dot b/command/command_direct.dot deleted file mode 100644 index 50f6248..0000000 --- a/command/command_direct.dot +++ /dev/null @@ -1,11 +0,0 @@ -digraph "classes_switch_command" { -charset="utf-8" -rankdir=BT -"Receiver" [label="{Reciever|\l|do_command()\l}", shape="record"]; -"Client" [label="{Client|\l|\l}", shape="record"]; - - -"Client" -> "Receiver" [arrowhead="open", arrowtail="none", style="dashed"]; - - -} diff --git a/command/command_direct.png b/command/command_direct.png deleted file mode 100644 index 10f0aa0..0000000 Binary files a/command/command_direct.png and /dev/null differ diff --git a/command/command_pattern.png b/command/command_pattern.png deleted file mode 100644 index 214d14c..0000000 Binary files a/command/command_pattern.png and /dev/null differ diff --git a/command/interface_switch.py b/command/interface_switch.py new file mode 100644 index 0000000..9ce9126 --- /dev/null +++ b/command/interface_switch.py @@ -0,0 +1,11 @@ +"The switch interface, that all commands will implement" +from abc import ABCMeta, abstractmethod + + +class ISwitch(metaclass=ABCMeta): # pylint: disable=too-few-public-methods + "The switch interface, that all commands will implement" + + @staticmethod + @abstractmethod + def execute(): + "The required execute method that all command objects will use" diff --git a/command/light.py b/command/light.py new file mode 100644 index 0000000..ea70514 --- /dev/null +++ b/command/light.py @@ -0,0 +1,15 @@ +"The Light. The Receiver" + + +class Light: + "The Receiver" + + @staticmethod + def turn_on(): + "A set of instructions to run" + print("Light turned ON") + + @staticmethod + def turn_off(): + "A set of instructions to run" + print("Light turned OFF") diff --git a/command/slider_command.png b/command/slider_command.png deleted file mode 100644 index c5646f0..0000000 Binary files a/command/slider_command.png and /dev/null differ diff --git a/command/slider_command.py b/command/slider_command.py deleted file mode 100644 index 5b35d6a..0000000 --- a/command/slider_command.py +++ /dev/null @@ -1,180 +0,0 @@ -"""The Command Design Pattern in Python -The command pattern is a behavioural design pattern, in which an abstraction -exists between an object that invokes a command, and the object that performs it. - -This is part 2 of the Command Design Pattern tutorial, -where I create a slider, instead of the switch from part 1. -The slider also accepts a variable percentage, rather than an ON/OFF -The history also records the variable settingg -And I also add UNDO/REDO to the Invoker so that you can go backawards and forwards through time - -""" - -from abc import ABCMeta, abstractstaticmethod -import time - - -class ICommand(metaclass=ABCMeta): - """The command interface, which all commands will implement""" - - @abstractstaticmethod - def execute(*args): - """The required execute method which all command objects will use""" - - -class IUndoRedo(metaclass=ABCMeta): - """The Undo Redo interface""" - @abstractstaticmethod - def history(): - """the history of the states""" - - @abstractstaticmethod - def undo(): - """for undoing the hsitory of the states""" - - @abstractstaticmethod - def redo(): - """for redoing the hsitory of the states""" - - -class Slider(IUndoRedo): - """The Invoker Class""" - - def __init__(self): - self._commands = {} - self._history = [(0.0, "OFF", ())] # A default setting of OFF - self._history_position = 0 # The position that is used for UNDO/REDO - - @property - def history(self): - """Return all records in the History list""" - return self._history - - def register(self, command_name, command): - """All commands are registered in the Invoker Class""" - self._commands[command_name] = command - - def execute(self, command_name, *args): - """Execute a pre defined command and log in history""" - if command_name in self._commands.keys(): - self._history_position += 1 - self._commands[command_name].execute(args) - if len(self._history) == self._history_position: - # This is a new event in hisory - self._history.append((time.time(), command_name, args)) - else: - # This occurs if there was one of more UNDOs and then a new - # execute command happened. In case of UNDO, the history_position - # changes, and executing new commands purges any history after - # the current position""" - self._history = self._history[:self._history_position+1] - self._history[self._history_position] = { - time.time(): [command_name, args] - } - else: - print(f"Command [{command_name}] not recognised") - - def undo(self): - """Undo a command if there is a command that can be undone. - Update the history position so that further UNDOs or REDOs - point to the correct index""" - if self._history_position > 0: - self._history_position -= 1 - self._commands[ - self._history[self._history_position][1] - ].execute(self._history[self._history_position][2]) - else: - print("nothing to undo") - - def redo(self): - """Perform a REDO if the history_position is less than the end of the history list""" - if self._history_position + 1 < len(self._history): - self._history_position += 1 - self._commands[ - self._history[self._history_position][1] - ].execute(self._history[self._history_position][2]) - else: - print("nothing to REDO") - - -class Heater: - """The Receiver""" - - def set_to_max(self): - print("Heater is ON and set to MAX (100%)") - - def set_to_percent(self, *args): - print(f"Heater is ON and set to {args[0][0]}%") - - def turn_off(self): - print("Heater is OFF") - - -class SliderMaxCommand(ICommand): - """A Command object, which implements the ICommand interface""" - - def __init__(self, heater): - self._heater = heater - - def execute(self, *args): - self._heater.set_to_max() - - -class SliderPercentCommand(ICommand): - """A Command object, which implements the ICommand interface""" - - def __init__(self, heater): - self._heater = heater - - def execute(self, *args): - self._heater.set_to_percent(args[0]) - - -class SliderOffCommand(ICommand): - """A Command object, which implements the ICommand interface""" - - def __init__(self, heater): - self._heater = heater - - def execute(self, *args): - self._heater.turn_off() - - -if __name__ == "__main__": - # The Client is the main python app - - # The HEATER is the Receiver - HEATER = Heater() - - # Create Commands - SLIDER_MAX = SliderMaxCommand(HEATER) - SLIDER_PERCENT = SliderPercentCommand(HEATER) - SLIDER_OFF = SliderOffCommand(HEATER) - - # Register the commands with the invoker (Switch) - SLIDER = Slider() - SLIDER.register("MAX", SLIDER_MAX) - SLIDER.register("PERCENT", SLIDER_PERCENT) - SLIDER.register("OFF", SLIDER_OFF) - - # Execute the commands that are registered on the Invoker - SLIDER.execute("PERCENT", 10) - SLIDER.execute("PERCENT", 20) - SLIDER.execute("PERCENT", 30) - SLIDER.execute("PERCENT", 40) - SLIDER.execute("PERCENT", 50) - print(SLIDER.history) - SLIDER.undo() - SLIDER.undo() - SLIDER.undo() - SLIDER.redo() - SLIDER.undo() - SLIDER.undo() - SLIDER.execute("PERCENT", 90) - SLIDER.execute("MAX") - SLIDER.execute("OFF") - print(SLIDER.history) - SLIDER.undo() - SLIDER.redo() - - print(SLIDER.history) diff --git a/command/switch.py b/command/switch.py new file mode 100644 index 0000000..8b3a5fa --- /dev/null +++ b/command/switch.py @@ -0,0 +1,42 @@ +""" +The Switch (Invoker) Class. +You can flick the switch and it then invokes a registered command +""" +from datetime import datetime +import time + + +class Switch: + "The Invoker Class." + + def __init__(self): + self._commands = {} + self._history = [] + + def show_history(self): + "Print the history of each time a command was invoked" + for row in self._history: + print( + f"{datetime.fromtimestamp(row[0]).strftime('%H:%M:%S')}" + f" : {row[1]}" + ) + + def register(self, command_name, command): + "Register commands in the Invoker" + self._commands[command_name] = command + + def execute(self, command_name): + "Execute any registered commands" + if command_name in self._commands.keys(): + self._commands[command_name].execute() + self._history.append((time.time(), command_name)) + else: + print(f"Command [{command_name}] not recognised") + + def replay_last(self, number_of_commands): + "Replay the last N commands" + commands = self._history[-number_of_commands:] + for command in commands: + self._commands[command[1]].execute() + #or if you want to record these replays in history + #self.execute(command[1]) diff --git a/command/switch_command.py b/command/switch_command.py deleted file mode 100644 index 4e5de0a..0000000 --- a/command/switch_command.py +++ /dev/null @@ -1,122 +0,0 @@ -"""The Command Design Pattern in Python -The command pattern is a behavioural design pattern, in which an abstraction -exists between an object that invokes a command, and the object that performs it. - -The components if the Command Design Pattern are, -The Receiver - The Object that will receive and execute the command -The Invoker - Which will send the command to the receiver -The Command Object - Itself, which implements an execute, or action method, -and contains all required information or module which is aware of -the Reciever, Invoker and Commands - -Eg, a button, will call the Invoker, which will call a pre registered Commands excute method, -which will perform the action on the Reciever. - -A Concrete Class will delegate a request to a command object, instead of -implementing the request directly. -Using a command design pattern allows you to seperate concerns a little easier and to -solve problems of the concenrs independantly of each of the layers. -eg, logging the execution of a command and it's outcome. - -Uses: -GUI Buttons, menus -Macro recording -Multi level undo/redo -networking - send whole command objects across a network, even as a batch -parallel processing or thread pools, -transactional behaviour -Wizards - -Notes: -The receiver object should manages it's own state, not the command object -There can be one or more invokers which can execute the command at a later date. - -""" - -from abc import ABCMeta, abstractstaticmethod -import time - - -class ICommand(metaclass=ABCMeta): - """The command interface, which all commands will implement""" - - @abstractstaticmethod - def execute(): - """The required execute method which all command obejcts will use""" - - -class Switch: - """The Invoker Class""" - - def __init__(self): - self._commands = {} - self._history = [] - - @property - def history(self): - return self._history - - def register(self, command_name, command): - self._commands[command_name] = command - - def execute(self, command_name): - if command_name in self._commands.keys(): - self._history.append((time.time(), command_name)) - self._commands[command_name].execute() - else: - print(f"Command [{command_name}] not recognised") - - -class Light: - """The Reciever""" - - def turn_on(self): - print("Light turned ON") - - def turn_off(self): - print("Light turned OFF") - - -class SwitchOnCommand(ICommand): - """A Command object, which implemets the ICommand interface""" - - def __init__(self, light): - self._light = light - - def execute(self): - self._light.turn_on() - - -class SwitchOffCommand(ICommand): - """A Command object, which implemets the ICommand interface""" - - def __init__(self, light): - self._light = light - - def execute(self): - self._light.turn_off() - - -if __name__ == "__main__": - # The Client is the main python app - - # The Light is the Reciever - LIGHT = Light() - - # Create Commands - SWITCH_ON = SwitchOnCommand(LIGHT) - SWITCH_OFF = SwitchOffCommand(LIGHT) - - # Register the commands with the invoker (Switch) - SWITCH = Switch() - SWITCH.register("ON", SWITCH_ON) - SWITCH.register("OFF", SWITCH_OFF) - - # Execute the commands that are registered on the Invoker - SWITCH.execute("ON") - SWITCH.execute("OFF") - SWITCH.execute("ON") - SWITCH.execute("OFF") - - # For fun, we can see the history - print(SWITCH.history) diff --git a/command/switch_command_pattern.png b/command/switch_command_pattern.png deleted file mode 100644 index c5287f3..0000000 Binary files a/command/switch_command_pattern.png and /dev/null differ diff --git a/command/switch_off_command.py b/command/switch_off_command.py new file mode 100644 index 0000000..a496262 --- /dev/null +++ b/command/switch_off_command.py @@ -0,0 +1,15 @@ +""" +A Command object, that implements the ISwitch interface and runs the +command on the designated receiver +""" +from interface_switch import ISwitch + + +class SwitchOffCommand(ISwitch): # pylint: disable=too-few-public-methods + "Switch Off Command" + + def __init__(self, light): + self._light = light + + def execute(self): + self._light.turn_off() diff --git a/command/switch_on_command.py b/command/switch_on_command.py new file mode 100644 index 0000000..be9255a --- /dev/null +++ b/command/switch_on_command.py @@ -0,0 +1,15 @@ +""" +A Command object, that implements the ISwitch interface and runs the +command on the designated receiver +""" +from interface_switch import ISwitch + + +class SwitchOnCommand(ISwitch): # pylint: disable=too-few-public-methods + "Switch On Command" + + def __init__(self, light): + self._light = light + + def execute(self): + self._light.turn_on() diff --git a/composite/README.md b/composite/README.md index 1f0a41e..7a9f7b0 100644 --- a/composite/README.md +++ b/composite/README.md @@ -1,16 +1,127 @@ # Composite Design Pattern -The Composite design pattern, -- allows you to represent individual entities and groups of entities in the same manner. -- is a structural design pattern that lets you compose objects into a tree. -- is great if you need the option of swapping hierarchal relationships around. -- makes it easier for you to add new kinds of components -- provides flexibility of structure -- conform to the Single Responsibility Principle in the way that it separates the aggregation of objects from the features of the object. +## Videos -Examples of using the Composite Design Pattern can be seen in a filesystem directory structure, where you can swap the hierarchy of folders, and in a drawing program where you can group, un-group and transform objects, and multiple objects at the same time. +Section | Video Links +-|- +Composite Overview | Composite Overview Composite Overview Composite Overview +Composite Use Case | Composite Use Case Composite Use Case Composite Use Case +Conditional Expressions | Conditional Expressions Conditional Expressions Conditional Expressions -![Composite Pattern UML Diagram](composite.png) +## Book +Cover | Links +-|- +![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    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 +## Overview +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Terminology + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Composite UML Diagram + +![Composite Pattern UML Diagram](/img/composite_concept.svg) + +## Source Code + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Output + +``` bash +python ./composite/composite_concept.py + +LEAF_A id:2050574298848 +LEAF_B id:2050574298656 +COMPOSITE_1 id:2050574298272 +COMPOSITE_2 id:2050574298128 + + id:2050574298656 Parent: None + id:2050574298128 Parent: None Components:2 + id:2050574298848 Parent: 2050574298128 + id:2050574298272 Parent: 2050574298128 Components:0 +``` + +## Composite Example Use Case + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Composite Example UML Diagram + +![Composite Pattern Use Case UML Diagram](/img/composite_example.svg) + +## Output + +``` bash +python ./composite/client.py + root id:2028913323984 Components: 4 +.. abc.txt id:2028913323888 Parent: 2028913323984 +.. 123.txt id:2028913323792 Parent: 2028913323984 +.. folder_a id:2028913432848 Components: 1 +.... xyz.txt id:2028913433088 Parent: 2028913432848 +.. folder_b id:2028913433184 Components: 1 +.... 456.txt id:2028913434432 Parent: 2028913433184 + + root id:2028913323984 Components: 3 +.. abc.txt id:2028913323888 Parent: 2028913323984 +.. 123.txt id:2028913323792 Parent: 2028913323984 +.. folder_b id:2028913433184 Components: 2 +.... 456.txt id:2028913434432 Parent: 2028913433184 +.... folder_a id:2028913432848 Components: 1 +...... xyz.txt id:2028913433088 Parent: 2028913432848 +``` + +## New Coding Concepts + +### Conditional Expressions (Ternary Operators). + +In [/composite/composite_concept.py](/composite/composite_concept.py), there are two conditional expressions. + +Conditional expressions an alternate form of `if/else` statement. + +``` python +id(self.reference_to_parent) if self.reference_to_parent is not None else None +``` + +If the `self.reference_to_parent` is not `None`, it will return the memory address (id) of `self.reference_to_parent`, otherwise it returns `None`. + +This conditional expression follows the format + +``` python +value_if_true if condition else value_if_false +``` + +eg, + +``` python +SUN = "bright" +SUN_IS_BRIGHT = True if SUN == "bright" else False +print(SUN_IS_BRIGHT) +``` + +or + +``` python +ICE_IS_COLD = True +ICE_TEMPERATURE = "cold" if ICE_IS_COLD == True else "hot" +print(ICE_TEMPERATURE) +``` + +or + +``` python +CURRENT_VALUE = 99 +DANGER = 100 +ALERTING = True if CURRENT_VALUE >= DANGER else False +print(ALERTING) +``` + +Visit [https://docs.python.org/3/reference/expressions.html#conditional-expressions](https://docs.python.org/3/reference/expressions.html#conditional-expressions) for more examples of conditional expressions. + +## Summary + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ \ No newline at end of file diff --git a/composite/classes.dot b/composite/classes.dot deleted file mode 100644 index d886b4b..0000000 --- a/composite/classes.dot +++ /dev/null @@ -1,9 +0,0 @@ -digraph "classes" { -charset="utf-8" -rankdir=BT -"0" [label="{Composite|entities : list\l|add()\lfunc()\l}", shape="record"]; -"1" [label="{Entity|\l|func()\l}", shape="record"]; -"2" [label="{IEntity|\l|func()\l}", shape="record"]; -"0" -> "2" [arrowhead="empty", arrowtail="none"]; -"1" -> "2" [arrowhead="empty", arrowtail="none"]; -} diff --git a/composite/client.py b/composite/client.py new file mode 100644 index 0000000..009c513 --- /dev/null +++ b/composite/client.py @@ -0,0 +1,24 @@ +"A use case of the composite pattern." + +from folder import Folder +from file import File + +FILESYSTEM = Folder("root") +FILE_1 = File("abc.txt") +FILE_2 = File("123.txt") +FILESYSTEM.attach(FILE_1) +FILESYSTEM.attach(FILE_2) +FOLDER_A = Folder("folder_a") +FILESYSTEM.attach(FOLDER_A) +FILE_3 = File("xyz.txt") +FOLDER_A.attach(FILE_3) +FOLDER_B = Folder("folder_b") +FILE_4 = File("456.txt") +FOLDER_B.attach(FILE_4) +FILESYSTEM.attach(FOLDER_B) +FILESYSTEM.dir() + +# now move FOLDER_A and its contents to FOLDER_B +print() +FOLDER_B.attach(FOLDER_A) +FILESYSTEM.dir() diff --git a/composite/composite.png b/composite/composite.png deleted file mode 100644 index 7f3cd84..0000000 Binary files a/composite/composite.png and /dev/null differ diff --git a/composite/composite.py b/composite/composite.py deleted file mode 100644 index 3980b2f..0000000 --- a/composite/composite.py +++ /dev/null @@ -1,47 +0,0 @@ -from abc import ABCMeta, abstractmethod - -class IGraphic(metaclass=ABCMeta): - @staticmethod - @abstractmethod - def print(): - """print information""" - -class Ellipse(IGraphic): - def print(self): - print("Ellipse") - -class Circle(IGraphic): - def print(self): - print("Circle") - -class CompositeGraphic(IGraphic): - def __init__(self): - self.child_graphics = [] - - def add(self, graphic): - self.child_graphics.append(graphic) - - def print(self): - for g in self.child_graphics: - g.print() - - -ELLIPSE1 = Ellipse() -CIRCLE1 = Circle() - -COMPOSITE1 = CompositeGraphic() -COMPOSITE1.add(ELLIPSE1) - -COMPOSITE2 = CompositeGraphic() -COMPOSITE2.add(CIRCLE1) -COMPOSITE2.add(COMPOSITE1) - -COMPOSITE2.print() - - - -# ELLIPSE1.print() -# CIRCLE1.print() - - - diff --git a/composite/composite_concept.py b/composite/composite_concept.py new file mode 100644 index 0000000..b61280e --- /dev/null +++ b/composite/composite_concept.py @@ -0,0 +1,99 @@ +# pylint: disable=arguments-differ +"The Composite pattern concept" +from abc import ABCMeta, abstractmethod + + +class IComponent(metaclass=ABCMeta): + """ + A component interface describing the common + fields and methods of leaves and composites + """ + + reference_to_parent = None + + @staticmethod + @abstractmethod + def method(): + "A method each Leaf and composite container should implement" + + @staticmethod + @abstractmethod + def detach(): + "Called before a leaf is attached to a composite" + + +class Leaf(IComponent): + "A Leaf can be added to a composite, but not a leaf" + + def method(self): + parent_id = (id(self.reference_to_parent) + if self.reference_to_parent is not None else None) + print( + f"\t\tid:{id(self)}\tParent:\t{parent_id}" + ) + + def detach(self): + "Detaching this leaf from its parent composite" + if self.reference_to_parent is not None: + self.reference_to_parent.delete(self) + + +class Composite(IComponent): + "A composite can contain leaves and composites" + + def __init__(self): + self.components = [] + + def method(self): + parent_id = (id(self.reference_to_parent) + if self.reference_to_parent is not None else None) + print( + f"\tid:{id(self)}\tParent:\t{parent_id}\t" + f"Components:{len(self.components)}") + + for component in self.components: + component.method() + + def attach(self, component): + """ + Detach leaf/composite from any current parent reference and + then set the parent reference to this composite (self) + """ + component.detach() + component.reference_to_parent = self + self.components.append(component) + + def delete(self, component): + "Removes leaf/composite from this composite self.components" + self.components.remove(component) + + def detach(self): + "Detaching this composite from its parent composite" + if self.reference_to_parent is not None: + self.reference_to_parent.delete(self) + self.reference_to_parent = None + + +# The Client +LEAF_A = Leaf() +LEAF_B = Leaf() +COMPOSITE_1 = Composite() +COMPOSITE_2 = Composite() + +print(f"LEAF_A\t\tid:{id(LEAF_A)}") +print(f"LEAF_B\t\tid:{id(LEAF_B)}") +print(f"COMPOSITE_1\tid:{id(COMPOSITE_1)}") +print(f"COMPOSITE_2\tid:{id(COMPOSITE_2)}") + +# Attach LEAF_A to COMPOSITE_1 +COMPOSITE_1.attach(LEAF_A) + +# Instead, attach LEAF_A to COMPOSITE_2 +COMPOSITE_2.attach(LEAF_A) + +# Attach COMPOSITE1 to COMPOSITE_2 +COMPOSITE_2.attach(COMPOSITE_1) + +print() +LEAF_B.method() # not in any composites +COMPOSITE_2.method() # COMPOSITE_2 contains both COMPOSITE_1 and LEAF_A diff --git a/composite/file.py b/composite/file.py new file mode 100644 index 0000000..19aa84e --- /dev/null +++ b/composite/file.py @@ -0,0 +1,22 @@ +"A File class" +from interface_component import IComponent + + +class File(IComponent): + "The File Class. The files are leaves" + + def __init__(self, name): + self.name = name + + def dir(self, indent): + parent_id = (id(self.reference_to_parent) + if self.reference_to_parent is not None else None) + print( + f"{indent} {self.name}\t\t" + f"id:{id(self)}\tParent:\t{parent_id}" + ) + + def detach(self): + "Detaching this file (leaf) from its parent composite" + if self.reference_to_parent is not None: + self.reference_to_parent.delete(self) diff --git a/composite/folder.py b/composite/folder.py new file mode 100644 index 0000000..8da5393 --- /dev/null +++ b/composite/folder.py @@ -0,0 +1,39 @@ +"A Folder, that acts as a composite." +from interface_component import IComponent + + +class Folder(IComponent): + "The Folder class can contain other folders and files" + + def __init__(self, name): + self.name = name + self.components = [] + + def dir(self, indent=""): + print( + f"{indent} {self.name}\t\tid:{id(self)}\t" + f"Components: {len(self.components)}") + for component in self.components: + component.dir(indent + "..") + + def attach(self, component): + """ + Detach file/folder from any current parent reference + and then set the parent reference to this folder + """ + component.detach() + component.reference_to_parent = self + self.components.append(component) + + def delete(self, component): + """ + Removes file/folder from this folder so that self.components" + is cleaned + """ + self.components.remove(component) + + def detach(self): + "Detaching this folder from its parent folder" + if self.reference_to_parent is not None: + self.reference_to_parent.delete(self) + self.reference_to_parent = None diff --git a/composite/interface_component.py b/composite/interface_component.py new file mode 100644 index 0000000..73ecfc1 --- /dev/null +++ b/composite/interface_component.py @@ -0,0 +1,24 @@ +""" +A component interface describing the common +fields and methods of leaves and composites +""" +from abc import ABCMeta, abstractmethod + + +class IComponent(metaclass=ABCMeta): + "The Component Interface" + + reference_to_parent = None + + @staticmethod + @abstractmethod + def dir(indent): + "A method each Leaf and composite container should implement" + + @staticmethod + @abstractmethod + def detach(): + """ + Called before a leaf is attached to a composite + so that it can clean any parent references + """ diff --git a/decorator/Decorator Design Pattern.md b/decorator/Decorator Design Pattern.md deleted file mode 100644 index 9939778..0000000 --- a/decorator/Decorator Design Pattern.md +++ /dev/null @@ -1,9 +0,0 @@ -## Decorator Design Pattern - -The decorator pattern is a structural pattern, that allows you to attach additional responsibilities to an object at run time. - -The decorator pattern is used in both the Object Oriented and Functional paradigms. - -The decorator pattern is different than the Python language feature of Python Decorators in it's syntax, but the application of it is the same, in the way that it is essentially a wrapper. - -The Decorator pattern adds extensibility, without modifying the original function. \ No newline at end of file diff --git a/decorator/Decorator Design Pattern.pdf b/decorator/Decorator Design Pattern.pdf deleted file mode 100644 index a99c85d..0000000 Binary files a/decorator/Decorator Design Pattern.pdf and /dev/null differ diff --git a/decorator/README.md b/decorator/README.md index 6176ce4..31c7f86 100644 --- a/decorator/README.md +++ b/decorator/README.md @@ -1,11 +1,136 @@ -## Decorator Design Pattern +# Decorator Design Pattern -The decorator pattern is a structural pattern, that allows you to attach additional responsibilities to an object at run time. +## Videos -The decorator pattern is used in both the Object Oriented and Functional paradigms. +Section | Video Links +-|- +Decorator Overview | Decorator Overview Decorator Overview Decorator Overview +Decorator Use Case | Decorator Use Case Decorator Use Case Decorator Use Case +**\__str\__** Dunder Method| __str__ Dunder Method __str__ Dunder Method __str__ Dunder Method +Python **getattr()** Method | getattr() Method getattr() Method getattr() Method -The decorator pattern is different than the Python language feature of Python Decorators in it's syntax, but the application of it is the same, in the way that it is essentially a wrapper. +## Book -The Decorator pattern adds extensibility, without modifying the original function. +Cover | Links +-|- +![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    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 -![Decorator Pattern UML Diagram](decorator.png) \ No newline at end of file +## Overview + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Terminology + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Builder UML Diagram + +![Decorator Pattern UML Diagram](/img/decorator_concept.svg) + +## Output + +``` bash +python ./decorator/decorator_concept.py +Component Method +Decorator Method(Component Method) +``` + +## Example Use Case + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Example UML Diagram + +![Decorator Pattern in Context](/img/decorator_example.svg) + +## Output + +``` bash +python ./decorator/client.py +3 +101 +4 +6 +-1 +106 +113 +114 +1 +2 +5 +``` + +## New Coding Concepts + +### Python `getattr()` Function + +Syntax: `getattr(object, attribute, default)` + +In the `Sub` and `Add` classes, I use the `getattr()` method like a ternary operator. + +When initializing the `Add` or `Sub` classes, you have the option of providing an integer or an existing instance of the `Value`, `Sub` or `Add` classes. + +So, for example, the line in the `Sub` class, + +``` python + +val1 = getattr(val1, 'value', val1) +``` + +is saying, if the `val1` just passed into the function already has an attribute `value`, then `val1` must be an object of `Value`, `Sub` or `Add` . Otherwise, the `val1` that was passed in is a new integer and it will use that instead to calculate the final value of the instance on the next few lines of code. This behavior allows the `Sub` and `Add` classes to be used recursively. + +E.g., + +``` python +A = Value(2) +Add(Sub(Add(200, 15), A), 100) +``` + +### Dunder `__str__` method + +When you `print()` an object, it will print out the objects type and memory location in hex. + +``` python +class ExampleClass: + abc = 123 + +print(ExampleClass()) +``` + +Outputs + +``` text +<__main__.ExampleClass object at 0x00000283038B1D00> +``` + +You can change this default output by implementing the `__str__` dunder method in your class. Dunder is short for saying double underscore. + +Dunder methods are predefined methods in python that you can override with your own implementations. + +``` python +class ExampleClass: + abc = 123 + + def __str__(self): + return "Something different" + +print(ExampleClass()) +``` + +Now outputs + +``` text +Something different +``` + +In all the classes in the above use case example that implement the `IValue` interface, the `__str__` method is overridden to return a string version of the integer value. This allows to print the numerical value of any object that implements the `IValue` interface rather than printing a string that resembles something like below. + +``` bash +<__main__.ValueClass object at 0x00000283038B1D00> +``` + +The `__str__` dunder was also overridden in the [/prototype/prototype_concept.py](/prototype/prototype_concept.py) concept code. + +## Summary + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ \ No newline at end of file diff --git a/decorator/add.py b/decorator/add.py new file mode 100644 index 0000000..2a74d97 --- /dev/null +++ b/decorator/add.py @@ -0,0 +1,17 @@ +# pylint: disable=too-few-public-methods +"The Add Decorator" +from interface_value import IValue + + +class Add(IValue): + "A Decorator that Adds a number to a number" + + def __init__(self, val1, val2): + # val1 and val2 can be int or the custom Value + # object that contains the `value` attribute + val1 = getattr(val1, 'value', val1) + val2 = getattr(val2, 'value', val2) + self.value = val1 + val2 + + def __str__(self): + return str(self.value) diff --git a/decorator/client.py b/decorator/client.py new file mode 100644 index 0000000..4b0807b --- /dev/null +++ b/decorator/client.py @@ -0,0 +1,20 @@ +"Decorator Use Case Example Code" +from value import Value +from add import Add +from sub import Sub + +A = Value(1) +B = Value(2) +C = Value(5) + +print(Add(A, B)) +print(Add(A, 100)) +print(Sub(C, A)) +print(Sub(Add(C, B), A)) +print(Sub(100, 101)) +print(Add(Sub(Add(C, B), A), 100)) +print(Sub(123, Add(C, C))) +print(Add(Sub(Add(C, 10), A), 100)) +print(A) +print(B) +print(C) diff --git a/decorator/decorator.dot b/decorator/decorator.dot deleted file mode 100644 index a7e32a2..0000000 --- a/decorator/decorator.dot +++ /dev/null @@ -1,9 +0,0 @@ -digraph "classes" { -charset="utf-8" -rankdir=BT -"0" [label="{UndecoratedObject|\l|get()\l}", shape="record"]; -"1" [label="{Decorate|decorated\l|get()\l}", shape="record"]; -"2" [label="{DecorateWithANewMethod|decorated\l|draw()\lget()\l}", shape="record"]; - - -} diff --git a/decorator/decorator.png b/decorator/decorator.png deleted file mode 100644 index 3be9c01..0000000 Binary files a/decorator/decorator.png and /dev/null differ diff --git a/decorator/decorator.py b/decorator/decorator.py deleted file mode 100644 index 03682d8..0000000 --- a/decorator/decorator.py +++ /dev/null @@ -1,35 +0,0 @@ -""" -Decorator Design Pattern -""" - -class UndecoratedObject: - @staticmethod - def get(): - return "UndecoratedObject" - - -class Decorate: - def __init__(self, undecorated): - self.undecorated = undecorated - - def get(self): - return self.undecorated.get().replace("Undecorated", "Decorated") - - -# class DecorateWithANewMethod: -# def __init__(self, undecorated): -# self.undecorated = undecorated - -# def get(self): -# return self.undecorated.get() - -# def draw(self): -# print(self.undecorated.get()) - - -UNDECORATED = UndecoratedObject() -print(UNDECORATED.get()) -DECORATED = Decorate(UNDECORATED) -print(DECORATED.get()) -#DECORATEDWITHNEWMETHOD = DecorateWithANewMethod(DECORATED) -#DECORATEDWITHNEWMETHOD.draw() diff --git a/decorator/decorator_concept.py b/decorator/decorator_concept.py new file mode 100644 index 0000000..617add5 --- /dev/null +++ b/decorator/decorator_concept.py @@ -0,0 +1,38 @@ +# pylint: disable=too-few-public-methods +# pylint: disable=arguments-differ +"Decorator Concept Sample Code" +from abc import ABCMeta, abstractmethod + + +class IComponent(metaclass=ABCMeta): + "Methods the component must implement" + @staticmethod + @abstractmethod + def method(): + "A method to implement" + + +class Component(IComponent): + "A component that can be decorated or not" + + def method(self): + "An example method" + return "Component Method" + + +class Decorator(IComponent): + "The Decorator also implements the IComponent" + + def __init__(self, obj): + "Set a reference to the decorated object" + self.object = obj + + def method(self): + "A method to implement" + return f"Decorator Method({self.object.method()})" + + +# The Client +COMPONENT = Component() +print(COMPONENT.method()) +print(Decorator(COMPONENT).method()) diff --git a/decorator/interface_value.py b/decorator/interface_value.py new file mode 100644 index 0000000..ae372d5 --- /dev/null +++ b/decorator/interface_value.py @@ -0,0 +1,11 @@ +# pylint: disable=too-few-public-methods +"The Interface that Value should implement" +from abc import ABCMeta, abstractmethod + + +class IValue(metaclass=ABCMeta): + "Methods the component must implement" + @staticmethod + @abstractmethod + def __str__(): + "Override the object to return the value attribute by default" diff --git a/decorator/sub.py b/decorator/sub.py new file mode 100644 index 0000000..36eb186 --- /dev/null +++ b/decorator/sub.py @@ -0,0 +1,17 @@ +# pylint: disable=too-few-public-methods +"The Subtract Decorator" +from interface_value import IValue + + +class Sub(IValue): + "A Decorator that subtracts a number from a number" + + def __init__(self, val1, val2): + # val1 and val2 can be int or the custom Value + # object that contains the `value` attribute + val1 = getattr(val1, 'value', val1) + val2 = getattr(val2, 'value', val2) + self.value = val1 - val2 + + def __str__(self): + return str(self.value) diff --git a/decorator/value.py b/decorator/value.py new file mode 100644 index 0000000..c330146 --- /dev/null +++ b/decorator/value.py @@ -0,0 +1,13 @@ +# pylint: disable=too-few-public-methods +"The Custom Value class" +from interface_value import IValue + + +class Value(IValue): + "A component that can be decorated or not" + + def __init__(self, value): + self.value = value + + def __str__(self): + return str(self.value) diff --git a/docs/CNAME b/docs/CNAME deleted file mode 100644 index b6f0086..0000000 --- a/docs/CNAME +++ /dev/null @@ -1 +0,0 @@ -designpatterns.seanwasere.com \ No newline at end of file diff --git a/docs/abstract_factory.md b/docs/abstract_factory.md deleted file mode 100644 index 2687321..0000000 --- a/docs/abstract_factory.md +++ /dev/null @@ -1,206 +0,0 @@ -# Abstract Factory Design Pattern - -## Video Lecture - -Skillshare : [https://skl.sh/34SM2Xg](https://skl.sh/34SM2Xg "Abstract Factory Design Pattern") - -Udemy : [Abstract Factory Design Pattern](https://www.udemy.com/course/design-patterns-in-python/learn/lecture/16396782/?referralCode=7B677DD7A9580F2FFD8F "Abstract Factory Design Pattern") - -## Description - -The Abstract Factory Pattern adds an abstract layer over multiple factory method implementations. - -The Abstract Factory contains or composites one or more than one factory method - -![Abstract Factory Overview](abstract_factory.png) - -Abstract Factory in the context of a Furniture factory ![Abstract Factory in context](abstract_factory_furniture.png) - -## Source Code - -### **`chair_factory.py`** -```python -from abc import ABCMeta, abstractstaticmethod - - -class IChair(metaclass=ABCMeta): # pylint: disable=too-few-public-methods - """The Chair Interface""" - - @abstractstaticmethod - def dimensions(): - """A static inteface method""" - - -class BigChair(IChair): # pylint: disable=too-few-public-methods - """The Big Chair Concrete Class which implements the IChair interface""" - - def __init__(self): - self._height = 80 - self._width = 80 - self._depth = 80 - - def dimensions(self): - return {"width": self._width, "depth": self._depth, "height": self._height} - - -class MediumChair(IChair): # pylint: disable=too-few-public-methods - """The Medium Chair Concrete Class which implements the IChair interface""" - - def __init__(self): - self._height = 60 - self._width = 60 - self._depth = 60 - - def dimensions(self): - return {"width": self._width, "depth": self._depth, "height": self._height} - - -class SmallChair(IChair): # pylint: disable=too-few-public-methods - """The Small Chair Concrete Class which implements the IChair interface""" - - def __init__(self): - self._height = 40 - self._width = 40 - self._depth = 40 - - def dimensions(self): - return {"width": self._width, "depth": self._depth, "height": self._height} - - -class ChairFactory: # pylint: disable=too-few-public-methods - """Tha Factory Class""" - - @staticmethod - def get_chair(chair): - """A static method to get a table""" - try: - if chair == "BigChair": - return BigChair() - if chair == "MediumChair": - return MediumChair() - if chair == "SmallChair": - return SmallChair() - raise AssertionError("Chair Not Found") - except AssertionError as _e: - print(_e) - return None - - -if __name__ == "__main__": - CHAIR_FACTORY = ChairFactory().get_chair("SmallChair") - print(CHAIR_FACTORY.dimensions()) -``` - -### **`table_factory.py`** -```python -from abc import ABCMeta, abstractstaticmethod - - -class ITable(metaclass=ABCMeta): # pylint: disable=too-few-public-methods - """The Table Interface""" - - @abstractstaticmethod - def dimensions(): - """Get the table dimensions""" - - -class BigTable(ITable): # pylint: disable=too-few-public-methods - """The Big Table Concrete Class which implements the ITable interface""" - - def __init__(self): - self._height = 60 - self._width = 120 - self._depth = 80 - - def dimensions(self): - return {"width": self._width, "depth": self._depth, "height": self._height} - - -class MediumTable(ITable): # pylint: disable=too-few-public-methods - """The Medium Table Concrete Class which implements the ITable interface""" - - def __init__(self): - self._height = 60 - self._width = 110 - self._depth = 70 - - def dimensions(self): - return {"width": self._width, "depth": self._depth, "height": self._height} - - -class SmallTable(ITable): # pylint: disable=too-few-public-methods - """The Small Table Concrete Class which implements the ITable interface""" - - def __init__(self): - self._height = 60 - self._width = 100 - self._depth = 60 - - def dimensions(self): - return {"width": self._width, "depth": self._depth, "height": self._height} - - -class TableFactory: # pylint: disable=too-few-public-methods - """Tha Factory Class""" - - @staticmethod - def get_table(table): - """A static method to get a table""" - try: - if table == "BigTable": - return BigTable() - if table == "MediumTable": - return MediumTable() - if table == "SmallTable": - return SmallTable() - raise AssertionError("Table Not Found") - except AssertionError as _e: - print(_e) - return None - - -if __name__ == "__main__": - TABLE = TableFactory().get_table("SmalTable") - print(TABLE) - -``` - -### **`furniture_abstract_factory.py`** -```python -from abc import ABCMeta, abstractstaticmethod -from chair_factory import ChairFactory -from table_factory import TableFactory - - -class IFurnitureFactory(metaclass=ABCMeta): # pylint: disable=too-few-public-methods - """Furniture Factory Interface""" - - @abstractstaticmethod - def get_furniture(furniture): - """The static funiture factory inteface method""" - - -class FurnitureFactory(IFurnitureFactory): # pylint: disable=too-few-public-methods - """The Furniture Factory Concrete Class""" - - @staticmethod - def get_furniture(furniture): - """Static get_furniture method""" - try: - if furniture in ["SmallChair", "MediumChair", "BigChair"]: - return ChairFactory().get_chair(furniture) - if furniture in ["SmallTable", "MediumTable", "BigTable"]: - return TableFactory().get_table(furniture) - raise AssertionError("No Furniture Factory Found") - except AssertionError as _e: - print(_e) - return None - - -FURNITURE = FurnitureFactory.get_furniture("SmallChair") -print(f"{FURNITURE.__class__} : {FURNITURE.dimensions()}") - -FURNITURE = FurnitureFactory.get_furniture("MediumTable") -print(f"{FURNITURE.__class__} : {FURNITURE.dimensions()}") - -``` \ No newline at end of file diff --git a/docs/abstract_factory.pdf b/docs/abstract_factory.pdf deleted file mode 100644 index 09271ee..0000000 Binary files a/docs/abstract_factory.pdf and /dev/null differ diff --git a/docs/abstract_factory.png b/docs/abstract_factory.png deleted file mode 100644 index ba53bc3..0000000 Binary files a/docs/abstract_factory.png and /dev/null differ diff --git a/docs/abstract_factory_furniture.png b/docs/abstract_factory_furniture.png deleted file mode 100644 index 2ccfe0b..0000000 Binary files a/docs/abstract_factory_furniture.png and /dev/null differ diff --git a/docs/adapter.md b/docs/adapter.md deleted file mode 100644 index a5ffb1e..0000000 --- a/docs/adapter.md +++ /dev/null @@ -1,77 +0,0 @@ -# Adapter Design Pattern - -## Video Lecture -Skillshare : https://skl.sh/34SM2Xg - -Udemy : Adapter Design Pattern - -## Description - -The adapter design pattern solves these problems: - -- How can a class be reused that does not have an interface that a client requires? -- How can classes that have incompatible interfaces work together? -- How can an alternative interface be provided for a class? - -In this lecture, I have 2 classes, they don't share the same interface. The client requires it's objects to use an already standardised interface. - -So we need to create an adapter, that wraps the incompatible object, but implements the standardised interface. - -## Two Incompatible Classes -![Adapter Design Pattern](adapter_before.png) - -## After Creating an Adapter -![Adapter Design Pattern](adapter.png) - -## Source Code - -### **`adapter.py`** -```python -from abc import ABCMeta, abstractmethod - - -class IA(metaclass=ABCMeta): - @staticmethod - @abstractmethod - def method_a(): - """An abstract method A""" - - -class ClassA(IA): - def method_a(self): - print("method A") - - -class IB(metaclass=ABCMeta): - @staticmethod - @abstractmethod - def method_b(): - """An abstract method B""" - - -class ClassB(IB): - def method_b(self): - print("method B") - - -"""ClassB does not have a method_a, so we create an adapter""" - - -class ClassBAdapter(IA): - def __init__(self): - self.class_b = ClassB() - - def method_a(self): - """calls the class b method_b instead""" - self.class_b.method_b() - - -# client - -#ITEM = ClassA() -#ITEM = ClassB() # has no method_a -ITEM = ClassBAdapter() - -ITEM.method_a() - -``` \ No newline at end of file diff --git a/docs/adapter.pdf b/docs/adapter.pdf deleted file mode 100644 index 7e15199..0000000 Binary files a/docs/adapter.pdf and /dev/null differ diff --git a/docs/adapter.png b/docs/adapter.png deleted file mode 100644 index 762368e..0000000 Binary files a/docs/adapter.png and /dev/null differ diff --git a/docs/adapter_before.png b/docs/adapter_before.png deleted file mode 100644 index 6a2416c..0000000 Binary files a/docs/adapter_before.png and /dev/null differ diff --git a/docs/atm.png b/docs/atm.png deleted file mode 100644 index 7ffb880..0000000 Binary files a/docs/atm.png and /dev/null differ diff --git a/docs/builder.md b/docs/builder.md deleted file mode 100644 index 960ee2e..0000000 --- a/docs/builder.md +++ /dev/null @@ -1,150 +0,0 @@ -# Builder Design Pattern - -## Video Lecture - -Skillshare : [https://skl.sh/34SM2Xg](https://skl.sh/34SM2Xg "Builder Design Pattern") - -Udemy : [Builder Design Pattern](https://www.udemy.com/course/design-patterns-in-python/learn/lecture/16396852/?referralCode=7B677DD7A9580F2FFD8F "Builder Design Pattern") - -## Description - -The Builder Pattern is a creational pattern whose intent is to separate the construction of a complex object from its representation so that you can use the same construction process to create different representations. - -The Builder Pattern tries to solve, - -- How can a class create different representations of a complex object? -- How can a class that includes creating a complex object be simplified? - -The Builder and Factory patterns are very similar in the fact they both instantiate new objects at run time. The difference is when the process of creating the object is more complex, so rather than the Factory returning a new instance of `ObjectA`, it could call the builders director construct method `ObjectA.construct()`. Both return an Object. - -Parts of the Builder Pattern - -- **Product** - The Product being built -- **Concrete Builder** - Build the concrete product. Implements the IBuilder interface -- **Builder Interface** - The Interface which the Concrete builder should implement -- **Director** - Has a construct method which when called creates a customised product - -![Builder Pattern Overview](builder.png) - -The Builder Pattern in the context of a House Builder. There are multiple directors creating there own complex objects - -![Builder Pattern in Context](house_builder.png). - -## Source Code - -### **`builder.py`** - -```python -from abc import ABCMeta, abstractstaticmethod - - -class IHouseBuilder(metaclass=ABCMeta): - """The Builder Interface""" - - @abstractstaticmethod - def set_wall_material(value): - """Set the wall_material""" - - @abstractstaticmethod - def set_building_type(value): - """Set the building_type""" - - @abstractstaticmethod - def set_number_doors(value): - """Set the number of doors""" - - @abstractstaticmethod - def set_number_windows(value): - """Set the number of windows""" - - @abstractstaticmethod - def get_result(): - """Return the house""" - - -class HouseBuilder(IHouseBuilder): - """The Concrete Builder.""" - - def __init__(self): - self.house = House() - - def set_wall_material(self, value): - self.house.wall_material = value - return self - - def set_building_type(self, value): - self.house.building_type = value - return self - - def set_number_doors(self, value): - self.house.doors = value - return self - - def set_number_windows(self, value): - self.house.windows = value - return self - - def get_result(self): - return self.house - - -class House(): - """The Product""" - - def __init__(self, building_type="Apartment", doors=0, windows=0, wall_material="Brick"): - #brick, wood, straw, ice - self.wall_material = wall_material - # Apartment, Bungalow, Caravan, Hut, Castle, Duplex, HouseBoat, Igloo - self.building_type = building_type - self.doors = doors - self.windows = windows - - def __str__(self): - return "This is a {0} {1} with {2} door(s) and {3} window(s).".format( - self.wall_material, self.building_type, self.doors, self.windows - ) - - -class IglooDirector: - """The Director, building a different representation.""" - @staticmethod - def construct(): - return HouseBuilder()\ - .set_building_type("Igloo")\ - .set_wall_material("Ice")\ - .set_number_doors(1)\ - .set_number_windows(0)\ - .get_result() - - -class HouseBoatDirector: - """The Director, building a different representation.""" - @staticmethod - def construct(): - return HouseBuilder()\ - .set_building_type("House Boat")\ - .set_wall_material("Wooden")\ - .set_number_doors(6)\ - .set_number_windows(8)\ - .get_result() - - -class CastleDirector: - """The Director, building a different representation.""" - @staticmethod - def construct(): - return HouseBuilder()\ - .set_building_type("Castle")\ - .set_wall_material("Granite")\ - .set_number_doors(100)\ - .set_number_windows(200).get_result() - - -if __name__ == "__main__": - IGLOO = IglooDirector.construct() - HOUSE_BOAT = HouseBoatDirector.construct() - CASTLE = CastleDirector.construct() - print(IGLOO) - print(HOUSE_BOAT) - print(CASTLE) -``` diff --git a/docs/builder.pdf b/docs/builder.pdf deleted file mode 100644 index c47f06e..0000000 Binary files a/docs/builder.pdf and /dev/null differ diff --git a/docs/builder.png b/docs/builder.png deleted file mode 100644 index cf194b1..0000000 Binary files a/docs/builder.png and /dev/null differ diff --git a/docs/chain_of_responsibility.md b/docs/chain_of_responsibility.md deleted file mode 100644 index f4ab783..0000000 --- a/docs/chain_of_responsibility.md +++ /dev/null @@ -1,176 +0,0 @@ -# Chain of Responsibility Design Pattern - -## Video Lecture -Skillshare : https://skl.sh/34SM2Xg - -Udemy : Chain of Responsibility Design Pattern - -## Description - -Chain of responsibility pattern is a behavioural pattern used to achieve loose coupling -in software design. -In this example, a request from a client is passed to a chain of objects to process them. -The objects in the chain will decide how to process them and/or pass them to the next in the chain. -The objects can also modify the next in the chain if for example you wanted to run objects in a recursive manner. - - -### Chain of Responsibility Diagram -![Chain of Responsibility UML Diagram](chain_of_responsibility.png) - - -### Chain of Responsibility UML Diagram in the context of an ATM -![Chain of Responsibility UML Diagram in the context of an ATM](atm.png) - -In the ATM example, the chain is created to dispense an amount of £50, then £20s and then £10s in order. -The successor chain is hard coded in the chain client. - -```python -def __init__(self): - # initialize the successor chain - self.chain1 = Dispenser50() - self.chain2 = Dispenser20() - self.chain3 = Dispenser10() - - # set the chain of responsibility - # The Client may compose chains once or - # the handler can set them dynamically at - # handle time - self.chain1.set_successor(self.chain2) - self.chain2.set_successor(self.chain3) - -``` -You also have the option to set the next successor on logic at handle time. - -### Output -```bash -$ python atm.py -Enter amount to withdrawal -130 -Dispensing 2 £50 note -Dispensing 1 £20 note -Dispensing 1 £10 note -Go spoil yourself -``` - -## Source Code - -### **`atm.py`** -```python -from abc import ABCMeta, abstractstaticmethod - - -class IHandler(metaclass=ABCMeta): - @abstractstaticmethod - def set_successor(successor): - """Set the next handler in the chain""" - - @abstractstaticmethod - def handle(amount): - """Handle the event""" - - -class Dispenser50(IHandler): - """ConcreteHandler - Dispense £50 notes if applicable, - otherwise continue to successor - """ - - def __init__(self): - self._successor = None - - def set_successor(self, successor): - """Set the successor""" - self._successor = successor - - def handle(self, amount): - """Handle the dispensing of notes""" - if amount >= 50: - num = amount // 50 - remainder = amount % 50 - print(f"Dispensing {num} £50 note") - if remainder != 0: - self._successor.handle(remainder) - else: - self._successor.handle(amount) - - -class Dispenser20(IHandler): - """ConcreteHandler - Dispense £20 notes if applicable, - otherwise continue to successor - """ - - def __init__(self): - self._successor = None - - def set_successor(self, successor): - """Set the successor""" - self._successor = successor - - def handle(self, amount): - """Handle the dispensing of notes""" - if amount >= 20: - num = amount // 20 - remainder = amount % 20 - print(f"Dispensing {num} £20 note") - if remainder != 0: - self._successor.handle(remainder) - else: - self._successor.handle(amount) - - -class Dispenser10(IHandler): - """ConcreteHandler - Dispense £10 notes if applicable, - otherwise continue to successor - """ - - def __init__(self): - self._successor = None - - def set_successor(self, successor): - """Set the successor""" - self._successor = successor - - def handle(self, amount): - """Handle the dispensing of notes""" - if amount >= 10: - num = amount // 10 - remainder = amount % 10 - print(f"Dispensing {num} £10 note") - if remainder != 0: - self._successor.handle(remainder) - else: - self._successor.handle(amount) - - -class ATMDispenserChain: # pylint: disable=too-few-public-methods - """The Chain Client""" - - def __init__(self): - # initialize the successor chain - self.chain1 = Dispenser50() - self.chain2 = Dispenser20() - self.chain3 = Dispenser10() - - # set the chain of responsibility - # The Client may compose chains once or - # the hadler can set them dynamically at - # handle time - self.chain1.set_successor(self.chain2) - self.chain2.set_successor(self.chain3) - - -if __name__ == "__main__": - - ATM = ATMDispenserChain() - - AMOUNT = int(input("Enter amount to withdrawal : ")) - if AMOUNT < 10 or AMOUNT % 10 != 0: - print("Amount should be positive and in multiple of 10s.") - exit() - # process the request - ATM.chain1.handle(AMOUNT) - print("Now go spoil yourself") - -``` \ No newline at end of file diff --git a/docs/chain_of_responsibility.pdf b/docs/chain_of_responsibility.pdf deleted file mode 100644 index a32b2e9..0000000 Binary files a/docs/chain_of_responsibility.pdf and /dev/null differ diff --git a/docs/chain_of_responsibility.png b/docs/chain_of_responsibility.png deleted file mode 100644 index d6e01ed..0000000 Binary files a/docs/chain_of_responsibility.png and /dev/null differ diff --git a/docs/command.md b/docs/command.md deleted file mode 100644 index 3d730dc..0000000 --- a/docs/command.md +++ /dev/null @@ -1,136 +0,0 @@ -# Command Design Pattern - -## Video Lecture -Skillshare : https://skl.sh/34SM2Xg - -Udemy : Command Design Pattern - -## Description - -The command pattern is a behavioural design pattern, in which an abstraction exists between an object that invokes a command, and the object that performs it. - -The components of the Command Design Pattern are, - -- **Receiver** : The Object that will receive and execute the command -- **Invoker** : Which will send the command to the receiver -- **Command Object** : Itself, which implements an execute, or action method, and contains all required information to execute it -- **Client** : The application or component which is aware of the Receiver, Invoker and Commands - -Eg, a button, will call the Invoker, which will call a pre registered Commands execute method, which the Receiver will perform. - -A Concrete Class will delegate a request to a command object, instead of implementing the request directly. -Using a command design pattern allows you to separate concerns a little easier and to solve problems of the concerns independently of each of the layers. -eg, logging the execution of a command and it's outcome. - -Uses: - -- GUI Buttons, menus -- Macro recording -- Multi level undo/redo -- Networking - send whole command objects across a network, even as a batch -- Parallel processing or thread pools -- Transactional behaviour -- Wizards - -Notes: -The receiver object should manages it's own state, not the command object -There can be one or more invokers which can execute the command at a later date. - -![The Command Pattern Overview](command_pattern.png) - -The Command Pattern in the context of a light switch - -![The Command Pattern Switch](switch_command_pattern.png) - -## Source Code -### **`switch_command.py`** -```python -from abc import ABCMeta, abstractstaticmethod -import time - - -class ICommand(metaclass=ABCMeta): - """The command interface, which all commands will implement""" - - @abstractstaticmethod - def execute(): - """The required execute method which all command objects will use""" - - -class Switch: - """The Invoker Class""" - - def __init__(self): - self._commands = {} - self._history = [] - - @property - def history(self): - return self._history - - def register(self, command_name, command): - self._commands[command_name] = command - - def execute(self, command_name): - if command_name in self._commands.keys(): - self._history.append((time.time(), command_name)) - self._commands[command_name].execute() - else: - print(f"Command [{command_name}] not recognised") - - -class Light: - """The Receiver""" - - def turn_on(self): - print("Light turned ON") - - def turn_off(self): - print("Light turned OFF") - - -class SwitchOnCommand(ICommand): - """A Command object, which implements the ICommand interface""" - - def __init__(self, light): - self._light = light - - def execute(self): - self._light.turn_on() - - -class SwitchOffCommand(ICommand): - """A Command object, which implements the ICommand interface""" - - def __init__(self, light): - self._light = light - - def execute(self): - self._light.turn_off() - - -if __name__ == "__main__": - # The Client is the main python app - - # The Light is the Receiver - LIGHT = Light() - - # Create Commands - SWITCH_ON = SwitchOnCommand(LIGHT) - SWITCH_OFF = SwitchOffCommand(LIGHT) - - # Register the commands with the invoker (Switch) - SWITCH = Switch() - SWITCH.register("ON", SWITCH_ON) - SWITCH.register("OFF", SWITCH_OFF) - - # Execute the commands that are registered on the Invoker - SWITCH.execute("ON") - SWITCH.execute("OFF") - SWITCH.execute("ON") - SWITCH.execute("OFF") - - # For fun, we can see the history - print(SWITCH.history) - -``` diff --git a/docs/command.pdf b/docs/command.pdf deleted file mode 100644 index b20da16..0000000 Binary files a/docs/command.pdf and /dev/null differ diff --git a/docs/command_direct.png b/docs/command_direct.png deleted file mode 100644 index 10f0aa0..0000000 Binary files a/docs/command_direct.png and /dev/null differ diff --git a/docs/command_pattern.png b/docs/command_pattern.png deleted file mode 100644 index 214d14c..0000000 Binary files a/docs/command_pattern.png and /dev/null differ diff --git a/docs/command_undo_redo.md b/docs/command_undo_redo.md deleted file mode 100644 index 41c5dec..0000000 --- a/docs/command_undo_redo.md +++ /dev/null @@ -1,193 +0,0 @@ -# Command Design Pattern (Undo/Redo) - -## Video Lecture -Skillshare : https://skl.sh/34SM2Xg - -Udemy : Command (Undo/Redo) Design Pattern - -## Description - -This is part 2 of the Command Design Pattern tutorial, where I create a slider, instead of the switch from part 1. - -The slider also accepts a variable percentage, rather than an ON/OFF. - -The history also records the variable setting. - -And I also add UNDO/REDO to the Invoker so that you can go backwards and forwards through time. - -![The Command Pattern Slider](slider_command.png) - -## Source Code -### **`slider_command.py`** -```python -from abc import ABCMeta, abstractstaticmethod -import time - - -class ICommand(metaclass=ABCMeta): - """The command interface, which all commands will implement""" - - @abstractstaticmethod - def execute(*args): - """The required execute method which all command obejcts will use""" - - -class IUndoRedo(metaclass=ABCMeta): - """The Undo Redo interface""" - @abstractstaticmethod - def history(): - """the history of the states""" - - @abstractstaticmethod - def undo(): - """for undoing the hsitory of the states""" - - @abstractstaticmethod - def redo(): - """for redoing the hsitory of the states""" - - -class Slider(IUndoRedo): - """The Invoker Class""" - - def __init__(self): - self._commands = {} - self._history = [(0.0, "OFF", ())] # A default setting of OFF - self._history_position = 0 # The position that is used for UNDO/REDO - - @property - def history(self): - """Return all records in the History list""" - return self._history - - def register(self, command_name, command): - """All commands are registered in the Invoker Class""" - self._commands[command_name] = command - - def execute(self, command_name, *args): - """Execute a pre defined command and log in history""" - if command_name in self._commands.keys(): - self._history_position += 1 - self._commands[command_name].execute(args) - if len(self._history) == self._history_position: - # This is a new event in hisory - self._history.append((time.time(), command_name, args)) - else: - # This occurs if there was one of more UNDOs and then a new - # execute command happened. In case of UNDO, the history_position - # changes, and executing new commands purges any history after - # the current position""" - self._history = self._history[:self._history_position+1] - self._history[self._history_position] = { - time.time(): [command_name, args] - } - else: - print(f"Command [{command_name}] not recognised") - - def undo(self): - """Undo a command if there is a command that can be undone. - Update the history position so that further UNDOs or REDOs - point to the correct index""" - if self._history_position > 0: - self._history_position -= 1 - self._commands[ - self._history[self._history_position][1] - ].execute(self._history[self._history_position][2]) - else: - print("nothing to undo") - - def redo(self): - """Perform a REDO if the history_position is less than the end of the history list""" - if self._history_position + 1 < len(self._history): - self._history_position += 1 - self._commands[ - self._history[self._history_position][1] - ].execute(self._history[self._history_position][2]) - else: - print("nothing to REDO") - - -class Heater: - """The Receiver""" - - def set_to_max(self): - print("Heater is ON and set to MAX (100%)") - - def set_to_percent(self, *args): - print(f"Heater is ON and set to {args[0][0]}%") - - def turn_off(self): - print("Heater is OFF") - - -class SliderMaxCommand(ICommand): - """A Command object, which implements the ICommand interface""" - - def __init__(self, heater): - self._heater = heater - - def execute(self, *args): - self._heater.set_to_max() - - -class SliderPercentCommand(ICommand): - """A Command object, which implements the ICommand interface""" - - def __init__(self, heater): - self._heater = heater - - def execute(self, *args): - self._heater.set_to_percent(args[0]) - - -class SliderOffCommand(ICommand): - """A Command object, which implements the ICommand interface""" - - def __init__(self, heater): - self._heater = heater - - def execute(self, *args): - self._heater.turn_off() - - -if __name__ == "__main__": - # The Client is the main python app - - # The HEATER is the Receiver - HEATER = Heater() - - # Create Commands - SLIDER_MAX = SliderMaxCommand(HEATER) - SLIDER_PERCENT = SliderPercentCommand(HEATER) - SLIDER_OFF = SliderOffCommand(HEATER) - - # Register the commands with the invoker (Switch) - SLIDER = Slider() - SLIDER.register("MAX", SLIDER_MAX) - SLIDER.register("PERCENT", SLIDER_PERCENT) - SLIDER.register("OFF", SLIDER_OFF) - - # Execute the commands that are registered on the Invoker - SLIDER.execute("PERCENT", 10) - SLIDER.execute("PERCENT", 20) - SLIDER.execute("PERCENT", 30) - SLIDER.execute("PERCENT", 40) - SLIDER.execute("PERCENT", 50) - print(SLIDER.history) - SLIDER.undo() - SLIDER.undo() - SLIDER.undo() - SLIDER.redo() - SLIDER.undo() - SLIDER.undo() - SLIDER.execute("PERCENT", 90) - SLIDER.execute("MAX") - SLIDER.execute("OFF") - print(SLIDER.history) - SLIDER.undo() - SLIDER.redo() - - print(SLIDER.history) - - -``` diff --git a/docs/command_undo_redo.pdf b/docs/command_undo_redo.pdf deleted file mode 100644 index 3b6cc12..0000000 Binary files a/docs/command_undo_redo.pdf and /dev/null differ diff --git a/docs/composite.md b/docs/composite.md deleted file mode 100644 index b689565..0000000 --- a/docs/composite.md +++ /dev/null @@ -1,71 +0,0 @@ -# Composite Design Pattern - -## Video Lecture -Skillshare : https://skl.sh/34SM2Xg - -Udemy : Composite Design Pattern - -## Description - -The Composite design pattern, -- allows you to represent individual entities and groups of entities in the same manner. -- is a structural design pattern that lets you compose objects into a tree. -- is great if you need the option of swapping hierarchal relationships around. -- makes it easier for you to add new kinds of components -- provides flexibility of structure -- conform to the Single Responsibility Principle in the way that it separates the aggregation of objects from the features of the object. - -Examples of using the Composite Design Pattern can be seen in a filesystem directory structure, where you can swap the hierarchy of folders, and in a drawing program where you can group, un-group and transform objects, and multiple objects at the same time. - -![Composite Pattern UML Diagram](composite.png) - -## Source Code - -### **`composite.py`** -```python -from abc import ABCMeta, abstractmethod - -class IGraphic(metaclass=ABCMeta): - @staticmethod - @abstractmethod - def print(): - """print information""" - -class Ellipse(IGraphic): - def print(self): - print("Ellipse") - -class Circle(IGraphic): - def print(self): - print("Circle") - -class CompositeGraphic(IGraphic): - def __init__(self): - self.child_graphics = [] - - def add(self, graphic): - self.child_graphics.append(graphic) - - def print(self): - for g in self.child_graphics: - g.print() - - -ELLIPSE1 = Ellipse() -CIRCLE1 = Circle() - -COMPOSITE1 = CompositeGraphic() -COMPOSITE1.add(ELLIPSE1) - -COMPOSITE2 = CompositeGraphic() -COMPOSITE2.add(CIRCLE1) -COMPOSITE2.add(COMPOSITE1) - -COMPOSITE2.print() - -# ELLIPSE1.print() -# CIRCLE1.print() - -``` - - diff --git a/docs/composite.pdf b/docs/composite.pdf deleted file mode 100644 index eb2ab0d..0000000 Binary files a/docs/composite.pdf and /dev/null differ diff --git a/docs/composite.png b/docs/composite.png deleted file mode 100644 index 7f3cd84..0000000 Binary files a/docs/composite.png and /dev/null differ diff --git a/docs/decorator.md b/docs/decorator.md deleted file mode 100644 index 9935f4d..0000000 --- a/docs/decorator.md +++ /dev/null @@ -1,55 +0,0 @@ -# Decorator Design Pattern - -## Video Lecture -Skillshare : https://skl.sh/34SM2Xg - -Udemy : Decorator Design Pattern - -## Description -The decorator pattern is a structural pattern, that allows you to attach additional responsibilities to an object at run time. - -The decorator pattern is used in both the Object Oriented and Functional paradigms. - -The decorator pattern is different than the Python language feature of Python Decorators in it's syntax, but the application of it is the same, in the way that it is essentially a wrapper. - -The Decorator pattern adds extensibility, without modifying the original function. - -![Decorator Pattern UML Diagram](decorator.png) - -## Source Code - -### **`decorator.py`** -```python -class UndecoratedObject: - @staticmethod - def get(): - return "UndecoratedObject" - - -class Decorate: - def __init__(self, undecorated): - self.undecorated = undecorated - - def get(self): - return self.undecorated.get().replace("Undecorated", "Decorated") - - -# class DecorateWithANewMethod: -# def __init__(self, undecorated): -# self.undecorated = undecorated - -# def get(self): -# return self.undecorated.get() - -# def draw(self): -# print(self.undecorated.get()) - - -UNDECORATED = UndecoratedObject() -print(UNDECORATED.get()) -DECORATED = Decorate(UNDECORATED) -print(DECORATED.get()) -#DECORATEDWITHNEWMETHOD = DecorateWithANewMethod(DECORATED) -#DECORATEDWITHNEWMETHOD.draw() - -``` \ No newline at end of file diff --git a/docs/decorator.pdf b/docs/decorator.pdf deleted file mode 100644 index b8bd37a..0000000 Binary files a/docs/decorator.pdf and /dev/null differ diff --git a/docs/decorator.png b/docs/decorator.png deleted file mode 100644 index 3be9c01..0000000 Binary files a/docs/decorator.png and /dev/null differ diff --git a/docs/facade.md b/docs/facade.md deleted file mode 100644 index 3d155a5..0000000 --- a/docs/facade.md +++ /dev/null @@ -1,56 +0,0 @@ -# Facade Design Pattern - -## Video Lecture -Skillshare : https://skl.sh/34SM2Xg - -Udemy : Facade Design Pattern - -## Description - -The Facade Pattern is a structural design pattern. -It provides a simplified interface to a set of other interfaces, abstractions and implementations within a system that may be full of complexity and/or tightly coupled. - -![Facade Design Pattern](facade.png) - -## Source Code - -### **`facade.py`** -```python -class SubSystemClassA: - @staticmethod - def method(): - return "A" - - -class SubSystemClassB: - @staticmethod - def method(): - return "B" - - -class SubSystemClassC: - @staticmethod - def method(): - return "C" - - -# facade -class Facade: - def __init__(self): - self.sub_system_class_a = SubSystemClassA() - self.sub_system_class_b = SubSystemClassB() - self.sub_system_class_c = SubSystemClassC() - - def create(self): - result = self.sub_system_class_a.method() - result += self.sub_system_class_b.method() - result += self.sub_system_class_c.method() - return result - - -# client -FACADE = Facade() -RESULT = FACADE.create() -print("The Result = %s" % RESULT) - -``` \ No newline at end of file diff --git a/docs/facade.pdf b/docs/facade.pdf deleted file mode 100644 index de3f978..0000000 Binary files a/docs/facade.pdf and /dev/null differ diff --git a/docs/facade.png b/docs/facade.png deleted file mode 100644 index 94352b1..0000000 Binary files a/docs/facade.png and /dev/null differ diff --git a/docs/factory.md b/docs/factory.md deleted file mode 100644 index b29a9be..0000000 --- a/docs/factory.md +++ /dev/null @@ -1,93 +0,0 @@ -# Factory Design Pattern - -## Video Lecture -Skillshare : https://skl.sh/34SM2Xg - -Udemy : Factory Design Pattern - - -## Description - -The Factory Pattern is a creational pattern that defines an Interface for creating an object and defers instantiation until runtime. - -Used when you don't know how many or what type of objects will be needed until or during runtime - -![Factory Pattern Overview](factory_pattern.png) - -The Factory Pattern in the context of a Chair Factory ![Factory Pattern In Context](factory_pattern_chair.png) - -## Source Code - -### **`chair_factory.py`** - -```python -from abc import ABCMeta, abstractstaticmethod - - -class IChair(metaclass=ABCMeta): # pylint: disable=too-few-public-methods - """The Chair Interface""" - - @abstractstaticmethod - def dimensions(): - """A static inteface method""" - - -class BigChair(IChair): # pylint: disable=too-few-public-methods - """The Big Chair Concrete Class which implements the IChair interface""" - - def __init__(self): - self._height = 80 - self._width = 80 - self._depth = 80 - - def dimensions(self): - return {"width": self._width, "depth": self._depth, "height": self._height} - - -class MediumChair(IChair): # pylint: disable=too-few-public-methods - """The Medium Chair Concrete Class which implements the IChair interface""" - - def __init__(self): - self._height = 60 - self._width = 60 - self._depth = 60 - - def dimensions(self): - return {"width": self._width, "depth": self._depth, "height": self._height} - - -class SmallChair(IChair): # pylint: disable=too-few-public-methods - """The Small Chair Concrete Class which implements the IChair interface""" - - def __init__(self): - self._height = 40 - self._width = 40 - self._depth = 40 - - def dimensions(self): - return {"width": self._width, "depth": self._depth, "height": self._height} - - -class ChairFactory: # pylint: disable=too-few-public-methods - """Tha Factory Class""" - - @staticmethod - def get_chair(chair): - """A static method to get a table""" - try: - if chair == "BigChair": - return BigChair() - if chair == "MediumChair": - return MediumChair() - if chair == "SmallChair": - return SmallChair() - raise AssertionError("Chair Not Found") - except AssertionError as _e: - print(_e) - return None - - -if __name__ == "__main__": - CHAIR_FACTORY = ChairFactory().get_chair("SmallChair") - print(CHAIR_FACTORY.dimensions()) -``` diff --git a/docs/factory.pdf b/docs/factory.pdf deleted file mode 100644 index 563ac5a..0000000 Binary files a/docs/factory.pdf and /dev/null differ diff --git a/docs/factory.png b/docs/factory.png deleted file mode 100644 index 90cc146..0000000 Binary files a/docs/factory.png and /dev/null differ diff --git a/docs/factory_pattern.png b/docs/factory_pattern.png deleted file mode 100644 index cc01bdb..0000000 Binary files a/docs/factory_pattern.png and /dev/null differ diff --git a/docs/factory_pattern_chair.png b/docs/factory_pattern_chair.png deleted file mode 100644 index 98ac417..0000000 Binary files a/docs/factory_pattern_chair.png and /dev/null differ diff --git a/docs/house_builder.png b/docs/house_builder.png deleted file mode 100644 index d672560..0000000 Binary files a/docs/house_builder.png and /dev/null differ diff --git a/docs/img/skillshare_btn.png b/docs/img/skillshare_btn.png deleted file mode 100644 index c7a65ef..0000000 Binary files a/docs/img/skillshare_btn.png and /dev/null differ diff --git a/docs/img/udemy_btn.png b/docs/img/udemy_btn.png deleted file mode 100644 index 68c7c24..0000000 Binary files a/docs/img/udemy_btn.png and /dev/null differ diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index a629a35..0000000 --- a/docs/index.md +++ /dev/null @@ -1,59 +0,0 @@ -# Design Patterns In Python - -The tutorials in this documentation supplement my **Design Patterns in Python** Courses on Skillshare and -Udemy. - -To register for this course visit - - - - - - - - -
* Get 2 Months Free Premium Membership to 1000s of Courses
* Full Lifetime Access
* Subscription Model
* Cancel Any Time

* New Student Discount
* Full Lifetime Access
* Certificate of Completion
* 30 Day Money-Back Guarantee
-
- -This course is about common GOF (Gang Of Four) Design Patterns implemented in Python. - -A Design Pattern is a description or template that can be repeatedly applied to a commonly recurring problem in software design. - -You will find a familiarity with Design Patterns very useful when planning, discussing, developing, managing and documenting your applications from now on and into the future. - -You will learn these Design Patterns - -* Creational - * [Factory](factory) - * [Abstract Factory](abstract_factory) - * [Builder](builder) - * [Prototype](prototype) - * Singleton -* Structural - * [Decorator](decorator) - * [Adapter](adapter) - * [Facade](facade) - * Bridge - * [Composite](composite) - * Flyweight - * [Proxy](proxy) -* Behavioural - * [Command](command) - * [Chain of Responsibility](chain_of_responsibility) - * [Observer Pattern](observer) - * Interpreter - * [Iterator](iterator) - * [Mediator](mediator) - * Memento - * State - * Strategy - * Template - * Visitor - - - - -## Introduction Video - -[![Design Patterns in Python Introduction](https://img.youtube.com/vi/OOxyTUWsY7A/0.jpg)](https://youtu.be/OOxyTUWsY7A) diff --git a/docs/iterator.md b/docs/iterator.md deleted file mode 100644 index 49b3905..0000000 --- a/docs/iterator.md +++ /dev/null @@ -1,69 +0,0 @@ -# Iterator Design Pattern - -## Video Lecture -Skillshare : https://skl.sh/34SM2Xg - -Udemy : Iterator Design Pattern - -## Description - -An interface with **next** and **has_next** methods. - -**next** returns the next object in the aggregate(collection, list) - -**has_next** returns a value, usually a boolean indicating if the iterable is at the end of the list or not. - -The benefits of using the Iterator pattern is that the client, can traverse an aggregate without needing to understand it's internal representation and data structures. - -![Iterator Pattern UML Diagram](iterator.png) - -## Source Code - -### **`iterator.py`** -```python -from abc import ABCMeta, abstractmethod - -class IIterator(metaclass=ABCMeta): - @staticmethod - @abstractmethod - def has_next(): - """Returns Boolean whether at end of collection or not""" - - @staticmethod - @abstractmethod - def next(): - """Return the object in collection""" - -class Iterable(IIterator): - def __init__(self): - self.index = 0 - self.maximum = 7 - - def next(self): - if self.index < self.maximum: - x = self.index - self.index += 1 - return x - else: - raise Exception("AtEndOfIteratorException", "At End of Iterator") - - def has_next(self): - return self.index < self.maximum - -ITERABLE = Iterable() - -while ITERABLE.has_next(): - print(ITERABLE.next()) - - -# print(ITERABLE.next()) -# print(ITERABLE.next()) -# print(ITERABLE.next()) -# print(ITERABLE.next()) -# print(ITERABLE.next()) -# print(ITERABLE.next()) -# print(ITERABLE.next()) -# print(ITERABLE.next()) - - -``` \ No newline at end of file diff --git a/docs/iterator.pdf b/docs/iterator.pdf deleted file mode 100644 index ee32502..0000000 Binary files a/docs/iterator.pdf and /dev/null differ diff --git a/docs/iterator.png b/docs/iterator.png deleted file mode 100644 index e2e656f..0000000 Binary files a/docs/iterator.png and /dev/null differ diff --git a/docs/mediator.md b/docs/mediator.md deleted file mode 100644 index 4e32109..0000000 --- a/docs/mediator.md +++ /dev/null @@ -1,73 +0,0 @@ -# Mediator Design Pattern - -## Video Lecture -Skillshare : https://skl.sh/34SM2Xg - -Udemy : Mediator Design Pattern - -## Description - -The mediator pattern is a behavioural pattern that defines an object that encapsulates how a set of objects interact. - -With the mediator pattern, communication between objects is encapsulated within a mediator object. - -Objects communicate through the mediator rather than directly with each other. - -![Mediator Pattern UML Diagram](mediator.png) - -## Source Code - -### **`mediator.py`** -```python -from abc import ABCMeta, abstractmethod - - -class IComponent(metaclass=ABCMeta): - @staticmethod - @abstractmethod - def notify(msg): - """The required notify method""" - - @staticmethod - @abstractmethod - def receive(msg): - """The required receive method""" - - -class Component(IComponent): - def __init__(self, mediator, name): - self.mediator = mediator - self.name = name - - def notify(self, message): - print(self.name + ": >>> Out >>> : " + message) - self.mediator.notify(message, self) - - def receive(self, message): - print(self.name + ": <<< In <<< : " + message) - - -class Mediator(): - def __init__(self): - self.components = [] - - def add(self, component): - self.components.append(component) - - def notify(self, message, component): - for _component in self.components: - if _component != component: - _component.receive(message) - - -MEDIATOR = Mediator() -COMPONENT1 = Component(MEDIATOR, "Component1") -COMPONENT2 = Component(MEDIATOR, "Component2") -COMPONENT3 = Component(MEDIATOR, "Component3") -MEDIATOR.add(COMPONENT1) -MEDIATOR.add(COMPONENT2) -MEDIATOR.add(COMPONENT3) - -COMPONENT1.notify("data") - -``` \ No newline at end of file diff --git a/docs/mediator.pdf b/docs/mediator.pdf deleted file mode 100644 index 121982a..0000000 Binary files a/docs/mediator.pdf and /dev/null differ diff --git a/docs/mediator.png b/docs/mediator.png deleted file mode 100644 index 597d426..0000000 Binary files a/docs/mediator.png and /dev/null differ diff --git a/docs/observer.md b/docs/observer.md deleted file mode 100644 index dc649fd..0000000 --- a/docs/observer.md +++ /dev/null @@ -1,81 +0,0 @@ -# Observer Pattern - -## Video Lecture -Skillshare : https://skl.sh/34SM2Xg - -Udemy : Observer Design Pattern - -## Description - -The observer pattern is a software design pattern in which an object, called the subject or observable, manages a list of dependents, called observers, and notifies them automatically of any internal state changes, and calls one of their methods. - -![Observer Pattern](observer.png) - -## Source Code - -### **`observer.py`** -```python -""" -Observer Design Pattern -""" - -from abc import ABCMeta, abstractmethod - - -class IObservable(metaclass=ABCMeta): - @staticmethod - @abstractmethod - def subscribe(observer): - """The subscribe method""" - - @staticmethod - @abstractmethod - def unsubscribe(observer): - """The unsubscribe method""" - - @staticmethod - @abstractmethod - def notify(observer): - """The notify method""" - - -class Subject(IObservable): - def __init__(self): - self._observers = set() - - def subscribe(self, observer): - self._observers.add(observer) - - def unsubscribe(self, observer): - self._observers.remove(observer) - - def notify(self, *args, **kwargs): - for observer in self._observers: - observer.notify(self, *args, **kwargs) - - -class IObserver(metaclass=ABCMeta): - @staticmethod - @abstractmethod - def notify(observable, *args, **kwargs): - """Receive notifications""" - - -class Observer(IObserver): - def __init__(self, observable): - observable.subscribe(self) - - def notify(self, observable, *args, **kwargs): - print("Observer received", args, kwargs) - - -SUBJECT = Subject() -OBSERVERA = Observer(SUBJECT) -OBSERVERB = Observer(SUBJECT) - -SUBJECT.notify("Hello Observers") - -SUBJECT.unsubscribe(OBSERVERB) -SUBJECT.notify("Hello Observers") - -``` \ No newline at end of file diff --git a/docs/observer.pdf b/docs/observer.pdf deleted file mode 100644 index 27ecf4b..0000000 Binary files a/docs/observer.pdf and /dev/null differ diff --git a/docs/observer.png b/docs/observer.png deleted file mode 100644 index 595ce02..0000000 Binary files a/docs/observer.png and /dev/null differ diff --git a/docs/prototype.md b/docs/prototype.md deleted file mode 100644 index a787c88..0000000 --- a/docs/prototype.md +++ /dev/null @@ -1,99 +0,0 @@ -# Prototype Design Pattern - -## Video Lecture - -Skillshare : [https://skl.sh/34SM2Xg](https://skl.sh/34SM2Xg "Prototype Design Pattern") - -Udemy : [Prototype Design Pattern](https://www.udemy.com/course/design-patterns-in-python/learn/lecture/16396926/?referralCode=7B677DD7A9580F2FFD8F "Prototype Design Pattern") - -## Description - -Prototype design pattern is good for when creating a new objects may require more resources than you want to use or have available, versus just making a new copy in memory. -Eg, A file you've downloaded from a server may be large, but since it is already in memory, you could just clone it, and work on the new copy independently of the original. - -In the prototype patterns interface, you create a static clone method that should be implemented by all classes that use the interface. -How the clone method is implemented in the concrete class is up to you. -You will need to decide whether a shallow or deep copy is required. - -- A shallow copy, copies and creates new references 1 level deep, -- A deep copy, copies and creates new references for all levels. - -In Python you have mutable objects such as Lists, Dictionaries, Sets and any custom Objects you have created. A shallow copy, will create new copies of the objects with new references in memory, but the underlying data will point to the same location as the original copy. Be sure to test your implementation that -the copy method you use works as you expect. - -![Prototype UML Diagram](prototype.png) - -## Source Code - -### **`prototype.py`** - -```python -from abc import ABCMeta, abstractstaticmethod -import copy - - -class IProtoType(metaclass=ABCMeta): - """interface with clone method""" - @abstractstaticmethod - def clone(): - """The clone, deep or shallow, is up to how you - want implement the details in your concrete class?""" - - -class ConcreteClass1(IProtoType): - """concrete class 1""" - - def __init__(self, i=0, s="", l=[], d={}): - self.i = i - self.s = s - self.l = l - self.d = d - - def clone(self): - return type(self)( - self.i, - self.s, - self.l.copy(), - self.d.copy()) - - def __str__(self): - return f"{id(self)}\ti={self.i}\ts={self.s}\tl={self.l}\td={self.d}" - - -class ConcreteClass2(IProtoType): - """concrete class 2""" - - def __init__(self, i=0, s="", l=[], d={}): - self.i = i - self.s = s - self.l = l - self.d = d - - def clone(self): - return type(self)( - self.i, - self.s, - copy.deepcopy(self.l), - copy.deepcopy(self.d)) - - def __str__(self): - return f"i={self.i}\t\ts={self.s}\tl={self.l}\td={self.d}\n{id(self.i)}\t{id(self.s)}\t{id(self.l)}\t{id(self.d)}\t" - - -if __name__ == "__main__": - - OBJECT1 = ConcreteClass1( - 1, - "OBJECT1", - [1, 2, 3], - {"a": 4, "b": 5, "c": 6} - ) - print(f"OBJECT1 {OBJECT1}") - - OBJECT2 = OBJECT1.clone() - OBJECT2.s = "OBJECT2" - OBJECT2.l[0] = 10 - print(f"OBJECT2 {OBJECT2}") - print(f"OBJECT1 {OBJECT1}") - -``` \ No newline at end of file diff --git a/docs/prototype.pdf b/docs/prototype.pdf deleted file mode 100644 index 2fecee0..0000000 Binary files a/docs/prototype.pdf and /dev/null differ diff --git a/docs/prototype.png b/docs/prototype.png deleted file mode 100644 index 209f77c..0000000 Binary files a/docs/prototype.png and /dev/null differ diff --git a/docs/proxy.md b/docs/proxy.md deleted file mode 100644 index 7fa5753..0000000 --- a/docs/proxy.md +++ /dev/null @@ -1,63 +0,0 @@ -# Proxy Design Pattern - -## Video Lecture -Skillshare : https://skl.sh/34SM2Xg - -Udemy : Proxy Design Pattern - -## Description - -The proxy design pattern is a class functioning as an interface to another class or object. - -A proxy could be for anything, such as a network connection, an object in memory, a file, or anything else you need to provide an abstraction between. - -It is a wrapper called by a client to access the real underlying object. - -Additional functionality can be provided at in the proxy abstraction if required. -eg, caching, authorization, validation, lazy initialization, logging. - -The proxy should implement the subject interface as much as practicable so that the proxy and subject appear identical to the client. - -The Proxy Pattern may occasionally also be referred to as Monkey Patching or -Object Augmentation - -![Proxy Pattern UML Diagram](proxy.png) - - -## Source Code - -### **`proxy.py`** -```python -from abc import ABCMeta, abstractmethod -import datetime - - -class IComponent(metaclass=ABCMeta): - @staticmethod - @abstractmethod - def method(self): - """A method to implement""" - - -class Component(IComponent): - def method(self): - print("The method has been called") - - -class ProxyComponent(IComponent): - def __init__(self): - self.component = Component() - - def method(self): - f = open("log.txt", "a") - f.write("%s : method was proxied\n" % (datetime.datetime.now())) - self.component.method() - - -COMPONENT1 = Component() -COMPONENT1.method() - -COMPONENT2 = ProxyComponent() -COMPONENT2.method() - -``` \ No newline at end of file diff --git a/docs/proxy.pdf b/docs/proxy.pdf deleted file mode 100644 index 42d3c50..0000000 Binary files a/docs/proxy.pdf and /dev/null differ diff --git a/docs/proxy.png b/docs/proxy.png deleted file mode 100644 index fe8de44..0000000 Binary files a/docs/proxy.png and /dev/null differ diff --git a/docs/shop_facade.png b/docs/shop_facade.png deleted file mode 100644 index b328f78..0000000 Binary files a/docs/shop_facade.png and /dev/null differ diff --git a/docs/slider_command.png b/docs/slider_command.png deleted file mode 100644 index c5646f0..0000000 Binary files a/docs/slider_command.png and /dev/null differ diff --git a/docs/switch_command_pattern.png b/docs/switch_command_pattern.png deleted file mode 100644 index c5287f3..0000000 Binary files a/docs/switch_command_pattern.png and /dev/null differ diff --git a/facade/Facade Design Pattern.md b/facade/Facade Design Pattern.md deleted file mode 100644 index f81f043..0000000 --- a/facade/Facade Design Pattern.md +++ /dev/null @@ -1,6 +0,0 @@ -## Facade Design Pattern - -The Facade Pattern is a structural design pattern. -It provides a simplified interface to a set of other interfaces, abstractions and implementations within a system that may be full of complexity and/or tightly coupled. - -![Facade Design Pattern](facade.png) diff --git a/facade/Facade Design Pattern.pdf b/facade/Facade Design Pattern.pdf deleted file mode 100644 index 3c8f227..0000000 Binary files a/facade/Facade Design Pattern.pdf and /dev/null differ diff --git a/facade/README.md b/facade/README.md index f81f043..242a2d6 100644 --- a/facade/README.md +++ b/facade/README.md @@ -1,6 +1,173 @@ -## Facade Design Pattern +# Facade Design Pattern -The Facade Pattern is a structural design pattern. -It provides a simplified interface to a set of other interfaces, abstractions and implementations within a system that may be full of complexity and/or tightly coupled. +## Videos -![Facade Design Pattern](facade.png) +Section | Video Links +-|- +Facade Overview | Facade Overview Facade Overview Facade Overview +Facade Use Case | Facade Use Case Facade Use Case Facade Use Case +Python **Decimal** | Python Decimal Python Decimal Python Decimal +Python Type Hints | Python Type Hints Python Type Hints Python Type Hints + +## Book + +Cover | Links +-|- +![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    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 + +## Overview + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Facade UML Diagram + +![Facade Design Pattern](/img/facade_concept.svg) + +## Output + +``` bash +python ./facade/facade_concept.py +A +B +{'C': [1, 2, 3]} +A +B +{'C': [1, 2, 3]} +``` + +## Example Use Case + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Example UML Diagram + +![Facade Example UML Diagram](/img/facade_example.svg) + +## Output + +``` bash +python ./facade/client.py + +---- Gamestate Snapshot ---- +{'clock': 59, 'game_open': True, 'entries': [('sean', Decimal('5'))]} + +---- Reports History ---- +0 : 1614087127.327007 : new user `sean` created +1 : 1614087127.327007 : wallet for `sean` created and set to 0 +2 : 1614087127.327007 : Give new user `sean` sign up bonus of 10 +3 : 1614087127.327007 : Balance adjustment for `sean`. New balance = 10 +4 : 1614087128.3278701 : Balance check for `sean` = 10 +5 : 1614087128.3278701 : Balance adjustment for `sean`. New balance = 9 +6 : 1614087128.3278701 : New entry `5` submitted by `sean` + +---- Gamestate Snapshot ---- +{'clock': 58, 'game_open': True, 'entries': [('sean', Decimal('5'))]} +``` + +## New Coding Concepts + +### Python `decimal` Module + +The `decimal` module provides support for correctly rounded decimal floating-point arithmetic. + +If representing money values in python, it is better to use the `decimal` type rather than `float` . + +Floats will have rounding errors versus decimal. + +``` python +from decimal import Decimal + +print(1.1 + 2.2) # adding floats +print(Decimal('1.1') + Decimal('2.2')) # adding decimals +``` + +Outputs + +``` + +3.3000000000000003 +3.3 +``` + +Note how the float addition results in `3.3000000000000003` whereas the decimal addition result equals `3.3` . + +Be aware though that when creating decimals, be sure to pass in a string representation, otherwise it will create a decimal from a float. + +``` python +from decimal import * + +print(Decimal(1.1)) # decimal from float +print(Decimal('1.1')) # decimal from string +``` + +Outputs + +``` + +1.100000000000000088817841970012523233890533447265625 +1.1 +``` + +Python Decimal: [https://docs.python.org/3/library/decimal.html](https://docs.python.org/3/library/decimal.html) + +### Type Hints + +In the Facade use case example, I have added type hints to the method signatures and class attributes. + +``` python + _clock: int = 0 + _entries: list[tuple[str, Decimal]] = [] + + ... + + def get_balance(user_id: str) -> Decimal: + "Get a players balance" + ... + + ... + + def register_user(cls, new_user: dict[str, str]) -> str: + "register a user" + ... + +``` + +See the extra `: str` after the `user_id` attribute, and the `-> Decimal` before the final colon in the `get_balance()` snippet. + +This is indicating that if you use the `get_balance()` method, that the `user_id` should be a type of `string`, and that the method will return a `Decimal` . + +Note that the Python runtime does not enforce the type hints and that they are optional. However, where they are beneficial is in the IDE of your choice or other third party tools such type checkers. + +In VSCode, when typing code, it will show the types that the method needs. + +![VSCode Type Hints](/img/ide_hint.jpg) + +For type checking, you can install an extra module called `mypy` + +``` bash +pip install mypy +``` + +and then run it against your code, + +``` bash +mypy ./facade/client.py +Success: no issues found in 1 source file +``` + +Mypy will also check any imported modules at the same time. + +If working with money, then it is advisable to add extra checks to your code. Checking that type usage is consistent throughout your code, especially when using Decimals, is a good idea that will make your code more robust. + +For example, if I wasn't consistent in using the Decimal throughout my code, then I would see a warning highlighted. + +``` bash +mypy ./facade/client.py +facade/game_engine.py:45: error: Argument 1 to "append" of "list" has incompatible type "Tuple[str, int]"; expected "Tuple[str, Decimal]" +facade/game_api.py:34: error: Argument 2 to "submit_entry" of "GameEngine" has incompatible type "Decimal"; expected "int" +Found 2 errors in 2 files (checked 1 source file) +``` + +## Summary + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ \ No newline at end of file diff --git a/facade/car_facade.dot b/facade/car_facade.dot deleted file mode 100644 index 6a426c9..0000000 --- a/facade/car_facade.dot +++ /dev/null @@ -1,11 +0,0 @@ -digraph "classes" { -charset="utf-8" -rankdir=BT -"0" [label="{CarBody|\l|SetBody()\l}", shape="record"]; -"1" [label="{CarEngine|\l|SetEngine()\l}", shape="record"]; -"2" [label="{CarFacade|carBody\lcarEngine\lcarModel\l|CreateCompleteCar()\l}", shape="record"]; -"3" [label="{CarModel|\l|SetModel()\l}", shape="record"]; -"0" -> "2" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="carBody", style="solid"]; -"1" -> "2" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="carEngine", style="solid"]; -"3" -> "2" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="carModel", style="solid"]; -} diff --git a/facade/classes.dot b/facade/classes.dot deleted file mode 100644 index c6a5cdd..0000000 --- a/facade/classes.dot +++ /dev/null @@ -1,11 +0,0 @@ -digraph "classes" { -charset="utf-8" -rankdir=BT -"0" [label="{Discount|\l|calc()\l}", shape="record"]; -"1" [label="{Fees|\l|calc()\l}", shape="record"]; -"2" [label="{Shipping|\l|calc()\l}", shape="record"]; -"3" [label="{ShopFacade|discount\lfees\lshipping\l|calc_price()\l}", shape="record"]; -"0" -> "3" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="discount", style="solid"]; -"1" -> "3" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="fees", style="solid"]; -"2" -> "3" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="shipping", style="solid"]; -} diff --git a/facade/client.py b/facade/client.py new file mode 100644 index 0000000..7c10fbc --- /dev/null +++ b/facade/client.py @@ -0,0 +1,30 @@ +"The Facade Example Use Case" +import time +from decimal import Decimal +from game_api import GameAPI + +USER = {"user_name": "sean"} +USER_ID = GameAPI.register_user(USER) + +time.sleep(1) + +GameAPI.submit_entry(USER_ID, Decimal('5')) + +time.sleep(1) + +print() +print("---- Gamestate Snapshot ----") +print(GameAPI.game_state()) + +time.sleep(1) + +HISTORY = GameAPI.get_history() + +print() +print("---- Reports History ----") +for row in HISTORY: + print(f"{row} : {HISTORY[row][0]} : {HISTORY[row][1]}") + +print() +print("---- Gamestate Snapshot ----") +print(GameAPI.game_state()) diff --git a/facade/facade.dot b/facade/facade.dot deleted file mode 100644 index 3e28a35..0000000 --- a/facade/facade.dot +++ /dev/null @@ -1,11 +0,0 @@ -digraph "classes" { -charset="utf-8" -rankdir=BT -"0" [label="{Facade|sub_system_class_a\lsub_system_class_b\lsub_system_class_c\l|create()\l}", shape="record"]; -"1" [label="{SubSystemClassA|\l|method()\l}", shape="record"]; -"2" [label="{SubSystemClassB|\l|method()\l}", shape="record"]; -"3" [label="{SubSystemClassC|\l|method()\l}", shape="record"]; -"1" -> "0" [arrowhead="diamond", arrowtail="none"]; -"2" -> "0" [arrowhead="diamond", arrowtail="none"]; -"3" -> "0" [arrowhead="diamond", arrowtail="none"]; -} diff --git a/facade/facade.png b/facade/facade.png deleted file mode 100644 index 94352b1..0000000 Binary files a/facade/facade.png and /dev/null differ diff --git a/facade/facade.py b/facade/facade.py deleted file mode 100644 index fec302b..0000000 --- a/facade/facade.py +++ /dev/null @@ -1,41 +0,0 @@ -""" -Facade Design Pattern -""" - - -class SubSystemClassA: - @staticmethod - def method(): - return "A" - - -class SubSystemClassB: - @staticmethod - def method(): - return "B" - - -class SubSystemClassC: - @staticmethod - def method(): - return "C" - - -# facade -class Facade: - def __init__(self): - self.sub_system_class_a = SubSystemClassA() - self.sub_system_class_b = SubSystemClassB() - self.sub_system_class_c = SubSystemClassC() - - def create(self): - result = self.sub_system_class_a.method() - result += self.sub_system_class_b.method() - result += self.sub_system_class_c.method() - return result - - -# client -FACADE = Facade() -RESULT = FACADE.create() -print("The Result = %s" % RESULT) diff --git a/facade/facade_concept.py b/facade/facade_concept.py new file mode 100644 index 0000000..7d12f55 --- /dev/null +++ b/facade/facade_concept.py @@ -0,0 +1,56 @@ +# pylint: disable=too-few-public-methods +"The Facade pattern concept" + + +class SubSystemClassA: + "A hypothetically complicated class" + @staticmethod + def method(): + "A hypothetically complicated method" + return "A" + + +class SubSystemClassB: + "A hypothetically complicated class" + @staticmethod + def method(value): + "A hypothetically complicated method" + return value + + +class SubSystemClassC: + "A hypothetically complicated class" + @staticmethod + def method(value): + "A hypothetically complicated method" + return value + + +class Facade(): + "A simplified facade offering the services of subsystems" + @staticmethod + def sub_system_class_a(): + "Use the subsystems method" + return SubSystemClassA().method() + + @staticmethod + def sub_system_class_b(value): + "Use the subsystems method" + return SubSystemClassB().method(value) + + @staticmethod + def sub_system_class_c(value): + "Use the subsystems method" + return SubSystemClassC().method(value) + + +# The Client +# call potentially complicated subsystems directly +print(SubSystemClassA.method()) +print(SubSystemClassB.method("B")) +print(SubSystemClassC.method({"C": [1, 2, 3]})) + +# or use the simplified facade +print(Facade().sub_system_class_a()) +print(Facade().sub_system_class_b("B")) +print(Facade().sub_system_class_c({"C": [1, 2, 3]})) diff --git a/facade/game_api.py b/facade/game_api.py new file mode 100644 index 0000000..32e6159 --- /dev/null +++ b/facade/game_api.py @@ -0,0 +1,40 @@ +"The Game API facade" +from decimal import Decimal +from users import Users +from wallets import Wallets +from game_engine import GameEngine +from reports import Reports + + +class GameAPI(): + "The Game API facade" + @staticmethod + def get_balance(user_id: str) -> Decimal: + "Get a players balance" + return Wallets.get_balance(user_id) + + @staticmethod + def game_state() -> dict: + "Get the current game state" + return GameEngine().get_game_state() + + @staticmethod + def get_history() -> dict: + "get the game history" + return Reports.get_history() + + @staticmethod + def change_pwd(user_id: str, password: str) -> bool: + "change users password" + return Users.change_pwd(user_id, password) + + @staticmethod + def submit_entry(user_id: str, entry: Decimal) -> bool: + "submit a bet" + return GameEngine().submit_entry(user_id, entry) + + @staticmethod + def register_user(value: dict[str, str]) -> str: # Python 3.9 + # def register_user(value) -> str: # Python 3.8 and earlier + "register a new user and returns the new id" + return Users.register_user(value) diff --git a/facade/game_engine.py b/facade/game_engine.py new file mode 100644 index 0000000..3dd6ecd --- /dev/null +++ b/facade/game_engine.py @@ -0,0 +1,56 @@ +"The Game Engine" +import time +from decimal import Decimal +from wallets import Wallets +from reports import Reports + + +class GameEngine(): + "The Game Engine" + _instance = None + _start_time: int = 0 + _clock: int = 0 + _entries: list[tuple[str, Decimal]] = [] # Python 3.9 + # _entries = [] # Python 3.8 or earlier + _game_open = True + + def __new__(cls): + if cls._instance is None: + cls._instance = GameEngine + cls._start_time = int(time.time()) + cls._clock = 60 + return cls._instance + + @classmethod + def get_game_state(cls) -> dict: + "Get a snapshot of the current game state" + now = int(time.time()) + time_remaining = cls._start_time - now + cls._clock + if time_remaining < 0: + time_remaining = 0 + cls._game_open = False + return { + "clock": time_remaining, + "game_open": cls._game_open, + "entries": cls._entries + } + + @classmethod + def submit_entry(cls, user_id: str, entry: Decimal) -> bool: + "Submit a new entry for the user in this game" + now = int(time.time()) + time_remaining = cls._start_time - now + cls._clock + if time_remaining > 0: + if Wallets.get_balance(user_id) > Decimal('1'): + if Wallets.adjust_balance(user_id, Decimal('-1')): + cls._entries.append((user_id, entry)) + Reports.log_event( + f"New entry `{entry}` submitted by `{user_id}`") + return True + Reports.log_event( + f"Problem adjusting balance for `{user_id}`") + return False + Reports.log_event(f"User Balance for `{user_id}` to low") + return False + Reports.log_event("Game Closed") + return False diff --git a/facade/reports.py b/facade/reports.py new file mode 100644 index 0000000..0f86456 --- /dev/null +++ b/facade/reports.py @@ -0,0 +1,24 @@ +"A Singleton Dictionary of Reported Events" +import time + + +class Reports(): + "A Singleton Dictionary of Reported Events" + _reports: dict[int, tuple[float, str]] = {} # Python 3.9 + # _reports = {} # Python 3.8 or earlier + _row_id = 0 + + def __new__(cls): + return cls + + @classmethod + def get_history(cls) -> dict: + "A method to retrieve all historic events" + return cls._reports + + @classmethod + def log_event(cls, event: str) -> bool: + "A method to add a new event to the record" + cls._reports[cls._row_id] = (time.time(), event) + cls._row_id = cls._row_id + 1 + return True diff --git a/facade/shop_facade.dot b/facade/shop_facade.dot deleted file mode 100644 index c6a5cdd..0000000 --- a/facade/shop_facade.dot +++ /dev/null @@ -1,11 +0,0 @@ -digraph "classes" { -charset="utf-8" -rankdir=BT -"0" [label="{Discount|\l|calc()\l}", shape="record"]; -"1" [label="{Fees|\l|calc()\l}", shape="record"]; -"2" [label="{Shipping|\l|calc()\l}", shape="record"]; -"3" [label="{ShopFacade|discount\lfees\lshipping\l|calc_price()\l}", shape="record"]; -"0" -> "3" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="discount", style="solid"]; -"1" -> "3" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="fees", style="solid"]; -"2" -> "3" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="shipping", style="solid"]; -} diff --git a/facade/shop_facade.png b/facade/shop_facade.png deleted file mode 100644 index b328f78..0000000 Binary files a/facade/shop_facade.png and /dev/null differ diff --git a/facade/shop_facade.py b/facade/shop_facade.py deleted file mode 100644 index b782fb1..0000000 --- a/facade/shop_facade.py +++ /dev/null @@ -1,40 +0,0 @@ -"""A Facade Demo""" - - -class Discount: - @staticmethod - def calc(value): - return value * 0.9 - - -class Shipping: - @staticmethod - def calc(): - return 5 - - -class Fees: - @staticmethod - def calc(value): - return value * 1.5 - - -# facade -class ShopFacade: - def __init__(self): - self.discount = Discount() - self.shipping = Shipping() - self.fees = Fees() - - def calc_price(self, price): - price = self.discount.calc(price) - price = self.fees.calc(price) - price += self.shipping.calc() - return price - - -# client -SHOPFACADE = ShopFacade() -BUYPRICE = 100 -COST = SHOPFACADE.calc_price(BUYPRICE) -print("The Sell Cost = %.2f" % COST) diff --git a/facade/users.py b/facade/users.py new file mode 100644 index 0000000..f199bee --- /dev/null +++ b/facade/users.py @@ -0,0 +1,46 @@ +"A Singleton Dictionary of Users" +from decimal import Decimal +from wallets import Wallets +from reports import Reports + + +class Users(): + "A Singleton Dictionary of Users" + _users: dict[str, dict[str, str]] = {} # Python 3.9 + # _users = {} # Python 3.8 or earlier + + def __new__(cls): + return cls + + @classmethod + def register_user(cls, new_user: dict[str, str]) -> str: # Python 3.9 + # def register_user(cls, new_user) -> str: # Python 3.8 or earlier + "register a user" + if not new_user["user_name"] in cls._users: + # generate really complicated unique user_id. + # Using the existing user_name as the id for simplicity + user_id = new_user["user_name"] + cls._users[user_id] = new_user + Reports.log_event(f"new user `{user_id}` created") + # create a wallet for the new user + Wallets().create_wallet(user_id) + # give the user a sign up bonus + Reports.log_event( + f"Give new user `{user_id}` sign up bonus of 10") + Wallets().adjust_balance(user_id, Decimal(10)) + return user_id + return "" + + @classmethod + def edit_user(cls, user_id: str, user: dict): + "do nothing" + print(user_id) + print(user) + return False + + @classmethod + def change_pwd(cls, user_id: str, password: str): + "do nothing" + print(user_id) + print(password) + return False diff --git a/facade/wallets.py b/facade/wallets.py new file mode 100644 index 0000000..996c610 --- /dev/null +++ b/facade/wallets.py @@ -0,0 +1,39 @@ +"A Singleton Dictionary of User Wallets" +from decimal import Decimal +from reports import Reports + + +class Wallets(): + "A Singleton Dictionary of User Wallets" + _wallets: dict[str, Decimal] = {} # Python 3.9 + # _wallets = {} # Python 3.8 or earlier + + def __new__(cls): + return cls + + @classmethod + def create_wallet(cls, user_id: str) -> bool: + "A method to initialize a users wallet" + if not user_id in cls._wallets: + cls._wallets[user_id] = Decimal('0') + Reports.log_event( + f"wallet for `{user_id}` created and set to 0") + return True + return False + + @classmethod + def get_balance(cls, user_id: str) -> Decimal: + "A method to check a users balance" + Reports.log_event( + f"Balance check for `{user_id}` = {cls._wallets[user_id]}") + return cls._wallets[user_id] + + @classmethod + def adjust_balance(cls, user_id: str, amount: Decimal) -> Decimal: + "A method to adjust a user balance up or down" + cls._wallets[user_id] = cls._wallets[user_id] + Decimal(amount) + Reports.log_event( + f"Balance adjustment for `{user_id}`. " + f"New balance = {cls._wallets[user_id]}" + ) + return cls._wallets[user_id] diff --git a/factory/Factory Design Pattern.md b/factory/Factory Design Pattern.md deleted file mode 100644 index d5a7802..0000000 --- a/factory/Factory Design Pattern.md +++ /dev/null @@ -1,46 +0,0 @@ -# Factory Design Pattern - -The Factory Pattern is a creational pattern that defines an Interface for creating an object and defers instantiation until runtime. - -Used when you don't know how many or what type of objects will be needed until or during runtime - -![Factory Pattern Overview](factory_pattern.png) - -The Factory Pattern in the context of a Chair Factory -![Factory Pattern In Context](factory_pattern_chair.png) - -```python -class ObjectFactory: - """Tha Factory Class""" - - @staticmethod - def get_concrete_object(object_type): - """A static method to get a concrete object of type class""" - try: - if object_type == "ObjectA": - return ObjectA() - if object_type == "ObjectB": - return ObjectB() - if object_type == "ObjectC": - return ObjectC() - raise AssertionError("Object Not Found") - except AssertionError as _e: - print(_e) - return None -``` - -Each Object implements a Common Interface -```python -class ObjectA(IObjectType): - """The Object Concrete Class which implements the IObjectType interface""" - - ... -``` - -Request from the factory at run time -```python -if __name__ == "__main__": - MY_OBJECT = ObjectFactory().get_concrete_object("ObjectB") - print(MY_OBJECT.dimensions()) -``` - diff --git a/factory/Factory Design Pattern.pdf b/factory/Factory Design Pattern.pdf deleted file mode 100644 index 0086754..0000000 Binary files a/factory/Factory Design Pattern.pdf and /dev/null differ diff --git a/factory/README.md b/factory/README.md index bcc23fa..4a6f2b9 100644 --- a/factory/README.md +++ b/factory/README.md @@ -1,45 +1,96 @@ # Factory Design Pattern -The Factory Pattern is a creational pattern that defines an Interface for creating an object and defers instantiation until runtime. - -Used when you don't know how many or what type of objects will be needed until or during runtime - -![Factory Pattern Overview](factory_pattern.png) - -The Factory Pattern in the context of a Chair Factory -![Factory Pattern In Context](factory_pattern_chair.png) - -```python -class ObjectFactory: - """Tha Factory Class""" - - @staticmethod - def get_concrete_object(object_type): - """A static method to get a concrete object of type class""" - try: - if object_type == "ObjectA": - return ObjectA() - if object_type == "ObjectB": - return ObjectB() - if object_type == "ObjectC": - return ObjectC() - raise AssertionError("Object Not Found") - except AssertionError as _e: - print(_e) - return None +## Videos + +Section | Video Links +-|- +Factory Overview | Factory Overview Factory Overview Factory Overview +Factory Use Case | Factory Use Case Factory Use Case Factory Use Case +**ABCMeta** Module | ABCMeta Module ABCMeta Module ABCMeta Module + +## Book + +Cover | Links +-|- +![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    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 + +## Overview + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Terminology + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Factory UML Diagram + +![Factory Pattern Overview](/img/factory_concept.svg) + +## Source Code + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Output + +``` bash +python ./factory/factory_concept.py +ConcreteProductB ``` -Each Object implements a Common Interface -```python -class ObjectA(IObjectType): - """The Object Concrete Class which implements the IObjectType interface""" +## Example Use Case + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Factory Example UML Diagram + +![Chair Factory](/img/factory_example.svg) + +## Output + +``` bash +python ./factory/client.py +{'width': 40, 'depth': 40, 'height': 40} - ... ``` -Request from the factory at run time -```python -if __name__ == "__main__": - MY_OBJECT = ObjectFactory().get_concrete_object("ObjectB") - print(MY_OBJECT.dimensions()) +## New Coding Concepts + +### ABCMeta + +ABCMeta classes are a development tool that help you to write classes that conform to a specified interface that you've designed. + +ABCMeta refers to **A**bstract **B**ase **C**lasses. + +The benefits of using ABCMeta classes to create abstract classes is that your IDE and Pylint will indicate to you at development time whether your inheriting classes conform to the class definition that you've asked them to. + +Abstract classes are not instantiated directly in your scripts, but instead inherited by subclasses that will provide the implementation code for the abstract methods. E.g., you don't create `IChair`, but you create `SmallChair` that implemented the methods described in the `IChair` interface. + +An abstract method is a method that is declared, but contains no implementation. The implementation happens at the class that inherits the abstract class. + +You don't need to use ABCMeta classes and interfaces that you have created in your final python code. You code will still work without them. + +You can try it by removing the interfaces from all of the chair classes above, and you will see that your python program will still run. + +eg, change + +``` python +class BigChair(IChair): ``` + +to + +``` python +class BigChair(): +``` + +and it will still work. + +While it is possible to ensure your classes are correct without using abstract classes, it is often easier to use abstract classes as a backup method of checking correctness, especially if your projects become very large and involve many developers. + +Note that in all my code examples, the abstract classes are prefixed with a capital **I**, to indicate that they are abstract interfaces. They have no code in their methods. They do not require a `self` or `cls` argument due to the use of `@staticmethod` . The inheriting class will implement the code in each of the methods that the abstract class is describing. If subclasses are inheriting an abstract base class, and they do not implement the methods as described, there will be [Pylint error or warning message (E0110)](/coding-conventions.md#common-pylint-warning-and-error-messages). + +See PEP 3119 : [https://www.python.org/dev/peps/pep-3119/](https://www.python.org/dev/peps/pep-3119/) + +## Summary + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ \ No newline at end of file diff --git a/factory/big_chair.py b/factory/big_chair.py new file mode 100644 index 0000000..9345d91 --- /dev/null +++ b/factory/big_chair.py @@ -0,0 +1,19 @@ +# pylint: disable=too-few-public-methods +"A Class of Chair" +from interface_chair import IChair + + +class BigChair(IChair): + "The Big Chair Concrete Class implements the IChair interface" + + def __init__(self): + self._height = 80 + self._width = 80 + self._depth = 80 + + def get_dimensions(self): + return { + "width": self._width, + "depth": self._depth, + "height": self._height + } diff --git a/factory/chair_factory.py b/factory/chair_factory.py index 2dc1b3d..68bb14f 100644 --- a/factory/chair_factory.py +++ b/factory/chair_factory.py @@ -1,75 +1,19 @@ -"""A Factory Pattern Example -The Factory Pattern is a creational pattern that defines an Interface for creating an object -and defers instantiation until runtime. -Used when you don't know how many or what type of objects will be needed until during runtime -""" - -from abc import ABCMeta, abstractstaticmethod - - -class IChair(metaclass=ABCMeta): # pylint: disable=too-few-public-methods - """The Chair Interface""" - - @abstractstaticmethod - def dimensions(): - """A static inteface method""" - - -class BigChair(IChair): # pylint: disable=too-few-public-methods - """The Big Chair Concrete Class which implements the IChair interface""" - - def __init__(self): - self._height = 80 - self._width = 80 - self._depth = 80 - - def dimensions(self): - return {"width": self._width, "depth": self._depth, "height": self._height} - - -class MediumChair(IChair): # pylint: disable=too-few-public-methods - """The Medium Chair Concrete Class which implements the IChair interface""" - - def __init__(self): - self._height = 60 - self._width = 60 - self._depth = 60 - - def dimensions(self): - return {"width": self._width, "depth": self._depth, "height": self._height} - - -class SmallChair(IChair): # pylint: disable=too-few-public-methods - """The Small Chair Concrete Class which implements the IChair interface""" - - def __init__(self): - self._height = 40 - self._width = 40 - self._depth = 40 - - def dimensions(self): - return {"width": self._width, "depth": self._depth, "height": self._height} +"The Factory Class" +from small_chair import SmallChair +from medium_chair import MediumChair +from big_chair import BigChair class ChairFactory: # pylint: disable=too-few-public-methods - """Tha Factory Class""" + "The Factory Class" @staticmethod def get_chair(chair): - """A static method to get a table""" - try: - if chair == "BigChair": - return BigChair() - if chair == "MediumChair": - return MediumChair() - if chair == "SmallChair": - return SmallChair() - raise AssertionError("Chair Not Found") - except AssertionError as _e: - print(_e) + "A static method to get a chair" + if chair == 'BigChair': + return BigChair() + if chair == 'MediumChair': + return MediumChair() + if chair == 'SmallChair': + return SmallChair() return None - - -if __name__ == "__main__": - CHAIR_FACTORY = ChairFactory().get_chair("SmallChair") - print(CHAIR_FACTORY.dimensions()) diff --git a/factory/classes_chair_factory.dot b/factory/classes_chair_factory.dot deleted file mode 100644 index fae051c..0000000 --- a/factory/classes_chair_factory.dot +++ /dev/null @@ -1,16 +0,0 @@ -digraph "classes_chair_factory" { -charset="utf-8" -rankdir=BT -{rank=same; 0,3,4 } -"0" [label="{Factory1|\l|get_object()\l}", shape="record"]; -"2" [label="{IChair|\l|get_object()\l}", shape="record"]; -"3" [label="{Factory2|\l|get_object()\l}", shape="record"]; -"4" [label="{Factory2|\l|get_object()\l}", shape="record"]; -"1" [label="{Factory|\l|get_object()\l}", shape="record"]; -"0" -> "2" [arrowhead="empty", arrowtail="none"]; -"3" -> "2" [arrowhead="empty", arrowtail="none"]; -"4" -> "2" [arrowhead="empty", arrowtail="none"]; -"1" -> "0" [arrowhead="open", arrowtail="none", style="dashed"]; -"1" -> "3" [arrowhead="open", arrowtail="none", style="dashed"]; -"1" -> "4" [arrowhead="open", arrowtail="none", style="dashed"]; -} diff --git a/factory/classes_factory.dot b/factory/classes_factory.dot deleted file mode 100644 index eb59de9..0000000 --- a/factory/classes_factory.dot +++ /dev/null @@ -1,12 +0,0 @@ -digraph "classes_chair_factory" { -charset="utf-8" -rankdir=BT -"0" [label="{Factory|\l|get_object()\l}", shape="record"]; -"1" [label="{IObject|\l|dimensions()\l}", shape="record"]; -"2" [label="{Object1|\l|dimensions()\l}", shape="record"]; -"3" [label="{Object2|\l|dimensions()\l}", shape="record"]; -"4" [label="{Object3|\l|dimensions()\l}", shape="record"]; -"2" -> "1" [arrowhead="empty", arrowtail="none"]; -"3" -> "1" [arrowhead="empty", arrowtail="none"]; -"4" -> "1" [arrowhead="empty", arrowtail="none"]; -} diff --git a/factory/client.py b/factory/client.py new file mode 100644 index 0000000..c4f9bc5 --- /dev/null +++ b/factory/client.py @@ -0,0 +1,7 @@ +"Factory Use Case Example Code" + +from chair_factory import ChairFactory + +# The Client +CHAIR = ChairFactory.get_chair("SmallChair") +print(CHAIR.get_dimensions()) diff --git a/factory/factory.png b/factory/factory.png deleted file mode 100644 index 90cc146..0000000 Binary files a/factory/factory.png and /dev/null differ diff --git a/factory/factory_concept.py b/factory/factory_concept.py new file mode 100644 index 0000000..6dbb529 --- /dev/null +++ b/factory/factory_concept.py @@ -0,0 +1,63 @@ +# pylint: disable=too-few-public-methods +# pylint: disable=arguments-differ +"The Factory Concept" +from abc import ABCMeta, abstractmethod + + +class IProduct(metaclass=ABCMeta): + "A Hypothetical Class Interface (Product)" + + @staticmethod + @abstractmethod + def create_object(): + "An abstract interface method" + + +class ConcreteProductA(IProduct): + "A Concrete Class that implements the IProduct interface" + + def __init__(self): + self.name = "ConcreteProductA" + + def create_object(self): + return self + + +class ConcreteProductB(IProduct): + "A Concrete Class that implements the IProduct interface" + + def __init__(self): + self.name = "ConcreteProductB" + + def create_object(self): + return self + + +class ConcreteProductC(IProduct): + "A Concrete Class that implements the IProduct interface" + + def __init__(self): + self.name = "ConcreteProductC" + + def create_object(self): + return self + + +class Creator: + "The Factory Class" + + @staticmethod + def create_object(some_property): + "A static method to get a concrete product" + if some_property == 'a': + return ConcreteProductA() + if some_property == 'b': + return ConcreteProductB() + if some_property == 'c': + return ConcreteProductC() + return None + + +# The Client +PRODUCT = Creator.create_object('b') +print(PRODUCT.name) diff --git a/factory/factory_pattern.png b/factory/factory_pattern.png deleted file mode 100644 index cc01bdb..0000000 Binary files a/factory/factory_pattern.png and /dev/null differ diff --git a/factory/factory_pattern_chair.png b/factory/factory_pattern_chair.png deleted file mode 100644 index 98ac417..0000000 Binary files a/factory/factory_pattern_chair.png and /dev/null differ diff --git a/factory/interface_chair.py b/factory/interface_chair.py new file mode 100644 index 0000000..978e020 --- /dev/null +++ b/factory/interface_chair.py @@ -0,0 +1,12 @@ +# pylint: disable=too-few-public-methods +"The Chair Interface" +from abc import ABCMeta, abstractmethod + + +class IChair(metaclass=ABCMeta): + "The Chair Interface (Product)" + + @staticmethod + @abstractmethod + def get_dimensions(): + "A static interface method" diff --git a/factory/medium_chair.py b/factory/medium_chair.py new file mode 100644 index 0000000..c6c6e1f --- /dev/null +++ b/factory/medium_chair.py @@ -0,0 +1,19 @@ +# pylint: disable=too-few-public-methods +"A Class of Chair" +from interface_chair import IChair + + +class MediumChair(IChair): + "The Medium Chair Concrete Class implements the IChair interface" + + def __init__(self): + self._height = 60 + self._width = 60 + self._depth = 60 + + def get_dimensions(self): + return { + "width": self._width, + "depth": self._depth, + "height": self._height + } diff --git a/factory/small_chair.py b/factory/small_chair.py new file mode 100644 index 0000000..9db8d18 --- /dev/null +++ b/factory/small_chair.py @@ -0,0 +1,19 @@ +# pylint: disable=too-few-public-methods +"A Class of Chair" +from interface_chair import IChair + + +class SmallChair(IChair): + "The Small Chair Concrete Class implements the IChair interface" + + def __init__(self): + self._height = 40 + self._width = 40 + self._depth = 40 + + def get_dimensions(self): + return { + "width": self._width, + "depth": self._depth, + "height": self._height + } diff --git a/flyweight/README.md b/flyweight/README.md new file mode 100644 index 0000000..e0fbbc2 --- /dev/null +++ b/flyweight/README.md @@ -0,0 +1,89 @@ +# Flyweight Design Pattern + +## Videos + +Section | Video Links +-|- +Flyweight Overview | Flyweight Overview Flyweight Overview Flyweight Overview +Flyweight Use Case | Flyweight Use Case Flyweight Use Case Flyweight Use Case +String Justification | String Justification String Justification String Justification + +## Book + +Cover | Links +-|- +![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    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 + +## Overview + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Terminology + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Flyweight UML Diagram + +![Flyweight Pattern UML Diagram](/img/flyweight_concept.svg) + +## Source Code + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Output + +``` bash +python ./flyweight/flyweight_concept.py +abracadabra +abracadabra has 11 letters +FlyweightFactory has 5 flyweights +``` + +## Example Use Case + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Example UML Diagram + +![Flyweight Pattern Use Case UML Diagram](/img/flyweight_example.svg) + +## Output + +``` bash +python ./flyweight/client.py +----------------------------------------- +|abra | 112233 | cadabra| +|racadab | 12345 | 332211| +|cadabra | 445566 | aa 22 bb| +----------------------------------------- +FlyweightFactory has 12 flyweights +``` + +## New Coding Concepts + +### String Justification + +In [/flyweight/column.py](/flyweight/column.py), there are commands `center()`, `ljust()` and `rjust()` . + +These are special commands on strings that allow you to pad strings and align them left, right, center depending on total string length. + +eg, + +``` powershell +>>> "abcd".center(10) +' abcd ' +``` + +``` powershell +>>> "abcd".rjust(10) +' abcd' +``` + +``` powershell +>>> "abcd".ljust(10) +'abcd ' +``` + +## Summary + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ \ No newline at end of file diff --git a/flyweight/client.py b/flyweight/client.py new file mode 100644 index 0000000..b84d864 --- /dev/null +++ b/flyweight/client.py @@ -0,0 +1,30 @@ +"The Flyweight Use Case Example" + +from table import Table +from flyweight_factory import FlyweightFactory + +TABLE = Table(3, 3) + +TABLE.rows[0].columns[0].data = "abra" +TABLE.rows[0].columns[1].data = "112233" +TABLE.rows[0].columns[2].data = "cadabra" +TABLE.rows[1].columns[0].data = "racadab" +TABLE.rows[1].columns[1].data = "12345" +TABLE.rows[1].columns[2].data = "332211" +TABLE.rows[2].columns[0].data = "cadabra" +TABLE.rows[2].columns[1].data = "445566" +TABLE.rows[2].columns[2].data = "aa 22 bb" + +TABLE.rows[0].columns[0].justify = 1 +TABLE.rows[1].columns[0].justify = 1 +TABLE.rows[2].columns[0].justify = 1 +TABLE.rows[0].columns[2].justify = 2 +TABLE.rows[1].columns[2].justify = 2 +TABLE.rows[2].columns[2].justify = 2 +TABLE.rows[0].columns[1].width = 15 +TABLE.rows[1].columns[1].width = 15 +TABLE.rows[2].columns[1].width = 15 + +TABLE.draw() + +print(f"FlyweightFactory has {FlyweightFactory.get_count()} flyweights") diff --git a/flyweight/column.py b/flyweight/column.py new file mode 100644 index 0000000..36f24bc --- /dev/null +++ b/flyweight/column.py @@ -0,0 +1,25 @@ +"A Column that is used in a Row" +from flyweight_factory import FlyweightFactory + +class Column(): # pylint: disable=too-few-public-methods + """ + The columns are the contexts. + They will share the Flyweights via the FlyweightsFactory. + `data`, `width` and `justify` are extrinsic values. They are outside + of the flyweights. + """ + + def __init__(self, data="", width=11, justify=0) -> None: + self.data = data + self.width = width + self.justify = justify # 0:center, 1:left, 2:right + + def get_data(self): + "Get the flyweight value from the factory, and apply the extrinsic values" + ret = "" + for data in self.data: + ret = ret + FlyweightFactory.get_flyweight(data).code + ret = f"{ret.center(self.width)}" if self.justify == 0 else ret + ret = f"{ret.ljust(self.width)}" if self.justify == 1 else ret + ret = f"{ret.rjust(self.width)}" if self.justify == 2 else ret + return ret diff --git a/flyweight/flyweight.py b/flyweight/flyweight.py new file mode 100644 index 0000000..e7e33de --- /dev/null +++ b/flyweight/flyweight.py @@ -0,0 +1,8 @@ +"The Flyweight that contains an intrinsic value called code" + + +class Flyweight(): # pylint: disable=too-few-public-methods + "The Flyweight that contains an intrinsic value called code" + + def __init__(self, code: str) -> None: + self.code = code diff --git a/flyweight/flyweight_concept.py b/flyweight/flyweight_concept.py new file mode 100644 index 0000000..daa478f --- /dev/null +++ b/flyweight/flyweight_concept.py @@ -0,0 +1,62 @@ +# pylint: disable=too-few-public-methods +"The Flyweight Concept" + + +class IFlyweight(): + "Nothing to implement" + + +class Flyweight(IFlyweight): + "The Concrete Flyweight" + + def __init__(self, code: str) -> None: + self.code = code + + +class FlyweightFactory(): + "Creating the FlyweightFactory as a singleton" + + _flyweights: dict[str, Flyweight] = {} # Python 3.9 + # _flyweights = {} # Python 3.8 or earlier + + def __new__(cls): + return cls + + @classmethod + def get_flyweight(cls, code: str) -> Flyweight: + "A static method to get a flyweight based on a code" + if not code in cls._flyweights: + cls._flyweights[code] = Flyweight(code) + return cls._flyweights[code] + + @classmethod + def get_count(cls) -> int: + "Return the number of flyweights in the cache" + return len(cls._flyweights) + + +class Context(): + """ + An example context that holds references to the flyweights in a + particular order and converts the code to an ascii letter + """ + + def __init__(self, codes: str) -> None: + self.codes = list(codes) + + def output(self): + "The context specific output that uses flyweights" + ret = "" + for code in self.codes: + ret = ret + FlyweightFactory.get_flyweight(code).code + return ret + + +# The Client +CONTEXT = Context("abracadabra") + +# use flyweights in a context +print(CONTEXT.output()) + +print(f"abracadabra has {len('abracadabra')} letters") +print(f"FlyweightFactory has {FlyweightFactory.get_count()} flyweights") diff --git a/flyweight/flyweight_factory.py b/flyweight/flyweight_factory.py new file mode 100644 index 0000000..9206d38 --- /dev/null +++ b/flyweight/flyweight_factory.py @@ -0,0 +1,24 @@ +"Creating the FlyweightFactory as a singleton" +from flyweight import Flyweight + + +class FlyweightFactory(): + "Creating the FlyweightFactory as a singleton" + + _flyweights: dict[str, Flyweight] = {} # Python 3.9 + # _flyweights = {} # Python 3.8 or earlier + + def __new__(cls): + return cls + + @classmethod + def get_flyweight(cls, code: str) -> Flyweight: + "A static method to get a flyweight based on a code" + if not code in cls._flyweights: + cls._flyweights[code] = Flyweight(code) + return cls._flyweights[code] + + @classmethod + def get_count(cls) -> int: + "Return the number of flyweights in the cache" + return len(cls._flyweights) diff --git a/flyweight/row.py b/flyweight/row.py new file mode 100644 index 0000000..5ea7df2 --- /dev/null +++ b/flyweight/row.py @@ -0,0 +1,18 @@ +"A Row in the Table" +from column import Column + + +class Row(): # pylint: disable=too-few-public-methods + "A Row in the Table" + + def __init__(self, column_count: int) -> None: + self.columns = [] + for _ in range(column_count): + self.columns.append(Column()) + + def get_data(self): + "Format the row before returning it to the table" + ret = "" + for column in self.columns: + ret = f"{ret}{column.get_data()}|" + return ret diff --git a/flyweight/table.py b/flyweight/table.py new file mode 100644 index 0000000..96c455f --- /dev/null +++ b/flyweight/table.py @@ -0,0 +1,27 @@ +"A Formatted Table that includes rows and columns" + +from row import Row + + +class Table(): # pylint: disable=too-few-public-methods + "A Formatted Table" + + def __init__(self, row_count: int, column_count: int) -> None: + self.rows = [] + for _ in range(row_count): + self.rows.append(Row(column_count)) + + def draw(self): + "Draws the table formatted in the console" + max_row_length = 0 + rows = [] + for row in self.rows: + row_data = row.get_data() + rows.append(f"|{row_data}") + row_length = len(row_data) + 1 + if max_row_length < row_length: + max_row_length = row_length + print("-" * max_row_length) + for row in rows: + print(row) + print("-" * max_row_length) diff --git a/img/abstract_factory_concept.svg b/img/abstract_factory_concept.svg new file mode 100644 index 0000000..3e2903d --- /dev/null +++ b/img/abstract_factory_concept.svg @@ -0,0 +1,3 @@ + + +
Subclasses Implementing a common Interface
Subclasses Implementing a common Interface
IClass+ field: type+ create_object(type): typeClassA+ field: type+ create_object(type): typeClassB+ field: type+ create_object(type): typeClassC+ field: type+ create_object(type): type
Client Application
Client Application
FactoryA+ field: type+ create_object(type): type
Subclasses Implementing a common Interface
Subclasses Implementing a common Interface
IClass+ field: type+ create_object(type): typeClassA+ field: type+ create_object(type): typeClassB+ field: type+ create_object(type): typeClassC+ field: type+ create_object(type): typeFactoryB+ field: type+ create_object(type): typeAbstractFactory+ field: type+ create_object(type): typeIAbstractFactory+ field: type+ create_object(type): type
\ No newline at end of file diff --git a/img/abstract_furniture_factory.svg b/img/abstract_furniture_factory.svg new file mode 100644 index 0000000..6c3b9e2 --- /dev/null +++ b/img/abstract_furniture_factory.svg @@ -0,0 +1,3 @@ + + +
Subclasses Implementing a common Interface
Subclasses Implementing a common Interface
IChair+ get_dimensions(): dictSmallChair- _height: int- _width: int- _depth : int+ get_dimensions(): dictMediumChair- _height: int- _width: int- _depth : int+ get_dimensions(): dictBigChair- _height: int- _width: int- _depth : int+ get_dimensions(): dict
Client Application
Client Application
ChairFactory+ get_chair(type): type
Subclasses Implementing a common Interface
Subclasses Implementing a common Interface
ITable+ get_dimensions(): dictSmallTable- _height: int- _width: int- _depth : int+ get_dimensions(): dictMediumTable- _height: int- _width: int- _depth : int+ get_dimensions(): dictBigTable- _height: int- _width: int- _depth : int+ get_dimensions(): dictTableFactory+ get_table(type): typeFurnitureFactory+ get_furniture(type): typeIFurnitureFactory+ get_furniture(type): type
\ No newline at end of file diff --git a/img/adapter_concept.svg b/img/adapter_concept.svg new file mode 100644 index 0000000..df0bdda --- /dev/null +++ b/img/adapter_concept.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
IA+ field: type+ method_a(type): typeClassA+ field: type+ method_a(type): typeClassB+ field: type+ method_b(type): typeIB+ field: type+ method_b(type): typeClassBAdapter+ field: type+ method_a(type): type
\ No newline at end of file diff --git a/img/adapter_example.svg b/img/adapter_example.svg new file mode 100644 index 0000000..61350eb --- /dev/null +++ b/img/adapter_example.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
ICubeA+ width: int+ height: int+ depth: int+ manufacture(w, h, d)CubeA+ width: int+ height: int+ depth: int+ manufacture(w, h, d)CubeB+ top_left_front: [int, int, int]+ bottom_right_back: [int, int, int]+ create( tlf, brb)ICubeB+ top_left_front: [int, int, int]+ bottom_right_back: [int, int, int]+ create( tlf, brb)CubeBAdapter+ width: int+ height: int+ depth: int+ manufacture(w, h, d)
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/img/aggregates.svg b/img/aggregates.svg new file mode 100644 index 0000000..58e6d32 --- /dev/null +++ b/img/aggregates.svg @@ -0,0 +1,3 @@ + + +ClassB+ field: type+ method(type): typeBook+ field: type+ method(type): type
Conceptual
Conceptual
Example
Example
ClassA+ field: type+ method(type): typeLibrary+ field: type+ method(type): type
\ No newline at end of file diff --git a/img/basic_class.svg b/img/basic_class.svg new file mode 100644 index 0000000..d717b4c --- /dev/null +++ b/img/basic_class.svg @@ -0,0 +1,3 @@ + + +Classname+ field1: type- _field2: type- method_a(type): type+ method_b(type): type# method_c(type): typeCar- _wheel_count: int+ running: false+ start_engine(void): bool+ set_speed(int): void
Conceptual
Conceptual
Example
Example
public
public
private
private
\ No newline at end of file diff --git a/img/bridge_concept.svg b/img/bridge_concept.svg new file mode 100644 index 0000000..b45ea91 --- /dev/null +++ b/img/bridge_concept.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
IImplementer+ method(type): typeIAbstraction+ method(type): typeConcreteImplementerA+ method(type): typeConcreteImplementerB+ method(type): typeRefinedAbstractionA+ implementer: type+ method(type): typeRefinedAbstractionB+ implementer: type+ method(type): type
\ No newline at end of file diff --git a/img/bridge_example.svg b/img/bridge_example.svg new file mode 100644 index 0000000..3bd2b6c --- /dev/null +++ b/img/bridge_example.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
IShapeImplementer+ draw_implementation()IShape+ draw()SquareImplementer+ draw_implementation()CircleImplementer+ draw_implementation()Square+ implementer: type+ draw()Circle+ implementer: type+ draw()
\ No newline at end of file diff --git a/img/builder_concept.svg b/img/builder_concept.svg new file mode 100644 index 0000000..4554c02 --- /dev/null +++ b/img/builder_concept.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
Director+ construct(type): typeIBuilder+ build_part_a(type): type+ build_part_b(type): type+ build_part_c(type): typeBuilder+ build_part_a(type): type+ build_part_b(type): type+ build_part_c(type): typeProduct+ parts(type): type
\ No newline at end of file diff --git a/img/builder_example.svg b/img/builder_example.svg new file mode 100644 index 0000000..df60679 --- /dev/null +++ b/img/builder_example.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
IglooDirector+ construct(type): typeIHouseBuilder+ set_building_type(int)+ set_number_walls(int)+ set_number_windows(int)+ set_number_doors(int)+ get_result()HouseBuilder+ set_building_type(int)+ set_number_walls(int)+ set_number_windows(int)+ set_number_doors(int)+ get_result()CastleDirector+ construct(type): typeHouseBoatDirector+ construct(type): typeHouse+ buildng_type: str+ doors: int+ wall_material: str+ windows: int+ construction(): str
\ No newline at end of file diff --git a/img/by-nc.png b/img/by-nc.png new file mode 100644 index 0000000..6b175f6 Binary files /dev/null and b/img/by-nc.png differ diff --git a/img/chain_of_responsibility_concept.svg b/img/chain_of_responsibility_concept.svg new file mode 100644 index 0000000..e2bee07 --- /dev/null +++ b/img/chain_of_responsibility_concept.svg @@ -0,0 +1,3 @@ + + +
Client
Client
Successor1+ handle(payload)Successor2+ handle(payload)IHandler+ handle(payload)
\ No newline at end of file diff --git a/img/chain_of_responsibility_example.svg b/img/chain_of_responsibility_example.svg new file mode 100644 index 0000000..2b62cbf --- /dev/null +++ b/img/chain_of_responsibility_example.svg @@ -0,0 +1,3 @@ + + +
Client
Client
ATMDispenserChain+ chain1: Despenser10+ chain1: Despenser20+ chain1: Despenser50Dispenser10- _successor: IDispenser+ next_successor(IDispensor)+ handle(amount)IDispenser+ next_successor(IDispensor)+ handle(amount)Dispenser50- _successor: IDispenser+ next_successor(IDispensor)+ handle(amount)Dispenser20- _successor: IDispenser+ next_successor(IDispensor)+ handle(amount)
\ No newline at end of file diff --git a/img/class_extends.svg b/img/class_extends.svg new file mode 100644 index 0000000..9c9b441 --- /dev/null +++ b/img/class_extends.svg @@ -0,0 +1,3 @@ + + +ExtendedClass+ another_field: type+ another_method(type): typeFancy Car+ turbo_on: bool+ enable_turbo(bool): void
Conceptual
Conceptual
Example
Example
Class+ field: type+ method(type): typeCar+ wheel_count: int+ running: false+ start_engine(void): bool+ set_speed(int): void
\ No newline at end of file diff --git a/img/class_implements.svg b/img/class_implements.svg new file mode 100644 index 0000000..c0e1c63 --- /dev/null +++ b/img/class_implements.svg @@ -0,0 +1,3 @@ + + +ClassA+ field: type+ method(type): typeCar+ wheel_count: int+ running: false+ start_engine(void): bool+ set_speed(int): void
Conceptual
Conceptual
Example
Example
IClassname+ method(type): typeICar+ start_engine(void): bool+ set_speed(int): void
\ No newline at end of file diff --git a/img/command_concept.svg b/img/command_concept.svg new file mode 100644 index 0000000..bd326c1 --- /dev/null +++ b/img/command_concept.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
ICommand+ execute(type): typeReceiver+ run_command1(type): type+ run_command2(type): typeInvoker- _commands: type+ register(type): type+ execute(type): typeCommand1- _receiver: type+ __init__(receiver)+ method(type): typeCommand2- _receiver: type+ __init__(receiver)+ method(type): type
\ No newline at end of file diff --git a/img/command_example.svg b/img/command_example.svg new file mode 100644 index 0000000..1e14783 --- /dev/null +++ b/img/command_example.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
ISwitch+ execute()Light+ turn_on()+ turn_off()Switch- _commands: type+ register(command_name, command)+ execute(command_name)+ show_history()+ replay_last(number_of_commands)SwitchOffCommand- _receiver: light+ __init__(light)+ execute()SwitchOnCommand- _receiver: light+ __init__(light)+ execute()
\ No newline at end of file diff --git a/img/composite_concept.svg b/img/composite_concept.svg new file mode 100644 index 0000000..482c100 --- /dev/null +++ b/img/composite_concept.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
Leaf+ reference_to_parent: type+ method(type): type+ detach(type): typeComposite+ components: list+ reference_to_parent: type+ method(type): type+ attach(type): type+ detach(type): type+ delete(type): typeIComponent+ reference_to_parent: type+ method(type): type+ detach(type): type
\ No newline at end of file diff --git a/img/composite_example.svg b/img/composite_example.svg new file mode 100644 index 0000000..dfe3511 --- /dev/null +++ b/img/composite_example.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
File+ reference_to_parent: type+ dir(indent)+ detach()Folder+ components: list+ reference_to_parent: type+ dir(indent)+ attach(component)+ detach()+ delete(component)IComponent+ reference_to_parent: type+ dir(indent)+ detach()
\ No newline at end of file diff --git a/img/composition.svg b/img/composition.svg new file mode 100644 index 0000000..8a3836f --- /dev/null +++ b/img/composition.svg @@ -0,0 +1,3 @@ + + +ClassB+ field: type+ method(type): typeWings+ field: type+ method(type): type
Conceptual
Conceptual
Example
Example
ClassA+ field: type+ method(type): typeAeroplane+ field: type+ method(type): type
\ No newline at end of file diff --git a/img/decorator_concept.svg b/img/decorator_concept.svg new file mode 100644 index 0000000..1d69fed --- /dev/null +++ b/img/decorator_concept.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
IComponent+ method(type): typeComponent+ field: type+ method(type): typeDecorator+ field: type+ method(type): type
\ No newline at end of file diff --git a/img/decorator_example.svg b/img/decorator_example.svg new file mode 100644 index 0000000..a96e383 --- /dev/null +++ b/img/decorator_example.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
IValue+ __str__()Value+ value: int+ __str__(self): self.valueAdd+ value: int+ __str__(self): self.valueSub+ value: int+ __str__(self): self.value
\ No newline at end of file diff --git a/img/design_patterns_in_python_book.jpg b/img/design_patterns_in_python_book.jpg new file mode 100644 index 0000000..e9f2427 Binary files /dev/null and b/img/design_patterns_in_python_book.jpg differ diff --git a/img/design_patterns_in_python_book_125x178.jpg b/img/design_patterns_in_python_book_125x178.jpg new file mode 100644 index 0000000..0cf8aaa Binary files /dev/null and b/img/design_patterns_in_python_book_125x178.jpg differ diff --git a/img/directed_association.svg b/img/directed_association.svg new file mode 100644 index 0000000..97025f2 --- /dev/null +++ b/img/directed_association.svg @@ -0,0 +1,3 @@ + + +ClassA+ field: type+ method(type): typePerson+ field: type+ method(type): type
Conceptual
Conceptual
Example
Example
ClassB+ field: type+ method(type): typeCar+ field: type+ start_engine(void): bool+ set_speed(int): void
\ No newline at end of file diff --git a/img/dp_python_125.gif b/img/dp_python_125.gif new file mode 100644 index 0000000..601a5b3 Binary files /dev/null and b/img/dp_python_125.gif differ diff --git a/img/dp_python_250.jpg b/img/dp_python_250.jpg new file mode 100644 index 0000000..2305ab0 Binary files /dev/null and b/img/dp_python_250.jpg differ diff --git a/img/dp_typescript_250.jpg b/img/dp_typescript_250.jpg new file mode 100644 index 0000000..ccb35c1 Binary files /dev/null and b/img/dp_typescript_250.jpg differ diff --git a/img/facade_concept.svg b/img/facade_concept.svg new file mode 100644 index 0000000..9802e8c --- /dev/null +++ b/img/facade_concept.svg @@ -0,0 +1,3 @@ + + +Facade+ method_a(type): type+ method_b(type): type+ method_c(type): type+ method_d(type): type+ method_e(type): typeSubSystemA+ field: type+ method_a(type): typeSubSystemC+ field: type+ method_c(type): typeSubSystemB+ field: type+ method_b(type): typeSubSystemD+ field: type+ method_d(type): typeSubSystemE+ field: type+ method_e(type): type
Client
Client
\ No newline at end of file diff --git a/img/facade_example.svg b/img/facade_example.svg new file mode 100644 index 0000000..6b58d66 --- /dev/null +++ b/img/facade_example.svg @@ -0,0 +1,3 @@ + + +GameAPI+ get_balance(user_id): decimal+ game_state(game_id): dict+ get_history(): dict+ change_pwd(user_id): bool+ submit_entry(user_id, int): bool+ register_user(dict): strWallets- _balance: decimal+ get_balance(user_id): decimal+ adjust_balance(user_id, decimal):decimal+ create_wallet(user_id): boolReports+ get_history(): dict+ log_event(event): boolGameEngine- _clock: int- _entries: dict+ game_state: dict+ clock: int+ entries: list+ submit_entry(user_id, int): boolUsers- _user_id: str- _user_name: str- _password: str+ register_user(dict): int+ edit_user(user_id, dict): bool+ change_pwd(user_id, str) : bool
Client
Client
\ No newline at end of file diff --git a/img/factory_concept.svg b/img/factory_concept.svg new file mode 100644 index 0000000..aa55c0e --- /dev/null +++ b/img/factory_concept.svg @@ -0,0 +1,3 @@ + + +
Subclasses Implementing a common Interface
Subclasses Implementing a common Interface
IClass+ field: type+ create_object(type): typeClassA+ field: type+ create_object(type): typeClassB+ field: type+ create_object(type): typeClassC+ field: type+ create_object(type): type
Client Application
Client Application
FactoryClass+ field: type+ create_object(type): type
\ No newline at end of file diff --git a/img/factory_example.svg b/img/factory_example.svg new file mode 100644 index 0000000..5289c48 --- /dev/null +++ b/img/factory_example.svg @@ -0,0 +1,3 @@ + + +
Subclasses Implementing a common Interface
Subclasses Implementing a common Interface
IChair+ get_dimensions(): dictSmallChair- _height: int- _width: int- _depth : int+ get_dimensions(): dictMediumChair- _height: int- _width: int- _depth : int+ get_dimensions(): dictBigChair- _height: int- _width: int- _depth : int+ get_dimensions(): dict
Client Application
Client Application
ChairFactory+ get_chair(string): object
\ No newline at end of file diff --git a/img/factory_swimlane.svg b/img/factory_swimlane.svg new file mode 100644 index 0000000..c1b00f2 --- /dev/null +++ b/img/factory_swimlane.svg @@ -0,0 +1,3 @@ + + +
Subclasses Implementing a common Interface
Subclasses Implementing a common Interface
IClass+ field: type+ createObject(type): objectClassA+ field: type+ createObject(type): objectClassB+ field: type+ createObject(type): objectClassC+ field: type+ createObject(type): object
Client Application
Client Application
FactoryClass+ field: type+ createObject(type): object
Get
Object
Get...
Return
Object
Return...
\ No newline at end of file diff --git a/img/flag_au.gif b/img/flag_au.gif new file mode 100644 index 0000000..bcfe17a Binary files /dev/null and b/img/flag_au.gif differ diff --git a/img/flag_ca.gif b/img/flag_ca.gif new file mode 100644 index 0000000..c1a9363 Binary files /dev/null and b/img/flag_ca.gif differ diff --git a/img/flag_de.gif b/img/flag_de.gif new file mode 100644 index 0000000..d2a4c7b Binary files /dev/null and b/img/flag_de.gif differ diff --git a/img/flag_es.gif b/img/flag_es.gif new file mode 100644 index 0000000..894dfcf Binary files /dev/null and b/img/flag_es.gif differ diff --git a/img/flag_fr.gif b/img/flag_fr.gif new file mode 100644 index 0000000..ccc1808 Binary files /dev/null and b/img/flag_fr.gif differ diff --git a/img/flag_in.gif b/img/flag_in.gif new file mode 100644 index 0000000..21008e1 Binary files /dev/null and b/img/flag_in.gif differ diff --git a/img/flag_it.gif b/img/flag_it.gif new file mode 100644 index 0000000..1043bce Binary files /dev/null and b/img/flag_it.gif differ diff --git a/img/flag_jp.gif b/img/flag_jp.gif new file mode 100644 index 0000000..5c98a2d Binary files /dev/null and b/img/flag_jp.gif differ diff --git a/img/flag_uk.gif b/img/flag_uk.gif new file mode 100644 index 0000000..7938447 Binary files /dev/null and b/img/flag_uk.gif differ diff --git a/img/flag_us.gif b/img/flag_us.gif new file mode 100644 index 0000000..d6b659b Binary files /dev/null and b/img/flag_us.gif differ diff --git a/img/flyweight_concept.svg b/img/flyweight_concept.svg new file mode 100644 index 0000000..9f12f34 --- /dev/null +++ b/img/flyweight_concept.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
Context+ field: type+ method(type): typeFlyweightFactory+ field: type+ get_flyweight(type): typeFlyweight+ field: type+ method(type): typeIFlyweight+ field: type+ method(type): type
\ No newline at end of file diff --git a/img/flyweight_example.svg b/img/flyweight_example.svg new file mode 100644 index 0000000..79842e5 --- /dev/null +++ b/img/flyweight_example.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
Table+ rows: list+ draw()FlyweightFactory- _flyweights: dict+ get_flyweight(code): FlyweightFlyweight+ code: intRow+ columns: list+ get_data(): strColumn+ data: str+ width: int+ justify: int+ get_data(): str
\ No newline at end of file diff --git a/img/ide_hint.jpg b/img/ide_hint.jpg new file mode 100644 index 0000000..aed9cda Binary files /dev/null and b/img/ide_hint.jpg differ diff --git a/img/interpreter_ast.svg b/img/interpreter_ast.svg new file mode 100644 index 0000000..480ea2a --- /dev/null +++ b/img/interpreter_ast.svg @@ -0,0 +1,3 @@ + + +
terminal
3
terminal...
non-terminal
+
non-terminal...
terminal
5
terminal...
terminal
4
terminal...
non-terminal
-
non-terminal...
terminal
7
terminal...
non-terminal
+
non-terminal...
terminal
2
terminal...
non-terminal
-
non-terminal...
\ No newline at end of file diff --git a/img/interpreter_ast_roman_numeral.svg b/img/interpreter_ast_roman_numeral.svg new file mode 100644 index 0000000..2aea838 --- /dev/null +++ b/img/interpreter_ast_roman_numeral.svg @@ -0,0 +1,3 @@ + + +
terminal
3
terminal...
non-terminal
+
non-terminal...
terminal
5
terminal...
non-terminal
IV
non-terminal...
non-terminal
-
non-terminal...
non-terminal
VII
non-terminal...
non-terminal
+
non-terminal...
terminal
2
terminal...
non-terminal
-
non-terminal...
terminal
4
terminal...
terminal
7
terminal...
\ No newline at end of file diff --git a/img/interpreter_concept.svg b/img/interpreter_concept.svg new file mode 100644 index 0000000..0f22d92 --- /dev/null +++ b/img/interpreter_concept.svg @@ -0,0 +1,3 @@ + + +
Abstract Syntax Tree
Abstract Syntax Tree
Client Application
Client Application
AbstractExpression+ interpret()NonTerminalExpression+ left+ right+ interpret()TerminalExpression+ value+ interpret()
Context
Context
\ No newline at end of file diff --git a/img/interpreter_example.svg b/img/interpreter_example.svg new file mode 100644 index 0000000..bccd8b0 --- /dev/null +++ b/img/interpreter_example.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
AbstractExpression+ interpret()Add+ left+ right+ interpret()Number+ value+ interpret()RomanNumeral+ roman_numeral+ context+ interpret()Subtract+ left+ right+ interpret()RomanNumeral1+ one+ four+ five+ nine+ multiplier+ interpret()RomanNumeral1000+ one+ four+ five+ nine+ multiplier+ interpret()RomanNumeral10+ one+ four+ five+ nine+ multiplier+ interpret()RomanNumeral100+ one+ four+ five+ nine+ multiplier+ interpret()
\ No newline at end of file diff --git a/img/iterator_concept.svg b/img/iterator_concept.svg new file mode 100644 index 0000000..1c37d05 --- /dev/null +++ b/img/iterator_concept.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
IIterator+ has_next(): bool+ next(): objectIterator+ index: int+ collection: list+ has_next(): bool+ next(): objectIAggregate+ field: type+ method(type): typeIAggregate+ field: type+ method(type): type
\ No newline at end of file diff --git a/img/iterator_example.svg b/img/iterator_example.svg new file mode 100644 index 0000000..396a041 --- /dev/null +++ b/img/iterator_example.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
NumberWheel+ index: int+ next(): objectreturn self.index * 2 % 11
\ No newline at end of file diff --git a/img/mediator_concept.svg b/img/mediator_concept.svg new file mode 100644 index 0000000..050e622 --- /dev/null +++ b/img/mediator_concept.svg @@ -0,0 +1,3 @@ + + +Mediator+ field: type+ method_1(type): type+ method_2(type): typeColleague1+ field: type+ method_1(type): typeColleague2+ field: type+ method_2(type): typeColleague1+ field: type+ method_1(type): typeColleague2+ field: type+ method_2(type): type
Before
Before
After
After
\ No newline at end of file diff --git a/img/mediator_example.svg b/img/mediator_example.svg new file mode 100644 index 0000000..364b8e6 --- /dev/null +++ b/img/mediator_example.svg @@ -0,0 +1,3 @@ + + +Component1- _mediator- _name+ notify(message)+ receive(message)Mediator- _components: set()+ add(component)+ notify(message, originator)IComponent+ notify(message)+ receive(message)Component2- _mediator- _name+ notify(message)+ receive(message)Component3- _mediator- _name+ notify(message)+ receive(message)...if component != originator:    component.receive(message) \ No newline at end of file diff --git a/img/memento_concept.svg b/img/memento_concept.svg new file mode 100644 index 0000000..4758897 --- /dev/null +++ b/img/memento_concept.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
CareTaker- _originator- _mementos+ create()+ restore()Memento+ stateOriginator+ state+ memento
N
N
\ No newline at end of file diff --git a/img/memento_example.svg b/img/memento_example.svg new file mode 100644 index 0000000..dd3adf3 --- /dev/null +++ b/img/memento_example.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
CareTaker- _originator- _mementos+ save()+ restore()Memento+ score+ inventory+ level+ location
N
N
GameCharacter- _score- _inventory- _level- _location+ memento+ score()+ register_kill()+ add_inventory()+ progress_to_next_level()+ move_foreward()+ __str__()
\ No newline at end of file diff --git a/img/observer_concept.svg b/img/observer_concept.svg new file mode 100644 index 0000000..5014d46 --- /dev/null +++ b/img/observer_concept.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
IObservable+ field: type+ subscribe(type): type+ unsubscribe(type): type+ notify(type): typeIObserver+ notify(type): typeSubject- _observers: set+ subscribe(type): type+ unsubscribe(type): type+ notify(type): typeObserver+ notify(type): type
\ No newline at end of file diff --git a/img/observer_example.svg b/img/observer_example.svg new file mode 100644 index 0000000..080c4f9 --- /dev/null +++ b/img/observer_example.svg @@ -0,0 +1,3 @@ + + +
Hypothetical External Datasource
Hypothetical External Datasource
The Client Application
The Client Application
IDataModel+ subscribe(type): type+ unsubscribe(type): type+ notify(type): typeIDataView+ notify(observale, data)+ draw()+ delete()DataModel- _observers: dict- _counter- _data_controller+ subscribe(type): type+ unsubscribe(type): type+ notify(type): typeBarGraph+ init(observable)+ notify(observable, data)+ draw()+ delete()TableView+ init(observable)+ notify(observable, data)+ draw()+ delete()PieGraph+ init(observable)+ notify(observable, data)+ draw()+ delete()IDataController+ subscribe(type): type+ unsubscribe(type): type+ notify(type): typeDataController- _observers: set+ subscribe(type): type+ unsubscribe(type): type+ notify(type): type
\ No newline at end of file diff --git a/img/prototype_concept.svg b/img/prototype_concept.svg new file mode 100644 index 0000000..62c5509 --- /dev/null +++ b/img/prototype_concept.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
MyClass+ field: type+ clone(type): typeIPrototype+ clone(type): type
\ No newline at end of file diff --git a/img/prototype_example.svg b/img/prototype_example.svg new file mode 100644 index 0000000..feb1dda --- /dev/null +++ b/img/prototype_example.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
Document+ name : string+ list : [][]+ clone(mode):IPrototype+ clone(mode)
\ No newline at end of file diff --git a/img/proxy_concept.svg b/img/proxy_concept.svg new file mode 100644 index 0000000..01ae8e8 --- /dev/null +++ b/img/proxy_concept.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
ISubject+ request(type): typeProxy+ request(type): type...real_subject.request()...RealSubject+ request(type): type
\ No newline at end of file diff --git a/img/proxy_example.svg b/img/proxy_example.svg new file mode 100644 index 0000000..bdc4472 --- /dev/null +++ b/img/proxy_example.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
IProteus+ tell_me_the_future()+ tell_me_yout_form()Lion+ tell_me_the_future()+ tell_me_yout_form()Serpent+ tell_me_the_future()+ tell_me_yout_form()Leopard+ tell_me_the_future()+ tell_me_yout_form()
\ No newline at end of file diff --git a/img/pseudocode_annotation.svg b/img/pseudocode_annotation.svg new file mode 100644 index 0000000..abfd818 --- /dev/null +++ b/img/pseudocode_annotation.svg @@ -0,0 +1,3 @@ + + +ConcreteClass+ request(type): type...pseudocode()...ClassB+ method(type): type
Conceptual
Conceptual
Example
Example
for x in y:    print(x)
\ No newline at end of file diff --git a/img/singleton_concept.svg b/img/singleton_concept.svg new file mode 100644 index 0000000..8f8d9eb --- /dev/null +++ b/img/singleton_concept.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
Singleton+ value: type+ __new__(cls)
\ No newline at end of file diff --git a/img/singleton_example.svg b/img/singleton_example.svg new file mode 100644 index 0000000..833eba2 --- /dev/null +++ b/img/singleton_example.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
Leaderboard- _table: dictionary+ __new__(cls)+ print()+ add_winner(position, name)Game1+ init()+ add_winner(position, name)Game2+ init()+ add_winner(position, name)Game3+ init()+ add_winner(position, name)IGame+ add_winner(position, name)
\ No newline at end of file diff --git a/img/skillshare_btn.png b/img/skillshare_btn.png deleted file mode 100644 index c7a65ef..0000000 Binary files a/img/skillshare_btn.png and /dev/null differ diff --git a/img/skillshare_btn_sm.gif b/img/skillshare_btn_sm.gif new file mode 100644 index 0000000..aa17453 Binary files /dev/null and b/img/skillshare_btn_sm.gif differ diff --git a/img/state_concept.svg b/img/state_concept.svg new file mode 100644 index 0000000..a5ce26d --- /dev/null +++ b/img/state_concept.svg @@ -0,0 +1,3 @@ + + +Context+ request()IState+ method(type): typeConcreteStateA+ method(type): typeConcreteStateB+ method(type): typeConcreteStateC+ method(type): typestate.handle() \ No newline at end of file diff --git a/img/state_example.svg b/img/state_example.svg new file mode 100644 index 0000000..54b794d --- /dev/null +++ b/img/state_example.svg @@ -0,0 +1,3 @@ + + +Context+ request()IState+ __call__Started+ method()Running+ method()Finished+ method()_handle.iter.__next__()() \ No newline at end of file diff --git a/img/strategy_concept.svg b/img/strategy_concept.svg new file mode 100644 index 0000000..1768c77 --- /dev/null +++ b/img/strategy_concept.svg @@ -0,0 +1,3 @@ + + +Context+ request(handle)IStrategy+ method(type): typeConcreteStrategyA+ method(type): typeConcreteStrategyB+ method(type): typeConcreteStrategyC+ method(type): type \ No newline at end of file diff --git a/img/strategy_example.svg b/img/strategy_example.svg new file mode 100644 index 0000000..8abe986 --- /dev/null +++ b/img/strategy_example.svg @@ -0,0 +1,3 @@ + + +GameCharacter+ move(movement_style)IMove+ __call__Walking+ walk()Running+ run()Crawling+ crawl()movement_style() \ No newline at end of file diff --git a/img/template_concept.svg b/img/template_concept.svg new file mode 100644 index 0000000..2bf87f7 --- /dev/null +++ b/img/template_concept.svg @@ -0,0 +1,3 @@ + + +ConcreteClassA+ step_one(type): type+ step_two(type): type+ step_three(type): typeAbstractClass+ template_method(type): type+ step_one(type): type+ step_two(type): type+ step_three(type): typeConcreteClassB+ step_one(type): type+ step_two(type): type+ step_three(type): typeConcreteClassC+ step_one(type): type+ step_two(type): type+ step_three(type): type \ No newline at end of file diff --git a/img/template_example.svg b/img/template_example.svg new file mode 100644 index 0000000..e5d42c0 --- /dev/null +++ b/img/template_example.svg @@ -0,0 +1,3 @@ + + +TextDocument+ title()+ text()+ footer()AbstractDocument+ create_document(text)+ title()+ description()+ author()+ nackground_colour()+ text(text)+ footer()+ print()HTMLDocument+ title()+ text()+ print() \ No newline at end of file diff --git a/img/udemy_btn.png b/img/udemy_btn.png deleted file mode 100644 index 68c7c24..0000000 Binary files a/img/udemy_btn.png and /dev/null differ diff --git a/img/udemy_btn_sm.gif b/img/udemy_btn_sm.gif new file mode 100644 index 0000000..bab7b91 Binary files /dev/null and b/img/udemy_btn_sm.gif differ diff --git a/img/visitor_concept.svg b/img/visitor_concept.svg new file mode 100644 index 0000000..1b64e44 --- /dev/null +++ b/img/visitor_concept.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
Element+ field: type+ elements: set()+ method(type): type+ accept(visitor)IVisitable+ accept(visitor)IVisitor+ visit(element)Visitor+ field: type+ visit(element)
\ No newline at end of file diff --git a/img/visitor_example.svg b/img/visitor_example.svg new file mode 100644 index 0000000..63c1f8a --- /dev/null +++ b/img/visitor_example.svg @@ -0,0 +1,3 @@ + + +
Client Application
Client Application
Body- _name- _sku- _price+ name(value)+ sku(value)+ price(value)+ accept(visitor)IVisitable+ accept(visitor)IVisitor+ visit(element)PrintPartsVisitor+ visit(element)AbstractCarPart- _name- _sku- _price+ name(value)+ sku(value)+ price(value)Engine- _name- _sku- _price+ name(value)+ sku(value)+ price(value)+ accept(visitor)Wheel- _name- _sku- _price+ name(value)+ sku(value)+ price(value)+ accept(visitor)Car- _name- _sku- _price- _parts []+ name(value)+ sku(value)+ price(value)+ accept(visitor)TotalPriceVisitor+ total_price+ visit(element)
\ No newline at end of file diff --git a/img/yt_btn_sm.gif b/img/yt_btn_sm.gif new file mode 100644 index 0000000..20b119a Binary files /dev/null and b/img/yt_btn_sm.gif differ diff --git a/interpreter/README.md b/interpreter/README.md index 35d1f81..c49e7d8 100644 --- a/interpreter/README.md +++ b/interpreter/README.md @@ -1,25 +1,174 @@ -terminal or a non-terminal expression +# Interpreter Design Pattern -terminal means the terminated command. -non-termainl commands are the same as the composite commands -composite, can be reordered in hierachy like files and folders in a filesystem +## Videos +Section | Video Links +-|- +Interpreter Overview | Interpreter Overview Interpreter Overview Interpreter Overview +Interpreter Use Case | Interpreter Use Case Interpreter Use Case Interpreter Use Case +String Slicing | String Slicing String Slicing String Slicing +__repr__ Dunder Method | __repr__ Dunder Method __repr__ Dunder Method __repr__ Dunder Method -the terminal expression is the final result -the non-terminal expressionsa are the sub expressions -select [list] ffrom [list] where [list] = [list] +## Book -You want to create your own compiler -compiles stuff +Cover | Links +-|- +![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    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 -Each abstract base class of the interpreter defines the interpret() method, and each concrete class inheriting from the base class implements the interpret() method, which translates the specific part of the language required at the moment. +## Overview +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ -Context -AbstractExpression - interpret -Concretclasses(AbstractExpression) - interpret +## Terminology -Roman numeral converter -converts roman numerals to int \ No newline at end of file +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Interpreter UML Diagram + +![Interpreter UML Diagram](/img/interpreter_concept.svg) + +## Source Code + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Output + +``` bash +python ./interpreter/interpreter_concept.py +5 + 4 - 3 + 7 - 2 +['5', '+', '4', '-', '3', '+', '7', '-', '2'] +11 +((((5 Add 4) Subtract 3) Add 7) Subtract 2) +``` + +## Example Use Case + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +![Abstract Syntax Tree Example](/img/interpreter_ast_roman_numeral.svg) + +## Example UML Diagram + +![Interpreter Pattern Overview](/img/interpreter_example.svg) + +## Output + +``` bash +python ./interpreter/client.py +5 + IV - 3 + VII - 2 +['5', '+', 'IV', '-', '3', '+', 'VII', '-', '2'] +11 +((((5 Add IV(4)) Subtract 3) Add VII(7)) Subtract 2) +``` + +## New Coding Concepts + +### String Slicing + +Sometimes you want part of a string. In the example code, when I am interpreting the roman numerals, I am comparing the first one or two characters in the context with `IV` or `CM` or many other roman numeral combinations. If the match is true then I continue with further commands. + +The format is + +``` python +string[start: end: step] +``` + +E.g., the string may be + +``` text +MMMCMXCIX +``` + +and I want the first three characters, + +``` python +test = "MMMCMXCIX" +print(test[0: 3]) +``` + +Outputs + +``` text +MMM +``` + +or I want the last 4 characters + +``` python +test = "MMMCMXCIX" +print(test[-4:]) +``` + +Outputs + +``` text +XCIX +``` + +or I want a section in the middle + +``` python +test = "MMMCMXCIX" +print(test[2:5]) +``` + +Outputs + +``` text +MCM +``` + +or stepped + +``` python +test = "MMMCMXCIX" +print(test[2:9:2]) +``` + +Outputs + +``` text +MMCX +``` + +or even reversed + +``` python +test = "MMMCMXCIX" +print(test[::-1]) +``` + +Outputs + +``` text +XICXMCMMM +``` + +The technique is very common in examples of Python source code throughout the internet. So, when you see the `[]` with numbers and colons inside, eg, `[:-1:]`, it is likely to do with extracting a portion of a data structure. + +Note that the technique also works on [Lists](/builder#python-list) and [Tuples](/bridge#python-tuple). + +``` python +test = [1,2,3,4,5,6,7,8,9] +print(test[0: 3]) +print(test[-4:]) +print(test[2:5]) +print(test[2:9:2]) +print(test[::-1]) +print(test[:-1:]) +``` + +Outputs + +``` text +[1, 2, 3] +[6, 7, 8, 9] +[3, 4, 5] +[3, 5, 7, 9] +[9, 8, 7, 6, 5, 4, 3, 2, 1] +[1, 2, 3, 4, 5, 6, 7, 8] +``` + +## Summary + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ \ No newline at end of file diff --git a/interpreter/abstract_expression.py b/interpreter/abstract_expression.py new file mode 100644 index 0000000..2b5009c --- /dev/null +++ b/interpreter/abstract_expression.py @@ -0,0 +1,13 @@ +"An Abstract Expression" +# pylint: disable=too-few-public-methods +class AbstractExpression(): + """ + All Terminal and Non-Terminal expressions will implement an + `interpret` method + """ + @staticmethod + def interpret(): + """ + The `interpret` method gets called recursively for + each AbstractExpression + """ diff --git a/interpreter/add.py b/interpreter/add.py new file mode 100644 index 0000000..958094b --- /dev/null +++ b/interpreter/add.py @@ -0,0 +1,16 @@ +"Add Expression. This is a Non-Terminal Expression" +from abstract_expression import AbstractExpression + + +class Add(AbstractExpression): + "Non-Terminal Expression." + + def __init__(self, left, right): + self.left = left + self.right = right + + def interpret(self): + return self.left.interpret() + self.right.interpret() + + def __repr__(self): + return f"({self.left} Add {self.right})" diff --git a/interpreter/client.py b/interpreter/client.py new file mode 100644 index 0000000..0499d63 --- /dev/null +++ b/interpreter/client.py @@ -0,0 +1,20 @@ +"The Interpreter Pattern Use Case Example" + +from sentence_parser import Parser + +# The sentence complies with a simple grammar of +# Number -> Operator -> Number -> etc, +SENTENCE = "5 + IV - 3 + VII - 2" +# SENTENCE = "V + IV - III + 7 - II" +# SENTENCE= "CIX + V" +# SENTENCE = "CIX + V - 3 + VII - 2" +# SENTENCE = "MMMCMXCIX - CXIX + MCXXII - MMMCDXII - XVIII - CCXXXV" +print(SENTENCE) + +AST_ROOT = Parser.parse(SENTENCE) + +# Interpret recursively through the full AST starting from the root. +print(AST_ROOT.interpret()) + +# Print out a representation of the AST_ROOT +print(AST_ROOT) diff --git a/interpreter/interpreter.py b/interpreter/interpreter.py deleted file mode 100644 index 1762a85..0000000 --- a/interpreter/interpreter.py +++ /dev/null @@ -1,141 +0,0 @@ -from abc import ABCMeta, abstractmethod - - -class Context: - def __init__(self): - self._input = "" - self._output = 0 - - # using property decorator - # a getter function - @property - def Input(self): - return self._input - - # a setter function - @Input.setter - def Input(self, a): - self._input = a - - @property - def Output(self): - return self._input - - # a setter function - @Output.setter - def Output(self, a): - self._input = a - - -class Expression(metaclass=ABCMeta): - @staticmethod - @abstractmethod - def One(): - "" - @staticmethod - @abstractmethod - def Four(): - "" - @staticmethod - @abstractmethod - def Five(): - "" - @staticmethod - @abstractmethod - def Nine(): - "" - @staticmethod - @abstractmethod - def Multiplier(): - "" - - @staticmethod - @abstractmethod - def Interpret(context:Context): - "" - -class HundredExpression(Expression): - def One(self): - return "C" - def Four(self): - return "CD" - def Five(self): - return "D" - def Nine(self): - return "CM" - def Multiplier(self): - return 100 - - def Interpret(self, context:Context): - if context.Input.Length == 0: - return - - if context.Input.StartsWith(self.Nine()): - context.Output += (9 * self.Multiplier()) - context.Input = context.Input.Substring(2) - elif context.Input.StartsWith(self.Four()): - context.Output += (4 * self.Multiplier()) - context.Input = context.Input.Substring(2) - elif context.Input.StartsWith(self.Five()): - context.Output += (5 * self.Multiplier()) - context.Input = context.Input.Substring(1) - - while context.Input.StartsWith(self.One()): - context.Output += (1 * self.Multiplier()) - context.Input = context.Input.Substring(1) - -# class OneExpression : Expression -# { -# public override string One() { return "I"; } -# public override string Four() { return "IV"; } -# public override string Five() { return "V"; } -# public override string Nine() { return "IX"; } -# public override int Multiplier() { return 1; } -# } - -# class TenExpression : Expression -# { -# public override string One() { return "X"; } -# public override string Four() { return "XL"; } -# public override string Five() { return "L"; } -# public override string Nine() { return "XC"; } -# public override int Multiplier() { return 10; } -# } - -# class ThousandExpression : Expression -# { -# public override string One() { return "M"; } -# public override string Four() { return " "; } -# public override string Five() { return " "; } -# public override string Nine() { return " "; } -# public override int Multiplier() { return 1000; } -# } - -# class Program -# { -# static void Main() -# { -# string roman = null; - -# while (!string.IsNullOrEmpty(roman = Console.ReadLine())) -# { -# Context context = new Context(roman); - -# List tree = new List(); -# tree.Add(new ThousandExpression()); -# tree.Add(new HundredExpression()); -# tree.Add(new TenExpression()); -# tree.Add(new OneExpression()); - -# foreach (Expression exp in tree) -# { -# exp.Interpret(context); -# } - -# Console.WriteLine("{0} = {1}", -# roman, context.Output); -# } - -# Console.ReadKey(); -# } -# } diff --git a/interpreter/interpreter_concept.py b/interpreter/interpreter_concept.py new file mode 100644 index 0000000..8439db5 --- /dev/null +++ b/interpreter/interpreter_concept.py @@ -0,0 +1,83 @@ +# pylint: disable=too-few-public-methods +# pylint: disable=arguments-differ +"The Interpreter Pattern Concept" + + +class AbstractExpression(): + "All Terminal and Non-Terminal expressions will implement an `interpret` method" + @staticmethod + def interpret(): + """ + The `interpret` method gets called recursively for each + AbstractExpression + """ + + +class Number(AbstractExpression): + "Terminal Expression" + + def __init__(self, value): + self.value = int(value) + + def interpret(self): + return self.value + + def __repr__(self): + return str(self.value) + + +class Add(AbstractExpression): + "Non-Terminal Expression." + + def __init__(self, left, right): + self.left = left + self.right = right + + def interpret(self): + return self.left.interpret() + self.right.interpret() + + def __repr__(self): + return f"({self.left} Add {self.right})" + + +class Subtract(AbstractExpression): + "Non-Terminal Expression" + + def __init__(self, left, right): + self.left = left + self.right = right + + def interpret(self): + return self.left.interpret() - self.right.interpret() + + def __repr__(self): + return f"({self.left} Subtract {self.right})" + + +# The Client +# The sentence complies with a simple grammar of +# Number -> Operator -> Number -> etc, +SENTENCE = "5 + 4 - 3 + 7 - 2" +print(SENTENCE) + +# Split the sentence into individual expressions that will be added to +# an Abstract Syntax Tree (AST) as Terminal and Non-Terminal expressions +TOKENS = SENTENCE.split(" ") +print(TOKENS) + +# Manually Creating an Abstract Syntax Tree from the tokens +AST: list[AbstractExpression] = [] # Python 3.9 +# AST = [] # Python 3.8 or earlier +AST.append(Add(Number(TOKENS[0]), Number(TOKENS[2]))) # 5 + 4 +AST.append(Subtract(AST[0], Number(TOKENS[4]))) # ^ - 3 +AST.append(Add(AST[1], Number(TOKENS[6]))) # ^ + 7 +AST.append(Subtract(AST[2], Number(TOKENS[8]))) # ^ - 2 + +# Use the final AST row as the root node. +AST_ROOT = AST.pop() + +# Interpret recursively through the full AST starting from the root. +print(AST_ROOT.interpret()) + +# Print out a representation of the AST_ROOT +print(AST_ROOT) diff --git a/interpreter/number.py b/interpreter/number.py new file mode 100644 index 0000000..a593a87 --- /dev/null +++ b/interpreter/number.py @@ -0,0 +1,14 @@ +"A Number. This is a leaf node Expression" +from abstract_expression import AbstractExpression + +class Number(AbstractExpression): + "Terminal Expression" + + def __init__(self, value): + self.value = int(value) + + def interpret(self): + return self.value + + def __repr__(self): + return str(self.value) diff --git a/interpreter/roman_numeral.py b/interpreter/roman_numeral.py new file mode 100644 index 0000000..465ba87 --- /dev/null +++ b/interpreter/roman_numeral.py @@ -0,0 +1,82 @@ +# pylint: disable=too-few-public-methods +"Roman Numeral Expression. This is a Non-Terminal Expression" +from abstract_expression import AbstractExpression +from number import Number + + +class RomanNumeral(AbstractExpression): + "Non Terminal expression" + + def __init__(self, roman_numeral): + self.roman_numeral = roman_numeral + self.context = [roman_numeral, 0] + + def interpret(self): + RomanNumeral1000.interpret(self.context) + RomanNumeral100.interpret(self.context) + RomanNumeral10.interpret(self.context) + RomanNumeral1.interpret(self.context) + return Number(self.context[1]).interpret() + + def __repr__(self): + return f"{self.roman_numeral}({self.context[1]})" + + +class RomanNumeral1(RomanNumeral): + "Roman Numerals 1 - 9" + one = "I" + four = "IV" + five = "V" + nine = "IX" + multiplier = 1 + + @classmethod + def interpret(cls, *args): + + context = args[0] + + if not context[0]: + return Number(context[1]).interpret() + + if context[0][0: 2] == cls.nine: + context[1] += (9 * cls.multiplier) + context[0] = context[0][2:] + elif context[0][0] == cls.five: + context[1] += (5 * cls.multiplier) + context[0] = context[0][1:] + elif context[0][0: 2] == cls.four: + context[1] += + (4 * cls.multiplier) + context[0] = context[0][2:] + + while context[0] and context[0][0] == cls.one: + context[1] += (1 * cls.multiplier) + context[0] = context[0][1:] + + return Number(context[1]).interpret() + + +class RomanNumeral10(RomanNumeral1): + "Roman Numerals 10 - 99" + one = "X" + four = "XL" + five = "L" + nine = "XC" + multiplier = 10 + + +class RomanNumeral100(RomanNumeral1): + "Roman Numerals 100 - 999" + one = "C" + four = "CD" + five = "D" + nine = "CM" + multiplier = 100 + + +class RomanNumeral1000(RomanNumeral1): + "Roman Numerals 1000 - 3999" + one = "M" + four = "" + five = "" + nine = "" + multiplier = 1000 diff --git a/interpreter/sentence_parser.py b/interpreter/sentence_parser.py new file mode 100644 index 0000000..a20051a --- /dev/null +++ b/interpreter/sentence_parser.py @@ -0,0 +1,70 @@ +"A Custom Parser for creating an Abstract Syntax Tree" + +from number import Number +from add import Add +from subtract import Subtract +from roman_numeral import RomanNumeral + + +class Parser: + "Dynamically create the Abstract Syntax Tree" + + @classmethod + def parse(cls, sentence): + "Create the AST from the sentence" + + tokens = sentence.split(" ") + print(tokens) + + tree = [] # Abstract Syntax Tree + while len(tokens) > 1: + + left_expression = cls.decide_left_expression(tree, tokens) + + # get the operator, make the token list shorter + operator = tokens.pop(0) + + right = tokens[0] + + if not right.isdigit(): + tree.append(RomanNumeral(tokens[0])) + if operator == '-': + tree.append(Subtract(left_expression, tree[-1])) + if operator == '+': + tree.append(Add(left_expression, tree[-1])) + else: + right_expression = Number(right) + if not tree: + # Empty Data Structures return False by default + if operator == '-': + tree.append( + Subtract(left_expression, right_expression)) + if operator == '+': + tree.append( + Add(left_expression, right_expression)) + else: + if operator == '-': + tree.append(Subtract(tree[-1], right_expression)) + if operator == '+': + tree.append(Add(tree[-1], right_expression)) + + return tree.pop() + + @staticmethod + def decide_left_expression(tree, tokens): + """ + On the First iteration, the left expression can be either a + number or roman numeral. Every consecutive expression is + reference to an existing AST row + """ + left = tokens.pop(0) + left_expression = None + if not tree: # only applicable if first round + if not left.isdigit(): # if 1st token a roman numeral + tree.append(RomanNumeral(left)) + left_expression = tree[-1] + else: + left_expression = Number(left) + else: + left_expression = tree[-1] + return left_expression diff --git a/interpreter/subtract.py b/interpreter/subtract.py new file mode 100644 index 0000000..5983681 --- /dev/null +++ b/interpreter/subtract.py @@ -0,0 +1,16 @@ +"Subtract Expression. This is a Non-Terminal Expression" +from abstract_expression import AbstractExpression + + +class Subtract(AbstractExpression): + "Non-Terminal Expression" + + def __init__(self, left, right): + self.left = left + self.right = right + + def interpret(self): + return self.left.interpret() - self.right.interpret() + + def __repr__(self): + return f"({self.left} Subtract {self.right})" diff --git a/iterator/README.md b/iterator/README.md index 682bbfa..c995307 100644 --- a/iterator/README.md +++ b/iterator/README.md @@ -1,11 +1,121 @@ # Iterator Design Pattern -An interface with **next** and **has_next** methods. +## Videos -**next** returns the next object in the aggregate(collection, list) +Section | Video Links +-|- +Iterator Overview | Iterator Overview Iterator Overview Iterator Overview +Iterator Use Case | Iterator Use Case Iterator Use Case Iterator Use Case +Python iter() Function | Python iter() Function Python iter() Function Python iter() Function -**has_next** returns a value, usually a boolean indicating if the iterable is at the end of the list or not. +## Book -The benefits of using the Iterator pattern is that the client, can traverse an aggregate without needing to understand it's internal representation and data structures. +Cover | Links +-|- +![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    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 -![Iterator Pattern UML Diagram](iterator.png) \ No newline at end of file +## Overview + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Terminology + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Iterator UML Diagram + +![Iterator Pattern Overview](/img/iterator_concept.svg) + +## Source Code + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Output + +``` bash +python ./iterator/iterator_concept.py +This method has been invoked +This method has been invoked +This method has been invoked +This method has been invoked +``` + +## Example Use Case + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Example UML Diagram + +![Iterator Pattern Overview](/img/iterator_example.svg) + +## Output + +``` bash +python ./iterator/client.py +2, 4, 6, 8, 10, 1, 3, 5, 7, 9, 0, 2, 4, 6, 8, 10, 1, 3, 5, 7, 9, 0, +``` + +## New Coding Concepts + +### Python iter() + +Python [Lists](/builder#python-list), [Dictionaries](/singleton#python-dictionary), [Sets](/observer#python-set) and [Tuples](/bridge#python-tuple) are already iterable, so if you want basic iteration for use in a for loop, then you only need to add your objects into one of those and it can be used right away. + +``` python +NAMES = ['SEAN','COSMO','EMMY'] +for name in NAMES: + print(name, end=", ") +#SEAN, COSMO, EMMY, +``` + +also, you can instantiate an iterable from the List, Dictionary, Tuple or Set by using the [Python iter()](#python-iter) method, or its own `__iter__()` dunder method, and then iterate over that using the `__next__()` method. + +``` python +NAMES = ['SEAN','COSMO','EMMY'] +ITERATOR = iter(NAMES) +print(ITERATOR.__next__()) +print(ITERATOR.__next__()) +print(ITERATOR.__next__()) +``` + +or + +``` python +NAMES = ['SEAN','COSMO','EMMY'] +ITERATOR = NAMES.__iter__() +print(ITERATOR.__next__()) +print(ITERATOR.__next__()) +print(ITERATOR.__next__()) +``` + +The Python `iter()` method also can accept a `sentinel` parameter. + +The `sentinel` parameter is useful for dynamically created objects that are returned from an iterator and indicates where the last item is in the iterator by raising a `StopIteration` exception. + +Usage : `iter(object, sentinel)` + +When using the `sentinel`, the object passed as the first argument in the `iter()` method will need to be callable. + +``` python +class Doubler(): + count = 1 + + @classmethod + def next(cls): + cls.count *= 2 + return cls.count + + __call__ = next + +ITERATOR = iter(Doubler(), 32) +print(list(ITERATOR)) +# Outputs [2, 4, 8, 16] +``` + +The `__call__ = next` line in the example above is setting the default method of the class to be `next` and that makes the class callable. See [Dunder __call__ Method](/state#dunder-__call__-method) for more information. + +Also note that the list being printed at the end is automatically filled from the iterator. The list constructor utilizes the default callable method and the `StopIteration` exception automatically during its creation without needing to write this in the code. + +## Summary + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ \ No newline at end of file diff --git a/iterator/client.py b/iterator/client.py new file mode 100644 index 0000000..f9be059 --- /dev/null +++ b/iterator/client.py @@ -0,0 +1,20 @@ +"The Iterator Pattern Concept" + + +class NumberWheel(): # pylint: disable=too-few-public-methods + "The concrete iterator (iterable)" + + def __init__(self): + self.index = 0 + + def next(self): + """Return a new number next in the wheel""" + self.index = self.index + 1 + return self.index * 2 % 11 + + +# The Client +NUMBERWHEEL = NumberWheel() + +for i in range(22): + print(NUMBERWHEEL.next(), end=", ") diff --git a/iterator/iterator.dot b/iterator/iterator.dot deleted file mode 100644 index 19b398b..0000000 --- a/iterator/iterator.dot +++ /dev/null @@ -1,7 +0,0 @@ -digraph "classes" { -charset="utf-8" -rankdir=BT -"0" [label="{IIterator|\l|has_next()\lnext()\l}", shape="record"]; -"1" [label="{Iterable|index : int\lmaximum : int\l|has_next()\lnext()\l}", shape="record"]; -"1" -> "0" [arrowhead="empty", arrowtail="none"]; -} diff --git a/iterator/iterator.png b/iterator/iterator.png deleted file mode 100644 index e2e656f..0000000 Binary files a/iterator/iterator.png and /dev/null differ diff --git a/iterator/iterator.py b/iterator/iterator.py deleted file mode 100644 index 4cadb1f..0000000 --- a/iterator/iterator.py +++ /dev/null @@ -1,45 +0,0 @@ -from abc import ABCMeta, abstractmethod - -class IIterator(metaclass=ABCMeta): - @staticmethod - @abstractmethod - def has_next(): - """Returns Boolean whether at end of collection or not""" - - @staticmethod - @abstractmethod - def next(): - """Return the object in collection""" - -class Iterable(IIterator): - def __init__(self): - self.index = 0 - self.maximum = 7 - - def next(self): - if self.index < self.maximum: - x = self.index - self.index += 1 - return x - else: - raise Exception("AtEndOfIteratorException", "At End of Iterator") - - def has_next(self): - return self.index < self.maximum - -ITERABLE = Iterable() - -while ITERABLE.has_next(): - print(ITERABLE.next()) - - -# print(ITERABLE.next()) -# print(ITERABLE.next()) -# print(ITERABLE.next()) -# print(ITERABLE.next()) -# print(ITERABLE.next()) -# print(ITERABLE.next()) -# print(ITERABLE.next()) -# print(ITERABLE.next()) - - \ No newline at end of file diff --git a/iterator/iterator_concept.py b/iterator/iterator_concept.py new file mode 100644 index 0000000..59ff128 --- /dev/null +++ b/iterator/iterator_concept.py @@ -0,0 +1,61 @@ +# pylint: disable=too-few-public-methods +# pylint: disable=arguments-differ +"The Iterator Pattern Concept" +from abc import ABCMeta, abstractmethod + + +class IIterator(metaclass=ABCMeta): + "An Iterator Interface" + @staticmethod + @abstractmethod + def has_next(): + "Returns Boolean whether at end of collection or not" + + @staticmethod + @abstractmethod + def next(): + "Return the object in collection" + + +class Iterable(IIterator): + "The concrete iterator (iterable)" + + def __init__(self, aggregates): + self.index = 0 + self.aggregates = aggregates + + def next(self): + if self.index < len(self.aggregates): + aggregate = self.aggregates[self.index] + self.index += 1 + return aggregate + raise Exception("AtEndOfIteratorException", "At End of Iterator") + + def has_next(self): + return self.index < len(self.aggregates) + + +class IAggregate(metaclass=ABCMeta): + "An interface that the aggregates should implement" + @staticmethod + @abstractmethod + def method(): + "a method to implement" + + +class Aggregate(IAggregate): + "A concrete object" + @staticmethod + def method(): + print("This method has been invoked") + + +# The Client +AGGREGATES = [Aggregate(), Aggregate(), Aggregate(), Aggregate()] +# AGGREGATES is a python list that is already iterable by default. + +# but we can create own own iterator on top anyway. +ITERABLE = Iterable(AGGREGATES) + +while ITERABLE.has_next(): + ITERABLE.next().method() diff --git a/mediator/README.md b/mediator/README.md index a3990f8..c62667a 100644 --- a/mediator/README.md +++ b/mediator/README.md @@ -1,9 +1,65 @@ # Mediator Design Pattern -The mediator pattern is a behavioural pattern that defines an object that encapsulates how a set of objects interact. +## Videos -With the mediator pattern, communication between objects is encapsulated within a mediator object. +Section | Video Links +-|- +Mediator Overview | Mediator Overview Mediator Overview Mediator Overview +Mediator Use Case | Mediator Use Case Mediator Use Case Mediator Use Case -Objects communicate through the mediator rather than directly with each other. +## Book -![Mediator Pattern UML Diagram](mediator.png) +Cover | Links +-|- +![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    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 + +## Overview + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Terminology + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Mediator UML Diagram + +![Mediator Pattern UML Diagram](/img/mediator_concept.svg) + +## Source Code + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Output + +``` bash +python ./mediator/mediator_concept.py +COLLEAGUE1 <--> Here is the Colleague2 specific data you asked for +COLLEAGUE2 <--> Here is the Colleague1 specific data you asked for +``` + +## Example Use Case + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Example UML Diagram + +![Mediator Pattern UML Diagram](/img/mediator_example.svg) + +## Output + +``` bash +python ./mediator/client.py +Component1: >>> Out >>> : data A +Component2: <<< In <<< : data A +Component3: <<< In <<< : data A +Component2: >>> Out >>> : data B +Component3: <<< In <<< : data B +Component1: <<< In <<< : data B +Component3: >>> Out >>> : data C +Component2: <<< In <<< : data C +Component1: <<< In <<< : data C +``` + +## Summary + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ \ No newline at end of file diff --git a/mediator/classes.dot b/mediator/classes.dot deleted file mode 100644 index 65f09da..0000000 --- a/mediator/classes.dot +++ /dev/null @@ -1,8 +0,0 @@ -digraph "classes" { -charset="utf-8" -rankdir=BT -"0" [label="{Component|mediator\lname\l|notify()\lreceive()\l}", shape="record"]; -"1" [label="{IComponent|\l|notify()\lreceive()\l}", shape="record"]; -"2" [label="{Mediator|components : list\l|add()\lnotify()\l}", shape="record"]; -"0" -> "1" [arrowhead="empty", arrowtail="none"]; -} diff --git a/mediator/client.py b/mediator/client.py new file mode 100644 index 0000000..2cf7662 --- /dev/null +++ b/mediator/client.py @@ -0,0 +1,15 @@ +"The Mediator Use Case Example" +from component import Component +from mediator import Mediator + +MEDIATOR = Mediator() +COMPONENT1 = Component(MEDIATOR, "Component1") +COMPONENT2 = Component(MEDIATOR, "Component2") +COMPONENT3 = Component(MEDIATOR, "Component3") +MEDIATOR.add(COMPONENT1) +MEDIATOR.add(COMPONENT2) +MEDIATOR.add(COMPONENT3) + +COMPONENT1.notify("data A") +COMPONENT2.notify("data B") +COMPONENT3.notify("data C") diff --git a/mediator/component.py b/mediator/component.py new file mode 100644 index 0000000..b9cf510 --- /dev/null +++ b/mediator/component.py @@ -0,0 +1,17 @@ +"Each component stays synchronized through a mediator" +from interface_component import IComponent + + +class Component(IComponent): + "Each component stays synchronized through a mediator" + + def __init__(self, mediator, name): + self._mediator = mediator + self._name = name + + def notify(self, message): + print(self._name + ": >>> Out >>> : " + message) + self._mediator.notify(message, self) + + def receive(self, message): + print(self._name + ": <<< In <<< : " + message) diff --git a/mediator/interface_component.py b/mediator/interface_component.py new file mode 100644 index 0000000..4592d9d --- /dev/null +++ b/mediator/interface_component.py @@ -0,0 +1,16 @@ +"An interface that each component will implement" +from abc import ABCMeta, abstractmethod + + +class IComponent(metaclass=ABCMeta): + "An interface that each component will implement" + + @staticmethod + @abstractmethod + def notify(message): + "The required notify method" + + @staticmethod + @abstractmethod + def receive(message): + "The required receive method" diff --git a/mediator/mediator.png b/mediator/mediator.png deleted file mode 100644 index 597d426..0000000 Binary files a/mediator/mediator.png and /dev/null differ diff --git a/mediator/mediator.py b/mediator/mediator.py index ccba1d5..4b081f0 100644 --- a/mediator/mediator.py +++ b/mediator/mediator.py @@ -1,50 +1,18 @@ -from abc import ABCMeta, abstractmethod - - -class IComponent(metaclass=ABCMeta): - @staticmethod - @abstractmethod - def notify(msg): - """The required notify method""" - - @staticmethod - @abstractmethod - def receive(msg): - """The required receive method""" - - -class Component(IComponent): - def __init__(self, mediator, name): - self.mediator = mediator - self.name = name - - def notify(self, message): - print(self.name + ": >>> Out >>> : " + message) - self.mediator.notify(message, self) - - def receive(self, message): - print(self.name + ": <<< In <<< : " + message) +"The Subject that all components will stay synchronized with" class Mediator(): + "A Subject whose notify method is mediated" + def __init__(self): - self.components = [] + self._components = set() def add(self, component): - self.components.append(component) - - def notify(self, message, component): - for _component in self.components: - if _component != component: - _component.receive(message) - - -MEDIATOR = Mediator() -COMPONENT1 = Component(MEDIATOR, "Component1") -COMPONENT2 = Component(MEDIATOR, "Component2") -COMPONENT3 = Component(MEDIATOR, "Component3") -MEDIATOR.add(COMPONENT1) -MEDIATOR.add(COMPONENT2) -MEDIATOR.add(COMPONENT3) - -COMPONENT1.notify("data") + "Add components" + self._components.add(component) + + def notify(self, message, originator): + "Add components except for the originator component" + for component in self._components: + if component != originator: + component.receive(message) diff --git a/mediator/mediator_concept.py b/mediator/mediator_concept.py new file mode 100644 index 0000000..61b52db --- /dev/null +++ b/mediator/mediator_concept.py @@ -0,0 +1,48 @@ +# pylint: disable=too-few-public-methods +"Mediator Concept Sample Code" + + +class Mediator(): + "The Mediator Concrete Class" + + def __init__(self): + self.colleague1 = Colleague1() + self.colleague2 = Colleague2() + + def colleague1_method(self): + "Calls the method provided by Colleague1" + return self.colleague1.method_1() + + def colleague2_method(self): + "Calls the method provided by Colleague2" + return self.colleague2.method_2() + + +class Colleague1(): + "This Colleague provides data for Colleague2" + + @staticmethod + def method_1(): + "A simple method" + return "Here is the Colleague1 specific data you asked for" + + +class Colleague2(): + "This Colleague provides data for Colleague1" + + @staticmethod + def method_2(): + "A simple method" + return "Here is the Colleague2 specific data you asked for" + + +# The Client +MEDIATOR = Mediator() + +# Colleague1 wants some data from Colleague2 +DATA = MEDIATOR.colleague2_method() +print(f"COLLEAGUE1 <--> {DATA}") + +# Colleague2 wants some data from Colleague1 +DATA = MEDIATOR.colleague1_method() +print(f"COLLEAGUE2 <--> {DATA}") diff --git a/memento/README.md b/memento/README.md index 330bb99..98e5fe7 100644 --- a/memento/README.md +++ b/memento/README.md @@ -1,21 +1,154 @@ # Memento Design Pattern -The Memento pattern is a behavioral design pattern that allows you to take a snapshot of an object's state and restore it back at a later time. It is a very common pattern to use whn implementing undo/redo functionality within your application. +## Videos -There 3 main objects -Originator - (Creator) The originator is some object that has an internal state -Caretaker - (Guardian) The caretaker is going to do something to the originator, but wants to be able to undo the change -Memento - the copy of the state before the caretaker changed it +Section | Video Links +-|- +Memento Overview | Memento Overview Memento Overview Memento Overview +Memento Use Case | Memento Use Case Memento Use Case Memento Use Case +Getters/Setters | Getters/Setters Getters/Setters Getters/Setters -the Caretaker class refers to the Originator class for saving (createMemento()) and restoring (restore(memento)) originator's internal state. -The Originator class implements -(1) createMemento() by creating and returning a Memento object that stores originator's current internal state and -(2) restore(memento) by restoring state from the passed in Memento object. +## Book +Cover | Links +-|- +![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    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 +## Overview -If you are undoing/redoing by executing commands on the state, that is the command pattern. -If you are undoing/redoing by replacing state from a cache of states, that is the memento. +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ -The Client, tells the Origianetor(Object) to create a memento (copy) of itself into the mememto. -The Client then tells the Guardian, to store a copy of the memonto. +## Terminology + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Memento UML Diagram + +![Memento UML Diagram](/img/memento_concept.svg) + +## Source Code + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +### Output + +``` bash +python ./memento/memento_concept.py +Originator: Setting state to `State #1` +Originator: Setting state to `State #2` +CareTaker: Getting a copy of Originators current state +Originator: Providing Memento of state to caretaker. +Originator: Setting state to `State #3` +CareTaker: Getting a copy of Originators current state +Originator: Providing Memento of state to caretaker. +Originator: Setting state to `State #4` +State #4 +CareTaker: Restoring Originators state from Memento +Originator: State after restoring from Memento: `State #2` +State #2 +CareTaker: Restoring Originators state from Memento +Originator: State after restoring from Memento: `State #3` +State #3 +``` + +## Example Use Case + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Example UML Diagram + +![Memento Use Case UML Diagram](/img/memento_example.svg) + +## Output + +``` bash +python ./memento/client.py +Score: 200, Level: 0, Location: {'x': 0, 'y': 0, 'z': 2} +Inventory: {'rifle', 'sword'} + +CareTaker: Game Save +Score: 500, Level: 1, Location: {'x': 0, 'y': 0, 'z': 13} +Inventory: {'motorbike', 'rifle', 'sword'} + +CareTaker: Game Save +Score: 600, Level: 2, Location: {'x': 0, 'y': 0, 'z': 14} +Inventory: {'motorbike', 'rifle', 'sword'} + +CareTaker: Restoring Characters attributes from Memento +Score: 200, Level: 0, Location: {'x': 0, 'y': 0, 'z': 2} +Inventory: {'rifle', 'sword'} +``` + +## New Coding Concepts + +### Python Getter/Setters + +Often when coding attributes in classes, you may want to provide methods to allow external functions to read or modify a classes internal attributes. + +A common approach would be to add two methods prefixed with `get_` and `set_`, + +``` python +class ExampleClass: + def __init__(self): + self._value = 123 + + def get_value(self): + return self._value + + def set_value(self, value): + self._value = value + +example = ExampleClass() +print(example.get_value()) +``` + +This makes perfect sense what the intentions are, but there is a more pythonic way of doing this and that is by using the inbuilt Python `@property` decorator. + +``` python +class ExampleClass: + def __init__(self): + self._value = 123 + + @property + def value(self): + return self._value + + @value.setter + def value(self, value): + self._value = value + +example = ExampleClass() +print(example.value) +``` + +Note that in the above example, there is an extra decorator named `@value.setter` . This is used for setting the `_value` attribute. + +Along with the above two new getter/setter methods, there is also another method for deleting an attribute called `deleter` . + +``` python +class ExampleClass: + def __init__(self): + self._value = 123 + + @property + def value(self): + return self._value + + @value.setter + def value(self, value): + self._value = value + + @value.deleter + def value(self): + print('Deleting _value') + del self._value + +example = ExampleClass() +print(example.value) +del example.value +print(example.value) # now raises an AttributeError +``` + +## Summary + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ \ No newline at end of file diff --git a/memento/caretaker.py b/memento/caretaker.py new file mode 100644 index 0000000..5588688 --- /dev/null +++ b/memento/caretaker.py @@ -0,0 +1,24 @@ +"The Save/Restore Game functionality" + + +class CareTaker(): + "Guardian. Provides a narrow interface to the mementos" + + def __init__(self, originator): + self._originator = originator + self._mementos = [] + + def save(self): + "Store a new Memento of the Characters current state" + print("CareTaker: Game Save") + memento = self._originator.memento + self._mementos.append(memento) + + def restore(self, index): + """ + Replace the Characters current attributes with the state + stored in the saved Memento + """ + print("CareTaker: Restoring Characters attributes from Memento") + memento = self._mementos[index] + self._originator.memento = memento diff --git a/memento/classes.dot b/memento/classes.dot deleted file mode 100644 index df70678..0000000 --- a/memento/classes.dot +++ /dev/null @@ -1,8 +0,0 @@ -digraph "classes" { -charset="utf-8" -rankdir=BT -"0" [label="{IMemento|\l|restore_memento()\lsave_memento()\l}", shape="record"]; -"1" [label="{Memento|\l|get()\l}", shape="record"]; -"2" [label="{Thing|\l|get()\lrestore_memento()\lsave_memento()\lset()\l}", shape="record"]; -"2" -> "0" [arrowhead="empty", arrowtail="none"]; -} diff --git a/memento/client.py b/memento/client.py new file mode 100644 index 0000000..7543b5e --- /dev/null +++ b/memento/client.py @@ -0,0 +1,42 @@ +"Memento example Use Case" + +from game_character import GameCharacter +from caretaker import CareTaker + +GAME_CHARACTER = GameCharacter() +CARETAKER = CareTaker(GAME_CHARACTER) + +# start the game +GAME_CHARACTER.register_kill() +GAME_CHARACTER.move_forward(1) +GAME_CHARACTER.add_inventory("sword") +GAME_CHARACTER.register_kill() +GAME_CHARACTER.add_inventory("rifle") +GAME_CHARACTER.move_forward(1) +print(GAME_CHARACTER) + +# save progress +CARETAKER.save() + +GAME_CHARACTER.register_kill() +GAME_CHARACTER.move_forward(1) +GAME_CHARACTER.progress_to_next_level() +GAME_CHARACTER.register_kill() +GAME_CHARACTER.add_inventory("motorbike") +GAME_CHARACTER.move_forward(10) +GAME_CHARACTER.register_kill() +print(GAME_CHARACTER) + +# save progress +CARETAKER.save() +GAME_CHARACTER.move_forward(1) +GAME_CHARACTER.progress_to_next_level() +GAME_CHARACTER.register_kill() +print(GAME_CHARACTER) + +# decide you made a mistake, go back to first save +CARETAKER.restore(0) +print(GAME_CHARACTER) + +# continue +GAME_CHARACTER.register_kill() diff --git a/memento/game_character.py b/memento/game_character.py new file mode 100644 index 0000000..33af916 --- /dev/null +++ b/memento/game_character.py @@ -0,0 +1,57 @@ +"The Game Character whose state changes" +from memento import Memento + + +class GameCharacter(): + "The Game Character whose state changes" + + def __init__(self): + self._score = 0 + self._inventory = set() + self._level = 0 + self._location = {"x": 0, "y": 0, "z": 0} + + @property + def score(self): + "A `getter` for the objects score" + return self._score + + def register_kill(self): + "The character kills its enemies as it progesses" + self._score += 100 + + def add_inventory(self, item): + "The character finds objects in the game" + self._inventory.add(item) + + def progress_to_next_level(self): + "The characer progresses to the next level" + self._level += 1 + + def move_forward(self, amount): + "The character moves around the environment" + self._location["z"] += amount + + def __str__(self): + return( + f"Score: {self._score}, " + f"Level: {self._level}, " + f"Location: {self._location}\n" + f"Inventory: {self._inventory}\n" + ) + + @ property + def memento(self): + "A `getter` for the characters attributes as a Memento" + return Memento( + self._score, + self._inventory.copy(), + self._level, + self._location.copy()) + + @ memento.setter + def memento(self, memento): + self._score = memento.score + self._inventory = memento.inventory + self._level = memento.level + self._location = memento.location diff --git a/memento/memento.py b/memento/memento.py index 48ce2b2..1998944 100644 --- a/memento/memento.py +++ b/memento/memento.py @@ -1,73 +1,11 @@ -""" -Memento pattern example. -""" +"A Memento to store character attributes" -from abc import ABCMeta, abstractmethod -class IComponent(metaclass=ABCMeta): - @staticmethod - @abstractmethod - def createMemento(msg): - """The required notify method""" +class Memento(): # pylint: disable=too-few-public-methods + "A container of characters attributes" - @staticmethod - @abstractmethod - def restore(msg): - """The required receive method""" - -class Memento(): - def __init__(self, state): - self._state = state - - def get_state(self): - return self._state - - -class Originator(): #Creator - _state = "" - - def set_state(self, state): - print("Originator: Setting state to", state) - self._state = state - - def get_state(self): - return self._state - - def save_to_memento(self): - print("Originator: Saving to Memento.") - return Memento(self._state) - - def restore_from_memento(self, memento): - self._state = memento.get_state() - print("Originator: State after restoring from Memento:", self._state) - - -class CareTaker(): #Guardian, Client - _memento_list = [] - - def add(self, state): - self._memento_list.append(state) - - def get(self, index): - return self._memento_list[index] - - -# client -ORIGINATOR = Originator() -CARETAKER = CareTaker() - -ORIGINATOR.set_state("State #1") -ORIGINATOR.set_state("State #2") -CARETAKER.add(ORIGINATOR.save_to_memento()) - -ORIGINATOR.set_state("State #3") -CARETAKER.add(ORIGINATOR.save_to_memento()) - -ORIGINATOR.set_state("State #4") -print(ORIGINATOR.get_state()) - -ORIGINATOR.restore_from_memento(CARETAKER.get(0)) -print(ORIGINATOR.get_state()) - -ORIGINATOR.restore_from_memento(CARETAKER.get(1)) -print(ORIGINATOR.get_state()) + def __init__(self, score, inventory, level, location): + self.score = score + self.inventory = inventory + self.level = level + self.location = location diff --git a/memento/memento2.py b/memento/memento2.py deleted file mode 100644 index 86f5d4e..0000000 --- a/memento/memento2.py +++ /dev/null @@ -1,59 +0,0 @@ -from abc import ABCMeta, abstractmethod - -class IMemento(metaclass=ABCMeta): - @staticmethod - @abstractmethod - def save(msg): - """The required save_memento method""" - - @staticmethod - @abstractmethod - def restore(msg): - """The required restore_memento method""" - - -class Memento(): - def __init__(self, state): - self._state = state - - def get(self): - return self._state - - -class Thing(IMemento): # Creator - _state = "" - - def set(self, state): - print("Originator: Setting state to", state) - self._state = state - - def get(self): - return self._state - - def save(self): - print("Saving Memento.") - return Memento(self._state) - - def restore(self, memento): - self._state = memento.get() - print("Restoring Memento:", self._state) - -# client -MEMENTOS = [] -ORIGINAL_OBJECT = Thing() - -ORIGINAL_OBJECT.set("State #1") -ORIGINAL_OBJECT.set("State #2") -MEMENTOS.append(ORIGINAL_OBJECT.save()) - -ORIGINAL_OBJECT.set("State #3") -MEMENTOS.append(ORIGINAL_OBJECT.save()) - -ORIGINAL_OBJECT.set("State #4") -print(ORIGINAL_OBJECT.get()) - -ORIGINAL_OBJECT.restore(MEMENTOS[0]) -print(ORIGINAL_OBJECT.get()) - -ORIGINAL_OBJECT.restore(MEMENTOS[1]) -print(ORIGINAL_OBJECT.get()) diff --git a/memento/memento_concept.py b/memento/memento_concept.py new file mode 100644 index 0000000..e458c5f --- /dev/null +++ b/memento/memento_concept.py @@ -0,0 +1,89 @@ +"Memento pattern concept" + + +class Memento(): # pylint: disable=too-few-public-methods + "A container of state" + + def __init__(self, state): + self.state = state + + +class Originator(): + "The Object in the application whose state changes" + + def __init__(self): + self._state = "" + + @property + def state(self): + "A `getter` for the objects state" + return self._state + + @state.setter + def state(self, state): + print(f"Originator: Setting state to `{state}`") + self._state = state + + @property + def memento(self): + "A `getter` for the objects state but packaged as a Memento" + print("Originator: Providing Memento of state to caretaker.") + return Memento(self._state) + + @memento.setter + def memento(self, memento): + self._state = memento.state + print( + f"Originator: State after restoring from Memento: " + f"`{self._state}`") + + +class CareTaker(): + "Guardian. Provides a narrow interface to the mementos" + + def __init__(self, originator): + self._originator = originator + self._mementos = [] + + def create(self): + "Store a new Memento of the Originators current state" + print("CareTaker: Getting a copy of Originators current state") + memento = self._originator.memento + self._mementos.append(memento) + + def restore(self, index): + """ + Replace the Originators current state with the state + stored in the saved Memento + """ + print("CareTaker: Restoring Originators state from Memento") + memento = self._mementos[index] + self._originator.memento = memento + + +# The Client +ORIGINATOR = Originator() +CARETAKER = CareTaker(ORIGINATOR) + +# originators state can change periodically due to application events +ORIGINATOR.state = "State #1" +ORIGINATOR.state = "State #2" + +# lets backup the originators +CARETAKER.create() + +# more changes, and then another backup +ORIGINATOR.state = "State #3" +CARETAKER.create() + +# more changes +ORIGINATOR.state = "State #4" +print(ORIGINATOR.state) + +# restore from first backup +CARETAKER.restore(0) +print(ORIGINATOR.state) + +# restore from second backup +CARETAKER.restore(1) +print(ORIGINATOR.state) diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index 2bb755d..0000000 --- a/mkdocs.yml +++ /dev/null @@ -1,39 +0,0 @@ -site_name: Design Patterns In Python -nav: - - Home: index.md - #- Creational: - - Factory: factory.md - - Abstract Factory: abstract_factory.md - - Builder: builder.md - - ProtoType: prototype.md - #- Structural: - - Decorator: decorator.md - - Adapter: adapter.md - - Facade: facade.md - - Composite: composite.md - - Proxy: proxy.md - #- Behavioural: - - Command: command.md - - Command Undo/Redo: command_undo_redo.md - - Chain of Responsibility: chain_of_responsibility.md - - Observer: observer.md - - Iterator: iterator.md - - Mediator: mediator.md -theme: - name: 'material' - custom_dir: 'G:\mkdocs-material\material' - language: 'en' - palette: - primary: 'Blue Grey' - accent: 'Blue Grey' - font: - false - -markdown_extensions: - - codehilite: - guess_lang: false -google_analytics: - - 'UA-147973042-3' - - 'auto' -site_author: 'Sean Bradley' -copyright: 'Copyright 2019 © Sean Bradley' diff --git a/notes b/notes deleted file mode 100644 index b1a57ad..0000000 --- a/notes +++ /dev/null @@ -1,5 +0,0 @@ -Adapter V Facade V Proxy V Decorator -• Adapter adapts an existing class/object to a new interface. Make incompatible interface compatible. -• Facade is a class that simplifies a complicated set of functionality. -• Proxy provides the same interface as the subject class and allows additional functionality to be added. -• Decorator is a wrapper to extend functionality to an existing class at runtime. \ No newline at end of file diff --git a/observer/Observer Design Pattern.md b/observer/Observer Design Pattern.md deleted file mode 100644 index 6e79515..0000000 --- a/observer/Observer Design Pattern.md +++ /dev/null @@ -1,6 +0,0 @@ -# Observer Pattern - -The observer pattern is a software design pattern in which an object, called the subject or observable, manages a list of dependents, called observers, and notifies them automatically of any internal state changes, and calls one of their methods. - -![Observer Pattern](observer.png) - diff --git a/observer/Observer Design Pattern.pdf b/observer/Observer Design Pattern.pdf deleted file mode 100644 index e8900a8..0000000 Binary files a/observer/Observer Design Pattern.pdf and /dev/null differ diff --git a/observer/README.md b/observer/README.md index 6e79515..1965ab3 100644 --- a/observer/README.md +++ b/observer/README.md @@ -1,6 +1,91 @@ # Observer Pattern -The observer pattern is a software design pattern in which an object, called the subject or observable, manages a list of dependents, called observers, and notifies them automatically of any internal state changes, and calls one of their methods. +## Videos -![Observer Pattern](observer.png) +Section | Video Links +-|- +Observer Overview | Observer Overview Observer Overview Observer Overview +Observer Use Case | Observer Use Case Observer Use Case Observer Use Case +Python **Set** | Python Set Python Set Python Set +## Book + +Cover | Links +-|- +![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    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 + +## Overview + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Terminology + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Observer UML Diagram + +![Observer Pattern Overview](/img/observer_concept.svg) + +## Source Code + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Output + +``` bash +python ./observer/observer_concept.py +Observer id:2084220160272 received ('First Notification', [1, 2, 3]) +Observer id:2084220160224 received ('First Notification', [1, 2, 3]) +Observer id:2084220160272 received ('Second Notification', {'A': 1, 'B': 2, 'C': 3}) +``` + +## Example Use Case + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Example UML Diagram + +![Observer Pattern in Context](/img/observer_example.svg) + +## Output + +``` bash +python ./observer/client.py +PieGraph, id:1 +Drawing a Pie graph using data:[1, 2, 3] +BarGraph, id:2 +Drawing a Bar graph using data:[1, 2, 3] +TableView, id:3 +Drawing a Table view using data:[1, 2, 3] +PieGraph, id:1 +Drawing a Pie graph using data:[4, 5, 6] +TableView, id:3 +Drawing a Table view using data:[4, 5, 6] +``` + +## New Coding Concepts + +### Python Set + +A Python **Set** is similar to a List. Except that the items in the Set are guaranteed to be unique, even if you try to add a duplicate. A set is a good choice for keeping a collection of observables, since the problem of duplicate observables is automatically handled. + +A Set can be instantiated using the curly braces `{}` or `set()`, verses `[]` for a [List](/builder#python-list) and `()` for a [Tuple](/bridge#python-tuple). It is not the same as a [Dictionary](/singleton#python-dictionary), which also uses `{}`, since the dictionary items are created as `key:value` pairs. ie `{"a": 1, "b": 2, "c": 3}` + +``` python +PS> python +>>> items = {"yankee", "doodle", "dandy", "doodle"} +>>> items +{'yankee', 'doodle', 'dandy'} +>>> items.add("grandy") +>>> items +{'grandy', 'yankee', 'doodle', 'dandy'} +>>> items.remove("doodle") +>>> items +{'grandy', 'yankee', 'dandy'} +``` + +Note, if instantiating an empty **Set** then use `my_object = Set()` rather than `my_object = {}` to reduce ambiguity with creating an empty [Dictionary](/singleton#python-dictionary). + +## Summary + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ \ No newline at end of file diff --git a/observer/bar_graph_view.py b/observer/bar_graph_view.py new file mode 100644 index 0000000..263129e --- /dev/null +++ b/observer/bar_graph_view.py @@ -0,0 +1,20 @@ +"An observer" +from interface_data_view import IDataView + + +class BarGraphView(IDataView): + "The concrete observer" + + def __init__(self, observable): + self._observable = observable + self._id = self._observable.subscribe(self) + + def notify(self, data): + print(f"BarGraph, id:{self._id}") + self.draw(data) + + def draw(self, data): + print(f"Drawing a Bar graph using data:{data}") + + def delete(self): + self._observable.unsubscribe(self._id) diff --git a/observer/classes.dot b/observer/classes.dot deleted file mode 100644 index a1a6cb9..0000000 --- a/observer/classes.dot +++ /dev/null @@ -1,10 +0,0 @@ -digraph "classes" { -charset="utf-8" -rankdir=BT -"0" [label="{IObservable|\l|notify()\lsubscribe()\lunsubscribe()\l}", shape="record"]; -"1" [label="{IObserver|\l|notify()\l}", shape="record"]; -"2" [label="{Observer|\l|notify()\l}", shape="record"]; -"3" [label="{Subject|\l|notify()\lsubscribe()\lunsubscribe()\l}", shape="record"]; -"2" -> "1" [arrowhead="empty", arrowtail="none"]; -"3" -> "0" [arrowhead="empty", arrowtail="none"]; -} diff --git a/observer/client.py b/observer/client.py new file mode 100644 index 0000000..5392c60 --- /dev/null +++ b/observer/client.py @@ -0,0 +1,28 @@ +"Observer Design Pattern Concept" + +from data_model import DataModel +from data_controller import DataController +from pie_graph_view import PieGraphView +from bar_graph_view import BarGraphView +from table_view import TableView + +# A local data view that the hypothetical external controller updates +DATA_MODEL = DataModel() + +# Add some visualisation that use the dataview +PIE_GRAPH_VIEW = PieGraphView(DATA_MODEL) +BAR_GRAPH_VIEW = BarGraphView(DATA_MODEL) +TABLE_VIEW = TableView(DATA_MODEL) + + +# A hypothetical data controller running in a different process +DATA_CONTROLLER = DataController() + +# The hypothetical external data controller updates some data +DATA_CONTROLLER.notify([1, 2, 3]) + +# Client now removes a local BAR_GRAPH +BAR_GRAPH_VIEW.delete() + +# The hypothetical external data controller updates the data again +DATA_CONTROLLER.notify([4, 5, 6]) diff --git a/observer/data_controller.py b/observer/data_controller.py new file mode 100644 index 0000000..8661b34 --- /dev/null +++ b/observer/data_controller.py @@ -0,0 +1,24 @@ +"A Data Conroller that is a Subject" +from interface_data_controller import IDataController + + +class DataController(IDataController): + "A Subject (a.k.a Observable)" + + _observers = set() + + def __new__(cls): + return cls + + @classmethod + def subscribe(cls, observer): + cls._observers.add(observer) + + @classmethod + def unsubscribe(cls, observer): + cls._observers.remove(observer) + + @classmethod + def notify(cls, *args): + for observer in cls._observers: + observer.notify(*args) diff --git a/observer/data_model.py b/observer/data_model.py new file mode 100644 index 0000000..9af6cb1 --- /dev/null +++ b/observer/data_model.py @@ -0,0 +1,26 @@ +"A Data Model that observes the Data Controller" +from interface_data_model import IDataModel +from data_controller import DataController + + +class DataModel(IDataModel): + "A Subject (a.k.a Observable)" + + def __init__(self): + self._observers = {} + self._counter = 0 + # subscribing to an external hypothetical data controller + self._data_controller = DataController() + self._data_controller.subscribe(self) + + def subscribe(self, observer): + self._counter = self._counter + 1 + self._observers[self._counter] = observer + return self._counter + + def unsubscribe(self, observer_id): + self._observers.pop(observer_id) + + def notify(self, data): + for observer in self._observers: + self._observers[observer].notify(data) diff --git a/observer/interface_data_controller.py b/observer/interface_data_controller.py new file mode 100644 index 0000000..cf650b2 --- /dev/null +++ b/observer/interface_data_controller.py @@ -0,0 +1,20 @@ +"A Data Controller Interface" +from abc import ABCMeta, abstractmethod + + +class IDataController(metaclass=ABCMeta): + "A Subject Interface" + @staticmethod + @abstractmethod + def subscribe(observer): + "The subscribe method" + + @staticmethod + @abstractmethod + def unsubscribe(observer): + "The unsubscribe method" + + @staticmethod + @abstractmethod + def notify(observer): + "The notify method" diff --git a/observer/interface_data_model.py b/observer/interface_data_model.py new file mode 100644 index 0000000..105baeb --- /dev/null +++ b/observer/interface_data_model.py @@ -0,0 +1,21 @@ +"A Data Model Interface" +from abc import ABCMeta, abstractmethod + + +class IDataModel(metaclass=ABCMeta): + "A Subject Interface" + + @staticmethod + @abstractmethod + def subscribe(observer): + "The subscribe method" + + @staticmethod + @abstractmethod + def unsubscribe(observer_id): + "The unsubscribe method" + + @staticmethod + @abstractmethod + def notify(data): + "The notify method" diff --git a/observer/interface_data_view.py b/observer/interface_data_view.py new file mode 100644 index 0000000..3016064 --- /dev/null +++ b/observer/interface_data_view.py @@ -0,0 +1,21 @@ +"The Data View interface" +from abc import ABCMeta, abstractmethod + + +class IDataView(metaclass=ABCMeta): + "A method for the Observer to implement" + + @staticmethod + @abstractmethod + def notify(data): + "Receive notifications" + + @staticmethod + @abstractmethod + def draw(data): + "Draw the view" + + @staticmethod + @abstractmethod + def delete(): + "a delete method to remove observer specific resources" diff --git a/observer/observer.dot b/observer/observer.dot deleted file mode 100644 index 3c8cf54..0000000 --- a/observer/observer.dot +++ /dev/null @@ -1,13 +0,0 @@ -digraph "classes" { -charset="utf-8" -nodesep=1 -rankdir=BT -{rank=same;2,3} -"0" [label="{IObservable|\l|notify()\lsubscribe()\lunsubscribe()\l}", shape="record"]; -"1" [label="{IObserver|\l|notify()\l}", shape="record"]; -"2" [label="{Observer|\l|notify()\l}", shape="record"]; -"3" [label="{Subject|\l|notify()\lsubscribe()\lunsubscribe()\l}", shape="record"]; -"2" -> "1" [arrowhead="empty", arrowtail="none"]; -"3" -> "0" [arrowhead="empty", arrowtail="none"]; -"2" -> "3" [arrowhead="ediamond", arrowtail="vee", dir="both"]; -} diff --git a/observer/observer.png b/observer/observer.png deleted file mode 100644 index 595ce02..0000000 Binary files a/observer/observer.png and /dev/null differ diff --git a/observer/observer.py b/observer/observer.py deleted file mode 100644 index 149c987..0000000 --- a/observer/observer.py +++ /dev/null @@ -1,62 +0,0 @@ -""" -Observer Design Pattern -""" - -from abc import ABCMeta, abstractmethod - - -class IObservable(metaclass=ABCMeta): - @staticmethod - @abstractmethod - def subscribe(observer): - """The subscribe method""" - - @staticmethod - @abstractmethod - def unsubscribe(observer): - """The unsubscribe method""" - - @staticmethod - @abstractmethod - def notify(observer): - """The notify method""" - - -class Subject(IObservable): - def __init__(self): - self._observers = set() - - def subscribe(self, observer): - self._observers.add(observer) - - def unsubscribe(self, observer): - self._observers.remove(observer) - - def notify(self, *args, **kwargs): - for observer in self._observers: - observer.notify(self, *args, **kwargs) - - -class IObserver(metaclass=ABCMeta): - @staticmethod - @abstractmethod - def notify(observable, *args, **kwargs): - """Receive notifications""" - - -class Observer(IObserver): - def __init__(self, observable): - observable.subscribe(self) - - def notify(self, observable, *args, **kwargs): - print("Observer received", args, kwargs) - - -SUBJECT = Subject() -OBSERVERA = Observer(SUBJECT) -OBSERVERB = Observer(SUBJECT) - -SUBJECT.notify("Hello Observers") - -SUBJECT.unsubscribe(OBSERVERB) -SUBJECT.notify("Hello Observers") diff --git a/observer/observer_concept.py b/observer/observer_concept.py new file mode 100644 index 0000000..1c29aa3 --- /dev/null +++ b/observer/observer_concept.py @@ -0,0 +1,71 @@ +# pylint: disable=too-few-public-methods +# pylint: disable=arguments-differ +"Observer Design Pattern Concept" + +from abc import ABCMeta, abstractmethod + + +class IObservable(metaclass=ABCMeta): + "The Subject Interface" + + @staticmethod + @abstractmethod + def subscribe(observer): + "The subscribe method" + + @staticmethod + @abstractmethod + def unsubscribe(observer): + "The unsubscribe method" + + @staticmethod + @abstractmethod + def notify(observer): + "The notify method" + + +class Subject(IObservable): + "The Subject (a.k.a Observable)" + + def __init__(self): + self._observers = set() + + def subscribe(self, observer): + self._observers.add(observer) + + def unsubscribe(self, observer): + self._observers.remove(observer) + + def notify(self, *args): + for observer in self._observers: + observer.notify(*args) + + +class IObserver(metaclass=ABCMeta): + "A method for the Observer to implement" + + @staticmethod + @abstractmethod + def notify(observable, *args): + "Receive notifications" + + +class Observer(IObserver): + "The concrete observer" + + def __init__(self, observable): + observable.subscribe(self) + + def notify(self, *args): + print(f"Observer id:{id(self)} received {args}") + + +# The Client +SUBJECT = Subject() +OBSERVER_A = Observer(SUBJECT) +OBSERVER_B = Observer(SUBJECT) + +SUBJECT.notify("First Notification", [1, 2, 3]) + +SUBJECT.unsubscribe(OBSERVER_B) +SUBJECT.notify("Second Notification", {"A": 1, "B": 2, "C": 3}) diff --git a/observer/pie_graph_view.py b/observer/pie_graph_view.py new file mode 100644 index 0000000..5980b63 --- /dev/null +++ b/observer/pie_graph_view.py @@ -0,0 +1,20 @@ +"An observer" +from interface_data_view import IDataView + + +class PieGraphView(IDataView): + "The concrete observer" + + def __init__(self, observable): + self._observable = observable + self._id = self._observable.subscribe(self) + + def notify(self, data): + print(f"PieGraph, id:{self._id}") + self.draw(data) + + def draw(self, data): + print(f"Drawing a Pie graph using data:{data}") + + def delete(self): + self._observable.unsubscribe(self._id) diff --git a/observer/table_view.py b/observer/table_view.py new file mode 100644 index 0000000..23fb8d5 --- /dev/null +++ b/observer/table_view.py @@ -0,0 +1,20 @@ +"An observer" +from interface_data_view import IDataView + + +class TableView(IDataView): + "The concrete observer" + + def __init__(self, observable): + self._observable = observable + self._id = self._observable.subscribe(self) + + def notify(self, data): + print(f"TableView, id:{self._id}") + self.draw(data) + + def draw(self, data): + print(f"Drawing a Table view using data:{data}") + + def delete(self): + self._observable.unsubscribe(self._id) diff --git a/observer/weather_observer.dot b/observer/weather_observer.dot deleted file mode 100644 index e0c98ef..0000000 --- a/observer/weather_observer.dot +++ /dev/null @@ -1,15 +0,0 @@ -digraph "classes" { -charset="utf-8" -nodesep=0.5 -ranksep=1 -{rank=same;3;8} -"0" [label="{ABCWeather|\l|notify()\l}", shape="record"]; -"1" [label="{BBCWeather|\l|notify()\l}", shape="record"]; -"3" [label="{IObserver|\l|notify()\l}", shape="record"]; -"4" [label="{NBCWeather|\l|notify()\l}", shape="record"]; -"8" [label="{Weather|\l|subscribe()\lunsubscribe()\lnotify()\l}", shape="record"]; -"0" -> "3" [arrowhead="empty", arrowtail="none"]; -"1" -> "3" [arrowhead="empty", arrowtail="none"]; -"4" -> "3" [arrowhead="empty", arrowtail="none"]; -"3" -> "8" [arrowhead="ediamond", arrowtail="vee", dir="both"]; -} diff --git a/observer/weather_observer.png b/observer/weather_observer.png deleted file mode 100644 index be35082..0000000 Binary files a/observer/weather_observer.png and /dev/null differ diff --git a/observer/weather_observer.py b/observer/weather_observer.py deleted file mode 100644 index f9c12c0..0000000 --- a/observer/weather_observer.py +++ /dev/null @@ -1,81 +0,0 @@ -""" -Observer Design Pattern -""" - -from abc import ABCMeta, abstractmethod -from enum import Enum -import time - - -class WeatherType(Enum): - SUNNY = 1 - RAINY = 2 - WINDY = 3 - COLD = 4 - - -class Weather: - """Observable""" - - def __init__(self): - self._observers = set() - - def subscribe(self, observer): - self._observers.add(observer) - - def unsubscribe(self, observer): - self._observers.remove(observer) - - def notify(self, weather_type): - for observer in self._observers: - observer.notify(weather_type) - - -class IObserver(metaclass=ABCMeta): - @staticmethod - @abstractmethod - def notify(weather_type): - """Update all the subscribed observers""" - - -class BBCWeather(IObserver): - def __init__(self, observable): - observable.subscribe(self) - - def notify(self, weather_type): - print(f"{__class__} : {repr(weather_type)}") - - -class ABCWeather(IObserver): - def __init__(self, observable): - observable.subscribe(self) - - def notify(self, weather_type): - print(f"{__class__} : {repr(weather_type)}") - - -class NBCWeather(IObserver): - def __init__(self, observable): - observable.subscribe(self) - - def notify(self, weather_type): - print(f"{__class__} : {repr(weather_type)}") - - -if __name__ == "__main__": - - WEATHERSERVICE = Weather() - - BBCWEATHER = BBCWeather(WEATHERSERVICE) - ABCWEATHER = ABCWeather(WEATHERSERVICE) - NBCWEATHER = NBCWeather(WEATHERSERVICE) - - WEATHERSERVICE.notify(WeatherType.RAINY) - WEATHERSERVICE.unsubscribe(BBCWEATHER) - time.sleep(2) - WEATHERSERVICE.notify(WeatherType.SUNNY) - WEATHERSERVICE.unsubscribe(NBCWEATHER) - time.sleep(2) - WEATHERSERVICE.notify(WeatherType.WINDY) - time.sleep(2) - WEATHERSERVICE.notify(WeatherType.COLD) diff --git a/prototype/Prototype Design Pattern.md b/prototype/Prototype Design Pattern.md deleted file mode 100644 index 4e8de9a..0000000 --- a/prototype/Prototype Design Pattern.md +++ /dev/null @@ -1,17 +0,0 @@ -# Prototype Design Pattern - -Prototype design pattern is good for when creating a new objects may require more resources than you want to use or have available, versus just making a new copy in memory. -Eg, A file you've downloaded from a server may be large, but since it is already in memory, you could just clone it, and work on the new copy independently of the original. - -In the prototype patterns interface, you create a static clone method that should be implemented by all classes that use the interface. -How the clone method is implemented in the concrete class is up to you. -You will need to decide whether a shallow or deep copy is required. - -* A shallow copy, copies and creates new references 1 level deep, -* A deep copy, copies and creates new references for all levels. - -In Python you have mutable objects such as Lists, Dictionaries, Sets and any custom Objects you have created. A shallow copy, will create new copies of the objects with new references in memory, but the underlying data will point to the same location as the original copy. Be sure to test your implementation that -the copy method you use works as you expect. - -![Prototype UML Diagram](prototype.png) - diff --git a/prototype/Prototype Design Pattern.pdf b/prototype/Prototype Design Pattern.pdf deleted file mode 100644 index d1790da..0000000 Binary files a/prototype/Prototype Design Pattern.pdf and /dev/null differ diff --git a/prototype/README.md b/prototype/README.md index 4e8de9a..0b69ff8 100644 --- a/prototype/README.md +++ b/prototype/README.md @@ -1,17 +1,35 @@ # Prototype Design Pattern -Prototype design pattern is good for when creating a new objects may require more resources than you want to use or have available, versus just making a new copy in memory. -Eg, A file you've downloaded from a server may be large, but since it is already in memory, you could just clone it, and work on the new copy independently of the original. +## Videos -In the prototype patterns interface, you create a static clone method that should be implemented by all classes that use the interface. -How the clone method is implemented in the concrete class is up to you. -You will need to decide whether a shallow or deep copy is required. +Section | Video Links +-|- +Prototype Overview | Prototype Overview Prototype Overview Prototype Overview +Prototype Use Case | Prototype Use Case Prototype Use Case Prototype Use Case +Python **id()** function | python id function python id function python id function -* A shallow copy, copies and creates new references 1 level deep, -* A deep copy, copies and creates new references for all levels. +## Book -In Python you have mutable objects such as Lists, Dictionaries, Sets and any custom Objects you have created. A shallow copy, will create new copies of the objects with new references in memory, but the underlying data will point to the same location as the original copy. Be sure to test your implementation that -the copy method you use works as you expect. +Cover | Links +-|- +![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    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 -![Prototype UML Diagram](prototype.png) +## Overview +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Terminology + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Prototype UML Diagram + +![Prototype UML Diagram](/img/prototype_concept.svg) + +## Source Code + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Summary + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ \ No newline at end of file diff --git a/prototype/classes_prototype.dot b/prototype/classes_prototype.dot deleted file mode 100644 index ae03e0f..0000000 --- a/prototype/classes_prototype.dot +++ /dev/null @@ -1,9 +0,0 @@ -digraph "classes_prototype" { -charset="utf-8" -rankdir=BT -"0" [label="{ConcreteClass1|d : dict\li : int\ll : list\ls : str\l|clone()\l}", shape="record"]; -"1" [label="{ConcreteClass2|d : dict\li : int\ll : list\ls : str\l|clone()\l}", shape="record"]; -"2" [label="{IProtoType|\l|clone()\l}", shape="record"]; -"0" -> "2" [arrowhead="empty", arrowtail="none"]; -"1" -> "2" [arrowhead="empty", arrowtail="none"]; -} diff --git a/prototype/client.py b/prototype/client.py new file mode 100644 index 0000000..502795b --- /dev/null +++ b/prototype/client.py @@ -0,0 +1,43 @@ +"Prototype Use Case Example Code" +from document import Document + +# Creating a document containing a list of two lists +ORIGINAL_DOCUMENT = Document("Original", [[1, 2, 3, 4], [5, 6, 7, 8]]) +print(ORIGINAL_DOCUMENT) +print() + +DOCUMENT_COPY_1 = ORIGINAL_DOCUMENT.clone(1) # shallow copy +DOCUMENT_COPY_1.name = "Copy 1" +# This also modified ORIGINAL_DOCUMENT because of the shallow copy +# when using mode 1 +DOCUMENT_COPY_1.list[1][2] = 200 +print(DOCUMENT_COPY_1) +print(ORIGINAL_DOCUMENT) +print() + +DOCUMENT_COPY_2 = ORIGINAL_DOCUMENT.clone(2) # 2 level shallow copy +DOCUMENT_COPY_2.name = "Copy 2" +# This does NOT modify ORIGINAL_DOCUMENT because it changes the +# list[1] reference that was deep copied when using mode 2 +DOCUMENT_COPY_2.list[1] = [9, 10, 11, 12] +print(DOCUMENT_COPY_2) +print(ORIGINAL_DOCUMENT) +print() + +DOCUMENT_COPY_3 = ORIGINAL_DOCUMENT.clone(2) # 2 level shallow copy +DOCUMENT_COPY_3.name = "Copy 3" +# This does modify ORIGINAL_DOCUMENT because it changes the element of +# list[1][0] that was NOT deep copied recursively when using mode 2 +DOCUMENT_COPY_3.list[1][0] = "1234" +print(DOCUMENT_COPY_3) +print(ORIGINAL_DOCUMENT) +print() + +DOCUMENT_COPY_4 = ORIGINAL_DOCUMENT.clone(3) # deep copy (recursive) +DOCUMENT_COPY_4.name = "Copy 4" +# This does NOT modify ORIGINAL_DOCUMENT because it +# deep copies all levels recursively when using mode 3 +DOCUMENT_COPY_4.list[1][0] = "5678" +print(DOCUMENT_COPY_4) +print(ORIGINAL_DOCUMENT) +print() diff --git a/prototype/document.py b/prototype/document.py new file mode 100644 index 0000000..9372d1b --- /dev/null +++ b/prototype/document.py @@ -0,0 +1,35 @@ +"A sample document to be used in the Prototype example" +import copy # a python library useful for deep copying +from interface_prototype import IProtoType + + +class Document(IProtoType): + "A Concrete Class" + + def __init__(self, name, l): + self.name = name + self.list = l + + def clone(self, mode): + " This clone method uses different copy techniques " + if mode == 1: + # results in a 1 level shallow copy of the Document + doc_list = self.list + if mode == 2: + # results in a 2 level shallow copy of the Document + # since it also create new references for the 1st level list + # elements aswell + doc_list = self.list.copy() + if mode == 3: + # recursive deep copy. Slower but results in a new copy + # where no sub elements are shared by reference + doc_list = copy.deepcopy(self.list) + + return type(self)( + self.name, # a shallow copy is returned of the name property + doc_list # copy method decided by mode argument + ) + + def __str__(self): + " Overriding the default __str__ method for our object." + return f"{id(self)}\tname={self.name}\tlist={self.list}" diff --git a/prototype/interface_prototype.py b/prototype/interface_prototype.py new file mode 100644 index 0000000..18f646f --- /dev/null +++ b/prototype/interface_prototype.py @@ -0,0 +1,13 @@ +# pylint: disable=too-few-public-methods +"Prototype Concept Sample Code" +from abc import ABCMeta, abstractmethod + + +class IProtoType(metaclass=ABCMeta): + "interface with clone method" + @staticmethod + @abstractmethod + def clone(mode): + """The clone, deep or shallow. + It is up to you how you want to implement + the details in your concrete class""" diff --git a/prototype/prototype.png b/prototype/prototype.png deleted file mode 100644 index 209f77c..0000000 Binary files a/prototype/prototype.png and /dev/null differ diff --git a/prototype/prototype.py b/prototype/prototype.py deleted file mode 100644 index 602f9a5..0000000 --- a/prototype/prototype.py +++ /dev/null @@ -1,89 +0,0 @@ -""" -Prototype design pattern is good for when creating a new object -may require more resources than you want to use or have available, -versus just making a new copy in memory. -Eg, A file you've downloaded from a server may be large, but -since it is already in memory, you could just clone it, and -work on the new copy independently of the original. - -In the prototype patterns interface, you create a static clone method that -should be implemented by all classes that use the interface. -How the clone method is implemented in the concrete class is up to you. -You will need to decide whether a shallow or deep copy is required. -A shallow copy, copies and creates new references 1 level deep, -A deep copy, copies and creates new references for all levels. -In Python you have mutable objects such as Lists, Dictionaries, -Sets and any custom Objects you have created. A shallow copy, -will create new copies of the objects with new references in memory, -but the underlying data will point to the same location as the original -copy. Be sure to test your implementation that -the copy method you use works as expected. -""" - -from abc import ABCMeta, abstractstaticmethod -import copy - - -class IProtoType(metaclass=ABCMeta): - """interface with clone method""" - @abstractstaticmethod - def clone(): - """The clone, deep or shallow, is up to how you - want implement the details in your concrete class?""" - - -class ConcreteClass1(IProtoType): - """concrete class 1""" - - def __init__(self, i=0, s="", l=[], d={}): - self.i = i - self.s = s - self.l = l - self.d = d - - def clone(self): - return type(self)( - self.i, - self.s, - self.l.copy(), - self.d.copy()) - - def __str__(self): - return f"{id(self)}\ti={self.i}\ts={self.s}\tl={self.l}\td={self.d}" - - -class ConcreteClass2(IProtoType): - """concrete class 2""" - - def __init__(self, i=0, s="", l=[], d={}): - self.i = i - self.s = s - self.l = l - self.d = d - - def clone(self): - return type(self)( - self.i, - self.s, - copy.deepcopy(self.l), - copy.deepcopy(self.d)) - - def __str__(self): - return f"i={self.i}\t\ts={self.s}\tl={self.l}\td={self.d}\n{id(self.i)}\t{id(self.s)}\t{id(self.l)}\t{id(self.d)}\t" - - -if __name__ == "__main__": - - OBJECT1 = ConcreteClass1( - 1, - "OBJECT1", - [1, 2, 3], - {"a": 4, "b": 5, "c": 6} - ) - print(f"OBJECT1 {OBJECT1}") - - OBJECT2 = OBJECT1.clone() - OBJECT2.s = "OBJECT2" - OBJECT2.l[0] = 10 - print(f"OBJECT2 {OBJECT2}") - print(f"OBJECT1 {OBJECT1}") diff --git a/prototype/prototype_concept.py b/prototype/prototype_concept.py new file mode 100644 index 0000000..3c82b1b --- /dev/null +++ b/prototype/prototype_concept.py @@ -0,0 +1,57 @@ +# pylint: disable=too-few-public-methods +# pylint: disable=arguments-differ +"Prototype Concept Sample Code" +from abc import ABCMeta, abstractmethod + + +class IProtoType(metaclass=ABCMeta): + "interface with clone method" + @staticmethod + @abstractmethod + def clone(): + """The clone, deep or shallow. + It is up to you how you want to implement + the details in your concrete class""" + + +class MyClass(IProtoType): + "A Concrete Class" + + def __init__(self, field): + self.field = field # any value of any type + + def clone(self): + " This clone method uses a shallow copy technique " + return type(self)( + self.field # a shallow copy is returned + # self.field.copy() # this is also a shallow copy, but has + # also shallow copied the first level of the field. So it + # is essentially a shallow copy but 2 levels deep. To + # recursively deep copy collections containing inner + # collections, + # eg lists of lists, + # Use https://docs.python.org/3/library/copy.html instead. + # See example below. + ) + + def __str__(self): + return f"{id(self)}\tfield={self.field}\ttype={type(self.field)}" + + +# The Client +OBJECT1 = MyClass([1, 2, 3, 4]) # Create the object containing a list +print(f"OBJECT1 {OBJECT1}") + +OBJECT2 = OBJECT1.clone() # Clone + +# Change the value of one of the list elements in OBJECT2, +# to see if it also modifies the list element in OBJECT1. +# If it changed OBJECT1s copy also, then the clone was done +# using a 1 level shallow copy process. +# Modify the clone method above to try a 2 level shallow copy instead +# and compare the output +OBJECT2.field[1] = 101 + +# Comparing OBJECT1 and OBJECT2 +print(f"OBJECT2 {OBJECT2}") +print(f"OBJECT1 {OBJECT1}") diff --git a/proxy/README.md b/proxy/README.md index 12f1f95..64456cf 100644 --- a/proxy/README.md +++ b/proxy/README.md @@ -1,19 +1,104 @@ # Proxy Design Pattern -The proxy design pattern is a class functioning as an interface to another class or object. +## Videos -A proxy could be for anything, such as a network connection, an object in memory, a file, or anything else you need to provide an abstraction between. +Section | Video Links +-|- +Proxy Overview | Proxy Overview Proxy Overview Proxy Overview +Proxy Use Case | Proxy Use Case Proxy Use Case Proxy Use Case +**\_\_class\_\_** Attribute | __class__ Attribute __class__ Attribute __class__ Attribute +Circular Imports | Circular Imports Circular Imports Circular Imports -It is a wrapper called by a client to access the real underlying object. +## Book -Additional functionality can be provided at in the proxy abstraction if required. -eg, caching, authorization, validation, lazy initialization, logging. +Cover | Links +-|- +![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    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 -The proxy should implement the subject interface as much as practicable so that the proxy and subject appear identical to the client. +## Overview -The Proxy Pattern may occasionally also be referred to as Monkey Patching or -Object Augmentation +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ -![Proxy Pattern UML Diagram](proxy.png) +## Terminology +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ +## Proxy UML Diagram + +![Proxy Pattern UML Diagram](/img/proxy_concept.svg) + +## Output + +``` bash +python ./proxy/proxy_concept.py +1848118706080 +pulling data from RealSubject +[1, 2, 3] +pulling data from Proxy cache +[1, 2, 3] +``` + +## Example Use Case + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Example UML Diagram + +![Proxy Use Case Example](/img/proxy_example.svg) + +## Output + +``` bash +python ./proxy/client.py +I am the form of a Lion +I am the form of a Leopard +I am the form of a Serpent +I am the form of a Leopard +I am the form of a Lion +``` + +## New Coding Concepts + +### Changing An Objects Class At Runtime. + +You change the class of an object by running `self.__class__ = SomeOtherClass` + +Note that doing this does not affect any variables created during initialisation, eg `self.variable_name = 'abc'`, since the object itself hasn't changed. Only its class methods and static attributes have been replaced with the class methods and static attributes of the other class. + +This explains how calling `tell_me_the_future()` and `tell_me_your_form()` produced different results after changing `self.__class__` + +### Avoiding Circular Imports. + +Normally in all the examples so far, I have been importing using the form + +``` python +from module import Class +``` + +In [/proxy/client.py](/proxy/client.py) I import the `Lion` module. The `Lion` module itself imports the `Leopard` and `Serpent` modules, which in turn also re import the `Lion` module again. This is a circular import and occurs in some situations when you separate your modules into individual files. + +Circular imports will prevent the python interpreter from compiling your `.py` file into byte code. + +The error will appear like, + +``` +cannot import name 'Lion' from partially initialized module 'lion' (most likely due to a circular import) +``` + +To avoid circular import errors, you can import modules using the form. + +``` python +import module +``` + +and when the import is actually needed in some method + +``` python +OBJECT = module.ClassName +``` + +See the [Lion](/proxy/lion.py), [Serpent](/proxy/serpent.py) and [Leopard](/proxy/leopard.py) classes for examples. + +## Summary + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ \ No newline at end of file diff --git a/proxy/classes.dot b/proxy/classes.dot deleted file mode 100644 index a03a778..0000000 --- a/proxy/classes.dot +++ /dev/null @@ -1,10 +0,0 @@ -digraph "classes" { -charset="utf-8" -rankdir=BT -"0" [label="{IComponent|\l|method()\l}", shape="record"]; -"1" [label="{Component|\l|method()\l}", shape="record"]; -"2" [label="{ProxyComponent|component\l|method()\l}", shape="record"]; -"1" -> "0" [arrowhead="empty", arrowtail="none"]; -"2" -> "0" [arrowhead="empty", arrowtail="none"]; -"1" -> "2" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="component", style="solid"]; -} diff --git a/proxy/client.py b/proxy/client.py new file mode 100644 index 0000000..639fbe0 --- /dev/null +++ b/proxy/client.py @@ -0,0 +1,15 @@ +"The Proxy Example Use Case" + +from lion import Lion + +PROTEUS = Lion() +PROTEUS.tell_me_your_form() +PROTEUS.tell_me_the_future() +PROTEUS.tell_me_your_form() +PROTEUS.tell_me_the_future() +PROTEUS.tell_me_your_form() +PROTEUS.tell_me_the_future() +PROTEUS.tell_me_your_form() +PROTEUS.tell_me_the_future() +PROTEUS.tell_me_your_form() +PROTEUS.tell_me_the_future() diff --git a/proxy/interface_proteus.py b/proxy/interface_proteus.py new file mode 100644 index 0000000..44db618 --- /dev/null +++ b/proxy/interface_proteus.py @@ -0,0 +1,17 @@ +"The Proteus Interface" + +from abc import ABCMeta, abstractmethod + + +class IProteus(metaclass=ABCMeta): # pylint: disable=too-few-public-methods + "A Greek mythological character that can change to many forms" + + @staticmethod + @abstractmethod + def tell_me_the_future(): + "Proteus will change form rather than tell you the future" + + @staticmethod + @abstractmethod + def tell_me_your_form(): + "The form of Proteus is elusive like the sea" diff --git a/proxy/leopard.py b/proxy/leopard.py new file mode 100644 index 0000000..83e22bf --- /dev/null +++ b/proxy/leopard.py @@ -0,0 +1,19 @@ +"A Leopard Class" +import random +from interface_proteus import IProteus +import lion +import serpent + + +class Leopard(IProteus): # pylint: disable=too-few-public-methods + "Proteus in the form of a Leopard" + + name = "Leopard" + + def tell_me_the_future(self): + "Proteus will change to something random" + self.__class__ = serpent.Serpent if random.randint(0, 1) else lion.Lion + + @classmethod + def tell_me_your_form(cls): + print("I am the form of a " + cls.name) diff --git a/proxy/lion.py b/proxy/lion.py new file mode 100644 index 0000000..87ed900 --- /dev/null +++ b/proxy/lion.py @@ -0,0 +1,20 @@ +"A Lion Class" +import random +from interface_proteus import IProteus +import leopard +import serpent + + +class Lion(IProteus): # pylint: disable=too-few-public-methods + "Proteus in the form of a Lion" + + name = "Lion" + + def tell_me_the_future(self): + "Proteus will change to something random" + self.__class__ = leopard.Leopard if random.randint( + 0, 1) else serpent.Serpent + + @classmethod + def tell_me_your_form(cls): + print("I am the form of a " + cls.name) diff --git a/proxy/log.txt b/proxy/log.txt deleted file mode 100644 index 40cc8e3..0000000 --- a/proxy/log.txt +++ /dev/null @@ -1 +0,0 @@ -2019-10-10 14:33:31.314205 : method was proxied diff --git a/proxy/proxy.png b/proxy/proxy.png deleted file mode 100644 index fe8de44..0000000 Binary files a/proxy/proxy.png and /dev/null differ diff --git a/proxy/proxy.py b/proxy/proxy.py deleted file mode 100644 index a4052af..0000000 --- a/proxy/proxy.py +++ /dev/null @@ -1,31 +0,0 @@ -from abc import ABCMeta, abstractmethod -import datetime - - -class IComponent(metaclass=ABCMeta): - @staticmethod - @abstractmethod - def method(self): - """A method to implement""" - - -class Component(IComponent): - def method(self): - print("The method has been called") - - -class ProxyComponent(IComponent): - def __init__(self): - self.component = Component() - - def method(self): - f = open("log.txt", "a") - f.write("%s : method was proxied\n" % (datetime.datetime.now())) - self.component.method() - - -COMPONENT1 = Component() -COMPONENT1.method() - -COMPONENT2 = ProxyComponent() -COMPONENT2.method() diff --git a/proxy/proxy_concept.py b/proxy/proxy_concept.py new file mode 100644 index 0000000..bdbe2f9 --- /dev/null +++ b/proxy/proxy_concept.py @@ -0,0 +1,58 @@ +# pylint: disable=too-few-public-methods +# pylint: disable=arguments-differ +"A Proxy Concept Example" + +from abc import ABCMeta, abstractmethod + + +class ISubject(metaclass=ABCMeta): + "An interface implemented by both the Proxy and Real Subject" + @staticmethod + @abstractmethod + def request(): + "A method to implement" + + +class RealSubject(ISubject): + "The actual real object that the proxy is representing" + + def __init__(self): + # hypothetically enormous amounts of data + self.enormous_data = [1, 2, 3] + + def request(self): + return self.enormous_data + + +class Proxy(ISubject): + """ + The proxy. In this case the proxy will act as a cache for + `enormous_data` and only populate the enormous_data when it + is actually necessary + """ + + def __init__(self): + self.enormous_data = [] + self.real_subject = RealSubject() + + def request(self): + """ + Using the proxy as a cache, and loading data into it only if + it is needed + """ + if not self.enormous_data: + print("pulling data from RealSubject") + self.enormous_data = self.real_subject.request() + return self.enormous_data + print("pulling data from Proxy cache") + return self.enormous_data + + +# The Client +SUBJECT = Proxy() +# use SUBJECT +print(id(SUBJECT)) +# load the enormous amounts of data because now we want to show it. +print(SUBJECT.request()) +# show the data again, but this time it retrieves it from the local cache +print(SUBJECT.request()) diff --git a/proxy/serpent.py b/proxy/serpent.py new file mode 100644 index 0000000..8222731 --- /dev/null +++ b/proxy/serpent.py @@ -0,0 +1,19 @@ +"A Serpent Class" +import random +from interface_proteus import IProteus +import lion +import leopard + + +class Serpent(IProteus): # pylint: disable=too-few-public-methods + "Proteus in the form of a Serpent" + + name = "Serpent" + + def tell_me_the_future(self): + "Proteus will change to something random" + self.__class__ = leopard.Leopard if random.randint(0, 1) else lion.Lion + + @classmethod + def tell_me_your_form(cls): + print("I am the form of a " + cls.name) diff --git a/singleton/README.md b/singleton/README.md new file mode 100644 index 0000000..c2cebd3 --- /dev/null +++ b/singleton/README.md @@ -0,0 +1,136 @@ +# Singleton Design Pattern + +## Videos + +Section | Video Links +-|- +Singleton Overview | Singleton Overview Singleton Overview Singleton Overview +Singleton Use Case | Singleton Use Case Singleton Use Case Singleton Use Case +Python **Dictionary** | Python Dictionary Python Dictionary Python Dictionary + +## Book + +Cover | Links | +-|-| +![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    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| + +## Overview + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Singleton UML Diagram + +![Singleton UML Diagram](/img/singleton_concept.svg) + +## Output + +``` bash +python ./singleton/singleton_concept.py +id(Singleton) = 2164775087968 +id(OBJECT1) = 2164775087968 +id(OBJECT2) = 2164775087968 +id(OBJECT3) = 2164775087968 +``` + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Example Use Case + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Example UML Diagram + +![Singleton Use Case Diagram](/img/singleton_example.svg) + +## Output + +``` bash +python ./singleton/client.py +-----------Leaderboard----------- +| 1 | Emmy | +| 2 | Cosmo | +| 3 | Sean | + +-----------Leaderboard----------- +| 1 | Emmy | +| 2 | Cosmo | +| 3 | Sean | + +-----------Leaderboard----------- +| 1 | Emmy | +| 2 | Cosmo | +| 3 | Sean | +``` + +## New Coding Concepts + +### Python Dictionary + +In the file [/singleton/leaderboard.py](/singleton/leaderboard.py), + +``` python linenums="4" + + "The Leaderboard as a Singleton" + _table = {} + +``` + +The `{}` is indicating a Python **Dictionary**. + +A Dictionary can be instantiated using the curly braces `{}` or `dict()` + +The Dictionary is similar to a [List](/builder#python-list), except that the items are `key:value` pairs. + +The Dictionary can store multiple `key:value` pairs, they can be changed, can be added and removed, can be re-ordered, can be pre-filled with `key:value` pairs when instantiated and is very flexible. + +Since Python 3.7, dictionaries are ordered in the same way that they are created. + +The keys of the dictionary are unique. + +You can refer to the dictionary items by key, which will return the value. + +``` python +PS> python +>>> items = {"abc": 123, "def": 456, "ghi": 789} +>>> items["abc"] +123 +``` + +You can change the value at a key, + +``` python +PS> python +>>> items = {"abc": 123, "def": 456, "ghi": 789} +>>> items["def"] = 101112 +>>> items["def"] +101112 +``` + +You can add new `key:value` pairs, and remove them by using the key. + +``` python +PS> python +>>> items = {"abc": 123, "def": 456, "ghi": 789} +>>> items["jkl"] = 101112 +>>> items["jkl"] +101112 +>>> items.pop('def') +456 +>>> items +{'abc': 123, 'ghi': 789, 'jkl': 101112} +``` + +You can order a dictionary alphabetically by key + +``` python +PS> python +>>> items = {"abc": 123, "ghi": 789, "def": 456} +>>> items +{'abc': 123, 'ghi': 789, 'def': 456} +>>> dict(sorted(items.items())) +{'abc': 123, 'def': 456, 'ghi': 789} +``` + +## Summary + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ \ No newline at end of file diff --git a/singleton/client.py b/singleton/client.py new file mode 100644 index 0000000..2aabc8e --- /dev/null +++ b/singleton/client.py @@ -0,0 +1,23 @@ +# pylint: disable=too-few-public-methods + +"Singleton Use Case Example Code." + +from game1 import Game1 +from game2 import Game2 +from game3 import Game3 + + +# The Client +# All games share and manage the same leaderboard because it is a singleton. +GAME1 = Game1() +GAME1.add_winner(2, "Cosmo") + +GAME2 = Game2() +GAME2.add_winner(3, "Sean") + +GAME3 = Game3() +GAME3.add_winner(1, "Emmy") + +GAME1.leaderboard.print() +GAME2.leaderboard.print() +GAME3.leaderboard.print() diff --git a/singleton/game1.py b/singleton/game1.py new file mode 100644 index 0000000..e20b3e8 --- /dev/null +++ b/singleton/game1.py @@ -0,0 +1,15 @@ +# pylint: disable=too-few-public-methods +"A Game Class that uses the Leaderboard Singleton" + +from leaderboard import Leaderboard +from interface_game import IGame + + +class Game1(IGame): + "Game1 implements IGame" + + def __init__(self): + self.leaderboard = Leaderboard() + + def add_winner(self, position, name): + self.leaderboard.add_winner(position, name) diff --git a/singleton/game2.py b/singleton/game2.py new file mode 100644 index 0000000..943f59c --- /dev/null +++ b/singleton/game2.py @@ -0,0 +1,14 @@ +"A Game Class that uses the Leaderboard Singleton" + +from leaderboard import Leaderboard +from interface_game import IGame + + +class Game2(IGame): # pylint: disable=too-few-public-methods + "Game2 implements IGame" + + def __init__(self): + self.leaderboard = Leaderboard() + + def add_winner(self, position, name): + self.leaderboard.add_winner(position, name) diff --git a/singleton/game3.py b/singleton/game3.py new file mode 100644 index 0000000..3c10608 --- /dev/null +++ b/singleton/game3.py @@ -0,0 +1,6 @@ +"A Game Class that uses the Leaderboard Singleton" +from game2 import Game2 + + +class Game3(Game2): # pylint: disable=too-few-public-methods + """Game 3 Inherits from Game 2 instead of implementing IGame""" diff --git a/singleton/interface_game.py b/singleton/interface_game.py new file mode 100644 index 0000000..2f2cff0 --- /dev/null +++ b/singleton/interface_game.py @@ -0,0 +1,13 @@ +# pylint: disable=too-few-public-methods + +"A Game Interface" + +from abc import ABCMeta, abstractmethod + + +class IGame(metaclass=ABCMeta): + "A Game Interface" + @staticmethod + @abstractmethod + def add_winner(position, name): + "Must implement add_winner" diff --git a/singleton/leaderboard.py b/singleton/leaderboard.py new file mode 100644 index 0000000..f116ca4 --- /dev/null +++ b/singleton/leaderboard.py @@ -0,0 +1,22 @@ +"A Leaderboard Singleton Class" + + +class Leaderboard(): + "The Leaderboard as a Singleton" + _table = {} + + def __new__(cls): + return cls + + @classmethod + def print(cls): + "A class level method" + print("-----------Leaderboard-----------") + for key, value in sorted(cls._table.items()): + print(f"|\t{key}\t|\t{value}\t|") + print() + + @classmethod + def add_winner(cls, position, name): + "A class level method" + cls._table[position] = name diff --git a/singleton/singleton_concept.py b/singleton/singleton_concept.py new file mode 100644 index 0000000..1d9d869 --- /dev/null +++ b/singleton/singleton_concept.py @@ -0,0 +1,37 @@ +# pylint: disable=too-few-public-methods +"Singleton Concept Sample Code" +import copy + + +class Singleton(): + "The Singleton Class" + value = [] + + def __new__(cls): + return cls + + # def __init__(self): + # print("in init") + + @staticmethod + def static_method(): + "Use @staticmethod if no inner variables required" + + @classmethod + def class_method(cls): + "Use @classmethod to access class level variables" + print(cls.value) + + +# The Client +# All uses of singleton point to the same memory address (id) +print(f"id(Singleton)\t= {id(Singleton)}") + +OBJECT1 = Singleton() +print(f"id(OBJECT1)\t= {id(OBJECT1)}") + +OBJECT2 = copy.deepcopy(OBJECT1) +print(f"id(OBJECT2)\t= {id(OBJECT2)}") + +OBJECT3 = Singleton() +print(f"id(OBJECT1)\t= {id(OBJECT3)}") diff --git a/state/README.md b/state/README.md new file mode 100644 index 0000000..4f1fce5 --- /dev/null +++ b/state/README.md @@ -0,0 +1,95 @@ +# State Design Pattern + +## Videos + +Section | Video Links +-|- +State Overview | State Overview State Overview State Overview +State Use Case | State Use Case State Use Case State Use Case +**\_\_call\_\_** Attribute | Dunder __call__ Attribute Dunder __call__ Attribute Dunder __call__ Attribute + +## Book + +Cover | Links +-|- +![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    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 + +## Overview + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Terminology + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## State UML Diagram + +![State UML Diagram](/img/state_concept.svg) + +## Source Code + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +### Output + +``` bash +python.exe ./state/state_concept.py +I am ConcreteStateB +I am ConcreteStateA +I am ConcreteStateB +I am ConcreteStateA +I am ConcreteStateC +``` + +## State Example Use Case + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## State Example Use Case UML Diagram + +![State Example Use Case UML Diagram](/img/state_example.svg) + +## Output + +``` bash +python.exe ./state/client.py +Task Started +Task Running +Task Finished +Task Started +Task Running +``` + +## New Coding Concepts + +### Dunder `__call__` Method + +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. + +``` python +class ExampleClass: + @staticmethod + def do_this_by_default(): + print("doing this") + +EXAMPLE = ExampleClass() +EXAMPLE.do_this_by_default() # needs to be explicitly called to execute +``` + +If you want a default method in your class, you can point to it using by the `__call__` method. + +``` python +class ExampleClass: + @staticmethod + def do_this_by_default(): + print("doing this") + + __call__ = do_this_by_default + +EXAMPLE = ExampleClass() +EXAMPLE() # function now gets called by default +``` + +## Summary + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ \ No newline at end of file diff --git a/state/client.py b/state/client.py new file mode 100644 index 0000000..19e1e57 --- /dev/null +++ b/state/client.py @@ -0,0 +1,76 @@ +# pylint: disable=too-few-public-methods +"The State Use Case Example" +from abc import ABCMeta, abstractmethod + + +class Context(): + "This is the object whose behavior will change" + + def __init__(self): + + self.state_handles = [ + Started(), + Running(), + Finished() + ] + self._handle = iter(self.state_handles) + + def request(self): + "Each time the request is called, a new class will handle it" + try: + self._handle.__next__()() + except StopIteration: + # resetting so it loops + self._handle = iter(self.state_handles) + + +class IState(metaclass=ABCMeta): + "A State Interface" + + @staticmethod + @abstractmethod + def __call__(): + "Set the default method" + + +class Started(IState): + "A ConcreteState Subclass" + + @staticmethod + def method(): + "A task of this class" + print("Task Started") + + __call__ = method + + +class Running(IState): + "A ConcreteState Subclass" + + @staticmethod + def method(): + "A task of this class" + print("Task Running") + + __call__ = method + + +class Finished(IState): + "A ConcreteState Subclass" + + @staticmethod + def method(): + "A task of this class" + print("Task Finished") + + __call__ = method + + +# The Client +CONTEXT = Context() +CONTEXT.request() +CONTEXT.request() +CONTEXT.request() +CONTEXT.request() +CONTEXT.request() +CONTEXT.request() diff --git a/state/state_concept.py b/state/state_concept.py new file mode 100644 index 0000000..20b4cf3 --- /dev/null +++ b/state/state_concept.py @@ -0,0 +1,53 @@ +# pylint: disable=too-few-public-methods +"The State Pattern Concept" +from abc import ABCMeta, abstractmethod +import random + +class Context(): + "This is the object whose behavior will change" + + def __init__(self): + self.state_handles = [ConcreteStateA(), + ConcreteStateB(), + ConcreteStateC()] + self.handle = None + + def request(self): + """A method of the state that dynamically changes which + class it uses depending on the value of self.handle""" + self.handle = self.state_handles[random.randint(0, 2)] + return self.handle + +class IState(metaclass=ABCMeta): + "A State Interface" + + @staticmethod + @abstractmethod + def __str__(): + "Set the default method" + +class ConcreteStateA(IState): + "A ConcreteState Subclass" + + def __str__(self): + return "I am ConcreteStateA" + +class ConcreteStateB(IState): + "A ConcreteState Subclass" + + def __str__(self): + return "I am ConcreteStateB" + +class ConcreteStateC(IState): + "A ConcreteState Subclass" + + def __str__(self): + return "I am ConcreteStateC" + +# The Client +CONTEXT = Context() +print(CONTEXT.request()) +print(CONTEXT.request()) +print(CONTEXT.request()) +print(CONTEXT.request()) +print(CONTEXT.request()) diff --git a/strategy/README.md b/strategy/README.md new file mode 100644 index 0000000..8f1a389 --- /dev/null +++ b/strategy/README.md @@ -0,0 +1,60 @@ +# Strategy Design Pattern + +## Videos + +Section | Video Links +-|- +Strategy Overview | Strategy Overview Strategy Overview Strategy Overview +Strategy Use Case | Strategy Use Case Strategy Use Case Strategy Use Case + +## Book + +Cover | Links +-|- +![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    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 + +## Overview + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Terminology + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Strategy UML Diagram + +![Strategy UML Diagram](/img/strategy_concept.svg) + +## Source Code + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Output + +``` bash +python ./strategy/strategy_concept.py +I am ConcreteStrategyA +I am ConcreteStrategyB +I am ConcreteStrategyC +``` + +## Strategy Example Use Case + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Strategy Example Use Case UML Diagram + +![Strategy Example Use Case UML Diagram](/img/strategy_example.svg) + +## Output + +``` bash +python ./strategy/client.py +I am Walking. New position = [1, 0] +I am Running. New position = [3, 0] +I am Crawling. New position = [3.5, 0] +``` + +## Summary + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ \ No newline at end of file diff --git a/strategy/client.py b/strategy/client.py new file mode 100644 index 0000000..33eca01 --- /dev/null +++ b/strategy/client.py @@ -0,0 +1,68 @@ +# pylint: disable=too-few-public-methods +"The Strategy Pattern Example Use Case" +from abc import ABCMeta, abstractmethod + + +class GameCharacter(): + "This is the context whose strategy will change" + + position = [0, 0] + + @classmethod + def move(cls, movement_style): + "The movement algorithm has been decided by the client" + movement_style(cls.position) + + +class IMove(metaclass=ABCMeta): + "A Concrete Strategy Interface" + + @staticmethod + @abstractmethod + def __call__(): + "Implementors must select the default method" + + +class Walking(IMove): + "A Concrete Strategy Subclass" + + @staticmethod + def walk(position): + "A walk algorithm" + position[0] += 1 + print(f"I am Walking. New position = {position}") + + __call__ = walk + + +class Running(IMove): + "A Concrete Strategy Subclass" + + @staticmethod + def run(position): + "A run algorithm" + position[0] += 2 + print(f"I am Running. New position = {position}") + + __call__ = run + + +class Crawling(IMove): + "A Concrete Strategy Subclass" + + @staticmethod + def crawl(position): + "A crawl algorithm" + position[0] += 0.5 + print(f"I am Crawling. New position = {position}") + + __call__ = crawl + + +# The Client +GAME_CHARACTER = GameCharacter() +GAME_CHARACTER.move(Walking()) +# Character sees the enemy +GAME_CHARACTER.move(Running()) +# Character finds a small cave to hide in +GAME_CHARACTER.move(Crawling()) diff --git a/strategy/strategy_concept.py b/strategy/strategy_concept.py new file mode 100644 index 0000000..234118a --- /dev/null +++ b/strategy/strategy_concept.py @@ -0,0 +1,50 @@ +# pylint: disable=too-few-public-methods +"The Strategy Pattern Concept" +from abc import ABCMeta, abstractmethod + + +class Context(): + "This is the object whose behavior will change" + + @staticmethod + def request(strategy): + """The request is handled by the class passed in""" + return strategy() + + +class IStrategy(metaclass=ABCMeta): + "A strategy Interface" + + @staticmethod + @abstractmethod + def __str__(): + "Implement the __str__ dunder" + + +class ConcreteStrategyA(IStrategy): + "A Concrete Strategy Subclass" + + def __str__(self): + return "I am ConcreteStrategyA" + + +class ConcreteStrategyB(IStrategy): + "A Concrete Strategy Subclass" + + def __str__(self): + return "I am ConcreteStrategyB" + + +class ConcreteStrategyC(IStrategy): + "A Concrete Strategy Subclass" + + def __str__(self): + return "I am ConcreteStrategyC" + + +# The Client +CONTEXT = Context() + +print(CONTEXT.request(ConcreteStrategyA)) +print(CONTEXT.request(ConcreteStrategyB)) +print(CONTEXT.request(ConcreteStrategyC)) diff --git a/template/README.md b/template/README.md new file mode 100644 index 0000000..fe00545 --- /dev/null +++ b/template/README.md @@ -0,0 +1,79 @@ +# Template Method Design Pattern + +## Videos + +Section | Video Links +-|- +Template Method Overview | Template Method Overview Template Method Overview Template Method Overview +Template Method Use Case | Template Method Use Case Template Method Use Case Template Method Use Case + +## Book + +Cover | Links +-|- +![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    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 + +## Overview + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Terminology + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Template Method UML Diagram + +![Template Method UML Diagram](/img/template_concept.svg) + +## Source Code + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Output + +``` bash +python ./template/template_concept.py +Class_A : Step Two (overridden) +Step Three is a hook that prints this line by default. +Class_B : Step One (overridden) +Class_B : Step Two. (overridden) +Class_B : Step Three. (overridden) +``` + +## Template Method Example Use Case + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Template Method Use Case UML Diagram + +![Template Method Use Case UML Diagram](/img/template_example.svg) + +## Output + +``` bash +python ./template/client.py +---------------------- +title : New Text Document +background_colour : white +text : Some Text +footer : -- Page 1 -- + + + + New HTML Document + + + +

Line 1

+

Line 2

+ + +``` + +## Summary + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ \ No newline at end of file diff --git a/template/abstract_document.py b/template/abstract_document.py new file mode 100644 index 0000000..c4b770d --- /dev/null +++ b/template/abstract_document.py @@ -0,0 +1,53 @@ +"An abstract document containing a combination of hooks and abstract methods" +from abc import ABCMeta, abstractmethod + + +class AbstractDocument(metaclass=ABCMeta): + "A template class containing a template method and primitive methods" + + @staticmethod + @abstractmethod + def title(document): + "must implement" + + @staticmethod + def description(document): + "optional" + + @staticmethod + def author(document): + "optional" + + @staticmethod + def background_colour(document): + "optional with a default behavior" + document["background_colour"] = "white" + + @staticmethod + @abstractmethod + def text(document, text): + "must implement" + + @staticmethod + def footer(document): + "optional" + + @staticmethod + def print(document): + "optional with a default behavior" + print("----------------------") + for attribute in document: + print(f"{attribute}\t: {document[attribute]}") + print() + + @classmethod + def create_document(cls, text): + "The template method" + _document = {} + cls.title(_document) + cls.description(_document) + cls.author(_document) + cls.background_colour(_document) + cls.text(_document, text) + cls.footer(_document) + cls.print(_document) diff --git a/template/client.py b/template/client.py new file mode 100644 index 0000000..e6e108f --- /dev/null +++ b/template/client.py @@ -0,0 +1,9 @@ +"The Template Pattern Use Case Example" +from text_document import TextDocument +from html_document import HTMLDocument + +TEXT_DOCUMENT = TextDocument() +TEXT_DOCUMENT.create_document("Some Text") + +HTML_DOCUMENT = HTMLDocument() +HTML_DOCUMENT.create_document("Line 1\nLine 2") diff --git a/template/html_document.py b/template/html_document.py new file mode 100644 index 0000000..fb1a41c --- /dev/null +++ b/template/html_document.py @@ -0,0 +1,43 @@ +"A HTML document concrete class of AbstractDocument" +from abstract_document import AbstractDocument + + +class HTMLDocument(AbstractDocument): + "Prints out a HTML formatted document" + @staticmethod + def title(document): + document["title"] = "New HTML Document" + + @staticmethod + def text(document, text): + "Putting multiple lines into there own p tags" + lines = text.splitlines() + markup = "" + for line in lines: + markup = markup + "

" + f"{line}

\n" + document["text"] = markup[:-1] + + @staticmethod + def print(document): + "overriding print to output with html tags" + print("") + print(" ") + for attribute in document: + if attribute in ["title", "description", "author"]: + print( + f" <{attribute}>{document[attribute]}" + f"" + ) + if attribute == "background_colour": + print(" ") + print(" ") + print(" ") + print(f"{document['text']}") + print(" ") + print("") diff --git a/template/template_concept.py b/template/template_concept.py new file mode 100644 index 0000000..635d8bf --- /dev/null +++ b/template/template_concept.py @@ -0,0 +1,74 @@ +# pylint: disable=too-few-public-methods +"The Template Method Pattern Concept" +from abc import ABCMeta, abstractmethod + + +class AbstractClass(metaclass=ABCMeta): + "A template class containing a template method and primitive methods" + + @staticmethod + def step_one(): + """ + Hooks are normally empty in the abstract class. The + implementing class can optionally override providing a custom + implementation + """ + + @staticmethod + @abstractmethod + def step_two(): + """ + An abstract method that must be overridden in the implementing + class. It has been given `@abstractmethod` decorator so that + pylint shows the error. + """ + + @staticmethod + def step_three(): + """ + Hooks can also contain default behavior and can be optionally + overridden + """ + print("Step Three is a hook that prints this line by default.") + + @classmethod + def template_method(cls): + """ + This is the template method that the subclass will call. + The subclass (implementing class) doesn't need to override this + method since it has would have already optionally overridden + the following methods with its own implementations + """ + cls.step_one() + cls.step_two() + cls.step_three() + + +class ConcreteClassA(AbstractClass): + "A concrete class that only overrides step two" + @staticmethod + def step_two(): + print("Class_A : Step Two (overridden)") + + +class ConcreteClassB(AbstractClass): + "A concrete class that only overrides steps one, two and three" + @staticmethod + def step_one(): + print("Class_B : Step One (overridden)") + + @staticmethod + def step_two(): + print("Class_B : Step Two. (overridden)") + + @staticmethod + def step_three(): + print("Class_B : Step Three. (overridden)") + + +# The Client +CLASS_A = ConcreteClassA() +CLASS_A.template_method() + +CLASS_B = ConcreteClassB() +CLASS_B.template_method() diff --git a/template/text_document.py b/template/text_document.py new file mode 100644 index 0000000..7f2784b --- /dev/null +++ b/template/text_document.py @@ -0,0 +1,17 @@ +"A text document concrete class of AbstractDocument" +from abstract_document import AbstractDocument + + +class TextDocument(AbstractDocument): + "Prints out a text document" + @staticmethod + def title(document): + document["title"] = "New Text Document" + + @staticmethod + def text(document, text): + document["text"] = text + + @staticmethod + def footer(document): + document["footer"] = "-- Page 1 --" diff --git a/visitor/README.md b/visitor/README.md new file mode 100644 index 0000000..1f969de --- /dev/null +++ b/visitor/README.md @@ -0,0 +1,144 @@ +# Visitor Design Pattern + +## Videos + +Section | Video Links +-|- +Visitor Overview | Visitor Overview Visitor Overview Visitor Overview +Visitor Use Case | Visitor Use Case Visitor Use Case Visitor Use Case +hasattr() Method | hasattr() Method hasattr() Method hasattr() Method +expandtabs() Method | expandtabs() Method expandtabs() Method expandtabs() Method + +## Book + +Cover | Links +-|- +![Design Patterns In Python (ASIN : B08XLJ8Z2J)](/img/design_patterns_in_python_book_125x178.jpg) |    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 + +## Overview + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Terminology + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Visitor UML Diagram + +![Visitor Pattern UML Diagram](/img/visitor_concept.svg) + +## Source Code + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Output + +``` bash +python ./visitor/visitor_concept.py +D +B +C +A +561 +``` + +## Visitor Example Use Case + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ + +## Visitor Example UML Diagram + +![Visitor Pattern Use Case UML Diagram](/img/visitor_example.svg) + +## Output + +``` bash +python ./visitor/client.py +Utility :ABC-123-21 +V8 engine :DEF-456-21 +FrontLeft :GHI-789FL-21 +FrontRight :GHI-789FR-21 +BackLeft :GHI-789BL-21 +BackRight :GHI-789BR-21 +Total Price = 4132 +``` + +## New Coding Concepts + +### Instance `hasattr()` + +In the Visitor objects in the example use case above, I test if the elements have a certain attribute during the visit operation. + +``` python +def visit(cls, element): + if hasattr(element, 'price'): + ... +``` + +The `hasattr()` method can be used to test if an instantiated object has an attribute of a particular name. + +``` python +class ClassA(): + name = "abc" + value = 123 + +CLASS_A = ClassA() +print(hasattr(CLASS_A, "name")) +print(hasattr(CLASS_A, "value")) +print(hasattr(CLASS_A, "date")) + +``` + +Outputs + +``` bash +True +True +False +``` + +### String `expandtabs()` + +When printing strings to the console, you can include special characters `\t` that print a series of extra spaces called tabs. The tabs help present multiline text in a more tabular form which appears to be neater to look at. + +``` bash +abc 123 +defg 456 +hi 78910 +``` + +The number of spaces added depends on the size of the word before the `\t` character in the string. By default, a tab makes up 8 spaces. + +Now, not all words separated by a tab will line up the same on the next line. + +``` bash +abcdef 123 +cdefghij 4563 +ghi 789 +jklmn 1011 + +``` + +The problem occurs usually when a word is already 8 or more characters long. + +To help solve the spacing issue, you can use the `expandtabs()` method on a string to set how many characters a tab will use. + +``` python +print("abcdef\t123".expandtabs(10)) +print("cdefghij\t4563".expandtabs(10)) +print("ghi\t789".expandtabs(10)) +print("jklmn\t1011".expandtabs(10)) +``` + +Now outputs + +``` bash +abcdef 123 +cdefghij 4563 +ghi 789 +jklmn 1011 +``` + +## Summary + +_... Refer to [Book](https://amzn.to/466lBN6) or [Design Patterns In Python website](https://sbcode.net/python/) to read textual content._ \ No newline at end of file diff --git a/visitor/client.py b/visitor/client.py new file mode 100644 index 0000000..293cd4a --- /dev/null +++ b/visitor/client.py @@ -0,0 +1,143 @@ +# pylint: disable=too-few-public-methods +# pylint: disable=arguments-differ +"The Visitor Pattern Use Case Example" +from abc import ABCMeta, abstractmethod + + +class IVisitor(metaclass=ABCMeta): + "An interface that custom Visitors should implement" + @staticmethod + @abstractmethod + def visit(element): + "Visitors visit Elements/Objects within the application" + + +class IVisitable(metaclass=ABCMeta): + """ + An interface that concrete objects should implement that allows + the visitor to traverse a hierarchical structure of objects + """ + @staticmethod + @abstractmethod + def accept(visitor): + """ + The Visitor traverses and accesses each object through this + method + """ + + +class AbstractCarPart(): + "The Abstract Car Part" + @property + def name(self): + "a name for the part" + return self._name + + @name.setter + def name(self, value): + self._name = value + + @property + def sku(self): + "The Stock Keeping Unit (sku)" + return self._sku + + @sku.setter + def sku(self, value): + self._sku = value + + @property + def price(self): + "The price per unit" + return self._price + + @price.setter + def price(self, value): + self._price = value + + +class Body(AbstractCarPart, IVisitable): + "A part of the car" + + def __init__(self, name, sku, price): + self.name = name + self.sku = sku + self.price = price + + def accept(self, visitor): + visitor.visit(self) + + +class Engine(AbstractCarPart, IVisitable): + "A part of the car" + + def __init__(self, name, sku, price): + self.name = name + self.sku = sku + self.price = price + + def accept(self, visitor): + visitor.visit(self) + + +class Wheel(AbstractCarPart, IVisitable): + "A part of the car" + + def __init__(self, name, sku, price): + self.name = name + self.sku = sku + self.price = price + + def accept(self, visitor): + visitor.visit(self) + + +class Car(AbstractCarPart, IVisitable): + "A Car with parts" + + def __init__(self, name): + self.name = name + self._parts = [ + Body("Utility", "ABC-123-21", 1001), + Engine("V8 engine", "DEF-456-21", 2555), + Wheel("FrontLeft", "GHI-789FL-21", 136), + Wheel("FrontRight", "GHI-789FR-21", 136), + Wheel("BackLeft", "GHI-789BL-21", 152), + Wheel("BackRight", "GHI-789BR-21", 152), + ] + + def accept(self, visitor): + for parts in self._parts: + parts.accept(visitor) + visitor.visit(self) + + +class PrintPartsVisitor(IVisitor): + "Print out the part name and sku" + @staticmethod + def visit(element): + if hasattr(element, 'sku'): + print(f"{element.name}\t:{element.sku}".expandtabs(6)) + + +class TotalPriceVisitor(IVisitor): + "Print out the total cost of the parts in the car" + total_price = 0 + + @classmethod + def visit(cls, element): + if hasattr(element, 'price'): + cls.total_price += element.price + return cls.total_price + + +# The Client +CAR = Car("DeLorean") + +# Print out the part name and sku using the PrintPartsVisitor +CAR.accept(PrintPartsVisitor()) + +# Calculate the total prince of the parts using the TotalPriceVisitor +TOTAL_PRICE_VISITOR = TotalPriceVisitor() +CAR.accept(TOTAL_PRICE_VISITOR) +print(f"Total Price = {TOTAL_PRICE_VISITOR.total_price}") diff --git a/visitor/visitor_concept.py b/visitor/visitor_concept.py new file mode 100644 index 0000000..f3c33fb --- /dev/null +++ b/visitor/visitor_concept.py @@ -0,0 +1,76 @@ +# pylint: disable=too-few-public-methods +# pylint: disable=arguments-differ +"The Visitor Pattern Concept" +from abc import ABCMeta, abstractmethod + +class IVisitor(metaclass=ABCMeta): + "An interface that custom Visitors should implement" + @staticmethod + @abstractmethod + def visit(element): + "Visitors visit Elements/Objects within the application" + +class IVisitable(metaclass=ABCMeta): + """ + An interface the concrete objects should implement that allows + the visitor to traverse a hierarchical structure of objects + """ + @staticmethod + @abstractmethod + def accept(visitor): + """ + The Visitor traverses and accesses each object through this + method + """ + +class Element(IVisitable): + "An Object that can be part of any hierarchy" + + def __init__(self, name, value, parent=None): + self.name = name + self.value = value + self.elements = set() + if parent: + parent.elements.add(self) + + def accept(self, visitor): + "required by the Visitor that will traverse" + for element in self.elements: + element.accept(visitor) + visitor.visit(self) + +# The Client +# Creating an example object hierarchy. +Element_A = Element("A", 101) +Element_B = Element("B", 305, Element_A) +Element_C = Element("C", 185, Element_A) +Element_D = Element("D", -30, Element_B) + +# Now Rather than changing the Element class to support custom +# operations, we can utilise the accept method that was +# implemented in the Element class because of the addition of +# the IVisitable interface + +class PrintElementNamesVisitor(IVisitor): + "Create a visitor that prints the Element names" + @staticmethod + def visit(element): + print(element.name) + +# Using the PrintElementNamesVisitor to traverse the object hierarchy +Element_A.accept(PrintElementNamesVisitor) + +class CalculateElementTotalsVisitor(IVisitor): + "Create a visitor that totals the Element values" + total_value = 0 + + @classmethod + def visit(cls, element): + cls.total_value += element.value + return cls.total_value + +# Using the CalculateElementTotalsVisitor to traverse the +# object hierarchy +TOTAL = CalculateElementTotalsVisitor() +Element_A.accept(CalculateElementTotalsVisitor) +print(TOTAL.total_value)