Elegance with Design Patterns - Python Unlocked (2015)

Python Unlocked (2015)

Chapter 5. Elegance with Design Patterns

In this chapter, we are going to learn some design patterns that will help us in writing better software, which is reusable and tidy. But, the biggest help is that they let developers think on an architectural level. They are solutions to recurring problems. While learning them is very helpful for compiled languages such as C and C++ because they are actually solutions to problems, in Python, developers often "just write code" without needing any design pattern due to the dynamism in the language and conciseness of code. This is largely true for developers whose first language is Python. My advice is to learn design patterns to be able to process information and design at an architectural level rather than function and classes.

In this chapter, we will cover the following topics:

· Observer pattern

· Strategy pattern

· Singleton pattern

· Template pattern

· Adaptor pattern

· Facade pattern

· Flyweight pattern

· Command pattern

· Abstract factory

· Registry pattern

· State pattern

Observer pattern

Key 1: Spreading information to all listeners.

This is the basic pattern in which an object tells other objects about something interesting. It is very useful in GUI applications, pub/sub applications, and those applications where we need to notify a lot of loosely-coupled application components about a change occurring at one source node. In the following code, Subject is the object to which other objects register themselves for events via register_observer. The observer objects are the listening objects. The observers start observing the function that registers the observersobject to Subject object. Whenever there is an event to Subject it cascades the event to all observers:

import weakref

class Subject(object):

"""Provider of notifications to other objects

"""

def __init__(self, name):

self.name = name

self._observers = weakref.WeakSet()

def register_observer(self, observer):

"""attach the observing object for this subject

"""

self._observers.add(observer)

print("observer {0} now listening on {1}".format(

observer.name, self.name))

def notify_observers(self, msg):

"""transmit event to all interested observers

"""

print("subject notifying observers about {}".format(msg,))

for observer in self._observers:

observer.notify(self, msg)

class Observer(object):

def __init__(self, name):

self.name = name

def start_observing(self, subject):

"""register for getting event for a subject

"""

subject.register_observer(self)

def notify(self, subject, msg):

"""notify all observers

"""

print("{0} got msg from {1} that {2}".format(

self.name, subject.name, msg))

class_homework = Subject("class homework")

student1 = Observer("student 1")

student2 = Observer("student 2")

student1.start_observing(class_homework)

student2.start_observing(class_homework)

class_homework.notify_observers("result is out")

del student2

class_homework.notify_observers("20/20 passed this sem")

The output for the preceding code is as follows:

(tag)[ ch5 ] $ python codes/B04885_05_code_01.py

observer student 1 now listening on class homework

observer student 2 now listening on class homework

subject notifying observers about result is out

student 1 got msg from class homework that result is out

student 2 got msg from class homework that result is out

subject notifying observers about 20/20 passed this sem

student 1 got msg from class homework that 20/20 passed this sem

Strategy pattern

Key 2: Changing the behavior of an algorithm.

Sometimes, the same piece of code must have different behavior for different invocation by different clients. For example, time-conversion for all countries must handle daylight-savings time in some countries and change their strategy in these cases. The main use is to switch the implementation. In this pattern, algorithm's behavior is selected on runtime. As Python is a dynamic language, it is trivial to assign functions to variables and change them on runtime. Similar to the following code segment, there are two implementations to calculate tax, namely, tax_simple, and tax_actual. For the following code snippet, tax_cal references clients that are used. The implementation can be changed by changing reference to the implementing function:

TAX_PERCENT = .12

def tax_simple(billamount):

return billamount * TAX_PERCENT

def tax_actual(billamount):

if billamount < 500:

return billamount * (TAX_PERCENT//2)

else:

return billamount * TAX_PERCENT

tax_cal = tax_simple

print(tax_cal(400),tax_cal(700))

tax_cal = tax_actual

print(tax_cal(400),tax_cal(700))

The output of the preceding code snippet is as follows:

48.0 84.0

0.0 84.0

But the issue with the preceding implementation is that at one time all clients will see the same strategy for tax calculation. We can improve this using a class that selects the implementation based on request parameters. In the following example, in theTaxCalculator class's instance, the strategy is determined for each call to it on runtime. If the request is for India IN, Tax is calculated as per the Indian standard, and if request is for US, it is calculated as per the US standard:

TAX_PERCENT = .12

class TaxIN(object):

def __init__(self,):

self.country_code = "IN"

def __call__(self, billamount):

return billamount * TAX_PERCENT

class TaxUS(object):

def __init__(self,):

self.country_code = "US"

def __call__(self,billamount):

if billamount < 500:

return billamount * (TAX_PERCENT//2)

else:

return billamount * TAX_PERCENT

class TaxCalculator(object):

def __init__(self):

self._impls = [TaxIN(),TaxUS()]

def __call__(self, country, billamount):

"""select the strategy based on country parameter

"""

for impl in self._impls:

if impl.country_code == country:

return impl(billamount)

else:

return None

tax_cal = TaxCalculator()

print(tax_cal("IN", 400), tax_cal("IN", 700))

print(tax_cal("US", 400), tax_cal("US", 700))

The output of the preceding code is as follows:

48.0 84.0

0.0 84.0

Singleton pattern

Key 3: Providing the same view to all.

The singleton pattern maintains the same state for all instances of a class. When we change an attribute at one place in a program, it is reflected in all references to this instance. As modules are globally shared, we can use them as singleton methods, and the variables defined in them are the same everywhere. But, there are similar issues in that as the module is reloaded, there may be more singleton classes that are needed. We can also create a singleton pattern using metaclasses in the following manner. The six is a third-party library to help in writing the same code that is runnable on Python 2 and Python 3.

In the following code, Singleton metaclass has a registry dictionary where the instance corresponding to each new class is stored. When any class asks for a new instance, this class is searched for in the registry, and if found, the old instance is passed. Otherwise, a new instance is created, stored in registry, and returned. This can be seen in the following code:

from six import with_metaclass

class Singleton(type):

_registry = {}

def __call__(cls, *args, **kwargs):

print(cls, args, kwargs)

if cls not in Singleton._registry:

Singleton._registry[cls] = type.__call__(cls, *args, **kwargs)

return Singleton._registry[cls]

class Me(with_metaclass(Singleton, object)):

def __init__(self, data):

print("init ran", data)

self.data = data

m = Me(2)

n = Me(3)

print(m.data, n.data)

The following is the output of the preceding code:

<class '__main__.Me'> (2,) {}

init ran 2

<class '__main__.Me'> (3,) {}

2 2

Template pattern

Key 4: Refining algorithm to use case.

In this pattern, we define the skeleton of an algorithm in a method called the template method, which defers some of its steps to subclasses. How we do this is as follows, we analyze the procedure, and break it down to logical steps, which are different for different use cases. Now, we may or may not implement the default implementation of these steps in the main class. The subclasses of the main class will implement the steps that are not implemented in the main class, and they may skip some generic steps implementation. In the following example, AlooDish is base class with the cook template method. It applies to normal Aloo fried dishes, which have a common cooking procedure. Each recipe is a bit different in ingredients, time to cook, and so on. Two variants,AlooMatar, and AlooPyaz, define some set of steps differently than others:

import six

class AlooDish(object):

def get_ingredients(self,):

self.ingredients = {}

def prepare_vegetables(self,):

for item in six.iteritems(self.ingredients):

print("take {0} {1} and cut into smaller pieces".format(item[0],item[1]))

print("cut all vegetables in small pieces")

def fry(self,):

print("fry for 5 minutes")

def serve(self,):

print("Dish is ready to be served")

def cook(self,):

self.get_ingredients()

self.prepare_vegetables()

self.fry()

self.serve()

class AlooMatar(AlooDish):

def get_ingredients(self,):

self.ingredients = {'aloo':"1 Kg",'matar':"1/2 kg"}

def fry(self,):

print("wait 10 min")

class AlooPyaz(AlooDish):

def get_ingredients(self):

self.ingredients = {'aloo':"1 Kg",'pyaz':"1/2 kg"}

aloomatar = AlooMatar()

aloopyaz = AlooPyaz()

print("******************* aloomatar cook")

aloomatar.cook()

print("******************* aloopyaz cook")

aloopyaz.cook()

The following is the output of the preceding example code:

******************* aloomatar cook

take matar 1/2 kg and cut into smaller pieces

take aloo 1 Kg and cut into smaller pieces

cut all vegetables in small pieces

wait 10 min

Dish is ready to be served

******************* aloopyaz cook

take pyaz 1/2 kg and cut into smaller pieces

take aloo 1 Kg and cut into smaller pieces

cut all vegetables in small pieces

fry for 5 minutes

Dish is ready to be served

Adaptor pattern

Key 5: Bridging class interfaces.

This pattern is used to adapt a given class to a new interface. It solves the problem for an interface mismatch. To demonstrate this, let's assume that we have an API function that creates a competition to run different animals. Animals should have a running_speedfunction, which tells their speed to compare them. Cat is one such class. Now, if we have a Fish class in a different library, which also wants to participate in the function, it must be able to know its running_speed function. As changing the implementation of Fish is not good option, we can create an adaptor class that can adapt the Fish class to run by providing the necessary bridge:

def running_competition(*list_of_animals):

if len(list_of_animals)<1:

print("No one Running")

return

fastest_animal = list_of_animals[0]

maxspeed = fastest_animal.running_speed()

for animal in list_of_animals[1:]:

runspeed = animal.running_speed()

if runspeed > maxspeed:

fastest_animal = animal

maxspeed = runspeed

print("winner is {0} with {1} Km/h".format(fastest_animal.name,maxspeed))

class Cat(object):

def __init__(self, name, legs):

self.name = name

self.legs = legs

def running_speed(self,):

if self.legs>4 :

return 20

else:

return 40

running_competition(Cat('cat_a',4),Cat('cat_b',3))

class Fish(object):

def __init__(self, name, age):

self.name = name

self.age = age

def swim_speed(self):

if self.age < 2:

return 40

else:

return 60

# to let our fish to participate in tournament it should have similar interface as

# cat, we can also do this by using an adaptor class RunningFish

class RunningFish(object):

def __init__(self, fish):

self.legs = 4 # dummy

self.fish = fish

def running_speed(self):

return self.fish.swim_speed()

def __getattr__(self, attr):

return getattr(self.fish,attr)

running_competition(Cat('cat_a',4),

Cat('cat_b',3),

RunningFish(Fish('nemo',3)),

RunningFish(Fish('dollar',1)))

The output of the preceding code is follows:

winner is cat_a with 40 Km/h

winner is nemo with 60 Km/h

Facade pattern

Key 6: Hiding system complexity for a simpler interface.

In this pattern, a main class called facade exports a simpler interface to client classes and encapsulates the complexity of interaction with many other classes of the system. It is like a gateway to a complex set of functionality, such as in the following example, theWalkingDrone class hides the complexity of synchronization of the Leg classes and provides a simpler interface to client classes:

class Leg(object):

def __init__(self,name):

self.name = name

def forward(self):

print("{0},".format(self.name), end="")

class WalkingDrone(object):

def __init__(self, name):

self.name = name

self.frontrightleg = Leg('Front Right Leg')

self.frontleftleg = Leg('Front Left Leg')

self.backrightleg = Leg('Back Right Leg')

self.backleftleg = Leg('Back Left Leg')

def walk(self):

print("\nmoving ",end="")

self.frontrightleg.forward()

self.backleftleg.forward()

print("\nmoving ",end="")

self.frontleftleg.forward()

self.backrightleg.forward()

def run(self):

print("\nmoving ",end="")

self.frontrightleg.forward()

self.frontleftleg.forward()

print("\nmoving ",end="")

self.backrightleg.forward()

self.backleftleg.forward()

wd = WalkingDrone("RoboDrone" )

print("\nwalking")

wd.walk()

print("\nrunning")

wd.run()

This code will give us the following output:

walking

moving Front Right Leg,Back Left Leg,

moving Front Left Leg,Back Right Leg,

running

moving Front Right Leg,Front Left Leg,

moving Back Right Leg,Back Left Leg,Summary

Flyweight pattern

Key 7: Consuming less memory with shared objects.

A flyweight design pattern is useful to save memory. When we have lots of object count, we store references to previous similar objects and provide them instead of creating new objects. In the following example, we have a Link class used by the browser, which stores the link data.

The browser uses this data, and there may be a lot of data that is associated with pictures referenced by the link, such as image content, size, and so on, and images can be reused over the page. Hence, the nodes using it only store a flyweight BrowserImage object to decrease the memory footprint. When the link class tries to create a new BrowserImage instance, the BrowserImage class checks whether it has an instance in its _resources mapping for the resource path. If it does, it will just pass the old instance:

import weakref

class Link(object):

def __init__(self, ref, text, image_path=None):

self.ref = ref

if image_path:

self.image = BrowserImage(image_path)

else:

self.image = None

self.text = text

def __str__(self):

if not self.image:

return "<Link (%s)>" % self.text

else:

return "<Link (%s,%s)>" % (self.text, str(self.image))

class BrowserImage(object):

_resources = weakref.WeakValueDictionary()

def __new__(cls, location):

image = BrowserImage._resources.get(location, None)

if not image:

image = object.__new__(cls)

BrowserImage._resources[location] = image

image.__init(location)

return image

def __init(self, location):

self.location = location

# self.content = load picture into memory

def __str__(self,):

return "<BrowserImage(%s)>" % self.location

icon = Link("www.pythonunlocked.com",

"python unlocked book",

"http://pythonunlocked.com/media/logo.png")

footer_icon = Link("www.pythonunlocked.com/#bottom",

"unlocked series python book",

"http://pythonunlocked.com/media/logo.png")

twitter_top_header_icon = Link("www.twitter.com/pythonunlocked",

"python unlocked twitter link",

"http://pythonunlocked.com/media/logo.png")

print(icon,)

print(footer_icon,)

print(twitter_top_header_icon,)

The output of the preceding code is follows:

<Link (python unlocked book,<BrowserImage(http://pythonunlocked.com/media/logo.png)>)>

<Link (unlocked series python book,<BrowserImage(http://pythonunlocked.com/media/logo.png)>)>

<Link (python unlocked twitter link,<BrowserImage(http://pythonunlocked.com/media/logo.png)>)>

Command pattern

Key 8: Easy-execution management for commands.

In this pattern, we encapsulate information that is needed to execute a command in an object so that command itself can have further capabilities, such as undo, cancel, and metadata that are needed at a later point of time. For example, let's create a simple Chef in a restaurant, users can issue orders (commands), commands here have metadata that are needed to cancel them. This is similar to a notepad app where each user action is recorded with an undo method. This makes coupling loose between caller and the invoker, shown as follows:

import time

import threading

class Chef(threading.Thread):

def __init__(self,name):

self.q = []

self.doneq = []

self.do_orders = True

threading.Thread.__init__(self,)

self.name = name

self.start()

def makeorder(self, order):

print("%s Preparing Menu :"%self.name )

for item in order.items:

print("cooking ",item)

time.sleep(1)

order.completed = True

self.doneq.append(order)

def run(self,):

while self.do_orders:

if len(self.q) > 0:

order = self.q.pop(0)

self.makeorder(order)

time.sleep(1)

def work_on_order(self,order):

self.q.append(order)

def cancel(self, order):

if order in self.q:

if order.completed == True:

print("cannot cancel, order completed")

return

else:

index = self.q.index(order)

del self.q[index]

print(" order canceled %s"%str(order))

return

if order in self.doneq:

print("order completed, cannot be canceled")

return

print("Order not given to me")

class Check(object):

def execute(self,):

raise NotImplementedError()

def cancel(self,):

raise NotImplementedError()

class MenuOrder(Check):

def __init__(self,*items):

self.items = items

self.completed = False

def execute(self,chef):

self.chef = chef

chef.work_on_order(self)

def cancel(self,):

if self.chef.cancel(self):

print("order cancelled")

def __str__(self,):

return ''.join(self.items)

c = Chef("Arun")

order1 = MenuOrder("Omellette", "Dosa", "Idli")

order2 = MenuOrder("Mohito", "Pizza")

order3 = MenuOrder("Rajma", )

order1.execute(c)

order2.execute(c)

order3.execute(c)

time.sleep(1)

order3.cancel()

time.sleep(9)

c.do_orders = False

c.join()

The output of the preceding code is as follows:

Arun Preparing Menu :

cooking Omellette

order canceled Rajma

cooking Dosa

cooking Idli

Arun Preparing Menu :

cooking Mohito

cooking Pizza

Abstract factory

This design pattern creates an interface to create a family of interrelated objects without specifying their concrete class. It is similar to a superfactory. Its advantage is that we can add further variants, and clients will not have to worry further about the interface or actual classes for the new variants. It is helpful in supporting various platforms, windowing systems, data types, and so on. In the following example, the Animal class is the interface that the client will know about for any animal instance. AnimalFactory is the abstract factory that DogFactory and CatFactory implement. Now, on the runtime by user input, or configuration file, or runtime environment check, we can decide whether we will have all Dog or Cat instances. It is very convenient to add a new class implementation, as follows:

import os

import abc

import six

class Animal(six.with_metaclass(abc.ABCMeta, object)):

""" clients only need to know this interface for animals"""

@abc.abstractmethod

def sound(self, ):

pass

class AnimalFactory(six.with_metaclass(abc.ABCMeta, object)):

"""clients only need to know this interface for creating animals"""

@abc.abstractmethod

def create_animal(self,name):

pass

class Dog(Animal):

def __init__(self, name):

self.name = name

def sound(self, ):

print("bark bark")

class DogFactory(AnimalFactory):

def create_animal(self,name):

return Dog(name)

class Cat(Animal):

def __init__(self, name):

self.name = name

def sound(self, ):

print("meow meow")

class CatFactory(AnimalFactory):

def create_animal(self,name):

return Cat(name)

class Animals(object):

def __init__(self,factory):

self.factory = factory

def create_animal(self, name):

return self.factory.create_animal(name)

if __name__ == '__main__':

atype = input("what animal (cat/dog) ?").lower()

if atype == 'cat':

animals = Animals(CatFactory())

elif atype == 'dog':

animals = Animals(DogFactory())

a = animals.create_animal('bulli')

a.sound()

The preceding code will give us the following output:

1st run:

what animal (cat/dog) ?dog

bark bark

2nd run:

what animal (cat/dog) ?cat

meow meow

Registry pattern

Key 9: Adding functionality from anywhere in code to class.

This is one of my favorite patterns and comes to help a lot. In this pattern, we register classes to a registry, which tracks the naming to functionality. Hence, we can add functionality to the main class from anywhere in the code. In the following code, Convertor tracks all convertors from dictionary to Python objects. We can easily add further functionalities to the system using the convertor.register decorator from anywhere in the code, as follows:

class ConvertError(Exception):

"""Error raised on errors on conversion"""

pass

class Convertor(object):

def __init__(self,):

"""create registry for storing method mapping """

self.__registry = {}

def to_object(self, data_dict):

"""convert to python object based on type of dictionary"""

dtype = data_dict.get('type', None)

if not dtype:

raise ConvertError("cannot create object, type not defined")

elif dtype not in self.__registry:

raise ConvertError("cannot convert type not registered")

else:

convertor = self.__registry[dtype]

return convertor.to_python(data_dict['data'])

def register(self, convertor):

iconvertor = convertor()

self.__registry[iconvertor.dtype] = iconvertor

convertor = Convertor()

class Person():

""" a class in application """

def __init__(self, name, age):

self.name = name

self.age = age

def __str__(self,):

return "<Person (%s, %s)>" % (self.name, self.age)

@convertor.register

class PersonConvertor(object):

def __init__(self,):

self.dtype = 'person'

def to_python(self, data):

# not checking for errors in dictionary to instance creation

p = Person(data['name'], data['age'])

return p

print(convertor.to_object(

{'type': 'person', 'data': {'name': 'arun', 'age': 12}}))

The following is the output for the preceding code:

<Person (arun, 12)>

State pattern

Key 10: Changing execution based on state.

State machines are very useful for an algorithm whose vector-flow of control depends on the state of the application. Similar to when parsing a log output with sections, you may want to change the parser logic on every next section. It is also very useful to write code for network servers/clients who enable certain commands in a certain scope:

def outputparser(loglines):

state = 'header'

program,end_time,send_failure= None,None,False

for line in loglines:

if state == 'header':

program = line.split(',')[0]

state = 'body'

elif state == 'body':

if 'send_failure' in line:

send_failure = True

if '======' in line:

state = 'footer'

elif state == 'footer':

end_time = line.split(',')[0]

return program, end_time, send_failure

print(outputparser(['sampleapp,only a sampleapp',

'logline1 sadfsfdf',

'logline2 send_failure',

'=====================',

'30th Jul 2016,END']))

This will give us the following output:

('sampleapp', '30th Jul 2016', True)

Summary

In this chapter, we saw various design patterns that can help us better organize the code, and in some cases, increase performance. The good thing about patterns is they let you think beyond classes, and they provide strategy for architecture of your application. As closing advice for this chapter, do not code to use design pattern; when you code and see a good fit, only then use design pattern.

Now, we will go onto testing, which is a must for any serious development effort.