Structural Patterns

Adapter

The Adapter is about getting the interface you want from the interface that you were given, in real world terms think of a plug adapter that you use in different countries. You create a component which aggregates (has a reference to...) the adaptee.

Adapter example
# Adapter Pattern
class Synthesizer:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return 'the {} synthesizer'.format(self.name)

    def play(self):
        return 'is playing an electronic song'


class Human:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return '{} the human'.format(self.name)

    def speak(self):
        return 'says hello'


class Computer:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return 'the {} computer'.format(self.name)

    def execute(self):
        return 'executes a program'


class Adapter:
    def __init__(self, obj, adapted_methods):
        self.obj = obj
        self.__dict__.update(adapted_methods)

    def __str__(self):
        return str(self.obj)


def main():
    objects = [Computer('Asus')]
    synth = Synthesizer('moog')
    objects.append(Adapter(synth, dict(execute=synth.play)))
    human = Human('Bob')
    objects.append(Adapter(human, dict(execute=human.speak)))

    for i in objects:
        print('{} {}'.format(str(i), i.execute()))


if __name__ == "__main__":
    main()

Decorator

The Decorator Pattern adds behavior without altering the class itself, so you want to add additional functionality to an existing class but don't want to amend the existing class, thus breaking the Open-Closed Princple (OCP), you many want to keep the new functionality separate (SRP). To summarize it facilitates the addition of behaviors to individual objects with inheriting from them. You have two options:

Decorator example
# Classic Decorator Pattern
import six
from abc import ABCMeta


@six.add_metaclass(ABCMeta)
class Abstract_Coffee(object):

    def get_cost(self):
        pass

    def get_ingredients(self):
        pass

    def get_tax(self):
        return 0.1 * self.get_cost()


class Concrete_Coffee(Abstract_Coffee):

    def get_cost(self):
        return 1.00

    def get_ingredients(self):
        return 'coffee'


@six.add_metaclass(ABCMeta)
class Abstract_Coffee_Decorator(Abstract_Coffee):

    def __init__(self, decorated_coffee):
        self.decorated_coffee = decorated_coffee

    def get_cost(self):
        return self.decorated_coffee.get_cost()

    def get_ingredients(self):
        return self.decorated_coffee.get_ingredients()


class Sugar(Abstract_Coffee_Decorator):

    def __init__(self, decorated_coffee):
        Abstract_Coffee_Decorator.__init__(self, decorated_coffee)

    def get_cost(self):
        return self.decorated_coffee.get_cost()

    def get_ingredients(self):
        return self.decorated_coffee.get_ingredients() + ', sugar'


class Milk(Abstract_Coffee_Decorator):

    def __init__(self, decorated_coffee):
        Abstract_Coffee_Decorator.__init__(self, decorated_coffee)

    def get_cost(self):
        return self.decorated_coffee.get_cost() + 0.25

    def get_ingredients(self):
        return self.decorated_coffee.get_ingredients() + ', milk'


class Vanilla(Abstract_Coffee_Decorator):

    def __init__(self, decorated_coffee):
        Abstract_Coffee_Decorator.__init__(self, decorated_coffee)

    def get_cost(self):
        return self.decorated_coffee.get_cost() + 0.75

    def get_ingredients(self):
        return self.decorated_coffee.get_ingredients() + ', vanilla'


def main():
    myCoffee = Concrete_Coffee()
    print('Ingredients: ' + myCoffee.get_ingredients() +
          '; Cost: ' + str(myCoffee.get_cost()) + '; sales tax = ' + str(myCoffee.get_tax()))

    myCoffee = Milk(myCoffee)
    print('Ingredients: ' + myCoffee.get_ingredients() +
          '; Cost: ' + str(myCoffee.get_cost()) + '; sales tax = ' + str(myCoffee.get_tax()))

    myCoffee = Vanilla(myCoffee)
    print('Ingredients: ' + myCoffee.get_ingredients() +
          '; Cost: ' + str(myCoffee.get_cost()) + '; sales tax = ' + str(myCoffee.get_tax()))

    myCoffee = Sugar(myCoffee)
    print('Ingredients: ' + myCoffee.get_ingredients() +
          '; Cost: ' + str(myCoffee.get_cost()) + '; sales tax = ' + str(myCoffee.get_tax()))


if __name__ == '__main__':
    main()

Facade

The Facade Pattern exposes several components through a single interface, think of a single universal remote controller that can control a TV, DVD Player, Amplifier, Projector, etc. To summarize you provide a simplified API over a set of classes. 

Facade example
# Simple Facade Pattern
class EventManager(object):
    def __init__(self):
        print("Event Manager:: Let me talk to the folks\n")

    def arrange(self):
        self.hotelier = Hotelier()
        self.hotelier.bookHotel()
        self.florist = Florist()
        self.florist.setFlowerRequirements()
        self.caterer = Caterer()
        self.caterer.setCuisine()
        self.musician = Musician()
        self.musician.setMusicType()


class Hotelier(object):
    def __init__(self):
        print("Arranging the Hotel for Marriage? --")

    def __isAvailable(self):
        print("Is the Hotel free for the event on given day?")
        return True

    def bookHotel(self):
        if self.__isAvailable():
            print("Registered the Booking\n\n")


class Florist(object):
    def __init__(self):
        print("Flower Decorations for the Event? --")

    def setFlowerRequirements(self):
        print("Carnations, Roses and Lilies would be used for Decorations\n\n")


class Caterer(object):
    def __init__(self):
        print("Food Arrangements for the Event --")

    def setCuisine(self):
        print("Chinese & Continental Cuisine to be served\n\n")


class Musician(object):
    def __init__(self):
        print("Musical Arrangements for the Marriage --")

    def setMusicType(self):
        print("Jazz and Classical will be played\n\n")


class You(object):
    def __init__(self):
        print("You:: Whoa! Marriage Arrangements??!!!")

    def askEventManager(self):
        print("You:: Let's Contact the Event Manager\n\n")
        em = EventManager()
        em.arrange()

    def __del__(self):
        print("You:: Thanks to Event Manager, all preparations done! Phew!")


def main():
    you = You()
    you.askEventManager()


if __name__ == '__main__':
    main()

Flyweight

The Flyweight Pattern is a space optimization technique that lets us use less memory by storing externally the data associated with similar objects, the bottom line is to avoid redundancy when storing data, think of it like compressing a file or image. It allows programs to support vast quantities of objects by keeping their memory consumption low.

Flyweight example
# Flyweight example
import random
from enum import Enum

TreeType = Enum('TreeType', 'apple_tree cherry_tree peach_tree')


# 3 instances will be created later but all share the cache
class Tree:
    pool = dict()                                   # this is the cache and is shared

    def __new__(cls, tree_type):
        obj = cls.pool.get(tree_type, None)
        if not obj:
            obj = object.__new__(cls)
            cls.pool[tree_type] = obj
            obj.tree_type = tree_type
        return obj

    def render(self, age, x, y):
        print('render a tree of type {} and age {} at ({}, {})'.format(self.tree_type, age, x, y))


def main():
    rnd = random.Random()
    age_min, age_max = 1, 30                        # in years
    min_point, max_point = 0, 100                   # years will be between 0 - 100
    tree_counter = 0

    for _ in range(10):                             # loop 10 times
        t1 = Tree(TreeType.apple_tree)
        t1.render(rnd.randint(age_min, age_max),
            rnd.randint(min_point, max_point),
            rnd.randint(min_point, max_point))
        tree_counter += 1

    for _ in range(3):                              # loop 3 times
        t2 = Tree(TreeType.cherry_tree)
        t2.render(rnd.randint(age_min, age_max),
            rnd.randint(min_point, max_point),
            rnd.randint(min_point, max_point))
        tree_counter += 1

    for _ in range(5):                              # loop 5 times
        t3 = Tree(TreeType.peach_tree)
        t3.render(rnd.randint(age_min, age_max),
            rnd.randint(min_point, max_point),
            rnd.randint(min_point, max_point))
        tree_counter += 1

    print('trees rendered: {}'.format(tree_counter))
    print('trees actually created: {}'.format(len(Tree.pool)))
    t4 = Tree(TreeType.cherry_tree)
    t5 = Tree(TreeType.cherry_tree)
    t6 = Tree(TreeType.apple_tree)
    print('{} == {}? {}'.format(id(t4), id(t5), id(t4) == id(t5)))
    print('{} == {}? {}'.format(id(t5), id(t6), id(t5) == id(t6)))


if __name__ == '__main__':
    main()

Proxy

The Proxy Pattern is a interface for accessing a particular resource, allows access control by acting as a pass through entity or a placeholder object. Some examples of Proxies can be logging, security access, etc. Remember that will call the original underlying Objects code they are just a wrapper with additional functionality.

Proxy and Decorator patterns are very similar, the key difference is that the Proxy Pattern will have an identical interface where as the Decorator pattern provides an enhanced interface, also the Decorator Pattern has reference to what its decorating, the Proxy Pattern does not.

Proxy example (basic)
# Proxy example
class Actor(object):
    def __init__(self):
        self.isBusy = False

    def occupied(self):
        self.isBusy = True
        print(type(self).__name__, "is occupied with current movie")

    def available(self):
        self.isBusy = False
        print(type(self).__name__, "is free for the movie")

    def getStatus(self):
        return self.isBusy


class Agent(object):
    def __init__(self):
        self.actor = Actor()  # the proxy has access to the the Actor
        self.principal = None

    def work(self):
        if self.actor.getStatus():
            self.actor.occupied()
        else:
            self.actor.available()


if __name__ == '__main__':
    r = Agent()  # You have to go via the proxy to get to the Actor
    r.work()
Proxy example (security)
# Proxy example
class SensitiveInfo:
    def __init__(self):
        self.users = ['nick', 'tom', 'ben', 'mike']

    def read(self):
        print('There are {} users: {}'.format(len(self.users), ' '.join(self.users)))

    def add(self, user):
        self.users.append(user)
        print('Added user {}'.format(user))


class Info:
    """protection proxy to SensitiveInfo"""

    def __init__(self):
        self.protected = SensitiveInfo()            # the proxy has access to the SensitiveInfo
        self.secret = '0xdeadbeef'

    def read(self):
        self.protected.read()

    def add(self, user):
        sec = input('what is the secret? ')
        self.protected.add(user) if sec == self.secret else print("That's wrong!")


def main():
    info = Info()
    while True:
        print('1. read list \n2. add user \n3. quit')
        key = input('choose option: ')
        if key == '1':
            info.read()
        elif key == '2':
            name = input('choose username: ')
            info.add(name)
        elif key == '3':
            exit()
        else:
            print('unknown option: {}'.format(key))


if __name__ == '__main__':
    main()