├── README.md ├── abstractfactory.py ├── adapter.py ├── blackboard.py ├── bridge.py ├── chainofresp.py ├── closure.py ├── command.py ├── composite.py ├── decorator.py ├── facade.py ├── factory.py ├── flyweight.py ├── iterator.py ├── memento.py ├── null.py ├── observer.py ├── prototype.py ├── proxy.py ├── singleton.py ├── state.py ├── strategy.py ├── template.py ├── visitor.py └── wrapper.py /README.md: -------------------------------------------------------------------------------- 1 | # Design patterns in Python 2 | 3 | A collection of popular design patterns implemented in Python programming language. 4 | -------------------------------------------------------------------------------- /abstractfactory.py: -------------------------------------------------------------------------------- 1 | """Implementation of the abstract factory pattern""" 2 | import random 3 | 4 | 5 | class PetShop: 6 | """A pet shop""" 7 | 8 | def __init__(self, animal_factory=None): 9 | """pet_factory is our abstract factory. We can set it at will.""" 10 | self.pet_factory = animal_factory 11 | 12 | def show_pet(self): 13 | """Creates and shows a pet using the abstract factory""" 14 | pet = self.pet_factory.get_pet() 15 | print "This is a lovely", pet 16 | print "It says", pet.speak() 17 | print "It eats", self.pet_factory.get_food() 18 | 19 | # Stuff that our factory makes 20 | 21 | 22 | class Dog: 23 | def speak(self): 24 | return "woof" 25 | 26 | def __str__(self): 27 | return "Dog" 28 | 29 | 30 | class Cat: 31 | def speak(self): 32 | return "meow" 33 | 34 | def __str__(self): 35 | return "Cat" 36 | 37 | 38 | # Factory classes 39 | class DogFactory: 40 | def get_pet(self): 41 | return Dog() 42 | 43 | def get_food(self): 44 | return "dog food" 45 | 46 | 47 | class CatFactory: 48 | def get_pet(self): 49 | return Cat() 50 | 51 | def get_food(self): 52 | return "cat food" 53 | 54 | 55 | # Create the proper family 56 | def get_factory(): 57 | """Let's be dynamic!""" 58 | return random.choice([DogFactory, CatFactory])() 59 | 60 | # Show pets with various factories 61 | shop = PetShop() 62 | for i in range(3): 63 | shop.pet_factory = get_factory() 64 | shop.show_pet() 65 | print "=" * 10 66 | -------------------------------------------------------------------------------- /adapter.py: -------------------------------------------------------------------------------- 1 | class Adaptee: 2 | def specific_request(self): 3 | return 'Adaptee' 4 | 5 | class Adapter: 6 | def __init__(self, adaptee): 7 | self.adaptee = adaptee 8 | 9 | def request(self): 10 | return self.adaptee.specific_request() 11 | 12 | client = Adapter(Adaptee()) 13 | print client.request() 14 | 15 | # --------- Second example (by Alex Martelli)------------ 16 | 17 | class UppercasingFile: 18 | def __init__(self, *a, **k): 19 | self.f = file(*a, **k) 20 | 21 | def write(self, data): 22 | self.f.write(data.upper()) 23 | 24 | def __getattr__(self, name): 25 | return getattr(self.f, name) 26 | -------------------------------------------------------------------------------- /blackboard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | @author: Eugene Duboviy | github.com/duboviy 6 | 7 | In Blackboard pattern several specialised sub-systems (knowledge sources) 8 | assemble their knowledge to build a possibly partial or approximate solution. 9 | In this way, the sub-systems work together to solve the problem, 10 | where the solution is the sum of its parts. 11 | 12 | https://en.wikipedia.org/wiki/Blackboard_system 13 | """ 14 | 15 | import abc 16 | import random 17 | 18 | 19 | class Blackboard(object): 20 | 21 | def __init__(self): 22 | self.experts = [] 23 | self.common_state = { 24 | 'problems': 0, 25 | 'suggestions': 0, 26 | 'contributions': [], 27 | 'progress': 0 # percentage, if 100 -> task is finished 28 | } 29 | 30 | def add_expert(self, expert): 31 | self.experts.append(expert) 32 | 33 | 34 | class Controller(object): 35 | 36 | def __init__(self, blackboard): 37 | self.blackboard = blackboard 38 | 39 | def run_loop(self): 40 | while self.blackboard.common_state['progress'] < 100: 41 | for expert in self.blackboard.experts: 42 | if expert.is_eager_to_contribute: 43 | expert.contribute() 44 | return self.blackboard.common_state['contributions'] 45 | 46 | 47 | class AbstractExpert(object): 48 | 49 | __metaclass__ = abc.ABCMeta 50 | 51 | def __init__(self, blackboard): 52 | self.blackboard = blackboard 53 | 54 | @abc.abstractproperty 55 | def is_eager_to_contribute(self): 56 | raise NotImplementedError('Must provide implementation in subclass.') 57 | 58 | @abc.abstractmethod 59 | def contribute(self): 60 | raise NotImplementedError('Must provide implementation in subclass.') 61 | 62 | 63 | class Student(AbstractExpert): 64 | 65 | @property 66 | def is_eager_to_contribute(self): 67 | return True 68 | 69 | def contribute(self): 70 | self.blackboard.common_state['problems'] += random.randint(1, 10) 71 | self.blackboard.common_state['suggestions'] += random.randint(1, 10) 72 | self.blackboard.common_state['contributions'] += [self.__class__.__name__] 73 | self.blackboard.common_state['progress'] += random.randint(1, 2) 74 | 75 | 76 | class Scientist(AbstractExpert): 77 | 78 | @property 79 | def is_eager_to_contribute(self): 80 | return random.randint(0, 1) 81 | 82 | def contribute(self): 83 | self.blackboard.common_state['problems'] += random.randint(10, 20) 84 | self.blackboard.common_state['suggestions'] += random.randint(10, 20) 85 | self.blackboard.common_state['contributions'] += [self.__class__.__name__] 86 | self.blackboard.common_state['progress'] += random.randint(10, 30) 87 | 88 | 89 | class Professor(AbstractExpert): 90 | 91 | @property 92 | def is_eager_to_contribute(self): 93 | return True if self.blackboard.common_state['problems'] > 100 else False 94 | 95 | def contribute(self): 96 | self.blackboard.common_state['problems'] += random.randint(1, 2) 97 | self.blackboard.common_state['suggestions'] += random.randint(10, 20) 98 | self.blackboard.common_state['contributions'] += [self.__class__.__name__] 99 | self.blackboard.common_state['progress'] += random.randint(10, 100) 100 | 101 | 102 | if __name__ == '__main__': 103 | blackboard = Blackboard() 104 | 105 | blackboard.add_expert(Student(blackboard)) 106 | blackboard.add_expert(Scientist(blackboard)) 107 | blackboard.add_expert(Professor(blackboard)) 108 | 109 | c = Controller(blackboard) 110 | contributions = c.run_loop() 111 | 112 | from pprint import pprint 113 | pprint(contributions) 114 | 115 | ### OUTPUT ### 116 | # ['Student', 117 | # 'Student', 118 | # 'Scientist', 119 | # 'Student', 120 | # 'Scientist', 121 | # 'Student', 122 | # 'Scientist', 123 | # 'Student', 124 | # 'Scientist', 125 | # 'Student', 126 | # 'Scientist', 127 | # 'Professor'] 128 | -------------------------------------------------------------------------------- /bridge.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Implementor 4 | class DrawingAPI: 5 | def drawCircle(x, y, radius): 6 | pass 7 | 8 | 9 | # ConcreteImplementor 1/2 10 | class DrawingAPI1(DrawingAPI): 11 | def drawCircle(self, x, y, radius): 12 | print "API1.circle at %f:%f radius %f" % (x, y, radius) 13 | 14 | 15 | # ConcreteImplementor 2/2 16 | class DrawingAPI2(DrawingAPI): 17 | def drawCircle(self, x, y, radius): 18 | print "API2.circle at %f:%f radius %f" % (x, y, radius) 19 | 20 | 21 | # Abstraction 22 | class Shape: 23 | # Low-level 24 | def draw(self): 25 | pass 26 | 27 | # High-level 28 | def resizeByPercentage(self, pct): 29 | pass 30 | 31 | 32 | # Refined Abstraction 33 | class CircleShape(Shape): 34 | def __init__(self, x, y, radius, drawingAPI): 35 | self.__x = x 36 | self.__y = y 37 | self.__radius = radius 38 | self.__drawingAPI = drawingAPI 39 | 40 | # low-level i.e. Implementation specific 41 | def draw(self): 42 | self.__drawingAPI.drawCircle(self.__x, self.__y, self.__radius) 43 | 44 | # high-level i.e. Abstraction specific 45 | def resizeByPercentage(self, pct): 46 | self.__radius *= pct 47 | 48 | 49 | def main(): 50 | shapes = [ 51 | CircleShape(1, 2, 3, DrawingAPI1()), 52 | CircleShape(5, 7, 11, DrawingAPI2()) 53 | ] 54 | 55 | for shape in shapes: 56 | shape.resizeByPercentage(2.5) 57 | shape.draw() 58 | 59 | if __name__ == "__main__": 60 | main() 61 | -------------------------------------------------------------------------------- /chainofresp.py: -------------------------------------------------------------------------------- 1 | class Car: 2 | def __init__(self): 3 | self.name = None 4 | self.km = 11100 5 | self.fuel = 5 6 | self.oil = 5 7 | 8 | 9 | def handle_fuel(car): 10 | if car.fuel < 10: 11 | print "added fuel" 12 | car.fuel = 100 13 | 14 | 15 | def handle_km(car): 16 | if car.km > 10000: 17 | print "made a car test." 18 | car.km = 0 19 | 20 | 21 | def handle_oil(car): 22 | if car.oil < 10: 23 | print "Added oil" 24 | car.oil = 100 25 | 26 | 27 | class Garage: 28 | def __init__(self): 29 | self.handlers = [] 30 | 31 | def add_handler(self, handler): 32 | self.handlers.append(handler) 33 | 34 | def handle_car(self, car): 35 | for handler in self.handlers: 36 | handler(car) 37 | 38 | if __name__ == '__main__': 39 | handlers = [handle_fuel, handle_km, handle_oil] 40 | garage = Garage() 41 | 42 | for handle in handlers: 43 | garage.add_handler(handle) 44 | garage.handle_car(Car()) 45 | -------------------------------------------------------------------------------- /closure.py: -------------------------------------------------------------------------------- 1 | def Dx(f, dx): 2 | def dfdx(x): 3 | return (f(x + dx) - f(x))/dx 4 | return dfdx 5 | 6 | def f(x): 7 | return 3*x**2+x 8 | 9 | print f(1.0) # 4.0 10 | print Dx(f, 0.01)(1.0) # 7.03 11 | print Dx(Dx(f, 0.01), 0.01)(1.0) 12 | -------------------------------------------------------------------------------- /command.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | from sys import stdout as console 4 | 5 | 6 | # Handling 'exit' command 7 | class SessionClosed(Exception): 8 | def __init__(self, value): 9 | self.value = value 10 | 11 | 12 | # Interface 13 | class Command: 14 | def execute(self): 15 | raise NotImplementedError() 16 | 17 | def cancel(self): 18 | raise NotImplementedError() 19 | 20 | def name(): 21 | raise NotImplementedError() 22 | 23 | 24 | # rm command 25 | class RmCommand(Command): 26 | def execute(self): 27 | console.write("You are executed \"rm\" command\n") 28 | 29 | def cancel(self): 30 | console.write("You are canceled \"rm\" command\n") 31 | 32 | def name(self): 33 | return "rm" 34 | 35 | 36 | # uptime command 37 | class UptimeCommand(Command): 38 | def execute(self): 39 | console.write("You are executed \"uptime\" command\n") 40 | 41 | def cancel(self): 42 | console.write("You are canceled \"uptime\" command\n") 43 | 44 | def name(self): 45 | return "uptime" 46 | 47 | 48 | # undo command 49 | class UndoCommand(Command): 50 | def execute(self): 51 | try: 52 | cmd = HISTORY.pop() 53 | TRASH.append(cmd) 54 | console.write("Undo command \"{0}\"\n".format(cmd.name())) 55 | cmd.cancel() 56 | 57 | except IndexError: 58 | console.write("ERROR: HISTORY is empty\n") 59 | 60 | def name(self): 61 | return "undo" 62 | 63 | 64 | # redo command 65 | class RedoCommand(Command): 66 | def execute(self): 67 | try: 68 | cmd = TRASH.pop() 69 | HISTORY.append(cmd) 70 | console.write("Redo command \"{0}\"\n".format(cmd.name())) 71 | cmd.execute() 72 | except IndexError: 73 | console.write("ERROR: TRASH is empty\n") 74 | 75 | def name(self): 76 | return "redo" 77 | 78 | 79 | # history command 80 | class HistoryCommand(Command): 81 | def execute(self): 82 | i = 0 83 | for cmd in HISTORY: 84 | console.write("{0}: {1}\n".format(i, cmd.name())) 85 | i = i + 1 86 | 87 | def name(self): 88 | print "history" 89 | 90 | 91 | # exit command 92 | class ExitCommand(Command): 93 | def execute(self): 94 | raise SessionClosed("Good day!") 95 | 96 | def name(self): 97 | return "exit" 98 | 99 | # available commands 100 | COMMANDS = {'rm': RmCommand(), 'uptime': UptimeCommand(), 'undo': 101 | UndoCommand(), 'redo': RedoCommand(), 'history': HistoryCommand(), 102 | 'exit': ExitCommand()} 103 | 104 | HISTORY = list() 105 | TRASH = list() 106 | 107 | 108 | # Shell 109 | def main(): 110 | try: 111 | while True: 112 | console.flush() 113 | console.write("pysh >> ") 114 | 115 | cmd = raw_input() 116 | 117 | try: 118 | command = COMMANDS[cmd] 119 | command.execute() 120 | if (not isinstance(command, UndoCommand) and not 121 | isinstance(command, RedoCommand) and not 122 | isinstance(command, HistoryCommand)): 123 | TRASH = list() 124 | HISTORY.append(command) 125 | 126 | except KeyError: 127 | console.write("ERROR: Command \"%s\" not found\n" % cmd) 128 | 129 | except SessionClosed as e: 130 | console.write(e.value) 131 | 132 | if __name__ == "__main__": 133 | main() 134 | -------------------------------------------------------------------------------- /composite.py: -------------------------------------------------------------------------------- 1 | class Component(object): 2 | def __init__(self, *args, **kw): 3 | pass 4 | 5 | def component_function(self): 6 | pass 7 | 8 | 9 | class Leaf(Component): 10 | def __init__(self, *args, **kw): 11 | Component.__init__(self, *args, **kw) 12 | 13 | def component_function(self): 14 | print "some function" 15 | 16 | 17 | class Composite(Component): 18 | def __init__(self, *args, **kw): 19 | Component.__init__(self, *args, **kw) 20 | self.children = [] 21 | 22 | def append_child(self, child): 23 | self.children.append(child) 24 | 25 | def remove_child(self, child): 26 | self.children.remove(child) 27 | 28 | def component_function(self): 29 | map(lambda x: x.component_function(), self.children) 30 | 31 | c = Composite() 32 | l = Leaf() 33 | l_two = Leaf() 34 | c.append_child(l) 35 | c.append_child(l_two) 36 | c.component_function() 37 | -------------------------------------------------------------------------------- /decorator.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | 4 | def time_this(func): 5 | """The time_this decorator""" 6 | 7 | def decorated(*args, **kwargs): 8 | start = time.time() 9 | result = func(*args, **kwargs) 10 | print 'Rain in', time.time() - start, 'seconds' 11 | return result 12 | return decorated 13 | 14 | 15 | # Decorator syntax 16 | @time_this 17 | def count(until): 18 | """Counts to 'until', then returns the result""" 19 | 20 | print "Counting to", until, "…" 21 | num = 0 22 | for i in xrange(to_num(until)): 23 | num += 1 24 | return num 25 | 26 | 27 | def to_num(numstr): 28 | """Turns a comma-separated number string to an int""" 29 | return int(numstr.replace(",", "")) 30 | 31 | # Run count with various values 32 | for number in ("10,000", "100,000", "1,000,000"): 33 | print count(number) 34 | print "-" * 20 35 | -------------------------------------------------------------------------------- /facade.py: -------------------------------------------------------------------------------- 1 | # Complex parts 2 | class CPU: 3 | def freeze(self): pass 4 | def jump(self, position): pass 5 | def execute(self): pass 6 | 7 | class Memory: 8 | def load(self, position, data): pass 9 | 10 | class HardDrive: 11 | def read(self, lba, size): pass 12 | 13 | # Facade 14 | class Computer: 15 | def __init__(self): 16 | self.cpu = CPU() 17 | self.memory = Memory() 18 | self.hard_drive = HardDrive() 19 | 20 | def start_computer(self): 21 | self.cpu.freeze() 22 | self.memory.load(0, self.hard_drive.read(0, 1024)) 23 | self.cpu.jump(10) 24 | self.cpu.execute() 25 | 26 | # Client 27 | if __name__ == '__main__': 28 | facade = Computer() 29 | facade.start_computer() 30 | -------------------------------------------------------------------------------- /factory.py: -------------------------------------------------------------------------------- 1 | class Pizza(object): 2 | def __init__(self): 3 | self._price = None 4 | 5 | def get_price(self): 6 | return self._price 7 | 8 | class HamAndMushroomPizza(Pizza): 9 | def __init__(self): 10 | self._price = 8.5 11 | 12 | class DeluxePizza(Pizza): 13 | def __init__(self): 14 | self._price = 10.5 15 | 16 | class HawaiianPizza(Pizza): 17 | def __init__(self): 18 | self._price = 11.5 19 | 20 | class PizzaFactory(object): 21 | @staticmethod 22 | def create_pizza(pizza_type): 23 | if pizza_type == 'HamMushroom': 24 | return HamAndMushroomPizza() 25 | elif pizza_type == 'Deluxe': 26 | return DeluxePizza() 27 | elif pizza_type == 'Hawaiian': 28 | return HawaiianPizza() 29 | 30 | if __name__ == '__main__': 31 | for pizza_type in ('HamMushroom', 'Deluxe', 'Hawaiian'): 32 | print('Price of {0} is {1}'.format(pizza_type, PizzaFactory.create_pizza(pizza_type).get_price())) 33 | 34 | -------------------------------------------------------------------------------- /flyweight.py: -------------------------------------------------------------------------------- 1 | """ 2 | Flyweight Design Pattern 3 | Desc: Sharing the shareable data between the common classes and thus 4 | reducing the memory usage 5 | Code: Believing that every person in a family will have same genetic 6 | structure, we will create a code to learn about 7 | genetics of each family. If a same member of a family is given, no new 8 | object is created. (You can also create new 9 | objects unlike here but keep a reference of the heavy weighted one in 10 | the new |||r object). 11 | """ 12 | 13 | 14 | class ComplexGenetics(object): 15 | """Returns a huge genetic pattern""" 16 | def __init__(self): 17 | pass 18 | 19 | def genes(self, gene_code): 20 | return "ComplexPatter[%s]TooHugeinSize" % (gene_code) 21 | 22 | 23 | class Families(object): 24 | """To learn about Family Genetic Pattern.""" 25 | family = {} 26 | 27 | def __new__(cls, name, family_id): 28 | """I have to capture the details before the class is created, __init__ 29 | is pseudo constructor.""" 30 | try: 31 | id = cls.family[family_id] 32 | except KeyError: 33 | id = object.__new__(cls) 34 | cls.family[family_id] = id 35 | return id 36 | 37 | def set_genetic_info(self, genetic_info): 38 | cg = ComplexGenetics() 39 | self.genetic_info = cg.genes(genetic_info) 40 | 41 | def get_genetic_info(self): 42 | return (self.genetic_info) 43 | 44 | 45 | def test(): 46 | data = (('a', 1, 'ATAG'), ('a', 2, 'AAGT'), ('b', 1, 'ATAG')) 47 | family_objects = [] 48 | for i in data: 49 | obj = Families(i[0], i[1]) 50 | obj.set_genetic_info(i[2]) 51 | family_objects.append(obj) 52 | 53 | for i in family_objects: 54 | print "id = " + str(id(i)) 55 | print i.get_genetic_info() 56 | print "similar id's says that they are same objects " 57 | 58 | if __name__ == '__main__': 59 | test() 60 | -------------------------------------------------------------------------------- /iterator.py: -------------------------------------------------------------------------------- 1 | # from a sequence 2 | x = [42, "test", -12.34] 3 | it = iter(x) 4 | try: 5 | while True: 6 | x = next(it) # in Python 2, you would use it.next() 7 | print x 8 | except StopIteration: 9 | pass 10 | 11 | 12 | # a generator 13 | def foo(n): 14 | for i in range(n): 15 | yield i 16 | 17 | it = foo(5) 18 | try: 19 | while True: 20 | x = next(it) # in Python 2, you would use it.next() 21 | print x 22 | except StopIteration: 23 | pass 24 | -------------------------------------------------------------------------------- /memento.py: -------------------------------------------------------------------------------- 1 | # TODO finish it 2 | import copy 3 | 4 | 5 | def Memento(obj, deep=False): 6 | state = (copy.copy, copy.deepcopy)[bool(deep)](obj.__dict__) 7 | def Restore(): 8 | obj.__dict__.clear() 9 | obj.__dict__.update(state) 10 | return Restore 11 | 12 | class Transaction: 13 | """A transaction guard. This is realy just syntactic suggar arount a memento 14 | closure.""" 15 | deep = False 16 | def __init__(self, *targets): 17 | self.targets = targets 18 | self.Commit() 19 | def Commit(self): 20 | self.states = [Memento(target, self.deep) for target in self.targets] 21 | def Rollback(self): 22 | for state in self.states: 23 | state() 24 | 25 | class transactional(object): 26 | """Adds transactional semantics to methods. Methods decorated with 27 | @transactional will rollback to entry state upon exceptions.""" 28 | def __init__(self, method): 29 | self.method = method 30 | def __get__(self, obj, T): 31 | def transaction(*args, **kwargs): 32 | state = Memento(obj) 33 | try: 34 | return self.method(obj, *args, **kwargs) 35 | except: 36 | state() 37 | raise 38 | return transaction 39 | 40 | if __name__ == '__main__': 41 | 42 | class NumObj(object): 43 | def __init__(self, value): 44 | self.value = value 45 | def __repr__(self): 46 | return '<%s: %r>' % (self.__class__.__name__, self.value) 47 | def Increment(self): 48 | self.value += 1 49 | @transactional 50 | def DoStuff(self): 51 | self.value = '1111' # <- invalid value 52 | self.Increment() # <- will fail and rollback 53 | 54 | print 55 | n = NumObj(-1) 56 | print n 57 | t = Transaction(n) 58 | try: 59 | for i in range(3): 60 | n.Increment() 61 | print n 62 | t.Commit() 63 | print '-- commited' 64 | for i in range(3): 65 | n.Increment() 66 | print n 67 | n.value += 'x' # will fail 68 | print n 69 | except: 70 | t.Rollback() 71 | print '-- rolled back' 72 | print n 73 | print '-- now doing stuff ...' 74 | try: 75 | n.DoStuff() 76 | except: 77 | print '-> doing stuff failed!' 78 | import traceback 79 | traceback.print_exc(0) 80 | pass 81 | print n 82 | -------------------------------------------------------------------------------- /null.py: -------------------------------------------------------------------------------- 1 | #!/user/bin/env python 2 | 3 | """null.py 4 | 5 | This is a sample implementation of the 'Null Object' design pattern. 6 | 7 | Roughly, the goal with Null objects is to provide an 'intelligent' 8 | replacement for the often used primitive data type None in Python or 9 | Null (or Null pointers) in other languages. These are used for many 10 | purposes including the important case where one member of some group 11 | of otherwise similar elements is special for whatever reason. Most 12 | often this results in conditional statements to distinguish between 13 | ordinary elements and the primitive Null value. 14 | 15 | Among the advantages of using Null objects are the following: 16 | 17 | - Superfluous conditional statements can be avoided 18 | by providing a first class object alternative for 19 | the primitive value None. 20 | 21 | - Code readability is improved. 22 | 23 | - Null objects can act as a placeholder for objects 24 | with behaviour that is not yet implemented. 25 | 26 | - Null objects can be replaced for any other class. 27 | 28 | - Null objects are very predictable at what they do. 29 | 30 | To cope with the disadvantage of creating large numbers of passive 31 | objects that do nothing but occupy memory space Null objects are 32 | often combined with the Singleton pattern. 33 | 34 | For more information use any internet search engine and look for 35 | combinations of these words: Null, object, design and pattern. 36 | 37 | Dinu C. Gherman, 38 | August 2001 39 | """ 40 | 41 | 42 | class Null: 43 | """A class for implementing Null objects. 44 | 45 | This class ignores all parameters passed when constructing or 46 | calling instances and traps all attribute and method requests. 47 | Instances of it always (and reliably) do 'nothing'. 48 | 49 | The code might benefit from implementing some further special 50 | Python methods depending on the context in which its instances 51 | are used. Especially when comparing and coercing Null objects 52 | the respective methods' implementation will depend very much 53 | on the environment and, hence, these special methods are not 54 | provided here. 55 | """ 56 | 57 | # Object constructing 58 | def __init__(self, *args, **kwargs): 59 | "Ignore parameters." 60 | return None 61 | 62 | # Object calling 63 | def __call__(self, *args, **kwargs): 64 | "Ignore method calls." 65 | return self 66 | 67 | # Attribute handling 68 | def __getattr__(self, mname): 69 | "Ignore attribute requests." 70 | return self 71 | 72 | def __setattr__(self, name, value): 73 | "Ignore attribute setting." 74 | return self 75 | 76 | def __delattr__(self, name): 77 | "Ignore deleting attributes." 78 | return self 79 | 80 | # Misc. 81 | def __repr__(self): 82 | "Return a string representation." 83 | return "" 84 | 85 | def __str__(self): 86 | "Convert to a string and return it." 87 | return "Null" 88 | 89 | 90 | def test(): 91 | "Perform some decent tests, or rather: demos." 92 | 93 | # Constructing and calling 94 | n = Null() 95 | n = Null('value') 96 | n = Null('value', param='value') 97 | 98 | n() 99 | n('value') 100 | n('value', param='value') 101 | 102 | # Attribute handling 103 | n.attr1 104 | n.attr1.attr2 105 | n.method1() 106 | n.method1().method2() 107 | n.method('value') 108 | n.method(param='value') 109 | n.method('value', param='value') 110 | n.attr1.method1() 111 | n.method1().attr1 112 | 113 | n.attr1 = 'value' 114 | n.attr1.attr2 = 'value' 115 | 116 | del n.attr1 117 | del n.attr1.attr2.attr3 118 | 119 | # Representation and conversion to a string 120 | assert repr(n) == '' 121 | assert str(n) == 'Null' 122 | 123 | 124 | if __name__ == '__main__': 125 | test() 126 | -------------------------------------------------------------------------------- /observer.py: -------------------------------------------------------------------------------- 1 | class AbstractSubject: 2 | def register(self, listener): 3 | raise NotImplementedError("Must subclass me") 4 | 5 | def unregister(self, listener): 6 | raise NotImplementedError("Must subclass me") 7 | 8 | def notify_listeners(self, event): 9 | raise NotImplementedError("Must subclass me") 10 | 11 | 12 | class Listener: 13 | def __init__(self, name, subject): 14 | self.name = name 15 | subject.register(self) 16 | 17 | def notify(self, event): 18 | print self.name, "received event", event 19 | 20 | 21 | class Subject(AbstractSubject): 22 | def __init__(self): 23 | self.listeners = [] 24 | self.data = None 25 | 26 | def getUserAction(self): 27 | self.data = raw_input('Enter something to do:') 28 | return self.data 29 | 30 | # Implement abstract Class AbstractSubject 31 | 32 | def register(self, listener): 33 | self.listeners.append(listener) 34 | 35 | def unregister(self, listener): 36 | self.listeners.remove(listener) 37 | 38 | def notify_listeners(self, event): 39 | for listener in self.listeners: 40 | listener.notify(event) 41 | 42 | 43 | if __name__ == "__main__": 44 | # Make a subject object to spy on 45 | subject = Subject() 46 | 47 | # Register two listeners to monitor it. 48 | listenerA = Listener("", subject) 49 | listenerB = Listener("", subject) 50 | 51 | # Simulated event 52 | subject.notify_listeners("") 53 | # Outputs: 54 | # received event 55 | # received event 56 | 57 | action = subject.getUserAction() 58 | subject.notify_listeners(action) 59 | # Enter something to do:hello 60 | # outputs: 61 | # received event hello 62 | # received event hello 63 | -------------------------------------------------------------------------------- /prototype.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy, copy 2 | 3 | copyfunc = deepcopy 4 | 5 | 6 | def Prototype(name, bases, dict): 7 | class Cls: 8 | pass 9 | Cls.__name__ = name 10 | Cls.__bases__ = bases 11 | Cls.__dict__ = dict 12 | inst = Cls() 13 | inst.__call__ = copyier(inst) 14 | return inst 15 | 16 | 17 | class copyier: 18 | def __init__(self, inst): 19 | self._inst = inst 20 | 21 | def __call__(self): 22 | newinst = copyfunc(self._inst) 23 | if copyfunc == deepcopy: 24 | newinst.__call__._inst = newinst 25 | else: 26 | newinst.__call__ = copyier(newinst) 27 | return newinst 28 | 29 | 30 | class Point: 31 | __metaclass__ = Prototype 32 | x = 0 33 | y = 0 34 | 35 | def move(self, x, y): 36 | self.x += x 37 | self.y += y 38 | 39 | a = Point() 40 | print a.x, a.y # prints 0 0 41 | a.move(100, 100) 42 | print a.x, a.y # prints 100 100 43 | 44 | Point.move(50, 50) 45 | print Point.x, Point.y # prints 50 50 46 | p = Point() 47 | print p.x, p.y # prints 50 50 48 | 49 | q = p() 50 | print q.x, q.y # prints 50 50 51 | -------------------------------------------------------------------------------- /proxy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | class IMath: 4 | """Interface for proxy and real subject.""" 5 | def add(self, x, y): 6 | raise NotImplementedError() 7 | 8 | def sub(self, x, y): 9 | raise NotImplementedError() 10 | 11 | def mul(self, x, y): 12 | raise NotImplementedError() 13 | 14 | def div(self, x, y): 15 | raise NotImplementedError() 16 | 17 | class Math(IMath): 18 | """Real subject.""" 19 | def add(self, x, y): 20 | return x + y 21 | 22 | def sub(self, x, y): 23 | return x - y 24 | 25 | def mul(self, x, y): 26 | return x * y 27 | 28 | def div(self, x, y): 29 | return x / y 30 | 31 | class Proxy(IMath): 32 | """Proxy.""" 33 | def __init__(self): 34 | self.math = Math() 35 | 36 | def add(self, x, y): 37 | return self.math.add(x, y) 38 | 39 | def sub(self, x, y): 40 | return self.math.sub(x, y) 41 | 42 | def mul(self, x, y): 43 | return self.math.mul(x, y) 44 | 45 | def div(self, x, y): 46 | if y == 0: 47 | return float('inf') # Вернуть positive infinity 48 | return self.math.div(x, y) 49 | 50 | p = Proxy() 51 | x, y = 4, 2 52 | print '4 + 2 = ' + str(p.add(x, y)) 53 | print '4 - 2 = ' + str(p.sub(x, y)) 54 | print '4 * 2 = ' + str(p.mul(x, y)) 55 | print '4 / 2 = ' + str(p.div(x, y)) 56 | -------------------------------------------------------------------------------- /singleton.py: -------------------------------------------------------------------------------- 1 | # ---------- Singletone ---------- 2 | 3 | 4 | # Real Singleton instance 5 | class Singleton(object): 6 | def __new__(type): 7 | if not '_the_instance' in type.__dict__: 8 | type._the_instance = object.__new__(type) 9 | return type._the_instance 10 | 11 | a = Singleton() 12 | a.toto = 12 13 | 14 | b = Singleton() 15 | print b.toto 16 | print id(a), id(b) # The same !! 17 | 18 | 19 | # ---------- Borg's singletone ---------- 20 | class Borg: 21 | __shared_state = {} 22 | 23 | def __init__(self): 24 | self.__dict__ = self.__shared_state 25 | 26 | a = Borg() 27 | a.toto = 12 28 | 29 | b = Borg() 30 | print b.toto 31 | print id(a), id(b) # different ! but states are sames 32 | 33 | # ---------- Alex's Martelli examples ---------- 34 | 35 | class Singleton(object): 36 | def __new__(cls, *a, **k): 37 | if not hasattr(cls, '_inst'): 38 | cls._inst = super(Singleton, cls 39 | ).__new__(cls, *a, **k) 40 | return cls._inst 41 | 42 | class Borg(object): 43 | """Subclassing is no problem.""" 44 | _shared_state = {} 45 | def __new__(cls, *a, **k): 46 | obj = super(Borg, cls).__new__(cls, *a, **k) 47 | obj.__dict__ = cls._shared_state 48 | return obj 49 | -------------------------------------------------------------------------------- /state.py: -------------------------------------------------------------------------------- 1 | """Implementation of the state pattern""" 2 | import itertools 3 | 4 | 5 | class State(object): 6 | """Base state. This is to share functionality""" 7 | 8 | def scan(self): 9 | """Scan the dial to the next station""" 10 | print "Scanning... Station is", self.stations.next(), self.name 11 | 12 | 13 | class AmState(State): 14 | def __init__(self, radio): 15 | self.radio = radio 16 | self.stations = itertools.cycle(["1250", "1380", "1510"]) 17 | self.name = "AM" 18 | 19 | def toggle_amfm(self): 20 | print "Switching to FM" 21 | self.radio.state = self.radio.fmstate 22 | 23 | 24 | class FmState(State): 25 | def __init__(self, radio): 26 | self.radio = radio 27 | self.stations = itertools.cycle(["81.3", "89.1", "103.9"]) 28 | self.name = "FM" 29 | 30 | def toggle_amfm(self): 31 | print "Switching to AM" 32 | self.radio.state = self.radio.amstate 33 | 34 | 35 | class Radio(object): 36 | """A radio. 37 | It has a scan button, and an AM/FM toggle switch.""" 38 | 39 | def __init__(self): 40 | """We have an AM state and an FM state""" 41 | 42 | self.amstate = AmState(self) 43 | self.fmstate = FmState(self) 44 | self.state = self.amstate 45 | 46 | def toggle_amfm(self): 47 | self.state.toggle_amfm() 48 | 49 | def scan(self): 50 | self.state.scan() 51 | 52 | 53 | def main(): 54 | ''' Test our radio out ''' 55 | radio = Radio() 56 | actions = ([radio.scan] * 2 + [radio.toggle_amfm] + [radio.scan] * 2) * 2 57 | for action in actions: 58 | action() 59 | 60 | if __name__ == '__main__': 61 | main() 62 | -------------------------------------------------------------------------------- /strategy.py: -------------------------------------------------------------------------------- 1 | class StrategyExample: 2 | 3 | def __init__(self, func=None): 4 | if func: 5 | self.execute = func 6 | 7 | def execute(self): 8 | print "Original execution" 9 | 10 | 11 | def executeReplacement1(self): 12 | print "Strategy 1" 13 | 14 | 15 | def executeReplacement2(self): 16 | print "Strategy 2" 17 | 18 | if __name__ == "__main__": 19 | 20 | strat0 = StrategyExample() 21 | strat1 = StrategyExample(executeReplacement1) 22 | strat2 = StrategyExample(executeReplacement2) 23 | 24 | strat0.execute() 25 | strat1.execute() 26 | strat2.execute() 27 | 28 | # -------------------- With classes -------------------- 29 | 30 | 31 | class AUsefulThing(object): 32 | def __init__(self, aStrategicAlternative): 33 | self.howToDoX = aStrategicAlternative 34 | 35 | def doX(self, someArg): 36 | self. howToDoX.theAPImethod(someArg, self) 37 | 38 | 39 | class StrategicAlternative(object): 40 | pass 41 | 42 | 43 | class AlternativeOne(StrategicAlternative): 44 | def theAPIMethod(self, someArg, theUsefulThing): 45 | pass # an implementation 46 | 47 | 48 | class AlternativeTwo(StrategicAlternative): 49 | def theAPImethod(self, someArg, theUsefulThing): 50 | pass # another implementation 51 | 52 | 53 | t = AUsefulThing(AlternativeOne()) 54 | t.doX('arg') 55 | -------------------------------------------------------------------------------- /template.py: -------------------------------------------------------------------------------- 1 | class AbstractGame: 2 | """An abstract class that is common to several games in which 3 | players play against the others, but only one is playing at a 4 | given time. 5 | """ 6 | def __init__(self, *args, **kwargs): 7 | if self.__class__ is AbstractGame: 8 | raise TypeError('abstract class cannot be instantiated') 9 | 10 | def playOneGame(self, playersCount): 11 | self.playersCount = playersCount 12 | self.initializeGame() 13 | j = 0 14 | while not self.endOfGame(): 15 | self.makePlay(j) 16 | j = (j + 1) % self.playersCount 17 | self.printWinner() 18 | 19 | def initializeGame(self): 20 | raise TypeError('abstract method must be overridden') 21 | 22 | def endOfGame(self): 23 | raise TypeError('abstract method must be overridden') 24 | 25 | def makePlay(self, player_num): 26 | raise TypeError('abstract method must be overridden') 27 | 28 | def printWinner(self): 29 | raise TypeError('abstract method must be overridden') 30 | 31 | 32 | # Now to create concrete (non-abstract) games, you subclass AbstractGame 33 | # and override the abstract methods. 34 | 35 | class Chess(AbstractGame): 36 | def initializeGame(self): 37 | # Put the pieces on the board. 38 | pass 39 | 40 | def makePlay(player): 41 | # Process a turn for the player 42 | pass 43 | 44 | # --------- Alex's Martelli example --------- 45 | 46 | class AbstractBase(object): 47 | def orgMethod(self): 48 | self.doThis() 49 | self.doThat() 50 | 51 | class Concrete(AbstractBase): 52 | def doThis(self): 53 | pass 54 | def doThat(self): 55 | pass 56 | -------------------------------------------------------------------------------- /visitor.py: -------------------------------------------------------------------------------- 1 | class CodeGeneratorVisitor(object): 2 | @dispatch.on('node') 3 | def visit(self, node): 4 | """This is the generic method""" 5 | 6 | @visit.when(ASTNode) 7 | def visit(self, node): 8 | map(self.visit, node.children) 9 | 10 | @visit.when(EchoStatement) 11 | def visit(self, node): 12 | self.visit(node.children) 13 | print "print" 14 | 15 | @visit.when(BinaryExpression) 16 | def visit(self, node): 17 | map(self.visit, node.children) 18 | print node.props['operator'] 19 | 20 | @visit.when(Constant) 21 | def visit(self, node): 22 | print "push %d" % node.props['value'] 23 | 24 | sometree = None 25 | CodeGeneratorVisitor().visit(sometree) 26 | # Output: 27 | # push 1 28 | # print 29 | # push 2 30 | # push 4 31 | # push 3 32 | # multiply 33 | # plus 34 | # print 35 | -------------------------------------------------------------------------------- /wrapper.py: -------------------------------------------------------------------------------- 1 | class RestrictingWrapper(object): 2 | def __init__(self, w, block): 3 | self._w = w 4 | self._block = block 5 | def __getattr__(self, n): 6 | if n in self._block: 7 | raise AttributeError, n 8 | return getattr(self._w, n) 9 | --------------------------------------------------------------------------------