Last active
April 26, 2023 22:24
-
-
Save tony-zeidan/5daf3c0ff7bfa181637ca3186294475b to your computer and use it in GitHub Desktop.
designpatterns.ipynb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "nbformat": 4, | |
| "nbformat_minor": 0, | |
| "metadata": { | |
| "colab": { | |
| "provenance": [], | |
| "authorship_tag": "ABX9TyM1Ow/r+ci/fgVZFQpcH3S0", | |
| "include_colab_link": true | |
| }, | |
| "kernelspec": { | |
| "name": "python3", | |
| "display_name": "Python 3" | |
| }, | |
| "language_info": { | |
| "name": "python" | |
| } | |
| }, | |
| "cells": [ | |
| { | |
| "cell_type": "markdown", | |
| "metadata": { | |
| "id": "view-in-github", | |
| "colab_type": "text" | |
| }, | |
| "source": [ | |
| "<a href=\"https://colab.research.google.com/gist/tony-zeidan/5daf3c0ff7bfa181637ca3186294475b/designpatterns.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "# **Disclaimer**\n", | |
| "I own absolutely no material inside of this Notebook. The images and content within are inspired by courses I took at Carleton University. The content was made manually, but was heavily inspired by [Refactoring Guru](https://refactoring.guru). The images are also from [Refactoring Guru](https://refactoring.guru)." | |
| ], | |
| "metadata": { | |
| "id": "6x-txkej1lYb" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "from abc import ABC, abstractmethod" | |
| ], | |
| "metadata": { | |
| "id": "q_Ha5bNgqgOe" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "# **Creational Design Patterns**\n", | |
| "These patterns deal with mechanisms for creating/instantiating objects. They control the manner in which objects are created." | |
| ], | |
| "metadata": { | |
| "id": "fI34-XZDxRsb" | |
| } | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "# Singleton Pattern\n", | |
| "*The Singleton pattern ensures that a class has only one instance and provides a global point of access to that instance.*\n", | |
| "\n", | |
| "" | |
| ], | |
| "metadata": { | |
| "id": "xUzhSuwBrCpA" | |
| } | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "We define a class such that when a new instance is made, it checks if there is a previously defined instance and returns that instead if there is one." | |
| ], | |
| "metadata": { | |
| "id": "yBLlJcjbrtOW" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class Singleton:\n", | |
| " _instance = None # global instance !\n", | |
| "\n", | |
| " # constructor\n", | |
| " def __init__(self, data=None):\n", | |
| " if data is not None:\n", | |
| " self._data = data\n", | |
| "\n", | |
| " # function called when a new instance is made\n", | |
| " def __new__(cls, *args, **kwargs):\n", | |
| " if cls._instance is None:\n", | |
| " cls._instance = super().__new__(cls)\n", | |
| " return cls._instance\n", | |
| "\n", | |
| " def get_data(self):\n", | |
| " return self._data\n", | |
| "\n", | |
| " def set_data(self, data):\n", | |
| " self._data = data" | |
| ], | |
| "metadata": { | |
| "id": "5Ke2xeB8rHQ2" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Demonstrating the power of the Singleton pattern..." | |
| ], | |
| "metadata": { | |
| "id": "V8HOWz3prYEg" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "instance1 = Singleton('data1')\n", | |
| "print(instance1.get_data()) # Output: data1\n", | |
| "\n", | |
| "instance2 = Singleton('data2')\n", | |
| "print(instance2.get_data()) # Output: data1 (not data2, because instance1 and instance2 are the same)\n", | |
| "\n", | |
| "print(instance1 is instance2) # Output: True (both variables point to the same instance)" | |
| ], | |
| "metadata": { | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "id": "bTRbEcRzrdgD", | |
| "outputId": "16f373f8-eb00-4bc9-f5ef-0e93b2ae1ba5" | |
| }, | |
| "execution_count": null, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "data1\n", | |
| "data2\n", | |
| "True\n" | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "# Builder Pattern\n", | |
| "*The Builder pattern is a creational design pattern that allows for the step-by-step creation of complex objects using the correct sequence of actions. It provides a clear separation between the construction and representation of an object.*\n", | |
| "\n", | |
| "\n", | |
| "" | |
| ], | |
| "metadata": { | |
| "id": "KDtvnpIrp7AT" | |
| } | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "We have a product class. This is the instance that the builders will create. " | |
| ], | |
| "metadata": { | |
| "id": "C0vSzmxAqYrF" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "6mi9wzasp5Zt" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "class House:\n", | |
| "\tdef __init__(self):\n", | |
| "\t\tself._foundation = None\n", | |
| "\t\tself._structure = None\n", | |
| "\t\tself._roof = None\n", | |
| "\n", | |
| "\tdef set_foundation(self, foundation):\n", | |
| "\t\tself._foundation = foundation\n", | |
| "\n", | |
| "\tdef set_structure(self, structure):\n", | |
| "\t\tself._structure = structure\n", | |
| "\n", | |
| "\tdef set_roof(self, roof):\n", | |
| "\t\tself._roof = roof\n", | |
| "\n", | |
| "\tdef show(self):\n", | |
| "\t\tprint(f\"House built with foundation: {self._foundation}, structure: {self._structure}, roof: {self._roof}\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Now we define an abstract Builder. This builder sets the building process for the product that the ConcreteBuilders must provide implementations for." | |
| ], | |
| "metadata": { | |
| "id": "6bemJ74_qac7" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class HouseBuilder(ABC): # in Python ABC means abstract\n", | |
| "\tdef __init__(self):\n", | |
| "\t\tself.house = House() # start with a house that is to undergo construction\n", | |
| "\n", | |
| "\t@abstractmethod\n", | |
| "\tdef build_foundation(self):\n", | |
| "\t\tpass # enforce concrete instances to implement this\n", | |
| "\n", | |
| "\t@abstractmethod\n", | |
| "\tdef build_structure(self):\n", | |
| "\t\tpass\n", | |
| "\n", | |
| "\t@abstractmethod\n", | |
| "\tdef build_roof(self):\n", | |
| "\t\tpass" | |
| ], | |
| "metadata": { | |
| "id": "QrciKyMVqcU1" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Now we define a concrete Builder." | |
| ], | |
| "metadata": { | |
| "id": "2vROsKS_ql2l" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class BrickHouseBuilder(HouseBuilder): # inheritance from abstract\n", | |
| "\tdef build_foundation(self):\n", | |
| "\t\tself.house.set_foundation(\"Concrete\")\n", | |
| "\n", | |
| "\tdef build_structure(self):\n", | |
| "\t\tself.house.set_structure(\"Brick\")\n", | |
| "\n", | |
| "\tdef build_roof(self):\n", | |
| "\t\tself.house.set_roof(\"Tile\")" | |
| ], | |
| "metadata": { | |
| "id": "KYeVCLR9qnSP" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Finally, we define a director whom takes a builder (like a strategy) and builds a house by invoking all of its functions." | |
| ], | |
| "metadata": { | |
| "id": "Mt2eW_L-qpxx" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class ConstructionDirector:\n", | |
| "\tdef __init__(self, builder):\n", | |
| "\t\tself._builder = builder\n", | |
| "\n", | |
| "\tdef change_builder(self, builder):\n", | |
| "\t\tself._builder = builder\n", | |
| "\n", | |
| "\tdef construct(self):\n", | |
| "\t\tself._builder.build_foundation()\n", | |
| "\t\tself._builder.build_structure()\n", | |
| "\t\tself._builder.build_roof()\n", | |
| "\n", | |
| "\t\treturn self._builder.house" | |
| ], | |
| "metadata": { | |
| "id": "NRvECH26qopQ" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Now as a user, this complex process becomes something like the following." | |
| ], | |
| "metadata": { | |
| "id": "5-rq-fUeqtAg" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "builder = BrickHouseBuilder()\n", | |
| "director = ConstructionDirector(builder)\n", | |
| "my_house = director.construct()\n", | |
| "my_house.show()" | |
| ], | |
| "metadata": { | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "id": "8qvcpmJKqsrn", | |
| "outputId": "3a5a872b-b75f-4f51-ffe8-c4b60de7ee6c" | |
| }, | |
| "execution_count": null, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "House built with foundation: Concrete, structure: Brick, roof: Tile\n" | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "# Factory Method Pattern\n", | |
| "*The Factory Method pattern is a creational design pattern that provides an interface for creating objects in a superclass but allows subclasses to alter the type of objects that will be created.*\n", | |
| "\n", | |
| "" | |
| ], | |
| "metadata": { | |
| "id": "RQmTSvChsjgD" | |
| } | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "First we define the product interface." | |
| ], | |
| "metadata": { | |
| "id": "Wh8JYVMlsmiC" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Python-specific but we use an abstract class as an interface in Python\n", | |
| "class Animal(ABC):\n", | |
| "\n", | |
| " @abstractmethod\n", | |
| " def speak(self):\n", | |
| " pass # concrete-products will implement this" | |
| ], | |
| "metadata": { | |
| "id": "NC6xUa-Csp8y" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Now, we can define many different concrete products." | |
| ], | |
| "metadata": { | |
| "id": "otyzDjgYs_O0" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class Dog(Animal):\n", | |
| "\n", | |
| " def speak(self):\n", | |
| " print(\"Woof\")\n", | |
| "\n", | |
| "class Cat(Animal):\n", | |
| "\n", | |
| " def speak(self):\n", | |
| " print(\"Meow\")" | |
| ], | |
| "metadata": { | |
| "id": "QvjvA8OGs-53" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Now we can define our creator interface." | |
| ], | |
| "metadata": { | |
| "id": "FLaJm1NAtNnO" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class AnimalFactory(ABC):\n", | |
| "\n", | |
| " @abstractmethod\n", | |
| " def create_animal(self):\n", | |
| " pass" | |
| ], | |
| "metadata": { | |
| "id": "bNE-svtHtULL" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "And finally, we create concrete implementations of creators." | |
| ], | |
| "metadata": { | |
| "id": "WIVBA-M6tfIq" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class DogFactory(AnimalFactory):\n", | |
| "\n", | |
| " def create_animal(self):\n", | |
| " return Dog()\n", | |
| "\n", | |
| "class CatFactory(AnimalFactory):\n", | |
| "\n", | |
| " def create_animal(self):\n", | |
| " return Cat()" | |
| ], | |
| "metadata": { | |
| "id": "GQCstGOEte3e" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Client's code would look like the following..." | |
| ], | |
| "metadata": { | |
| "id": "F0IcGSxVtwmp" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "def animal_speaker(animal_factory: AnimalFactory):\n", | |
| " animal = animal_factory.create_animal()\n", | |
| " animal.speak()\n", | |
| "\n", | |
| "concrete_factory_instance = DogFactory()\n", | |
| "animal_speaker(concrete_factory_instance)" | |
| ], | |
| "metadata": { | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "id": "ZJD-10Betzew", | |
| "outputId": "4d5f52b8-5205-409e-cfbb-2aa8e21590b1" | |
| }, | |
| "execution_count": null, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "Woof\n" | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "# Abstract Factory Pattern\n", | |
| "*The Abstract Factory pattern is a creational design pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes.*\n", | |
| "\n", | |
| "" | |
| ], | |
| "metadata": { | |
| "id": "-hcK_4KauuQO" | |
| } | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "First we can define our abstract products. The extensions of these are a family of related instances." | |
| ], | |
| "metadata": { | |
| "id": "ZVUclQ9fuxa-" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Abstract Products\n", | |
| "class Button(ABC):\n", | |
| " @abstractmethod\n", | |
| " def click(self):\n", | |
| " pass\n", | |
| "\n", | |
| "class CheckBox(ABC):\n", | |
| " @abstractmethod\n", | |
| " def check(self):\n", | |
| " pass" | |
| ], | |
| "metadata": { | |
| "id": "yBsW1MQPvSOg" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Next we can define many extensions of these abstract products (concrete products)." | |
| ], | |
| "metadata": { | |
| "id": "jYuSL9OQvTRq" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Concrete Products\n", | |
| "class WindowsButton(Button):\n", | |
| " def click(self):\n", | |
| " return \"WindowsButton clicked.\"\n", | |
| "\n", | |
| "class WindowsCheckBox(CheckBox):\n", | |
| " def check(self):\n", | |
| " return \"WindowsCheckBox checked.\"\n", | |
| "\n", | |
| "class MacOSButton(Button):\n", | |
| " def click(self):\n", | |
| " return \"MacOSButton clicked.\"\n", | |
| "\n", | |
| "class MacOSCheckBox(CheckBox):\n", | |
| " def check(self):\n", | |
| " return \"MacOSCheckBox checked.\"" | |
| ], | |
| "metadata": { | |
| "id": "Q66T7gMyvZkr" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Next we can define our AbstractFactory itself." | |
| ], | |
| "metadata": { | |
| "id": "9oMOMtDXvcDl" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Abstract Factory\n", | |
| "class GUIFactory(ABC):\n", | |
| " @abstractmethod\n", | |
| " def create_button(self):\n", | |
| " pass\n", | |
| "\n", | |
| " @abstractmethod\n", | |
| " def create_check_box(self):\n", | |
| " pass" | |
| ], | |
| "metadata": { | |
| "id": "gtCyVbb8vhY9" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Next we can define our ConcreteFactory classes themselves." | |
| ], | |
| "metadata": { | |
| "id": "PGBcp2fSvjcm" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Concrete Factories\n", | |
| "class WindowsFactory(GUIFactory):\n", | |
| " def create_button(self):\n", | |
| " return WindowsButton()\n", | |
| "\n", | |
| " def create_check_box(self):\n", | |
| " return WindowsCheckBox()\n", | |
| "\n", | |
| "class MacOSFactory(GUIFactory):\n", | |
| " def create_button(self):\n", | |
| " return MacOSButton()\n", | |
| "\n", | |
| " def create_check_box(self):\n", | |
| " return MacOSCheckBox()" | |
| ], | |
| "metadata": { | |
| "id": "Rn_w5iAHv8Ht" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Client code may look like:" | |
| ], | |
| "metadata": { | |
| "id": "jUHi6GUTv9tz" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Client code\n", | |
| "def client_code(factory: GUIFactory):\n", | |
| " button = factory.create_button()\n", | |
| " check_box = factory.create_check_box()\n", | |
| "\n", | |
| " print(button.click())\n", | |
| " print(check_box.check())\n", | |
| "\n", | |
| "# Usage\n", | |
| "windows_factory = WindowsFactory()\n", | |
| "client_code(windows_factory)\n", | |
| "# Output:\n", | |
| "# WindowsButton clicked.\n", | |
| "# WindowsCheckBox checked.\n", | |
| "\n", | |
| "macos_factory = MacOSFactory()\n", | |
| "client_code(macos_factory)\n", | |
| "# Output:\n", | |
| "# MacOSButton clicked.\n", | |
| "# MacOSCheckBox checked." | |
| ], | |
| "metadata": { | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "id": "yKMR2wyiv9ct", | |
| "outputId": "c51f347f-2904-4ed5-a41b-1f42e13ff474" | |
| }, | |
| "execution_count": null, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "WindowsButton clicked.\n", | |
| "WindowsCheckBox checked.\n", | |
| "MacOSButton clicked.\n", | |
| "MacOSCheckBox checked.\n" | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "# **Structural Design Patterns**\n", | |
| "These patterns deal with easing design complexity by identifying a simple way to realize relationships between entities. They provide structure to object relationships." | |
| ], | |
| "metadata": { | |
| "id": "uM4O27bIxNfu" | |
| } | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "# Adapter Pattern\n", | |
| "*The Adapter pattern is a structural design pattern that allows objects with incompatible interfaces to collaborate. It provides a wrapper with an interface expected by the system, which delegates the calls to the original object with a different interface.*\n", | |
| "\n", | |
| "" | |
| ], | |
| "metadata": { | |
| "id": "YnXo0p8NxZ_s" | |
| } | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "First we define our target interface." | |
| ], | |
| "metadata": { | |
| "id": "pNEUWK5qxcvk" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Target Interface\n", | |
| "class Target(ABC):\n", | |
| " @abstractmethod\n", | |
| " def request(self):\n", | |
| " pass" | |
| ], | |
| "metadata": { | |
| "id": "FlqkhSEcyESb" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Next we define the class that we will apply the adapter to." | |
| ], | |
| "metadata": { | |
| "id": "lziUk3bZyF9e" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Adaptee (with incompatible interface)\n", | |
| "class Adaptee:\n", | |
| " def specific_request(self):\n", | |
| " return \"Adaptee's specific_request.\"" | |
| ], | |
| "metadata": { | |
| "id": "hXNC1-3IyNbr" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Next we define our ObjectAdapter." | |
| ], | |
| "metadata": { | |
| "id": "1b9vT1qjyPWl" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Object Adapter (wraps Adaptee and provides Target interface)\n", | |
| "class ObjectAdapter(Target):\n", | |
| " def __init__(self, adaptee):\n", | |
| " self._adaptee = adaptee\n", | |
| "\n", | |
| " def request(self):\n", | |
| " return f\"Object Adapter: {self._adaptee.specific_request()}\"" | |
| ], | |
| "metadata": { | |
| "id": "NYMDYWFgyR3O" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "We can also define a ClassAdapter." | |
| ], | |
| "metadata": { | |
| "id": "Hj_hY0KwymYY" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Class Adapter (inherits from Adaptee and provides Target interface)\n", | |
| "class ClassAdapter(Adaptee, Target):\n", | |
| " def request(self):\n", | |
| " return f\"Class Adapter: {self.specific_request()}\"" | |
| ], | |
| "metadata": { | |
| "id": "xur6Q2S2yo2n" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Note the difference between both. The ClassAdapter inherits from both the Adaptee, and the Target interface. \n", | |
| "\n", | |
| "Next we can make our client code:" | |
| ], | |
| "metadata": { | |
| "id": "DwvFfFoeyq_J" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Usage\n", | |
| "adaptee = Adaptee()\n", | |
| "print(\"Client code working with Adaptee directly:\")\n", | |
| "print(adaptee.specific_request())\n", | |
| "\n", | |
| "object_adapter = ObjectAdapter(adaptee)\n", | |
| "print(\"\\nClient code working with Object Adapter:\")\n", | |
| "print(object_adapter.request())\n", | |
| "# Output:\n", | |
| "# Client code working with Adaptee directly:\n", | |
| "# Adaptee's specific_request.\n", | |
| "#\n", | |
| "# Client code working with Object Adapter:\n", | |
| "# Object Adapter: Adaptee's specific_request.\n", | |
| "\n", | |
| "class_adapter = ClassAdapter()\n", | |
| "print(\"\\nClient code working with Class Adapter:\")\n", | |
| "print(class_adapter.request())\n", | |
| "# Output:\n", | |
| "# Client code working with Class Adapter:\n", | |
| "# Class Adapter: Adaptee's specific_request." | |
| ], | |
| "metadata": { | |
| "id": "9yl1cUEvy1iI", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "8141b990-61a1-449a-cf26-fc035406a315" | |
| }, | |
| "execution_count": null, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "Client code working with Adaptee directly:\n", | |
| "Adaptee's specific_request.\n", | |
| "\n", | |
| "Client code working with Object Adapter:\n", | |
| "Object Adapter: Adaptee's specific_request.\n", | |
| "\n", | |
| "Client code working with Class Adapter:\n", | |
| "Class Adapter: Adaptee's specific_request.\n" | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "# Bridge Pattern\n", | |
| "*The Bridge pattern is a structural design pattern that decouples an abstraction from its implementation so that the two can evolve independently.*\n", | |
| "\n", | |
| "" | |
| ], | |
| "metadata": { | |
| "id": "fdsOHVYJ351L" | |
| } | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "First we define our Implementation interface." | |
| ], | |
| "metadata": { | |
| "id": "r-703NSv6Elr" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Implementor Interface\n", | |
| "class DrawingAPI(ABC):\n", | |
| " @abstractmethod\n", | |
| " def draw_circle(self, x, y, radius):\n", | |
| " pass" | |
| ], | |
| "metadata": { | |
| "id": "oZB0Iqq46EGY" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Next, we define the ConcreteImplementation classes." | |
| ], | |
| "metadata": { | |
| "id": "A6_qW6DH62Gp" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class DrawingAPI1(DrawingAPI):\n", | |
| " def draw_circle(self, x, y, radius):\n", | |
| " return f\"API1.circle at {x}:{y} with radius {radius}\"\n", | |
| "\n", | |
| "class DrawingAPI2(DrawingAPI):\n", | |
| " def draw_circle(self, x, y, radius):\n", | |
| " return f\"API2.circle at {x}:{y} with radius {radius}\"" | |
| ], | |
| "metadata": { | |
| "id": "UIEP_nan65-6" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Now we define our (required) Abstraction, and an optional RefinedAbstraction." | |
| ], | |
| "metadata": { | |
| "id": "HJFA58t-68q-" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Abstraction\n", | |
| "class Shape(ABC):\n", | |
| " def __init__(self, drawing_api: DrawingAPI):\n", | |
| " self._drawing_api = drawing_api\n", | |
| "\n", | |
| " @abstractmethod\n", | |
| " def draw(self):\n", | |
| " pass\n", | |
| "\n", | |
| " @abstractmethod\n", | |
| " def resize_by_percentage(self, percentage):\n", | |
| " pass\n", | |
| "\n", | |
| "# Refined Abstraction\n", | |
| "class CircleShape(Shape):\n", | |
| " def __init__(self, x, y, radius, drawing_api: DrawingAPI):\n", | |
| " super().__init__(drawing_api)\n", | |
| " self._x = x\n", | |
| " self._y = y\n", | |
| " self._radius = radius\n", | |
| "\n", | |
| " def draw(self):\n", | |
| " return self._drawing_api.draw_circle(self._x, self._y, self._radius)\n", | |
| "\n", | |
| " def resize_by_percentage(self, percentage):\n", | |
| " self._radius *= 1 + percentage / 100" | |
| ], | |
| "metadata": { | |
| "id": "TonUcmv97HWT" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Client code may look like the following:" | |
| ], | |
| "metadata": { | |
| "id": "HYhubHki7LcF" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Client code\n", | |
| "def client_code(shape: Shape):\n", | |
| " print(shape.draw())\n", | |
| "\n", | |
| "# Usage\n", | |
| "circle1 = CircleShape(1, 2, 3, DrawingAPI1())\n", | |
| "circle2 = CircleShape(1, 2, 3, DrawingAPI2())\n", | |
| "\n", | |
| "client_code(circle1) # Output: API1.circle at 1:2 with radius 3\n", | |
| "client_code(circle2) # Output: API2.circle at 1:2 with radius 3" | |
| ], | |
| "metadata": { | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "id": "hMa3z7E47LN4", | |
| "outputId": "0084210f-8810-49f5-f968-2dfad7a610c1" | |
| }, | |
| "execution_count": null, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "API1.circle at 1:2 with radius 3\n", | |
| "API2.circle at 1:2 with radius 3\n" | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "# Composite Pattern\n", | |
| "*The Composite pattern is a structural design pattern that composes objects into tree structures to represent part-whole hierarchies. It allows clients to treat individual objects and compositions of objects uniformly.*\n", | |
| "\n", | |
| "" | |
| ], | |
| "metadata": { | |
| "id": "1l0eJ0Xt8KRq" | |
| } | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "First we start by defining our Abstract component class." | |
| ], | |
| "metadata": { | |
| "id": "lGitX3bU8h05" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class FileSystemElement(ABC):\n", | |
| " @abstractmethod\n", | |
| " def display(self, indent=0):\n", | |
| " pass" | |
| ], | |
| "metadata": { | |
| "id": "FU9ui14a8rvL" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Next we can define our Leaf class." | |
| ], | |
| "metadata": { | |
| "id": "-saB0GBE8sYW" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Leaf\n", | |
| "class File(FileSystemElement):\n", | |
| " def __init__(self, name):\n", | |
| " self._name = name\n", | |
| "\n", | |
| " def display(self, indent=0):\n", | |
| " print(\" \" * indent + self._name)" | |
| ], | |
| "metadata": { | |
| "id": "G9u6QyAC8vEa" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Then we define our Composite class." | |
| ], | |
| "metadata": { | |
| "id": "OKQ2jow58wun" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Composite\n", | |
| "class Directory(FileSystemElement):\n", | |
| " def __init__(self, name):\n", | |
| " self._name = name\n", | |
| " self._elements = []\n", | |
| "\n", | |
| " def display(self, indent=0):\n", | |
| " print(\" \" * indent + self._name)\n", | |
| " for element in self._elements:\n", | |
| " element.display(indent + 1)\n", | |
| "\n", | |
| " def add(self, element):\n", | |
| " self._elements.append(element)\n", | |
| "\n", | |
| " def remove(self, element):\n", | |
| " self._elements.remove(element)\n", | |
| "\n", | |
| " def get_child(self, index):\n", | |
| " return self._elements[index]" | |
| ], | |
| "metadata": { | |
| "id": "g3ErnsVy80tD" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Finally, we can make our client code:" | |
| ], | |
| "metadata": { | |
| "id": "MxX9tRa785hm" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Usage\n", | |
| "root = Directory(\"root\")\n", | |
| "documents = Directory(\"Documents\")\n", | |
| "images = Directory(\"Images\")\n", | |
| "\n", | |
| "file1 = File(\"file1.txt\")\n", | |
| "file2 = File(\"file2.txt\")\n", | |
| "image1 = File(\"image1.jpg\")\n", | |
| "\n", | |
| "documents.add(file1)\n", | |
| "documents.add(file2)\n", | |
| "images.add(image1)\n", | |
| "\n", | |
| "root.add(documents)\n", | |
| "root.add(images)\n", | |
| "\n", | |
| "root.display()\n", | |
| "# Output:\n", | |
| "# root\n", | |
| "# Documents\n", | |
| "# file1.txt\n", | |
| "# file2.txt\n", | |
| "# Images\n", | |
| "# image1.jpg" | |
| ], | |
| "metadata": { | |
| "id": "UfnV9fzo87oh", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "outputId": "eb13ab1d-cae9-4818-f63f-88b2eee6a117" | |
| }, | |
| "execution_count": null, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "root\n", | |
| " Documents\n", | |
| " file1.txt\n", | |
| " file2.txt\n", | |
| " Images\n", | |
| " image1.jpg\n" | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "# Decorator Pattern\n", | |
| "*The Decorator pattern is a structural design pattern that allows adding new behaviors to objects dynamically by placing them inside special wrapper objects.*\n", | |
| "\n", | |
| "" | |
| ], | |
| "metadata": { | |
| "id": "KzmlWPXPSY9_" | |
| } | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "*The Decorator pattern is a structural design pattern that allows adding new behaviors to objects dynamically by placing them inside special wrapper objects.*\n", | |
| "\n", | |
| "First, we can define our Abstract Component class." | |
| ], | |
| "metadata": { | |
| "id": "yXRp4CqKSc9T" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Component\n", | |
| "class Beverage(ABC):\n", | |
| " @abstractmethod\n", | |
| " def cost(self):\n", | |
| " pass\n", | |
| "\n", | |
| " @abstractmethod\n", | |
| " def description(self):\n", | |
| " pass" | |
| ], | |
| "metadata": { | |
| "id": "pIH4-9FhSfnr" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Then we can define our Concrete Component class(es)." | |
| ], | |
| "metadata": { | |
| "id": "SIZNKZ88SlCR" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Concrete Component\n", | |
| "class Coffee(Beverage):\n", | |
| " def cost(self):\n", | |
| " return 2.0\n", | |
| "\n", | |
| " def description(self):\n", | |
| " return \"Coffee\"" | |
| ], | |
| "metadata": { | |
| "id": "3UgNQaomSmeh" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "We now define our Abstract Decorator. The Decorator inherits from our Abstract Component class. Think of the Decorator as extensions to the base class that can be applied at runtime." | |
| ], | |
| "metadata": { | |
| "id": "bnqtM9FjSn_9" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Decorator\n", | |
| "class BeverageDecorator(Beverage, ABC):\n", | |
| " def __init__(self, beverage: Beverage):\n", | |
| " self._beverage = beverage" | |
| ], | |
| "metadata": { | |
| "id": "tDjES7ObSpLO" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "We now define Concrete Decorator classes. " | |
| ], | |
| "metadata": { | |
| "id": "sGFz4l8rSti7" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Concrete Decorators\n", | |
| "class Milk(BeverageDecorator):\n", | |
| " def cost(self):\n", | |
| " return self._beverage.cost() + 0.5\n", | |
| "\n", | |
| " def description(self):\n", | |
| " return f\"{self._beverage.description()}, Milk\"\n", | |
| "\n", | |
| "class WhippedCream(BeverageDecorator):\n", | |
| " def cost(self):\n", | |
| " return self._beverage.cost() + 0.7\n", | |
| "\n", | |
| " def description(self):\n", | |
| " return f\"{self._beverage.description()}, Whipped Cream\"" | |
| ], | |
| "metadata": { | |
| "id": "Cvvv0h4fSut6" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Our Decorators can now be applied to any concrete instance of Beverage. The Decorators can apply anything to the base Abstract Component class. Look at the following client code:" | |
| ], | |
| "metadata": { | |
| "id": "b76WapZbSrOn" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Client code\n", | |
| "def client_code(beverage: Beverage):\n", | |
| " print(f\"{beverage.description()}: ${beverage.cost()}\")\n", | |
| "\n", | |
| "# Usage\n", | |
| "coffee = Coffee()\n", | |
| "client_code(coffee) # Output: Coffee: $2.0\n", | |
| "\n", | |
| "coffee_with_milk = Milk(coffee)\n", | |
| "client_code(coffee_with_milk) # Output: Coffee, Milk: $2.5\n", | |
| "\n", | |
| "coffee_with_milk_and_cream = WhippedCream(coffee_with_milk)\n", | |
| "client_code(coffee_with_milk_and_cream) # Output: Coffee, Milk, Whipped Cream: $3.2" | |
| ], | |
| "metadata": { | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "id": "Q-EXKurtS0fT", | |
| "outputId": "74bce0f6-a1af-4123-eb5a-bb4edd822107" | |
| }, | |
| "execution_count": null, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "Coffee: $2.0\n", | |
| "Coffee, Milk: $2.5\n", | |
| "Coffee, Milk, Whipped Cream: $3.2\n" | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "# Facade Pattern\n", | |
| "*The Facade pattern provides a simplified interface to a more complex subsystem. It can help to hide the complexity of the subsystem and make it easier to use.*\n", | |
| "\n", | |
| "" | |
| ], | |
| "metadata": { | |
| "id": "09zVU6ZtTq65" | |
| } | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "We can define the elements of our complex subsystem." | |
| ], | |
| "metadata": { | |
| "id": "HDG0XnUyTxJz" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Complex Subsystem\n", | |
| "class SubsystemA:\n", | |
| " def operation_a(self):\n", | |
| " return \"Subsystem A, Operation A\"\n", | |
| "\n", | |
| "class SubsystemB:\n", | |
| " def operation_b(self):\n", | |
| " return \"Subsystem B, Operation B\"\n", | |
| "\n", | |
| "class SubsystemC:\n", | |
| " def operation_c(self):\n", | |
| " return \"Subsystem C, Operation C\"" | |
| ], | |
| "metadata": { | |
| "id": "JSDOv_KLTy6G" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Next, we simply define a Facade class that performs an action with the subsystem. The Facade will pass in the required information to each of the components of the subsystem and perform some action. This may look like as follows:" | |
| ], | |
| "metadata": { | |
| "id": "UvRgE8llT02c" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Facade\n", | |
| "class Facade:\n", | |
| " def __init__(self):\n", | |
| " self._subsystem_a = SubsystemA()\n", | |
| " self._subsystem_b = SubsystemB()\n", | |
| " self._subsystem_c = SubsystemC()\n", | |
| "\n", | |
| " def perform_action(self):\n", | |
| " return f\"{self._subsystem_a.operation_a()}, {self._subsystem_b.operation_b()}, {self._subsystem_c.operation_c()}\"" | |
| ], | |
| "metadata": { | |
| "id": "U5d2mPR1T0mJ" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Our client code may now be simpler because of the wrapped functionality. We can create an instance of the Facade and interact with it." | |
| ], | |
| "metadata": { | |
| "id": "noRB0YilT4sY" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Client code\n", | |
| "def client_code(facade: Facade):\n", | |
| " print(facade.perform_action())\n", | |
| "\n", | |
| "# Usage\n", | |
| "facade = Facade()\n", | |
| "client_code(facade) # Output: Subsystem A, Operation A, Subsystem B, Operation B, Subsystem C, Operation C" | |
| ], | |
| "metadata": { | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "id": "zTYehf7nT6AI", | |
| "outputId": "127be535-add7-4177-d33d-5739165daf30" | |
| }, | |
| "execution_count": null, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "Subsystem A, Operation A, Subsystem B, Operation B, Subsystem C, Operation C\n" | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "# Proxy Pattern\n", | |
| "*The Proxy pattern provides a surrogate or placeholder object that controls access to another object. It can be used for various purposes, such as security, caching, or remote access.*\n", | |
| "\n", | |
| "" | |
| ], | |
| "metadata": { | |
| "id": "4c9IqdQ4T_h1" | |
| } | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "We begin by defining our Abstract Subject Interface." | |
| ], | |
| "metadata": { | |
| "id": "8TwdYErTUFUB" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Subject Interface\n", | |
| "class Service(ABC):\n", | |
| " @abstractmethod\n", | |
| " def request(self):\n", | |
| " pass" | |
| ], | |
| "metadata": { | |
| "id": "VTCuup8IUGdL" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "We can then make our Concrete Subject." | |
| ], | |
| "metadata": { | |
| "id": "0tXWHSQTUHp6" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Real Subject\n", | |
| "class RealService(Service):\n", | |
| " def request(self):\n", | |
| " return \"RealService: Handling request.\"" | |
| ], | |
| "metadata": { | |
| "id": "aHIRbqaQUI0F" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Now we can make our Proxy class. The Proxy inherits from the Abstract Subject. This means that for any functionality that the RealSubject implements from the Abstract Subject, the Proxy can also define methods that provide an extra function before and/or after invoking the same method in the RealSubject. This looks like as follows:" | |
| ], | |
| "metadata": { | |
| "id": "Bq5xkjsWUNDr" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Proxy\n", | |
| "class Proxy(Service):\n", | |
| " def __init__(self, real_service: RealService):\n", | |
| " self._real_service = real_service\n", | |
| "\n", | |
| " def _check_access(self):\n", | |
| " return True # Simulate access check\n", | |
| "\n", | |
| " def request(self):\n", | |
| " if self._check_access():\n", | |
| " result = self._real_service.request()\n", | |
| " return f\"Proxy: {result}\"\n", | |
| " else:\n", | |
| " return \"Proxy: Access denied.\"" | |
| ], | |
| "metadata": { | |
| "id": "e3EnvZTeUOr_" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "The Proxy is often used to check if it has access to something before performing the operation with the service given.\n", | |
| "\n", | |
| "Our client code may now look as follows:" | |
| ], | |
| "metadata": { | |
| "id": "ioNMNu3qUQks" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Usage\n", | |
| "real_service = RealService()\n", | |
| "proxy = Proxy(real_service)\n", | |
| "\n", | |
| "print(proxy.request()) # Output: Proxy: RealService: Handling request." | |
| ], | |
| "metadata": { | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "id": "Ex5IsE98URz3", | |
| "outputId": "f663240a-055a-4416-86d9-ec63dbb588ec" | |
| }, | |
| "execution_count": null, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "Proxy: RealService: Handling request.\n" | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "# **Behavioural Patterns**\n", | |
| "These patterns deal with identifying common communication patterns between object. They increase simplicity and flexibility in carrying out communication among objects." | |
| ], | |
| "metadata": { | |
| "id": "cNqj003Ewk2r" | |
| } | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "# Template Method Pattern\n", | |
| "*The Template Method pattern defines the skeleton of an algorithm in an operation, deferring some steps to subclasses. It lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.*\n", | |
| "\n", | |
| "" | |
| ], | |
| "metadata": { | |
| "id": "aJvQbMTrw3pg" | |
| } | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "We first make our Abstract Class that provides methods to override and the concrete steps in the process." | |
| ], | |
| "metadata": { | |
| "id": "MbVq2LUcw_CM" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class BeverageMaker(ABC):\n", | |
| " # The template method\n", | |
| " def prepare_beverage(self):\n", | |
| " self.boil_water()\n", | |
| " self.brew()\n", | |
| " self.pour_in_cup()\n", | |
| " self.add_condiments()\n", | |
| "\n", | |
| " def boil_water(self):\n", | |
| " print(\"Boiling water\")\n", | |
| "\n", | |
| " @abstractmethod\n", | |
| " def brew(self):\n", | |
| " pass\n", | |
| "\n", | |
| " def pour_in_cup(self):\n", | |
| " print(\"Pouring into cup\")\n", | |
| "\n", | |
| " @abstractmethod\n", | |
| " def add_condiments(self):\n", | |
| " pass" | |
| ], | |
| "metadata": { | |
| "id": "K-1o5TKrxAVx" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Now we define Concrete classes that provide implementations for the abstract steps in the process." | |
| ], | |
| "metadata": { | |
| "id": "_mg9Ui5lxB0I" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class CoffeeMaker(BeverageMaker):\n", | |
| " def brew(self):\n", | |
| " print(\"Brewing coffee\")\n", | |
| "\n", | |
| " def add_condiments(self):\n", | |
| " print(\"Adding milk and sugar\")\n", | |
| "\n", | |
| "class TeaMaker(BeverageMaker):\n", | |
| " def brew(self):\n", | |
| " print(\"Steeping tea\")\n", | |
| "\n", | |
| " def add_condiments(self):\n", | |
| " print(\"Adding lemon\")" | |
| ], | |
| "metadata": { | |
| "id": "WtgMYvE4xDTi" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Finally, we can use this code by creating an instance of whatever process we want to follow." | |
| ], | |
| "metadata": { | |
| "id": "i2UWPbrUxEsZ" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "coffee_maker = CoffeeMaker()\n", | |
| "print(\"Making coffee:\")\n", | |
| "coffee_maker.prepare_beverage()\n", | |
| "# Output\n", | |
| "# Making coffee:\n", | |
| "# Boling water\n", | |
| "# Brewing coffee\n", | |
| "# Pouring into cup\n", | |
| "# Adding milk and sugar\n", | |
| "\n", | |
| "print(\"\\nMaking tea:\")\n", | |
| "tea_maker = TeaMaker()\n", | |
| "tea_maker.prepare_beverage()\n", | |
| "# Output\n", | |
| "# Making tea:\n", | |
| "# Boling water\n", | |
| "# Steeping tea\n", | |
| "# Pouring into cup\n", | |
| "# Adding lemon" | |
| ], | |
| "metadata": { | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "id": "0hYfFO6ExGdG", | |
| "outputId": "2396b351-e1cf-4f33-b150-7eb14aa0adaa" | |
| }, | |
| "execution_count": null, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "Making coffee:\n", | |
| "Boiling water\n", | |
| "Brewing coffee\n", | |
| "Pouring into cup\n", | |
| "Adding milk and sugar\n", | |
| "\n", | |
| "Making tea:\n", | |
| "Boiling water\n", | |
| "Steeping tea\n", | |
| "Pouring into cup\n", | |
| "Adding lemon\n" | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "# Iterator Pattern\n", | |
| "*The Iterator pattern provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation.*\n", | |
| "\n", | |
| "" | |
| ], | |
| "metadata": { | |
| "id": "jdUoYA-wxKI_" | |
| } | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Let’s begin by defining our aggregate object." | |
| ], | |
| "metadata": { | |
| "id": "60NCeI06xNdP" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class MyList:\n", | |
| " def __init__(self, items):\n", | |
| " self.items = items\n", | |
| "\n", | |
| " def __iter__(self):\n", | |
| " return ConcreteIterator(self)" | |
| ], | |
| "metadata": { | |
| "id": "N3DttqDQxOyo" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "This custom data structure could be representation of objects or primitives. \n", | |
| "\n", | |
| "We can now define an abstract `Iterator` class." | |
| ], | |
| "metadata": { | |
| "id": "uC1zwCEmxP4b" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class Iterator(ABC):\n", | |
| " @abstractmethod\n", | |
| " def __iter__(self):\n", | |
| " pass\n", | |
| "\n", | |
| " @abstractmethod\n", | |
| " def __next__(self):\n", | |
| " pass" | |
| ], | |
| "metadata": { | |
| "id": "7dP90t3VxTUs" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Finally, we can make our custom `ConcreteIterator`." | |
| ], | |
| "metadata": { | |
| "id": "UBzvfhL9xVAr" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class ConcreteIterator(Iterator):\n", | |
| " def __init__(self, my_list):\n", | |
| " self.my_list = my_list\n", | |
| " self.position = 0\n", | |
| "\n", | |
| " def __iter__(self):\n", | |
| " return self\n", | |
| "\n", | |
| " def __next__(self):\n", | |
| " if self.position >= len(self.my_list.items):\n", | |
| " raise StopIteration\n", | |
| "\n", | |
| " item = self.my_list.items[self.position]\n", | |
| " self.position += 1\n", | |
| " return item" | |
| ], | |
| "metadata": { | |
| "id": "gcgUdkbTxW6w" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Client code would look like…" | |
| ], | |
| "metadata": { | |
| "id": "Yj682S2UxYdN" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "my_list = MyList([\"apple\", \"banana\", \"cherry\"])\n", | |
| "\n", | |
| "for item in my_list.__iter__(): # __iter__() returns the iterator, but in Python this isn't needed.\n", | |
| " print(item)" | |
| ], | |
| "metadata": { | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "id": "muD6tn0-xZq4", | |
| "outputId": "adfe007f-f22a-4241-bb25-fc95a051e17f" | |
| }, | |
| "execution_count": null, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "apple\n", | |
| "banana\n", | |
| "cherry\n" | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "# Observer Pattern\n", | |
| "*The Observer pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.*\n", | |
| "\n", | |
| "**Note: This pattern is often referred to as the Publisher-Subscriber pattern. I will be using this notation.**\n", | |
| "\n", | |
| "" | |
| ], | |
| "metadata": { | |
| "id": "kXOkE9_Pxia3" | |
| } | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "First, let’s begin by defining a `Publisher` (Observable subject)." | |
| ], | |
| "metadata": { | |
| "id": "a2wLulq5xmXE" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class ConcretePublisher:\n", | |
| " def __init__(self):\n", | |
| " self._observers = []\n", | |
| " self._state = None\n", | |
| "\n", | |
| " def attach(self, observer):\n", | |
| " self._observers.append(observer)\n", | |
| "\n", | |
| " def detach(self, observer):\n", | |
| " self._observers.remove(observer)\n", | |
| "\n", | |
| " def notify(self):\n", | |
| " for observer in self._observers:\n", | |
| " observer.update(self)\n", | |
| "\n", | |
| " def set_state(self, state):\n", | |
| " self._state = state\n", | |
| " self.notify()\n", | |
| "\n", | |
| " def get_state(self):\n", | |
| " return self._state" | |
| ], | |
| "metadata": { | |
| "id": "KVcGpQWvxn4Q" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "This is the class that the Observers (`Subscriber`s) will be attached to.\n", | |
| "\n", | |
| "Note that in some cases, the Publisher also inherits from an interface to make it more flexible.\n", | |
| "\n", | |
| "Next, we define an Abstract `Subscriber` class that enforces concrete implementations to be able to handle the Publisher’s notifications." | |
| ], | |
| "metadata": { | |
| "id": "mjfvpKB8xuqu" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class Subscriber(ABC):\n", | |
| " @abstractmethod\n", | |
| " def update(self, subject):\n", | |
| " pass" | |
| ], | |
| "metadata": { | |
| "id": "S4m7sb7Ux6Mt" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Finally, we make our Concrete implementations of `Subscriber`s." | |
| ], | |
| "metadata": { | |
| "id": "q0TU_3d4x7Y_" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class ConcreteSubscriberA(Subscriber):\n", | |
| " def update(self, subject):\n", | |
| " print(f\"ConcreteObserverA: Subject's state is {subject.get_state()}\")\n", | |
| "\n", | |
| "class ConcreteSubscriberB(Subscriber):\n", | |
| " def update(self, subject):\n", | |
| " print(f\"ConcreteObserverB: Subject's state is {subject.get_state()}\")" | |
| ], | |
| "metadata": { | |
| "id": "WudgfUEGx9TT" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "As a client, we can now create a `Publisher` instance and link as many `Subscriber`s as we want to it." | |
| ], | |
| "metadata": { | |
| "id": "wJ78IYawx_Rm" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "subject = ConcretePublisher()\n", | |
| "\n", | |
| "observer_a = ConcreteSubscriberA()\n", | |
| "observer_b = ConcreteSubscriberB()\n", | |
| "\n", | |
| "subject.attach(observer_a)\n", | |
| "subject.attach(observer_b)\n", | |
| "\n", | |
| "subject.set_state(\"New State 1\") # subscribers notified\n", | |
| "\n", | |
| "subject.detach(observer_a)\n", | |
| "\n", | |
| "subject.set_state(\"New State 2\") # subscribers notified" | |
| ], | |
| "metadata": { | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "id": "zBjtyyDKyB4S", | |
| "outputId": "11f90e16-a45f-468e-f964-77ece6c4c921" | |
| }, | |
| "execution_count": null, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "ConcreteObserverA: Subject's state is New State 1\n", | |
| "ConcreteObserverB: Subject's state is New State 1\n", | |
| "ConcreteObserverB: Subject's state is New State 2\n" | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "# State Pattern\n", | |
| "*The State pattern allows an object to change its behavior when its internal state changes. It appears as if the object has changed its class.*\n", | |
| "\n", | |
| "" | |
| ], | |
| "metadata": { | |
| "id": "lpQ1qv-wyhI7" | |
| } | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "First we define the `Context`. The `Context` contains the current state. When a client interacts with the `Context`, the internal state changes." | |
| ], | |
| "metadata": { | |
| "id": "6m_ilwBXyl6w" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Context\n", | |
| "class Context:\n", | |
| " def __init__(self, state):\n", | |
| " self._state = state\n", | |
| "\n", | |
| " def set_state(self, state):\n", | |
| " self._state = state\n", | |
| "\n", | |
| " def request(self):\n", | |
| " self._state.handle(self) # our states will be classes that perform handle()" | |
| ], | |
| "metadata": { | |
| "id": "mqTMAEdkyqhL" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Now we define an Abstract `State` class that enforces all `State` implementations to perform some functionality." | |
| ], | |
| "metadata": { | |
| "id": "2pJS2fb8ys80" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class State(ABC):\n", | |
| " @abstractmethod\n", | |
| " def handle(self, context):\n", | |
| " pass" | |
| ], | |
| "metadata": { | |
| "id": "2Pse07v2yvb1" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Next, we define our `ConcreteState` classes." | |
| ], | |
| "metadata": { | |
| "id": "bXrXkdkeywvk" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class ConcreteStateA(State):\n", | |
| " def handle(self, context):\n", | |
| " print(\"ConcreteStateA handling request.\")\n", | |
| " context.set_state(ConcreteStateB())\n", | |
| "\n", | |
| "class ConcreteStateB(State):\n", | |
| " def handle(self, context):\n", | |
| " print(\"ConcreteStateB handling request.\")\n", | |
| " context.set_state(ConcreteStateA())" | |
| ], | |
| "metadata": { | |
| "id": "xMcu_KSJyyz5" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "The client can now create a new `Context`, pass it an initial state, and interact with the `Context` as follows:" | |
| ], | |
| "metadata": { | |
| "id": "KuYL8Rg3y0Cj" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "context = Context(ConcreteStateA())\n", | |
| "\n", | |
| "context.request() # switches to ConcreteStateB (after execution)\n", | |
| "context.request() # back to ConcreteStateA (after execution)\n", | |
| "context.request() # back to ConcreteStateB (after execution)" | |
| ], | |
| "metadata": { | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "id": "7RVzzjBpy2lB", | |
| "outputId": "ef84ca84-28ac-4623-f5b8-fd61a980b64d" | |
| }, | |
| "execution_count": null, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "ConcreteStateA handling request.\n", | |
| "ConcreteStateB handling request.\n", | |
| "ConcreteStateA handling request.\n" | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "# Strategy Pattern\n", | |
| "*The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from the clients that use it.*\n", | |
| "\n", | |
| "" | |
| ], | |
| "metadata": { | |
| "id": "NyfsAMMNy4Xp" | |
| } | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Let’s begin by defining our `Context`. The `Context` contains an instance of an arbitrary `Strategy`, and provides a function that performs an operation including the `Strategy` functionality." | |
| ], | |
| "metadata": { | |
| "id": "z4h_okdmzAEs" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "# Context\n", | |
| "class Context:\n", | |
| " def __init__(self, strategy):\n", | |
| " self._strategy = strategy\n", | |
| "\n", | |
| " def set_strategy(self, strategy):\n", | |
| " self._strategy = strategy\n", | |
| "\n", | |
| " def execute_strategy(self, a, b):\n", | |
| " return self._strategy.execute(a, b)" | |
| ], | |
| "metadata": { | |
| "id": "Iorhqjoby7Ug" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Now we define our Abstract (arbitrary) `Strategy` interface. All `ConcreteStrategy` classes will have to implement the `execute` function that the `Context` requires to do its job. " | |
| ], | |
| "metadata": { | |
| "id": "gu7oIfxDzIrG" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class Strategy(ABC):\n", | |
| " @abstractmethod\n", | |
| " def execute(self, a, b):\n", | |
| " pass" | |
| ], | |
| "metadata": { | |
| "id": "jDhxgu2uzN2p" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Now we define our `ConcreteStrategy` classes, giving them their own implementation of the `execute` function." | |
| ], | |
| "metadata": { | |
| "id": "jyzdC26-zPPt" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class ConcreteStrategyAdd(Strategy):\n", | |
| " def execute(self, a, b):\n", | |
| " return a + b\n", | |
| "\n", | |
| "class ConcreteStrategySubtract(Strategy):\n", | |
| " def execute(self, a, b):\n", | |
| " return a - b\n", | |
| "\n", | |
| "class ConcreteStrategyMultiply(Strategy):\n", | |
| " def execute(self, a, b):\n", | |
| " return a * b" | |
| ], | |
| "metadata": { | |
| "id": "FukA4866zR2O" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Now the client can instantiate these `ConcreteStrategy` classes and pass them to the `Context` at run-time. They are interchangeable." | |
| ], | |
| "metadata": { | |
| "id": "PYetDm4PzTY7" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "context = Context(ConcreteStrategyAdd())\n", | |
| "\n", | |
| "a, b = 3, 4\n", | |
| "print(f\"{a} + {b} = {context.execute_strategy(a, b)}\")\n", | |
| "\n", | |
| "context.set_strategy(ConcreteStrategySubtract())\n", | |
| "print(f\"{a} - {b} = {context.execute_strategy(a, b)}\")\n", | |
| "\n", | |
| "context.set_strategy(ConcreteStrategyMultiply())\n", | |
| "print(f\"{a} * {b} = {context.execute_strategy(a, b)}\")" | |
| ], | |
| "metadata": { | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "id": "SvJg1DeSzViM", | |
| "outputId": "6fd170eb-8db0-4e86-fdce-c6bcecfe79b3" | |
| }, | |
| "execution_count": null, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "3 + 4 = 7 |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Once again, please refer to Refactoring Guru for more information on these patterns!