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() |
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() |
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() |
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() |
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() |