Behavioral Patterns

Chain of Responsibility

The Chain of Responsibility Pattern is a squence of handlers processing an event one after another or can abort the chain. Think of a car MOT's you go through the checks and if one fails then you can abort, if not then the chain is complete. The chain can be implemented as a chain of references or a centralized construct (see second example).

Chain of Responsibility example
# Chain of Responsibility example
class Event:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return self.name


class Widget:
    def __init__(self, parent=None):

        self.parent = parent

    def handle(self, event):
        handler = 'handle_{}'.format(event)             # the handlers, for example handle_close, handle_default, etc
        if hasattr(self, handler):
            method = getattr(self, handler)
            method(event)
        elif self.parent:
            self.parent.handle(event)
        elif hasattr(self, 'handle_default'):
            self.handle_default(event)


class MainWindow(Widget):
    def handle_close(self, event):
        print('MainWindow: {}'.format(event))

    def handle_default(self, event):
        print('MainWindow Default: {}'.format(event))


class SendDialog(Widget):
    def handle_paint(self, event):
        print('SendDialog: {}'.format(event))


class MsgText(Widget):
    def handle_down(self, event):
        print('MsgText: {}'.format(event))


def main():
    mw = MainWindow()
    sd = SendDialog(mw)
    msg = MsgText(sd)

    for e in ('down', 'paint', 'unhandled', 'close'):
        evt = Event(e)
    print('\nSending event -{}- to MainWindow'.format(evt))
    mw.handle(evt)
    print('Sending event -{}- to SendDialog'.format(evt))
    sd.handle(evt)
    print('Sending event -{}- to MsgText'.format(evt))
    msg.handle(evt)


if __name__ == '__main__':
    main()

Command

The Command Pattern uses an Object that represents an operation (action/s) that can be rolled back, can also be used for callbacks. You encapsulate all the details of an operation in a separate object (which could be stored to disk) which you can use to rollback any changes. Recording of history and can be used in callbacks, thread queues/pools - decouples thread from the command object action, undoing actions, recording history, GUI buttons, etc.

Command example
# Command example
import os

verbose = True


class RenameFile:
    def __init__(self, path_src, path_dest):
        self.src, self.dest = path_src, path_dest

    def execute(self):
        if verbose:
            print("[renaming '{}' to '{}']".format(self.src, self.dest))
        os.rename(self.src, self.dest)

    def undo(self):
        if verbose:
            print("[renaming '{}' back to '{}']".format(self.dest, self.src))
        os.rename(self.dest, self.src)


class CreateFile:
    def __init__(self, path, txt='hello world\n'):
        self.path, self.txt = path, txt

    def execute(self):
        if verbose:
            print("[creating file '{}']".format(self.path))
        with open(self.path, mode='w', encoding='utf-8') as out_file:
            out_file.write(self.txt)

    def undo(self):
        delete_file(self.path)


class ReadFile:
    def __init__(self, path):
        self.path = path

    def execute(self):
        if verbose:
            print("[reading file '{}']".format(self.path))
        with open(self.path, mode='r', encoding='utf-8') as in_file:
            print(in_file.read(), end='')


def delete_file(path):
    if verbose:
        print("deleting file '{}".format(path))
    os.remove(path)


def main():
    orig_name, new_name = 'file1', 'file2'
    commands = []                               # commands list

    for cmd in CreateFile(orig_name), ReadFile(orig_name), RenameFile(orig_name, new_name):
        commands.append(cmd)

    [c.execute() for c in commands]             # original order of the commands

    answer = input('reverse the executed commands? [y/n] ')
    if answer not in 'yY':
        print("the result is {}".format(new_name))
        exit()

    for c in reversed(commands):                # reverse the order of the commands
        try:
            c.undo()
        except AttributeError as e:
            pass


if __name__ == "__main__":
    main()

Interpreter

The Intepreter Pattern uses textual input (strings) that need to be processed (turned into OOP structures), compilers, HTML, regular expressions, etc are all intepreters. There are two stages firstly separate lexical tokens (lexing) and then interpreting sequences of said tokens (parsing). A good example is the string "3 + 5 * 4 / 2" and turning this into code and calculating the result.

Intepreter example
# Interpreter example
class RomanNumeralInterpreter(object):
    def __init__(self):
        self.grammar = {
            'I': 1,
            'V': 5,
            'X': 10,
            'L': 50,
            'C': 100,
            'D': 500,
            'M': 1000
        }

    def interpret(self, text):
        numbers = list(map(self.grammar.get, text))
        if None in numbers:
            raise ValueError('Error value: %s' % text)
        result = 0
        temp = None
        while numbers:
            num = numbers.pop(0)
            if temp is None or temp >= num:
                result += num
            else:
                result += (num - temp * 2)
            temp = num
        return result


interp = RomanNumeralInterpreter()
print("Result should equal 3999: " + str(interp.interpret('MMMCMXCIX') == 3999))        # interpret the string
print("Result should equal 1988: " + str(interp.interpret('MCMLXXXVIII') == 1988))      # interpret the string

Observer

The Observer Pattern gets informed when certain things/events happen, it listens to events and notifies when they occur. In observer pattern, the object that watch on the state of another object are called Observer and the object that is being watched is called Subject. Examples of this pattern is magazine publishing, Facebook notifications, Software updates (any thing with publish and subscribe).

Observer example
# Observer example
class Subject:
    def __init__(self):
        self.__observers = []

    def register(self, observer):
        self.__observers.append(observer)

    # go through the list of observers calling their own notify method
    def notifyAll(self, *args, **kwargs):
        for observer in self.__observers:
            observer.notify(self, *args, **kwargs)


class Observer1:
    def __init__(self, subject):
        subject.register(self)

    def notify(self, subject, *args):
        print(type(self).__name__, ':: Got', args, 'From', subject)


class Observer2:
    def __init__(self, subject):
        subject.register(self)

    def notify(self, subject, *args):
        print(type(self).__name__, ':: Got', args, 'From', subject)


def main():
    subject = Subject()
    observer1 = Observer1(subject)  # register the observer1
    observer2 = Observer2(subject)  # register the observer2
    subject.notifyAll('notification')


if __name__ == "__main__":
    main()
Observer example
# Observer example
from abc import ABCMeta, abstractmethod


class Subscriber(metaclass=ABCMeta):
    @abstractmethod
    def update(self):
        pass


class NewsPublisher:
    def __init__(self):
        self.__subscribers = []
        self.__latestNews = None

    def attach(self, subscriber):
        self.__subscribers.append(subscriber)

    def detach(self):
        return self.__subscribers.pop()

    def subscribers(self):
        return [type(x).__name__ for x in self.__subscribers]

    def notifySubscribers(self):
        for sub in self.__subscribers:
            sub.update()

    def addNews(self, news):
        self.__latestNews = news

    def getNews(self):
        return "Got News:", self.__latestNews


class SMSSubscriber:
    def __init__(self, publisher):
        self.publisher = publisher
        self.publisher.attach(self)

    def update(self):
        print(type(self).__name__, self.publisher.getNews())


class EmailSubscriber:
    def __init__(self, publisher):
        self.publisher = publisher
        self.publisher.attach(self)

    def update(self):
        print(type(self).__name__, self.publisher.getNews())


class AnyOtherSubscriber:
    def __init__(self, publisher):
        self.publisher = publisher
        self.publisher.attach(self)

    def update(self):
        print(type(self).__name__, self.publisher.getNews())


if __name__ == '__main__':
    news_publisher = NewsPublisher()

    # Register the subscribers
    for Subscribers in [SMSSubscriber, EmailSubscriber, AnyOtherSubscriber]:
        Subscribers(news_publisher)

    print("\nSubscribers:", news_publisher.subscribers())
    news_publisher.addNews('Hello World!')
    news_publisher.notifySubscribers()
    print("\nDetached:", type(news_publisher.detach()).__name__)
    print("\nSubscribers:", news_publisher.subscribers())
    news_publisher.addNews('My second news!')
    news_publisher.notifySubscribers()

State

The State Pattern changes in state can be explicit or in response to event (Observer Pattern), depending on your state machine you go from one state to another (trigger something to tranform from one state to another state), a formalized construct which manages state and transitions is called state machine. Examples of this are Gumball Machine, Jukebox, Traffic lights, Kettle (on/off), Record Player, etc.

State example
# State example
from abc import abstractmethod, ABCMeta


class State(metaclass=ABCMeta):
    @abstractmethod
    def doThis(self):
        pass


class StartState(State):
    def doThis(self):
        print("TV Switching ON..")


class StopState(State):
    def doThis(self):
        print("TV Switching OFF..")


class TVContext(State):
    def __init__(self):
        self.state = "OFF"               # default state is OFF

    def getState(self):
        return self.state

    def setState(self, state):
        self.state = state

    def doThis(self):
        self.state.doThis()


def main():
    context = TVContext()
    print("Default State: " + str(context.getState()))

    # Start state
    start = StartState()
    context.setState(start)
    context.doThis()

    # Stop state
    stop = StopState()
    context.setState(stop)
    context.doThis()


if __name__ == "__main__":
    main()
State example
# State example
class ComputerState(object):
    name = "state"
    allowed = []

    def switch(self, state):
        if state.name in self.allowed:
            print('Current:',self,' => switched to new state',state.name)
            self.__class__ = state
        else:
            print('Current:',self,' => switching to',state.name,'not possible.')

    def __str__(self):
        return self.name


class Off(ComputerState):
    name = "off"
    allowed = ['on']


class On(ComputerState):
    name = "on"
    allowed = ['off','suspend','hibernate']


class Suspend(ComputerState):
    name = "suspend"
    allowed = ['on']


class Hibernate(ComputerState):
    name = "hibernate"
    allowed = ['on']


class Computer(object):
    def __init__(self, model='HP'):
        self.model = model
        self.state = Off()

    def change(self, state):
        self.state.switch(state)


if __name__ == "__main__":
    comp = Computer()

    # Switch on
    comp.change(On)

    # Switch off
    comp.change(Off)

    # Switch on again
    comp.change(On)

    # Suspend
    comp.change(Suspend)

    # Try to hibernate - cannot!
    comp.change(Hibernate)

    # switch on back
    comp.change(On)

    # Finally off
    comp.change(Off)

Strategy

The Strategy Pattern is used to change a class behavior or its algorithm can be changed at run time (dynamic or static), many algorithms can be decomposed into higher and lower level parts the high level parts can then be reused, examples are a gamer figure could walk, run or swim (we don't know until he/she does it), the output of some text (could be XML, JSON, HTML) again we don't know until the users tells what he/she wants during runtime.

Strategy example
# Strategy example
class ImageOpener(object):
    @staticmethod
    def open(filename):                         # will have to be implemented by all derived classes
        raise NotImplementedError()


class PNGImageOpener(ImageOpener):
    @staticmethod
    def open(filename):
        print('PNG: open with Paint')


class JPEGImageOpener(ImageOpener):
    @staticmethod
    def open(filename):
        print('JPG/JPEG: open with ImageViewer')


class SVGImageOpener(ImageOpener):
    @staticmethod
    def open(filename):
        print('SVG: open with Illustrator')


class UnknownImageOpener(ImageOpener):
    @staticmethod
    def open(filename):
        print("You don't have program for %s extension" % filename.split('.')[-1].upper())


class Image(object):
    @classmethod
    def open_file(cls, filename):
        ext = filename.split('.')[-1]
        if ext == 'png':                        # generally in a strategy you have a if/switch/case statement
            opener = PNGImageOpener
        elif ext in ('jpg', 'jpeg'):
            opener = JPEGImageOpener
        elif ext == 'svg':
            opener = SVGImageOpener
        else:
            opener = UnknownImageOpener
        byterange = opener.open(filename)
        return cls(byterange, filename)

    def __init__(self, byterange, filename):
        self._byterange = byterange
        self._filename = filename


def main():
    Image.open_file('picture.png')
    Image.open_file('picture.jpg')
    Image.open_file('picture.svg')
    Image.open_file('picture.raw')

if __name__ == "__main__":
    main()

Template Method

The Template Method Pattern provides high-level blueprints for an algorithm to be completed by inheritors, the template method does the same as the strategy pattern but uses inheritance, the overall algorithm makes use of abstract member, inheritors override the abstract members and parent template method invoked, examples are game structure template, computer base template (add cpu, add memory, etc).

The Template Method Pattern can be confused with the strategy class, Strategy pattern defines a family of algorithms and makes them interchangeable. Client code can use different algorithms since the algorithms are encapsulated. Template method defines the outline of an algorithm and lets subclasses part of the algorithm's implementation. So you can have different implementations of an algorithms steps but retain the algorithm's structure

Template Method example
# Template Method example
from abc import ABCMeta, abstractmethod


class AbstractClass(metaclass=ABCMeta):
    def __init__(self):
        pass

    @abstractmethod
    def operation1(self):
        pass

    @abstractmethod
    def operation2(self):
        pass

    def template_method(self):
        print("Defining the Algorithm. Operation1 follows Operation2")
        self.operation2()
        self.operation1()


class ConcreteClass(AbstractClass):
    def operation1(self):
        print("My Concrete Operation1")

    def operation2(self):
        print("Operation 2 remains same")


class Client:
    def main(self):
        self.concreate = ConcreteClass()
        self.concreate.template_method()


def main():
    client = Client()
    client.main()


if __name__ == "__main__":
    main()
Template Method example
# Template Method example
from abc import abstractmethod, ABCMeta


class Trip(metaclass=ABCMeta):
    @abstractmethod
    def setTransport(self):
        pass

    @abstractmethod
    def day1(self):
        pass

    @abstractmethod
    def day2(self):
        pass

    @abstractmethod
    def day3(self):
        pass

    @abstractmethod
    def returnHome(self):
        pass

    def itinerary(self):
        self.setTransport()
        self.day1()
        self.day2()
        self.day3()
        self.returnHome()


class VeniceTrip(Trip):
    def setTransport(self):
        print("Take a boat and find your way in the Grand Canal")

    def day1(self):
        print("Visit St Mark's Basilica in St Mark's Square")

    def day2(self):
        print("Appreciate Doge's Palace")

    def day3(self):
        print("Enjoy the food near the Rialto Bridge")

    def returnHome(self):
        print("Get souvenirs for friends and get back")


class MaldivesTrip(Trip):
    def setTransport(self):
        print("On foot, on any island, Wow!")

    def day1(self):
        print("Enjoy the marine life of Banana Reef")

    def day2(self):
        print("Go for the water sports and snorkelling")

    def day3(self):
        print("Relax on the beach and enjoy the sun")

    def returnHome(self):
        print("Dont feel like leaving the beach..")


class TravelAgency:
    def arrange_trip(self):
        choice = input("What kind of place you'd like to go historical or to a beach?")

        if choice == 'historical':
            self.trip = VeniceTrip()
            self.trip.itinerary()
        elif choice == 'beach':
            self.trip = MaldivesTrip()
            self.trip.itinerary()


def main():
    TravelAgency().arrange_trip()


if __name__ == "__main__":
    main()