<jupyter_start><jupyter_text>Design Patterns  <NAME>  ID: 21918141  February - April 2021   Logbook Excercise 1Here I am understanding the Python types list, tuple, set and dict.<jupyter_code>shopping_list = ["milk", "eggs", "bread", "cheese", "tea", "coffee", "rice", "pasta", "milk", "tea"]
print("This is my shopping list: ", shopping_list)
shopping_tuple = ("milk", "eggs", "bread", "cheese", "tea", "coffee", "rice", "pasta", "milk", "tea")
print("This is my shopping tuple: ", shopping_tuple)
shopping_set = set(shopping_list)
print("This is my shopping set - note how duplicates have been removed: ", shopping_set)
shopping_dict = {"milk": "£1.20", "eggs": "£0.87", "bread": "£0.64", "cheese": "£1.75", "tea": "£1.06", "coffee": "£2.15", "rice": "£1.60", "pasta": "£1.53"}
print("This is my shopping_dict: ", shopping_dict)<jupyter_output>This is my shopping list:  ['milk', 'eggs', 'bread', 'cheese', 'tea', 'coffee', 'rice', 'pasta', 'milk', 'tea']
This is my shopping tuple:  ('milk', 'eggs', 'bread', 'cheese', 'tea', 'coffee', 'rice', 'pasta', 'milk', 'tea')
This is my shopping set - note how duplicates have been removed:  {'bread', 'coffee', 'milk', 'rice', 'tea', 'eggs', 'cheese', 'pasta'}
This is my shopping_dict:  {'milk': '£1.20', 'eggs': '£0.87', 'bread': '£0.64', 'cheese': '£1.75', 'tea': '£1.06', 'coffee': '£2.15', 'rice': '£1.60', 'pasta': '£1.53'}<jupyter_text>Logbook Excercise 2Here I am understanding how to interact with Python Sequences.<jupyter_code>li = ["USA","Mexico","Canada"]
print("Original list: ", li)
li.append("Greenland")
print("Greenland has been appended: ", li)
li.remove("Greenland")
print("Greenland has been removed: ", li)
li.insert(0, "Greenland")
print("Greenland has been inserted at the start: ", li)
print("The first two items are:", li[:2])
print("The last two items are:", li[-2:])
print("The middle two items are:", li[1:3])<jupyter_output>Original list:  ['USA', 'Mexico', 'Canada']
Greenland has been appended:  ['USA', 'Mexico', 'Canada', 'Greenland']
Greenland has been removed:  ['USA', 'Mexico', 'Canada']
Greenland has been inserted at the start:  ['Greenland', 'USA', 'Mexico', 'Canada']
The first two items are: ['Greenland', 'USA']
The last two items are: ['Mexico', 'Canada']
The middle two items are: ['USA', 'Mexico']<jupyter_text>Logbook Excercise 3Here I am understanding how to interact with Python Collections.<jupyter_code>a = [0,1,2,3,4,5,6,7,8,9,10]
b = [0,5,10,15,20,25]
topscores = {"Jo":999, "Sue":987, "Tara":960, "Mike":870}
print("List a: ", a)
print("List b: ", b)
print("The type of a is: ", type(a))

a = set(a)
b = set(b)
print("Set a: ", a)
print("Set b: ", b)
print("The type of a is: ", type(a))
print("The intesect of a and b is: ", a&b)
print("Union of a and b is: ", a|b)
print("Items unique to sets b are ", b-a)
print("topscores dictionary keys are: ", topscores.keys())
print("topscores dictionary values are: ", topscores.values())<jupyter_output>List a:  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
List b:  [0, 5, 10, 15, 20, 25]
The type of a is:  <class 'list'>
Set a:  {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
Set b:  {0, 5, 10, 15, 20, 25}
The type of a is:  <class 'set'>
The intesect of a and b is:  {0, 10, 5}
Union of a and b is:  {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25}
Items unique to sets b are  {25, 20, 15}
topscores dictionary keys are:  dict_keys(['Jo', 'Sue', 'Tara', 'Mike'])
topscores dictionary values are:  dict_values([999, 987, 960, 870])<jupyter_text>Logbook Excercise 4Here I am creating custom functions to exercise iterations and conditionals<jupyter_code>name_list = ["<NAME>","<NAME>","<NAME>"]
hs_number_street_list = ["2 West St","65 Deadend Cls","15 Magdalen Rd"]
town_list = ["Canterbury", "Reading", "Oxford"]
postcode_list = ["CT8 23RD", "RG4 1FG", "OX4 3AS"]

def address_machine(name, hs_number_street, town, postcode):
    address = "{0}, {1}, {2}, {3}".format(name, hs_number_street, town, postcode)
    return address

newlist = [address_machine(name, hs_number_street, town, postcode) for name, hs_number_street, town, postcode in zip(name_list, hs_number_street_list, town_list, postcode_list)]

for item in newlist:
    print(item)<jupyter_output>T Cruise, 2 West St, Canterbury, CT8 23RD
D Francis, 65 Deadend Cls, Reading, RG4 1FG
C White, 15 Magdalen Rd, Oxford, OX4 3AS<jupyter_text>Logbook Excercise 5Here I am demonstrating object orientated principles with Python.<jupyter_code>class Person:
    def __init__(self, first_name, second_name, postcode, age):
        self.fn = first_name
        self.sn = second_name
        self.pc = postcode
        self.yr = age
        
    def greeting(self):
        print(f"Hello, my name is  {self.fn} {self.sn}. I am {self.yr} years old and my postcode is {self.pc}")

class Student(Person):
    def __init__(self, first_name, second_name, postcode, age, degree_subject, student_ID):
        super().__init__(first_name, second_name, postcode, age)
        self.ds = degree_subject
        self.id = student_ID
   
    def studentGreeting(self):
        print(f"My student ID is {self.ds} and I am reading {self.id}")
            
student1 = Student("Dick", "Turpin", "HP11 2JZ", 32, "DT123456", "Highway Robbery")
student2 = Student("Dick", "Turpin", "SO14 7AA", 32, "DT123457", "Law")
student3 = Student("Oliver", "Cromwell", "OX35 14RE", 32, "OC123456", "History")

student_list = [student1, student2, student3]

for student in student_list:
    student.greeting()
    student.studentGreeting()<jupyter_output>Hello, my name is  <NAME>. I am 32 years old and my postcode is HP11 2JZ
My student ID is DT123456 and I am reading Highway Robbery
Hello, my name is  <NAME>. I am 32 years old and my postcode is SO14 7AA
My student ID is DT123457 and I am reading Law
Hello, my name is  <NAME>. I am 32 years old and my postcode is OX35 14RE
My student ID is OC123456 and I am reading History<jupyter_text>Logbook Excercise 6Design pattern: Model-View-Contoller<jupyter_code>#MVC_Template_01
#2014 May 23  by <NAME> http://makeAppPie.com
#Controller initializing MVC -- simplest version possible.
#Additional annotations by <NAME> - Oct 2018

# MVC EXTENDED WITH DELETE BUTTON

# Tkinter is a Python binding to the Tk GUI toolkit/library - RM
from tkinter import *
 
# A Model-View-Controller framework for TKinter.
# Model: Data Structure. Controller can send messages to it, and model can respond to message.
# View : User interface elements. Controller can send messages to it. View can call methods from Controller when an event happens.
# Controller: Ties View and Model together. turns UI responses into changes in data.
 


#Controller: Ties View and Model together.
#       --Performs actions based on View events.
#       --Sends messages to Model and View and gets responses
#       --Has Delegates - RM NOTE: essential for communications between view and model via control
 
class MyController():
    def __init__(self,parent):
        self.parent = parent
        # CRITICAL PART OF THE MVC 
        # RM NOTES:
        # MyController passes a reference to itself (self) when it creates MyModel and MyView objects
        # This allows the MyModel and MyView objects to create 'delegates' (see 'vc' = virtual controller?)
        # The vc objects can then call methods on the MyControl object, thereby allowing the View to update the Model via the Controller 
        self.model = MyModel(self)    # initializes the model
        self.view = MyView(self)  #initializes the view
        # Initialize objects in view
        self.view.setEntry_text('Add to Label') # a non cheat way to do MVC with tkinter control variables
        self.view.setLabel_text('Ready')
    
    #event handlers
    def quitButtonPressed(self):
        self.parent.destroy()
    def addButtonPressed(self):
        self.view.setLabel_text(self.view.entry_text.get())
        # CRITICAL PART OF THE MVC 
        # RM NOTES - In one operation:
        #[1] Get the text from the VIEW;
        #[2] Add it to the MODEL by appending it to the list
        self.model.addToList(self.view.entry_text.get())
        
    def deleteButtonPressed(self):
        self.view.setLabel_text(self.view.entry_text.get())
        self.model.deleteFromList(self.view.entry_text.get())
        #MYCODE - Here I am getting the text from the view and removing it from the model by deleting it from the list.
    
    def listChangedDelegate(self):
        #model internally changes and needs to signal a change
        print(self.model.getList())
 
#View : User interface elements.
#       --Controller can send messages to it.
#       --View can call methods from Controller vc when an event happens.
#       --NEVER communicates with Model.
#       --Has setters and getters to communicate with controller
 
class MyView(Frame):
    
    def __init__(self,vc):
        self.frame=Frame()
        self.frame.grid(row = 0,column=0)
        self.vc = vc
        self.entry_text = StringVar()
        self.entry_text.set('nil')
        self.label_text = StringVar()
        self.label_text.set('nil')
        self.loadView()

    def loadView(self):
        quitButton = Button(self.frame,text = 'Quit', command= self.vc.quitButtonPressed).grid(row = 0,column = 0)
        addButton = Button(self.frame,text = "Add", command = self.vc.addButtonPressed).grid(row = 0, column = 1)
        #MYCODE - Here I create the Delete button on the UI and link it to the deleteButtonPressed controller method.
        deleteButton = Button(self.frame,text = "Delete", command = self.vc.deleteButtonPressed).grid(row = 0, column = 2)
        entry = Entry(self.frame,textvariable = self.entry_text).grid(row = 1, column = 0, columnspan = 3, sticky = EW)
        label = Label(self.frame,textvariable = self.label_text).grid(row = 2, column = 0, columnspan = 3, sticky = EW)

    def getEntry_text(self):
    #returns a string of the entry text
        return self.entry_text.get()
    def setEntry_text(self,text):
    #sets the entry text given a string
        self.entry_text.set(text)
    def getLabel_text(self):
    #returns a string of the Label text
        return self.label_text.get()
    def setLabel_text(self,text):
    #sets the label text given a string
        self.label_text.set(text)
 
#Model: Data Structure.
#   --Controller can send messages to it, and model can respond to message.
#   --Uses delegates from vc to send messages to the Control of internal change
#   --NEVER communicates with View
#   --Has setters and getters to communicate with Controller
 
class MyModel():
    def __init__(self,vc):
        self.vc = vc
        self.myList = ['duck','duck','goose']
        self.count = 0
#Delegates-- Model would call this on internal change
    def listChanged(self):
        self.vc.listChangedDelegate()
#setters and getters
    def getList(self):
        return self.myList
    def initListWithList(self, aList):
        self.myList = aList
    def addToList(self,item):
        print("returned")
        self.myList.append(item)
        self.listChanged()
        
    #MYCODE - Here I create the delete functionality by enabling it to remove an item from the list. Note, I have also optimised the removal!     
    def deleteFromList(self, item):
        print("returned")
        self.myList.remove(item)
        self.listChanged()

 
def main():
    # Create a root window from the Tk GUI class
    root = Tk()
    frame = Frame(root,bg='#0555ff')
    root.title('<NAME>')
    app = MyController(root)
    root.mainloop()
 
if __name__ == '__main__':
    main()<jupyter_output>returned
['duck', 'duck', 'goose', 'Add to Label']
returned
['duck', 'duck', 'goose', 'Add to Label', 'Add to Label']
returned
['duck', 'duck', 'goose', 'Add to Label']<jupyter_text>Logbook Excercise 7Design pattern: Observer Pattern (Adding to our Model-View-Contoller)<jupyter_code>import matplotlib.pyplot as plt
import numpy as np


# Nicely abstracted structure by which any model can notify any observer (view) of changes in the model
class Subject(object): #Represents what is being 'observed'

    def __init__(self):
        self._observers = [] # This where references to all the observers are being kept
                             # Note that this is a one-to-many relationship: there will be one subject to be observed by multiple _observers

    def attach(self, observer):
        if observer not in self._observers: #If the observer is not already in the observers list
            self._observers.append(observer) # append the observer to the list

    def detach(self, observer): #Simply remove the observer
        try:
            self._observers.remove(observer)
        except ValueError:
            pass

    def notify(self, modifier=None):
        for observer in self._observers: # For all the observers in the list
            if modifier != observer: # Don't notify the observer who is actually doing the updating 
                observer.update(self) # Alert the observers!
                
# Represents the 'data' for which changes will produce notifications to any registered view/observer objects 
class Model(Subject): # Extends the Subject class

    def __init__(self, name):
        Subject.__init__(self)
        self._name = name # Set the name of the model
        self._labels = ['Python', 'C++', 'Java', 'Perl', 'Scala', 'Lisp']
        self._data = [10,8,6,4,2,1]

    @property #Getter that gets the labels
    def labels(self):
        return self._labels

    @labels.setter #Setter that sets the labels
    def labels(self, labels):
        self._labels = labels
        self.notify() # Notify the observers whenever somebody changes the labels
        
    @property #Getter that gets the data
    def data(self):
        return self._data

    @data.setter #Setter that sets the labels
    def data(self, data):
        self._data = data
        self.notify() # Notify the observers whenever somebody changes the data
        

# This is the 'standard' view/observer which also acts as an 'abstract' class whereby deriving Bar/Chart/Table views override the update() method
# This 'abstracted' layer is always shown in examples but is important to demonstrate potential polymorphic behaviour of update()
class View():
    
    def __init__(self, name=""):
         self._name = name #Set the name of the Viewer

    def update(self, subject): #Alert method that is invoked when the notify() method in a concrete subject is invoked
        print("Generalised Viewer '{}' has: \nName = {}; \nLabels = {}; \nData = {}".format(self._name, subject._name, subject._labels, subject._data))

# Table 'chart' viewer/ConcreteObserver - overrides the update() method
class TableView(View):
    
    def update(self, subject): #Alert method that is invoked when the notify() method in a concrete subject is invoked
        fig = plt.figure(dpi=80)
        ax = fig.add_subplot(1,1,1)
        table_data = list(map(list,zip(subject._labels, subject._data)))
        table = ax.table(cellText=table_data, loc='center')
        table.set_fontsize(14)
        table.scale(1,4)
        ax.axis('off')
        plt.show()

# Bar chart viewer/ConcreteObserver - overrides the update() method
class BarView(View):
        
    def update(self, subject): #Alert method that is invoked when the notify() method in a concrete subject is invoked
        objects = subject._labels
        y_pos = np.arange(len(objects))
        performance = subject._data
        plt.bar(y_pos, performance, align='center', alpha=0.5)
        plt.xticks(y_pos, objects)
        plt.ylabel('Usage')
        plt.title('Programming language usage')
        plt.show()
        
# Pie Chart viewer/ConcreteObserver - overrides the update() method
class PieView(View):
    def update(self, subject): #Alert method that is invoked when the notify() method in a concrete subject is invoked
        # Pie chart, where the slices will be ordered and plotted counter-clockwise:
        labels = subject._labels
        sizes = subject._data
        explode = (0.1, 0, 0, 0, 0, 0)  # only "explode" the 1st slice
        fig1, ax1 = plt.subplots()
        ax1.pie(sizes, explode=explode, labels=labels, autopct='%1.1f%%', shadow=True, startangle=90)
        ax1.axis('equal')  # Equal aspect ratio ensures that pie is drawn as a circle.
        plt.show()

#Let's create our subjects
m1 = Model("Model 1")
m2 = Model("Model 2") # This is never used!

#Let's create our observers
v1 = View("1: standard text viewer")
v2 = TableView("2: table viewer")
v3 = BarView("3: bar chart viewer")
v4 = PieView("4: pie chart viewer")
####

#Let's attach our observers to the first model
m1.attach(v1)
m1.attach(v2)
m1.attach(v3)
m1.attach(v4)
####

# Let's just call the notify() method to see all the charts in their unchanged state
m1.notify()

# Now Let's change the properties of our first model
# Change 1 triggers all 4 views and updates their labels
m1.labels = ['C#','PHP','JavaScript','ASP','Python','Smalltalk']
# Change 2 triggers all 4 views and updates their data
m1.data = [1,18,8,60,3,1]<jupyter_output>Generalised Viewer '1: standard text viewer' has: 
Name = Model 1; 
Labels = ['Python', 'C++', 'Java', 'Perl', 'Scala', 'Lisp']; 
Data = [10, 8, 6, 4, 2, 1]<jupyter_text>Logbook Excercise 8Design pattern: Factory Method.I have also answered the challenge question by developing an abstract creator.<jupyter_code>'''
Example modified from <NAME> (2017) Python GUI Programming Cookbook, Second Edition.  Packt Publishing 

<NAME> 2020

'''

'''
Challenge Question - I have refactored the code to create the abstract creator below which is extended by 
the concrere creators TextFactory and ButtonFactory.
'''

import tkinter as tk
from tkinter import ttk
from tkinter import Menu

#Abstract Creator <- Challenge Q Abstract Creator
class Creator():
    def create(self, type_):
        pass

#Concrete Creator <- Challenge Q Concrete Creator extending Abstract Creator
class ButtonFactory(Creator):
    def create(self, type_):
        return buttonTypes[type_]()

#Shared Abstract Product
class WidgetBase():
    def getConfig(self):
        pass

#Abstract Product
class ButtonBase(WidgetBase):     
    relief     ='flat'
    foreground ='white'
    def getConfig(self):
        return self.relief, self.foreground

#Concrete Product    
class ButtonRidge(ButtonBase):
    relief     ='ridge'
    foreground ='red'        

#Concrete Product    
class ButtonSunken(ButtonBase):
    relief     ='sunken'
    foreground ='blue'        

#Concrete Product    
class ButtonGroove(ButtonBase):
    relief     ='groove'
    foreground ='green'        

buttonTypes = [ButtonRidge, ButtonSunken, ButtonGroove]


#Concrete Creator <- Challenge Q Concrete Creator extending Abstract Creator
class TextFactory(Creator):
    def create(self, type_):
        return textTypes[type_]()
    
#Abstract Product
class TextBase(WidgetBase):
    textvariable ='yellow'
    background = 'yellow'
    def getConfig(self):
        return self.textvariable, self.background

#Concrete Product    
class Text_1(TextBase):
    textvariable = 'red type'
    background = 'red'

#Concrete Product    
class Text_2(TextBase):
    textvariable = 'blue type'
    background = 'blue'

#Concrete Product    
class Text_3(TextBase):
    textvariable = 'green type'
    background = 'green'
    
textTypes = [Text_1, Text_2, Text_3]

    
class OOP():
    def __init__(self): 
        self.win = tk.Tk()         
        self.win.title("Python GUI")      
        self.createWidgets()

    def createWidgets(self):    
        self.widgetFactory = ttk.LabelFrame(text=' Button Factory ')
        self.widgetFactory.grid(column=0, row=0, padx=8, pady=4)        

        self.createButtons()
        self.createTextFields()

    def createButtons(self):
            
        factory = ButtonFactory()

        # Button 1
        rel = factory.create(0).getConfig()[0]
        fg  = factory.create(0).getConfig()[1]
        action = tk.Button(self.widgetFactory, text="Button "+str(0+1), relief=rel, foreground=fg)   
        action.grid(column=0, row=1)  

        # Button 2
        rel = factory.create(1).getConfig()[0]
        fg  = factory.create(1).getConfig()[1]
        action = tk.Button(self.widgetFactory, text="Button "+str(1+1), relief=rel, foreground=fg)   
        action.grid(column=0, row=2)  
        
        # Button 3
        rel = factory.create(2).getConfig()[0]
        fg  = factory.create(2).getConfig()[1]
        action = tk.Button(self.widgetFactory, text="Button "+str(2+1), relief=rel, foreground=fg)   
        action.grid(column=0, row=3)   
        
    def createTextFields(self):
        factory = TextFactory()
        
        # Entry field 1
        sv=tk.StringVar()
        tx = factory.create(0).getConfig()[0]
        sv.set(tx)
        bg  = factory.create(0).getConfig()[1]
        action = tk.Entry(self.widgetFactory, textvariable=sv, background=bg, foreground="white")   
        action.grid(column=1, row=1)  
        
        # Entry field 2
        sv=tk.StringVar()
        tx = factory.create(1).getConfig()[0]
        sv.set(tx)
        bg  = factory.create(1).getConfig()[1]
        action = tk.Entry(self.widgetFactory, textvariable=sv, background=bg, foreground="white")   
        action.grid(column=1, row=2)  
        
        # Entry field 3
        sv=tk.StringVar()
        tx = factory.create(2).getConfig()[0]
        sv.set(tx)
        bg  = factory.create(2).getConfig()[1]
        action = tk.Entry(self.widgetFactory, textvariable=sv, background=bg, foreground="white")   
        action.grid(column=1, row=3)  
     
#==========================
oop = OOP()
oop.win.mainloop()<jupyter_output><empty_output><jupyter_text>Logbook Excercise 9Design pattern: Abstract Factory<jupyter_code>#Abstract factory
class AnimalFactory:
    """Abstract Factory"""
    def get_pet(self):
        pass
        """Returns an animal"""
        return Animal()
    def get_food(self):
        pass
        """Returns an animals food"""

#Abstract product
class Animal:
    def speak(self):
        pass
    def __str__(self): 
        pass

#Concrete product
class Dog(Animal):
    """One of the objects to be returned"""
    def speak(self):
        return "Woof!"
    def __str__(self):
        return "Dog"

#Concrete factory
class DogFactory(AnimalFactory):
    """Concrete Factory"""
    def get_pet(self):
        """Returns a Dog object"""
        return Dog()
    def get_food(self):
        """Returns a Dog Food object"""
        return "Dog Food!"

#Concrete product
class Cat(Animal):
    """One of the objects to be returned"""
    def speak(self):
        return "Meow!"
    def __str__(self):
        return "Cat"

#Concrete factory
class CatFactory(AnimalFactory):
    """Concrete Factory"""
    def get_pet(self):
        """Returns a Cat object"""
        return Dog()
    def get_food(self):
        """Returns a Cat Food object"""
        return "Cat Food!"        

class PetStore:
    """ PetStore houses our Abstract Factory """
    def __init__(self, pet_factory=None):
        """ pet_factory is our Abstract Factory """
        self._pet_factory = pet_factory
    def show_pet(self):
        """ Utility method displays details of objects returned by DogFactory """
        pet = self._pet_factory.get_pet()
        pet_food = self._pet_factory.get_food()
        print("Our pet is '{}'!".format(pet))
        print("Our pet says hello by '{}'".format(pet.speak()))
        print("Its food is '{}'!".format(pet_food))


#Create a Concrete Factory
factory = DogFactory()
#Create a pet store housing our Abstract Factory
shop = PetStore(factory)
#Invoke the utility method to show the details of our pet
shop.show_pet()

#Same pattern for cats
factory = CatFactory()
shop = PetStore(factory)
shop.show_pet()<jupyter_output>Our pet is 'Dog'!
Our pet says hello by 'Woof!'
Its food is 'Dog Food!'!
Our pet is 'Dog'!
Our pet says hello by 'Woof!'
Its food is 'Cat Food!'!<jupyter_text>Logbook Excercise 10Design pattern: Strategy PatternNote!I had to change the downsampling of the cartoonized effect to 0. This is because my cloud image contained an odd number of pixels and when it was downsampled for processing, it rounded the size of it up (e.g. from 101 to 51). Thus when the transformation had finished, it rescaled the image back up to a number greater than the original size (e.g. continuing the example from 51 to 102). Therefore, when it attempted to combine the two images (e.g. sizes 101 and 102 respectively), they were no longer the same size the code errors like so:```error: OpenCV(3.4.2) /opt/concourse/worker/volumes/live/9523d527-1b9e-48e0-7ed0-a36adde286f0/volume/opencv-suite_1535558719691/work/modules/core/src/arithm.cpp:225: error: (-209:Sizes of input arguments do not match) The operation is neither 'array op array' (where arrays have the same size and type), nor 'array op scalar', nor 'scalar op array' in function 'binary_op'```Removing the downsampling allows the two images to remain the same size and prevents this error.<jupyter_code># Example from <NAME> (2015)

# The 'types' module is needed to support dynamic creation of new types. In this case we dynamically create 
# ... a new method type
#Import the types module
import types 
import cv2
import numpy as np

# Although 'concrete' this performs the role of the Strategy interface in the GoF example 
class Strategy:
    """The Strategy Pattern class"""
    
    def __init__(self, function=None):
        self.image = "clouds.jpeg"
        
        #If a reference to a function is provided, replace the execute() method with the given function
        if function:
            # When a new method is added with MethodType (from the 'types' module) this binds a method 
            # ... at runtime so that behaves like a normal Python method for the Strategy object 
            self.execute = types.MethodType(function, self)
            
    # Although 'concrete' this performs an equivalent role to the GoF "AlgorithmInterface()"
    def execute(self): #This gets replaced by another version if another strategy is provided.
        print("The image {} is used to execute Strategy 0 - Display image".format(self.image))
        img_rgb = cv2.imread(self.image)
        cv2.imshow('Image', img_rgb)

# Replacement method 1 - this is a concrete implementation of 'AlgorithmInterface'
def strategy_grayscale(self):
    img_rgb = cv2.imread(self.image)
    img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2GRAY)
    cv2.imshow('Greyscale image', img_gray)
    
def strategy_blur(self):
    img_rgb = cv2.imread(self.image)
    img_blur = cv2.medianBlur(img_rgb, 7)
    cv2.imshow('Blurred image', img_blur) 
    
def strategy_colNegative(self):
    img_rgb = cv2.imread(self.image)
    for x in np.nditer(img_rgb, op_flags=['readwrite']):
        x[...] = (255 - x)
    cv2.imshow('Colour negative', img_rgb)
    
def strategy_greyNegative(self):
    img_rgb = cv2.imread(self.image)
    img_grey = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2GRAY)
    for x in np.nditer(img_grey, op_flags=['readwrite']):
        x[...] = (255 - x)
    cv2.imshow('Monochrome negative', img_grey)

    
def strategy_cartoonEffect(self):
 #Use bilateral filter for edge smoothing.
    num_down = 0 # number of downsampling steps
    num_bilateral = 7 # number of bilateral filtering steps
    img_rgb = cv2.imread(self.image)
    # downsample image using Gaussian pyramid
    img_color = img_rgb
    for _ in range(num_down):
        img_color = cv2.pyrDown(img_color)
    # repeatedly apply small bilateral filter instead of applying one large filter
    for _ in range(num_bilateral):
        img_color = cv2.bilateralFilter(img_color, d=9, sigmaColor=9, sigmaSpace=7)
    # upsample image to original size
    for _ in range(num_down):
        img_color = cv2.pyrUp(img_color)
    #Use median filter to reduce noise convert to grayscale and apply median blur
    img_grey = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2GRAY)
    img_blur = cv2.medianBlur(img_grey, 7)
    #Use adaptive thresholding to create an edge mask detect and enhance edges
    img_edge = cv2.adaptiveThreshold(img_blur, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, blockSize=9, C=2)
    # Combine color image with edge mask & display picture, convert back to color, bit-AND with color image
    img_edge = cv2.cvtColor(img_edge, cv2.COLOR_GRAY2RGB)
    print("Here was my debugging line which showed me the size of the two images did not match with downsampling:")
    print(len(img_color))
    print(len(img_edge))
    img_cartoon = cv2.bitwise_and(img_color, img_edge)
    # display
    cv2.imshow("Cartoon-ised image", img_cartoon); cv2.waitKey(0); cv2.destroyAllWindows()
    
# Let's create our default strategy
s0 = Strategy()
# Let's execute our default strategy
s0.execute()

# Let's create the first variation of our default strategy by providing a new behavior
s1 = Strategy(strategy_grayscale)
s1.execute()

s2 = Strategy(strategy_blur)
s2.execute()

s3 = Strategy(strategy_colNegative)
s3.execute()

s4 = Strategy(strategy_greyNegative)
s4.execute()

s5 = Strategy(strategy_cartoonEffect)
s5.execute()<jupyter_output><empty_output><jupyter_text>Logbook Excercise 11Design pattern: Iterator<jupyter_code>import sys

mylist = ['item1', 'item2', 'item3']

top4books=["<NAME> by <NAME>", "Madame Bovary by <NAME>", "War and Peace by Leo Tolstoy", 
           "Lolita by <NAME>"]

iter_top4books = iter(top4books) 

try: 
    print( next(iter_top4books))  
    print( next(iter_top4books))  
    print( next(iter_top4books))
    print( next(iter_top4books))
    # Exceeds 4 items so should raise StopIteration exception
    print( next(iter_top4books)) 
except Exception as e:
    print(e)
    print(sys.exc_info())<jupyter_output><NAME> by <NAME>
Madame Bovary by <NAME>
War and Peace by Leo Tolstoy
Lolita by <NAME>

(<class 'StopIteration'>, StopIteration(), <traceback object at 0x1079ec700>)<jupyter_text>Logbook Excercise 12Design pattern: The Adaptor Pattern<jupyter_code>import cartopy.crs as ccrs
import matplotlib.pyplot as plt

# 'Adapted' and extended from Jungwoo Ryoo (2015) - note that all 'country' classes have different speak_xxx() methods 
    
# The Cartopy Adaptees
class PlateCarree: 
    def __init__(self):
        self.name = "Plate Carree"
    def project_PlateCarree(self):
        ax = plt.axes(projection=ccrs.PlateCarree())
        return ax

class InterruptedGoodeHomolosine:
    def __init__(self):
        self.name = "Interrupted Goode Homolosine"    
    def project_InterruptedGoodeHomolosine(self):
        ax = plt.axes(projection=ccrs.InterruptedGoodeHomolosine())
        return ax

class AlbersEqualArea:
    def __init__(self):
        self.name = "Albers Equal Area"    
    def project_AlbersEqualArea(self):
        ax = plt.axes(projection=ccrs.AlbersEqualArea())
        return ax   

class Mollweide:
    def __init__(self):
        self.name = "Mollweide"    
    def project_Mollweide(self):
        ax = plt.axes(projection=ccrs.Mollweide())
        return ax

# The  GoF 'Adapter'
class Adapter:
    def __init__(self, object, **adapted_method):
        self._object = object
        self.__dict__.update(adapted_method)

    def __getattr__(self, attr):
        return getattr(self._object, attr)
    
#List to store projection objects
projectionObjects = []

#Creating the cartopy objects
plantCarree = PlateCarree()
interruptedGoodHomolsine = InterruptedGoodeHomolosine()
albersEqualArea = AlbersEqualArea()
mollweide = Mollweide()

#Append the objects to the objects list - note the mapping of "project()" to "project_Mollweide/project_InterruptedGoodeHomolosine etc."
projectionObjects.append(Adapter(plantCarree, project=plantCarree.project_PlateCarree))
projectionObjects.append(Adapter(interruptedGoodHomolsine, project=interruptedGoodHomolsine.project_InterruptedGoodeHomolosine))
projectionObjects.append(Adapter(albersEqualArea, project=albersEqualArea.project_AlbersEqualArea))
projectionObjects.append(Adapter(mollweide, project=mollweide.project_Mollweide))

for obj in projectionObjects:
  # Create an axes with the specified projection  
  ax = obj.project()
  # Attach Cartopy's default geospatially registered map/image of the world 
  ax.stock_img()
  # Add the coastlines - highlight these with a black vector 
  ax.coastlines()
  # Print the name of the object/projection
  print(obj.name)
  # Plot the axes, projection and render the map-image to the projection
  plt.show()<jupyter_output>Plate Carree<jupyter_text>Logbook Excercise 13Design pattern: The Decorator<jupyter_code># REPAIRED DP

def docStringDecorator(f):
    '''Decorator that automatically reports name and docstring for a decorated function'''
    print("<<< Name of the 'decorated' function ... ",f.__name__," >>> ")
    print("<<< Docstring for the 'decorated' function is ... ",f.__doc__," >>>")
    return f

@docStringDecorator
def aTestMethod():
    '''This is a method to test the docStringDecorator'''
    nm = input("What is your name? ... ")
    msg = "Hello ... " + nm
    return msg

print(aTestMethod())<jupyter_output><empty_output><jupyter_text>Logbook Excercise 14Design pattern: The Conventional Singleton<jupyter_code>## SINGLETON - Extended from Dusty Philips (2015) ##

class OneOnly:
    _singleton =  None
    def __new__(cls, *args, **kwargs):
        if not cls._singleton:
            cls._singleton = super(OneOnly, cls).__new__(cls, *args, **kwargs)
        return cls._singleton

firstOne = OneOnly()
secondOne = OneOnly()

singleton_list = [firstOne, secondOne]

for singleton in singleton_list:
    print(repr(singleton))

if (firstOne == secondOne):
    print("They occupy the same memory address");
else: 
    print("They do not occupy the same memory address");<jupyter_output><__main__.OneOnly object at 0x10700ad00>
<__main__.OneOnly object at 0x10700ad00>
They have the same memory address<jupyter_text>Logbook Excercise 15Design pattern: The Borg Singleton<jupyter_code># Singleton/BorgSingleton.py
# <NAME>'s 'Borg'

class Borg:
    _shared_state = {}
    
    def __init__(self):
        self.__dict__ = self._shared_state

class Singleton(Borg):
    def __init__(self, arg):
        # Here the 'static' Borg class is updated with the state of the new singleton object 
        Borg.__init__(self)
        self.val = arg
    def __str__(self): 
        return self.val
    

#Creating the Borg objects    
borg = Borg()
firstSingleton = Singleton(borg)
secondSingleton = Singleton(borg)
thirdSingleton = Singleton(borg)

borg_list = [firstSingleton, secondSingleton, thirdSingleton]

#Are the borgs the same object?    
if (repr(firstSingleton) is repr(secondSingleton) is repr(thirdSingleton)):
    print("They occupy the same memory address");
else: 
    print("They do not occupy the same memory addresses - they are different objects");
    
for borg in borg_list:
    print(repr(borg))    
    

#Do they share a state?    

firstSingleton.val = 10 #Changing the state of the first borg
print("The state change is reflected in all singletons: ", secondSingleton.val , thirdSingleton.val)#Look it is reflected in the second and third borg!
thirdSingleton.val = 20 #Changing the state of the third borg
print("The state change is reflected in all singletons: ", secondSingleton.val , firstSingleton.val) #Look it is reflected in the second and third borg!

#More proof of the shared state
if ((firstSingleton._shared_state is secondSingleton._shared_state) and (firstSingleton._shared_state is thirdSingleton._shared_state)):
    print("The shared state is only created once, when the first singleton is instantiated. It is held in the Borg parent class");
else: 
    print("The shared state is being created multiple times"); 
    
    
for borg in borg_list:
    print(borg._shared_state)<jupyter_output>They do not occupy the same memory addresses
<__main__.Singleton object at 0x10fbe8340>
<__main__.Singleton object at 0x10fbe8880>
<__main__.Singleton object at 0x10fbe8d30>
The state change is reflected in all singletons:  10 10
The state change is reflected in all singletons:  20 20
The shared state is only created once, when the first singleton is instantiated. It is held in the Borg parent class
{'val': 20}
{'val': 20}
{'val': 20}<jupyter_text>Logbook Excercise 15Chosen Design pattern: The Singleton Pattern  IntentThe singleton pattern is a design pattern that ensures a class only has a single instance. The class of the singleton object is responsible for restricting the instantation of itself to a single instance (Freeman and Robson, 2021).As a creational pattern, the singleton pattern is responsible for the creation of objects, specifically ensuring more than one object is not created. Furthermore, it is an object pattern because it deals with the relationships between objects - the unique instance object the class holds and the access by other objects - rather than relations between classes and their subclasses (Intent and Motivation for Singleton Design Patterns, 2021).As the singleton class must prevent other classes from creating new instances, it is therefore responsible for it's own creation, initialization, access and enforcement (Freeman and Robson, 2021).Moreover, the singleton class acts as a global access point to the instance, making the singleton pattern  particularly useful when wanting to create global state in an application. Furthermore, singleton objects can be created in a lazy manner - making it ideal for managing resource intensive objects (Freeman and Robson, 2021).  MotivationThe key motivation behind the singleton pattern is to ensure a single instance of a class only exists once in a system. Guaranteeing no more instances of the singleton class can be created and ensuring easy access to the single instance is necessary to prevent multiple instances of the same object from being created (Intent and Motivation for Singleton Design Patterns, 2021).In the table below, I have outlined several examples of applications and classes which apply the singleton pattern. All of the examples ensure there is an asynchronous gateway for data (Freeman and Robson, 2021).|Library| Singleton Application||:------|:---------------------||java.lang.runtime| Every Java application has a single instance of class Runtime that allows the application to interface with the environment in which the application is running. The current runtime can be obtained from the getRuntime() method.||java.lang.desktop|The Desktop class allows a Java application to launch associated applications registered on the native desktop to handle a URI or a file. This class also cannot be instantiated from application. Hence it is also a singleton class.||Technology|Singleton Application||:---------|:--------------------||Cache| Singleton objects can be used as caches as they have a global point of reference for all future calls to the cache object the client application will use the in-memory object.||Configuration Files| Using Singleton Objects for configuration files can increase performance as it prevents multiple users to repeatedly access and read the configuration file. Instead, it ensure a single instance of the configuration file is created  can be accessed by multiple calls concurrently as the static config data loaded into in-memory objects.||Logger| A log file can be created by the logger class as a singleton object. Typically, a logging utility has to produce a single log file based on messages recieved from the users. If there are multiple clients, multiple instances of the logging class could be created which in turn could cause issues during concurrent access of the same logger file. By using the logging utility class as a singleton we provide a single point to prevent users accessing this utility at the same time.||Hardware Interface Access| Singletons can be used to limit the resources of external hardware. For instance, printer spoolers can be definted by singleton objects in order to avoid multiple concurrent accesses and creating deadlock.|  Structure![singleton.png](attachment:singleton.png)Note, this diagram includes the client. The client is class outside the pattern; which only knows about the public interface singleton class exposes, rather than its' private implementation.Source: https://dzone.com/articles/singleton-pattern-a-deep-dive  Implementation and Sample CodeIn Java, the singleton design pattern is achieved by creating a class that is responsible for creating and managing the singleton object. This class has a single field called uniqueInstance, which points to the object that is the unique instance. The Singleton class has two methods, a static getInstance() method which returns a reference to the unique instance and a private constructor which ensures only the Singleton can instantiate the class. The singleton is created when the variable is initialized - at some point before the static method is first called.Here I will demonstrate the Singleton design pattern using Java. I have decided to iterate on the example below to demonstrate how the Singleton design pattern can be modified to ensure optimal performance of an application.<jupyter_code>//Example 1 - Singleton Class

public class BirdFeeder {
    private boolean empty;
    private boolean birdPresent;
    private int photoCount;
    private static BirdFeeder uniqueInstance;

//Private constructor ensures only the BirdFeeder can instantiate the class    
    private BirdFeeder() { 
        empty = true;
        birdPresent = false;
        photoCount = 0;
    }

//getInstance() enables us to instantiate the class and return the instance of it
    public static BirdFeeder getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new BirdFeeder();
        }
        return uniqueInstance;
    }

//Other useful class methods below    
    public boolean isEmpty() {
        return empty;
    }

    public boolean birdStatus() {
        return birdPresent;
    }

    public int photoStatus() {
        return photoCount;
    }

    public void addSeed() {
        if (isEmpty()) {
            empty = false;
            birdPresent = true;
        }
    }

    public void takeFlashPhoto() {
        if (birdStatus()) {
            birdPresent = false;
            photoCount = photoCount+1;
        }
    }


//Here I am demonstrating that only a single instance of the class is maintained
        BirdFeeder birdFeeder1 = BirdFeeder.getInstance();
        System.out.println("Initial bird presence state: " + birdFeeder1.birdStatus());
        birdFeeder1.addSeed();
        System.out.println("Seed is dispensed");
        System.out.println("A bird is now present on the bird feeder: " + birdFeeder1.birdStatus());

        BirdFeeder birdFeeder2 = BirdFeeder.getInstance();
        System.out.println("The bird state is reflected on the second bird feeder: " + birdFeeder2.birdStatus());

        birdFeeder2.takeFlashPhoto();
        System.out.println("A photo has been taken");

        System.out.println("The bird feeder object shows there are no birds: " + birdFeeder1.birdStatus() + " " +  birdFeeder2.birdStatus());
        System.out.println("The bird feeder object shows a single photo has been taken: " + birdFeeder1.photoStatus() + " " +  birdFeeder2.photoStatus());
}<jupyter_output><empty_output><jupyter_text>For most Java applications, it is important to ensure that the Singleton works in the presence of multiple threads. Example 1 may be at risk of creating multiple singleton objects if two threads access the class at the same time. This risk can be prevented by adding the synchronized keyword to the getInstance method so that we force every thread to wait its turn before it enters the method - and no two threads may enter the method at the same time (Example 2). However, synchronization can slow the application as it forces each thread to wait in turn. Furthermore, the synchronisation is only relevent the first time the method is accessed - this is because once the uniqueInstance variable is set to the uniqueInstance of Singleton, there is no longer any need to syncronize the method.<jupyter_code>//Example 2 - Synchronized 

public class BirdFeeder {
    private boolean empty;
    private boolean birdPresent;
    private int photoCount;
    private static BirdFeeder uniqueInstance;

    private BirdFeeder() {
        empty = true;
        birdPresent = false;
        photoCount = 0;
    }

//By adding the synchronized keyword to getInstance(), every thread must wait in turn to access the instance.
    public static synchronized BirdFeeder getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new BirdFeeder();
        }
        return uniqueInstance;
    }
//Note, I have ommited the other bird feeder methods, to reduce the size of the snippets.
}<jupyter_output><empty_output><jupyter_text>One possible way to circumvent performance issues is by creating the Singleton object eagerly (Example 3). This ensure that the JVM creates the unique instance of the Singleton when the class is loaded. The JVM ensures that the instance will be created before any thread accesses the static uniqueInstance variable. Therefore this will prevent multiple threads accessing the object. A downside of creating a Singleton object eagerly is that if it is resource intensive it may be consuming resources despite it not being utilised.<jupyter_code>// Example 3 - Eager Initialisation

public class BirdFeeder {
    private boolean empty;
    private boolean birdPresent;
    private int photoCount;
    
//Instance of the singleton is created in a static initializer
    private static BirdFeeder uniqueInstance = new BirdFeeder();
    
    private BirdFeeder() {
        empty = true;
        birdPresent = false;
        photoCount = 0;
    }
    
//Here we just return the instance
    public static BirdFeeder getInstance() {
        return uniqueInstance;
    }
//Note, I have ommited the other bird feeder methods, to reduce the size of the snippets.
}<jupyter_output><empty_output><jupyter_text>Another way to prevent threads from crossing is to use the ‘double-checked locking’ method. The ‘double-checked locking’ method first checks to see if an instance has been created, and then, if one hasn’t we synchronize. This ensures syncrhonization only occurs in the first run through. This method is arguably the best implementation of the four examples as it only consumes resources when instantiated and enables the singleton to be accessed asyncronously once it has been safely instantiated.<jupyter_code>// Example 4 - Double-Checked Locking

public class BirdFeeder {
    private boolean empty;
    private boolean birdPresent;
    private int photoCount;

//Volatile ensures that multiple threads handle the uniqueInstance correctly when it is being intialized to the singleton instance.
    private volatile static BirdFeeder uniqueInstance;

    private BirdFeeder() {
        empty = true;
        birdPresent = false;
        photoCount = 0;
    }

//Here we are only syncrhonizing on the first run through
    public static BirdFeeder getInstance() {
        if (uniqueInstance == null) {
            synchronized (BirdFeeder.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new BirdFeeder();
                }
            }
        }
        return uniqueInstance;
    }
//Note, I have omitted the other bird feeder methods, to reduce the size of the snippets.
}<jupyter_output><empty_output><jupyter_text>Finally, I would like to demonstrate how Singleton objects are defined in Scala (Example 5). In Scala a singleton object can be defined simply by using the object keyword. Just like static methods in the Java class, methods are called directly on the singleton object. In the example below, the Blood class uses the singleton Blood object to log blood pressure.<jupyter_code>//Example 5 - Scala

object Logger {
  def info(pressure: Int): Unit = println(s"OK Diastolic blood pressure: $pressure")
  def warn(pressure: Int): Unit = println(s"Warning High Diastolic blood pressure: $pressure")
  def error(pressure: Int): Unit = println(s"Act now Diastolic blood pressure: $pressure")
}

class Blood(diastolic: Int) {
  def diastolicHealth {
    diastolic match {
      case _ < 140 => info(diastolic)
      case _ >141 <179 => warn(diastolic)
      case _ >180 => error(diastolic)
    }
  }
}<jupyter_output><empty_output><jupyter_text>EvaluationAs demonstrated in the code examples above, the Singleton design pattern can be useful when wanting to use an object to maintain a global state. An advantage of Singleton over simply using global variables is that the singleton object class can guarantee only one object is instantiated. Furthermore, singleton objects do not take up space in the global namespace and can be initialized lazily to precent unecessary resource consumption (Freeman and Robson, 2021). Singleton objects are also superior to holding state within static classes, as singletons are more flexible as they can be passed as parameters or implement interfaces (Hevery, 2008).Despite it's advantages, the Singleton design pattern has been heavily criticised due to the fact it breaks object orientated design principles. Thus it is often described as an anti-pattern. A singleton class cannot be inheritted from, thus breaking the seperation of concern and making the code inflexible. Furthermore, the Singleton pattern prevents dependency injection because there is only a single instance of the class, a dependency cannot be injected into it (Freeman and Robson, 2021). Finally, the Singleton pattern is also arguably difficult to test. This is because Singletons break the Single Responsibility Principle, therefore when testing two classes, one class is implementing the Singlton and the other is not. The developers must pass in the Singleton as a parameter to the constructor, to enable the tester to mock out the Singleton class easily (Hevery, 2008).Thus, one must be careful to ensure a use case is suitable for employeeing the Singleton pattern and ensure to code against multi-threading scenarios. Perhaps, the best case of Singletons is for objects which don't effect the execution of code. For instance, the logging object works well as a Singleton object as data flows onse single way: from the application into the logger (Hevery, 2008).  References<NAME>. and <NAME>., 2021. Head First Design Patterns. Sebastopol: O'Reilly Media, Incorporated.Gofpatterns.com. 2021. Intent and Motivation for Singleton Design Patterns. [online] Available at:  [Accessed 30 April 2021].<NAME>., 2008. Root Cause of Singletons. [online] Misko.hevery.com. Available at:  [Accessed 14 May 2021].<NAME>., 2008. Singletons are Pathological Liars. [online] Misko.hevery.com. Available at:  [Accessed 14 May 2021].  Logbook Excercise 16Chosen Design pattern: The Decorator Pattern  IntentThe Decorator Pattern is a design pattern that allows behavior to be added to an individual object, without affecting the behavior of other objects from the same class. The decorator pattern enables an objects behaviours and responsinilities to be extended through composition, which in turn sets these attributes dynamically at runtime (Freeman and Robson, 2021). The Decorator is a structural pattern, because it is used to create large object structures across many disparate objects (Intent and Motivation for Singleton Design Patterns, 2021).Decorators are often described as wrappers. They have the same supertype as the objects they decorate. As a result,  we can pass around a decorated object in place of the original wrapped object. Thus new behaviour is added through composition without affecting the superclass. Decorators provide a flexible alternative to subclassing for extending functionality. Furthermore, they adhere to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern (Freeman and Robson, 2021).  MotivationThe decorator pattern attatches additional responsibilities to an object dynamically. Objects can be decorated at any time so we can decorate objects at runtime with as many decorators as you like. The decorator adds its own behaviour either before and/or after delegating to the object it decorates to add to the original functionality.A benefit of the decorator pattern is that functionality is added by writing new code rather than modifying existing code. This means less change of causing unintended side effects in existing code.A real world example of the decorator pattern is seen in the Java i.o class.InputStream, as described in the table below.|Decorator|Role|Usage||:---------|:--------------------|:-------||LineNumberInputStream|Concrete Decorator|Adds the ability to count the line numbers as it reads data.||BufferedInputStream|Concrete Decorator|Adds behaviour in two different ways: it buffers input to improve performance, and also augments the interface with a new method readLine() for reading character-based input, a line at a time.||FileInputStream|Decorated Component| The Java I/O library supplies several component including FileInputStream, StringBufferedInputStream, ByteArrayStream and a few others. All of these five us a base component to read bytes.|  Structure![decorator_pattern.png](attachment:decorator_pattern.png)Source: https://en.wikipedia.org/wiki/Decorator_pattern  Implementation and Sample CodeIn order to use the decorator pattern, a base decorator class must be created. The base decorated class should have a field for storing a reference to a wrapped object. Furthermore, the field should be declared with the component interface type to enable linking to concrete components as well as decorators. The base decorator delegates all work to the wrapped object. Concrete decorators are created by extending the base decorator. A concrete decorator must execute its behavior before or after the call to the parent method (which always delegates to the wrapped object).As a result, the decorated object can take the place of the original wrapped object. This results in composed behaviours.In the example below, I have created two abstract classes: Smoothie (the component) and ToppingsDecorator (the deocorator). ToppingsDecorator extends Smoothie to include additional functionality. The combination of these functionalities are outputted below to demonstrate different Smoothies with behaviours composed from different objects.<jupyter_code>public abstract class Smoothie { //The abstract component
    String description = "Unknown Smoothie";

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}


public class Strawberry extends Smoothie { //A concrete component
    public Strawberry() {
        description = "Strawberry";
    }

    public double cost() {
        return 2.99;
    }
}

public class Chocolate extends Smoothie {  //Another concrete component
    public Chocolate() {
        description = "Chocolate";
    }

    public double cost() {
        return 3.99;
    }
}

public abstract class ToppingDecorator extends Smoothie { //The decorator
    public abstract String getDescription();
}

public class HoneyComb extends ToppingDecorator { //Concrete decorator
    Smoothie smoothie;

    public HoneyComb(Smoothie smoothie) {
        this.smoothie = smoothie;
    }

    public String getDescription() {
        return smoothie.getDescription() + ", HoneyComb";
    }

    public double cost() {
        return 0.79 + smoothie.cost();
    }
}

public class Sprinkles extends ToppingDecorator { //Another concrete decorator
    Smoothie smoothie;

    public Sprinkles(Smoothie smoothie) {
        this.smoothie = smoothie;
    }

    public String getDescription() {
        return smoothie.getDescription() + ", Sprinkles";
    }

    public double cost() {
        return 0.99 + smoothie.cost();
    }
}

        Smoothie strawberrySmoothie = new Strawberry();
        System.out.println("Reciept: " + strawberrySmoothie.getDescription() + " " + strawberrySmoothie.cost());

        Smoothie chocolateSmoothie = new Chocolate();
        System.out.println("Reciept: " + chocolateSmoothie.getDescription() + " " + chocolateSmoothie.cost());

        HoneyComb honeyCombChoc = new HoneyComb(chocolateSmoothie); //Composing a topping and a smoothie flavour
        System.out.println("Reciept: " + honeyCombChoc.getDescription() + " " + honeyCombChoc.cost());

        Sprinkles sprinkleStrawb = new Sprinkles(strawberrySmoothie); 
        HoneyComb honeyCombSprinkleStrawb = new HoneyComb(sprinkleStrawb); //Composing 2 toppings and a smoothie flavour
        System.out.println("Reciept: " + honeyCombSprinkleStrawb.getDescription() + " " + honeyCombSprinkleStrawb.cost());
}<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09036419883287633, 'fraction_numerical': 0.014782394712789144, 'mean_word_length': 3.6906587517519993, 'pattern_counts': {'":': 12, '<': 119, '<?xml version=': 0, '>': 119, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '21', 'dir': 'jupyter-structured-clean-dedup', 'id': '161141', 'license': 'Apache-2.0', 'n_tokens_mistral': 16905, 'n_tokens_neox': 15826, 'n_words': 6280, 'path': 'notebooks/DP_ 21918141.ipynb', 'repo_name': 'jasminmartin/scijava-jupyter-kernel', 'size': '57040'}