├── Images ├── Python_Logo.png └── UML │ ├── Builder_general.png │ ├── Facade_specific.jpg │ ├── Builder_specific.png │ ├── Iterator_general.png │ ├── Iterator_specific.png │ ├── Strategy_general.jpg │ ├── Strategy_specific.png │ └── urls.txt ├── .gitignore ├── Behavioural ├── Observer.py ├── Iterator.py ├── State.py ├── Memento.py ├── DataCaching.py ├── Interpreter.py ├── MonoState.py ├── Strategy.py └── Strategy_old.py ├── Creational ├── ResourceAcquisitionIsInitialization.py ├── Singleton.py ├── Factory_Method.py └── Builder.py ├── Structural ├── Adapter.py ├── Proxy.py ├── Bridge.py └── Facade.py ├── README.md ├── makefile ├── UI ├── utest_MVC.py └── MVC.py └── Design_Patterns_In_Python.md /Images/Python_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidcorne/Design-Patterns-In-Python/HEAD/Images/Python_Logo.png -------------------------------------------------------------------------------- /Images/UML/Builder_general.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidcorne/Design-Patterns-In-Python/HEAD/Images/UML/Builder_general.png -------------------------------------------------------------------------------- /Images/UML/Facade_specific.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidcorne/Design-Patterns-In-Python/HEAD/Images/UML/Facade_specific.jpg -------------------------------------------------------------------------------- /Images/UML/Builder_specific.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidcorne/Design-Patterns-In-Python/HEAD/Images/UML/Builder_specific.png -------------------------------------------------------------------------------- /Images/UML/Iterator_general.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidcorne/Design-Patterns-In-Python/HEAD/Images/UML/Iterator_general.png -------------------------------------------------------------------------------- /Images/UML/Iterator_specific.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidcorne/Design-Patterns-In-Python/HEAD/Images/UML/Iterator_specific.png -------------------------------------------------------------------------------- /Images/UML/Strategy_general.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidcorne/Design-Patterns-In-Python/HEAD/Images/UML/Strategy_general.jpg -------------------------------------------------------------------------------- /Images/UML/Strategy_specific.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidcorne/Design-Patterns-In-Python/HEAD/Images/UML/Strategy_specific.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #ignore debug.log 2 | debug.log 3 | 4 | # ignore emacs temp files 5 | *~ 6 | 7 | # ignore the latex rubbish 8 | *.aux 9 | *.out 10 | *.log 11 | *.snm 12 | *.nav 13 | *.toc 14 | *.synctex.gz 15 | 16 | # ignore the finished product, attach as a download later 17 | Output/* 18 | 19 | # ignore the C++ examples I've made to better my understanding 20 | C++/* 21 | 22 | # ignore the python byte files 23 | *.pyc 24 | -------------------------------------------------------------------------------- /Behavioural/Observer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Written by: DGC 3 | 4 | import abc 5 | 6 | class Observer(object): 7 | __metaclass__ = abc.ABCMeta 8 | 9 | @abc.abstractmethod 10 | def update(self): 11 | raise 12 | 13 | class ConcreteObserver(Observer): 14 | pass 15 | 16 | if (__name__ == "__main__"): 17 | print("thing") 18 | conc = ConcreteObserver() 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Creational/ResourceAcquisitionIsInitialization.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Written by: DGC 3 | 4 | # python imports 5 | 6 | # local imports 7 | 8 | #============================================================================== 9 | class Box(object): 10 | 11 | def __init__(self, name): 12 | self.name = name 13 | 14 | def __enter__(self): 15 | print("Box " + self.name + " Opened") 16 | return self 17 | 18 | def __exit__(self, exception_type, exception, traceback): 19 | all_none = all( 20 | arg is None for arg in [exception_type, exception, traceback] 21 | ) 22 | if (not all_none): 23 | print("Exception: \"%s\" raised." %(str(exception))) 24 | print("Box Closed") 25 | print("") 26 | return all_none 27 | 28 | #============================================================================== 29 | if (__name__ == "__main__"): 30 | with Box("tupperware") as simple_box: 31 | print("Nothing in " + simple_box.name) 32 | with Box("Pandora's") as pandoras_box: 33 | raise Exception("All the evils in the world") 34 | print("end") 35 | -------------------------------------------------------------------------------- /Structural/Adapter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Written by: DGC 3 | 4 | #============================================================================== 5 | class RCCar(object): 6 | 7 | def __init__(self): 8 | self.speed = 0 9 | 10 | def change_speed(self, speed): 11 | self.speed = speed 12 | print("RC car is moving at " + str(self.speed)) 13 | 14 | #============================================================================== 15 | class RCAdapter(object): 16 | 17 | def __init__(self): 18 | self.car = RCCar() 19 | 20 | def move_forwards(self): 21 | self.car.change_speed(10) 22 | 23 | def move_backwards(self): 24 | self.car.change_speed(-10) 25 | 26 | def stop(self): 27 | self.car.change_speed(0) 28 | 29 | #============================================================================== 30 | class RemoteControl(object): 31 | 32 | def __init__(self): 33 | self.adapter = RCAdapter() 34 | 35 | def stick_up(self): 36 | self.adapter.move_forwards() 37 | 38 | def stick_down(self): 39 | self.adapter.move_backwards() 40 | 41 | def stick_middle(self): 42 | self.adapter.stop() 43 | 44 | #============================================================================== 45 | if (__name__ == "__main__"): 46 | controller = RemoteControl() 47 | controller.stick_up() 48 | controller.stick_middle() 49 | controller.stick_down() 50 | controller.stick_middle() 51 | 52 | -------------------------------------------------------------------------------- /Behavioural/Iterator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Written by: DGC 3 | 4 | #============================================================================== 5 | class ReverseIterator(object): 6 | """ 7 | Iterates the object given to it in reverse so it shows the difference. 8 | """ 9 | 10 | def __init__(self, iterable_object): 11 | self.list = iterable_object 12 | # start at the end of the iterable_object 13 | self.index = len(iterable_object) 14 | 15 | def __iter__(self): 16 | # return an iterator 17 | return self 18 | 19 | def next(self): 20 | """ Return the list backwards so it's noticeably different.""" 21 | if (self.index == 0): 22 | # the list is over, raise a stop index exception 23 | raise StopIteration 24 | self.index = self.index - 1 25 | return self.list[self.index] 26 | 27 | #============================================================================== 28 | class Days(object): 29 | 30 | def __init__(self): 31 | self.days = [ 32 | "Monday", 33 | "Tuesday", 34 | "Wednesday", 35 | "Thursday", 36 | "Friday", 37 | "Saturday", 38 | "Sunday" 39 | ] 40 | 41 | def reverse_iter(self): 42 | return ReverseIterator(self.days) 43 | 44 | #============================================================================== 45 | if (__name__ == "__main__"): 46 | days = Days() 47 | for day in days.reverse_iter(): 48 | print(day) 49 | -------------------------------------------------------------------------------- /Behavioural/State.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Written by: DGC 3 | 4 | #============================================================================== 5 | class Language(object): 6 | 7 | def greet(self): 8 | return self.greeting 9 | 10 | #============================================================================== 11 | class English(Language): 12 | 13 | def __init__(self): 14 | self.greeting = "Hello" 15 | 16 | #============================================================================== 17 | class French(Language): 18 | 19 | def __init__(self): 20 | self.greeting = "Bonjour" 21 | 22 | #============================================================================== 23 | class Spanish(Language): 24 | 25 | def __init__(self): 26 | self.greeting = "Hola" 27 | 28 | #============================================================================== 29 | class Multilinguist(object): 30 | 31 | def __init__(self, language): 32 | self.greetings = { 33 | "English": "Hello", 34 | "French": "Bonjour", 35 | "Spanish": "Hola" 36 | } 37 | self.language = language 38 | 39 | def greet(self): 40 | print(self.greetings[self.language]) 41 | 42 | #============================================================================== 43 | if (__name__ == "__main__"): 44 | 45 | # talking in English 46 | translator = Multilinguist("English") 47 | translator.greet() 48 | 49 | # meets a Frenchman 50 | translator.language = "French" 51 | translator.greet() 52 | 53 | # greets a Spaniard 54 | translator.language = "Spanish" 55 | translator.greet() 56 | -------------------------------------------------------------------------------- /Structural/Proxy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Written by: DGC 3 | 4 | # http://sourcemaking.com/design_patterns/proxy 5 | # give 4 good reasons for a proxy to be made. 6 | 7 | # A virtual proxy is a placeholder for "expensive to create" objects. The real 8 | # object is only created when a client first requests/accesses the object. 9 | # 10 | # A remote proxy provides a local representative for an object that resides in 11 | # a different address space. This is what the "stub" code in RPC and CORBA 12 | # provides. 13 | 14 | # A protective proxy controls access to a sensitive master object. The 15 | # "surrogate" object checks that the caller has the access permissions required 16 | # prior to forwarding the request. 17 | 18 | # A smart proxy interposes additional actions when an object is accessed. 19 | # Typical uses include: 20 | # o Counting the number of references to the real object so that it can be 21 | # freed automatically when there are no more references (aka smart pointer) 22 | # o Loading a persistent object into memory when it's first referenced, 23 | # o Checking that the real object is locked before it is accessed to ensure 24 | # that no other object can change it. 25 | 26 | 27 | #============================================================================== 28 | class SharedData(object): 29 | 30 | def __init__(self): 31 | self.resource = "A resource" 32 | 33 | #============================================================================== 34 | class AsyncProxy(object): 35 | 36 | def __init__(self, data): 37 | """ 38 | Takes some data which should now only be accessed through this class, 39 | otherwise you could get 40 | """ 41 | self.data = data 42 | 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Design-Patterns-In-Python 2 | ========================= 3 | 4 | This is the git repository containing the files for a book I am writing. 5 | This book is all about making reusable elements of software design. 6 | 7 | While I started to write this as an e-book, I now think the way to do 8 | this is write a blog post per design pattern and then compile them into 9 | a guide. These blog posts can be found [here](http://davidcorne.com/category/design-patterns-in-python/) 10 | 11 | These patterns will fit roughly into four categories: 12 | 13 | * Creational 14 | * Abstract factory 15 | * Builder 16 | * Factory method 17 | * __Lazy initialization__ 18 | * __Multiton__ 19 | * __Object pool__ 20 | * Prototype 21 | * __Resource acquisition is initialization__ 22 | * Singleton 23 | * Structural 24 | * Adapter 25 | * Bridge 26 | * Composite 27 | * Decorator 28 | * Facade 29 | * Flyweight 30 | * __Front Controller__ 31 | * __Module__ 32 | * Proxy 33 | * __Telescoping constructor__ 34 | * Behavioural 35 | * __Blackboard__ 36 | * Chain of Responsibility 37 | * Command 38 | * __Data Caching__ 39 | * Interpreter 40 | * Iterator 41 | * Mediator 42 | * Memento 43 | * __Null object__ 44 | * Observer (Publish/Subscribe) 45 | * State 46 | * __Servant__ 47 | * __Specification__ 48 | * Strategy 49 | * Template 50 | * Visitor 51 | * UI Patterns 52 | * __Model View Presenter__ 53 | * __Model View Controller__ 54 | * __Model View View-Model__ 55 | * __Model View Adapter__ 56 | * __Presentation Abstraction Control__ 57 | 58 | Patterns in __bold__ are non [gang of four](http://en.wikipedia.org/wiki/Gang_of_Four) patterns. 59 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | # Builds the e-book in multiple formats 2 | 3 | OPTIONS = -s --toc --toc-depth=2 --highlight-style haddock 4 | SOURCE_NAME = Design_Patterns_In_Python 5 | OUTPUT_NAME = Output/Design_Patterns_In_Python 6 | 7 | #============================================================================== 8 | all: $(OUTPUT_NAME).epub $(OUTPUT_NAME).pdf $(OUTPUT_NAME).html FAKE_IMAGES 9 | @echo -e "All made" 10 | 11 | #============================================================================== 12 | $(OUTPUT_NAME).html: $(SOURCE_NAME).md 13 | @mkdir -p Output 14 | @echo -e "Making html." 15 | pandoc $< $(OPTIONS) -o $@ 16 | @echo -e "html made.\n" 17 | 18 | #============================================================================== 19 | $(OUTPUT_NAME).pdf: $(SOURCE_NAME).md 20 | @mkdir -p Output 21 | @echo -e "Making pdf." 22 | pandoc $< $(OPTIONS) -o $@ 23 | @echo -e "pdf made.\n" 24 | 25 | #============================================================================== 26 | $(OUTPUT_NAME).epub: $(SOURCE_NAME).md 27 | @mkdir -p Output 28 | @echo -e "Making epub." 29 | pandoc $< $(OPTIONS) -o $@ 30 | @echo -e "epub made.\n" 31 | 32 | FAKE_IMAGES: 33 | @cp -r Images Output/ 34 | @echo "Images copied." 35 | 36 | #============================================================================== 37 | clean: FRC 38 | # remove the temporary files 39 | @rm -f *.pdf *.pyc *.html *.epub 40 | @echo "Removed all: pdfs, html, epubs and temp files." 41 | 42 | #============================================================================== 43 | #D Pseudo target causes all targets that depend on FRC to be remade even in 44 | #D case a file with the name of the target exists. Works unless there is a file 45 | #D called FRC in the directory. 46 | #------------------------------------------------------------------------------ 47 | FRC: -------------------------------------------------------------------------------- /Behavioural/Memento.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Written by: DGC 3 | 4 | import copy 5 | 6 | #============================================================================== 7 | class Memento(object): 8 | 9 | def __init__(self, data): 10 | # make a deep copy of every variable in the given class 11 | for attribute in vars(data): 12 | # mechanism for using properties without knowing their names 13 | setattr(self, attribute, copy.deepcopy(getattr(data, attribute))) 14 | 15 | #============================================================================== 16 | class Undo(object): 17 | 18 | def __init__(self): 19 | # each instance keeps the latest saved copy so that there is only one 20 | # copy of each in memory 21 | self.__last = None 22 | 23 | def save(self): 24 | self.__last = Memento(self) 25 | 26 | def undo(self): 27 | for attribute in vars(self): 28 | # mechanism for using properties without knowing their names 29 | setattr(self, attribute, getattr(self.__last, attribute)) 30 | 31 | #============================================================================== 32 | class Data(Undo): 33 | 34 | def __init__(self): 35 | super(Data, self).__init__() 36 | self.numbers = [] 37 | 38 | #============================================================================== 39 | if (__name__ == "__main__"): 40 | d = Data() 41 | repeats = 10 42 | # add a number to the list in data repeat times 43 | print("Adding.") 44 | for i in range(repeats): 45 | print("0" + str(i) + " times: " + str(d.numbers)) 46 | d.save() 47 | d.numbers.append(i) 48 | print("10 times: " + str(d.numbers)) 49 | d.save() 50 | print("") 51 | 52 | # now undo repeat times 53 | print("Using Undo.") 54 | for i in range(repeats): 55 | print("0" + str(i) + " times: " + str(d.numbers)) 56 | d.undo() 57 | print("10 times: " + str(d.numbers)) 58 | -------------------------------------------------------------------------------- /Structural/Bridge.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Written by: DGC 3 | 4 | import abc 5 | 6 | #============================================================================== 7 | class Shape(object): 8 | __metaclass__ = abc.ABCMeta 9 | 10 | @abc.abstractmethod 11 | def __init__(self): 12 | pass 13 | 14 | def area(self): 15 | """ 16 | Returns the area of the shape calculated using the shape specific 17 | implementation. 18 | """ 19 | assert self.calculator != None, "self.calculator not defined." 20 | return self.calculator(self) 21 | 22 | #============================================================================== 23 | class Rectangle(Shape): 24 | 25 | def __init__(self, x, y): 26 | self.calculator = rectangular_area_calculator 27 | self.x = x 28 | self.y = y 29 | 30 | #============================================================================== 31 | def rectangular_area_calculator(rectangle): 32 | return rectangle.x * rectangle.y 33 | 34 | #============================================================================== 35 | class Triangle(Shape): 36 | 37 | def __init__(self, base, height): 38 | self.calculator = triangular_area_calculator 39 | self.base = base 40 | self.height = height 41 | 42 | #============================================================================== 43 | def triangular_area_calculator(triangle): 44 | return 0.5 * triangle.base * triangle.height 45 | 46 | #============================================================================== 47 | if (__name__ == "__main__"): 48 | x = 4 49 | y = 5 50 | rect = Rectangle(x, y) 51 | print(str(x) + " x " + str(y) + " Rectangle area: " + str(rect.area())) 52 | 53 | base = 4 54 | height = 5 55 | tri = Triangle(base, height); 56 | print( 57 | "Base " + 58 | str(base) + 59 | ", Height " + 60 | str(height) + 61 | " Triangle area: " + 62 | str(tri.area()) 63 | ) 64 | -------------------------------------------------------------------------------- /Behavioural/DataCaching.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Written by: DGC 3 | 4 | # python imports 5 | import math 6 | 7 | #============================================================================== 8 | class DataCache(object): 9 | 10 | def __init__(self): 11 | """ A class representing cachable data, starts invalid.""" 12 | self.data = None 13 | 14 | def __call__(self): 15 | """ 16 | When an instance is called it returns the stored data or None if no 17 | data has been cached. 18 | e.g 19 | data = cached_data() 20 | """ 21 | return self.data 22 | 23 | def __nonzero__(self): 24 | """ 25 | Called on bool(instance) or if(instance) returns if there is data 26 | cached. 27 | e.g 28 | if (not data): 29 | # set data 30 | """ 31 | return self.data is not None 32 | 33 | def set(self, data): 34 | """ Sets the data. """ 35 | self.data = data 36 | 37 | def reset(self): 38 | """ Returns the class to an invalid state. """ 39 | self.data = None 40 | 41 | #============================================================================== 42 | class Line(object): 43 | 44 | def __init__(self, start, end): 45 | """ 46 | This is a class representing a 2D line. 47 | Takes a start point and end point represented by two pairs. 48 | """ 49 | self.start = start 50 | self.end = end 51 | self.length_data = DataCache() 52 | 53 | def length(self): 54 | if (not self.length_data): 55 | x_length = self.start[0] - self.end[0] 56 | y_length = self.start[1] - self.end[1] 57 | length = math.sqrt((x_length ** 2) + (y_length ** 2)) 58 | self.length_data.set(length) 59 | else: 60 | print("Cached value used") 61 | return self.length_data() 62 | 63 | #============================================================================== 64 | if (__name__ == "__main__"): 65 | l = Line((0, 0), (1, 0)) 66 | print(l.length()) 67 | print(l.length()) 68 | 69 | -------------------------------------------------------------------------------- /Images/UML/urls.txt: -------------------------------------------------------------------------------- 1 | This is to store the editable URLS from the online UML tool used to make these images 2 | 3 | Facade_specific.jpg 4 | http://yuml.me/diagram/plain;/class/edit/// Facade Class Diagram, [Car|battery;starter;engine;|turn_key()]++->[Engine|spin|start(charge))], [Car]++->[StarterMotor|spin|start(charge)], [Car]++->[Battery|charge|] 5 | 6 | Strategy_general.jpg 7 | http://yuml.me/diagram/plain;dir:RL;/class/edit/// Non-specific Strategy Class Diagram, [Caller]<>->[<>], [<>]^-.-[Algorithm1], [<>]^-.-[Algorithm2] 8 | 9 | Strategy_specific.png 10 | http://yuml.me9dfd5ea2 11 | 12 | // specific strategy pattern 13 | [PrimeFinder|algorithm;primes|calculate(limit);out()]+->[Algorithm|calculate(limit)] 14 | [Algorithm]^-.-[HardCoded|calculate(limit)] 15 | [Algorithm]^-.-[Standard|calculate(limit) 16 | 17 | 18 | Builder_general.png 19 | http://yuml.me/3f5049ad 20 | http://yuml.me/edit/3f5049ad 21 | 22 | // General Builder pattern 23 | [Director| builder| create()]<>->[Builder] 24 | [Builder| build_part_1();build_part_2()]^-.-[ConcreteBuilder2| build_part_1();build_part_2()] 25 | [Builder]^-.-[ConcreteBuilder1| build_part_1();build_part_2()] 26 | [ConcreteBuilder1]creates->[Product] 27 | [ConcreteBuilder2]creates->[Product] 28 | 29 | Builder_specific.png 30 | http://yuml.me/abcf254a 31 | http://yuml.me/edit/abcf254a 32 | 33 | // Specific Builder pattern 34 | [VehicleManufacturer| builder| create()]<>->[VehicleBuilder] 35 | [VehicleBuilder| |make_wheels();make_doors();make_seats()]^-.-[BikeBuilder| vehicle |make_wheels();make_doors();make_seats()] 36 | [VehicleBuilder]^-.-[CarBuilder| vehicle|make_wheels();make_doors();make_seats()] 37 | [CarBuilder]creates->[Vehicle| type;wheels;doors;seats | view() ] 38 | [BikeBuilder]creates->[Vehicle] 39 | 40 | Iterator_general.png 41 | http://yuml.me/7653a2e6 42 | http://yuml.me/edit/7653a2e6 43 | 44 | // General Iterator Pattern 45 | [Iterator|__iter__();next()|collection]<>->[Collection|__len__();__getitem__(key);...|] 46 | 47 | Iterator specific 48 | http://yuml.me/b8a2a966 49 | http://yuml.me/edit/b8a2a966 50 | 51 | // Specific Iterator Pattern 52 | [ReverseIterator|__iter__();next()|list;index]<>->[list] 53 | [Days|reverse_iter()|days]<>->[list] 54 | -------------------------------------------------------------------------------- /Structural/Facade.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Written by: DGC 3 | # An example of how the client interacts with a complex series of objects 4 | # (car engine, battery and starter motor) through a facade (the car) 5 | 6 | #============================================================================== 7 | class Engine(object): 8 | 9 | def __init__(self): 10 | # how much the motor is spinning in revs per minute 11 | self.spin = 0 12 | 13 | def start(self, spin): 14 | if (spin > 2000): 15 | self.spin = spin // 15 16 | 17 | #============================================================================== 18 | class StarterMotor(object): 19 | 20 | def __init__(self): 21 | # how much the starter motor is spinning in revs per minute 22 | self.spin = 0 23 | 24 | def start(self, charge): 25 | # if there is enough power then spin fast 26 | if (charge > 50): 27 | self.spin = 2500 28 | 29 | #============================================================================== 30 | class Battery(object): 31 | 32 | def __init__(self): 33 | # % charged, starts flat 34 | self.charge = 0 35 | 36 | #============================================================================== 37 | class Car(object): 38 | # the facade object that deals with the battery, engine and starter motor. 39 | 40 | def __init__(self): 41 | self.battery = Battery() 42 | self.starter = StarterMotor() 43 | self.engine = Engine() 44 | 45 | def turn_key(self): 46 | # use the battery to turn the starter motor 47 | self.starter.start(self.battery.charge) 48 | 49 | # use the starter motor to spin the engine 50 | self.engine.start(self.starter.spin) 51 | 52 | # if the engine is spinning the car is started 53 | if (self.engine.spin > 0): 54 | print("Engine Started.") 55 | else: 56 | print("Engine Not Started.") 57 | 58 | def jump(self): 59 | self.battery.charge = 100 60 | print("Jumped") 61 | 62 | #============================================================================== 63 | if (__name__ == "__main__"): 64 | corsa = Car() 65 | corsa.turn_key() 66 | corsa.jump() 67 | corsa.turn_key() 68 | 69 | -------------------------------------------------------------------------------- /Behavioural/Interpreter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Written by: DGC 3 | 4 | import re 5 | 6 | #============================================================================== 7 | class CamelCase(object): 8 | 9 | def __init__(self): 10 | self.SomeProperty = "A property" 11 | 12 | def SomeMethod(self, argument): 13 | print(argument) 14 | 15 | #============================================================================== 16 | class CamelCaseInterpreter(object): 17 | 18 | def __init__(self, old_class): 19 | super(CamelCaseInterpreter, self).__setattr__("__old_class", old_class) 20 | 21 | def __getattribute__(self, name): 22 | old_class = super(CamelCaseInterpreter, self).__getattribute__("__old_class") 23 | converter = super(CamelCaseInterpreter, self).__getattribute__("name_converter") 24 | return old_class.__getattribute__(converter(name)) 25 | 26 | def __setattr__(self, name, value): 27 | old_class = super(CamelCaseInterpreter, self).__getattribute__("__old_class") 28 | converter = super(CamelCaseInterpreter, self).__getattribute__("name_converter") 29 | old_class.__setattr__(converter(name), value) 30 | 31 | def name_converter(self, name): 32 | """ 33 | Converts function/property names which are lowercase with underscores 34 | to CamelCase. i.e some_property becomes SomeProperty. 35 | """ 36 | new_name = name[0].upper() 37 | previous_underscore = new_name == "_" 38 | for char in name[1:]: 39 | if (char == "_"): 40 | previous_underscore = True 41 | else: 42 | if (previous_underscore): 43 | new_name += char.upper() 44 | else: 45 | new_name += char 46 | previous_underscore = False 47 | return new_name 48 | 49 | #============================================================================== 50 | if (__name__ == "__main__"): 51 | old_class = CamelCase() 52 | 53 | interpreted_class = CamelCaseInterpreter(old_class) 54 | print(interpreted_class.some_property) 55 | 56 | interpreted_class.some_property = "Newly set property" 57 | print(interpreted_class.some_property) 58 | 59 | interpreted_class.some_method("Argument to some_method") 60 | 61 | -------------------------------------------------------------------------------- /Creational/Singleton.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Written by: DGC 3 | 4 | import abc 5 | 6 | #============================================================================== 7 | class Singleton(object): 8 | """ A generic base class to derive any singleton class from. """ 9 | __metaclass__ = abc.ABCMeta 10 | __instance = None 11 | 12 | def __new__(new_singleton, *arguments, **keyword_arguments): 13 | """Override the __new__ method so that it is a singleton.""" 14 | if new_singleton.__instance is None: 15 | new_singleton.__instance = object.__new__(new_singleton) 16 | new_singleton.__instance.init(*arguments, **keyword_arguments) 17 | return new_singleton.__instance 18 | 19 | @abc.abstractmethod 20 | def init(self, *arguments, **keyword_arguments): 21 | """ 22 | as __init__ will be called on every new instance of a base class of 23 | Singleton we need a function for initialisation. This will only be 24 | called once regardless of how many instances of Singleton are made. 25 | """ 26 | raise 27 | 28 | #============================================================================== 29 | class GlobalState(Singleton): 30 | 31 | def init(self): 32 | self.value = 0 33 | print("init() called once") 34 | print("") 35 | 36 | def __init__(self): 37 | print("__init__() always called") 38 | print("") 39 | 40 | class DerivedGlobalState(GlobalState): 41 | 42 | def __init__(self): 43 | print("derived made") 44 | super(DerivedGlobalState, self).__init__() 45 | 46 | def thing(self): 47 | print(self.value) 48 | 49 | #============================================================================== 50 | if (__name__ == "__main__"): 51 | d = DerivedGlobalState() 52 | print(type(d)) 53 | d.thing() 54 | d.value = -20 55 | e = DerivedGlobalState() 56 | e.thing() 57 | f = DerivedGlobalState() 58 | f.thing() 59 | 60 | a = GlobalState() 61 | # value is default, 0 62 | print("Expecting 0, value = %i" %(a.value)) 63 | print("") 64 | 65 | # set the value to 5 66 | a.value = 5 67 | 68 | # make a new object, the value will still be 5 69 | b = GlobalState() 70 | print("Expecting 5, value = %i" %(b.value)) 71 | print("") 72 | print("Is a == b? " + str(a == b)) 73 | -------------------------------------------------------------------------------- /Behavioural/MonoState.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Written by: DGC 3 | 4 | # python imports 5 | 6 | #============================================================================== 7 | class MonoState(object): 8 | __data = 5 9 | 10 | @property 11 | def data(self): 12 | return self.__class__.__data 13 | 14 | @data.setter 15 | def data(self, value): 16 | self.__class__.__data = value 17 | 18 | #============================================================================== 19 | class MonoState2(object): 20 | pass 21 | 22 | def add_monostate_property(cls, name, initial_value): 23 | """ 24 | Adds a property "name" to the class "cls" (should pass in a class object 25 | not a class instance) with the value "initial_value". 26 | 27 | This property is a monostate property so all instances of the class will 28 | have the same value property. You can think of it being a singleton 29 | property, the class instances will be different but the property will 30 | always be the same. 31 | 32 | This will add a variable __"name" to the class which is the internal 33 | storage for the property. 34 | 35 | Example usage: 36 | class MonoState(object): 37 | pass 38 | 39 | add_monostate_property(MonoState, "data", 5) 40 | m = MonoState() 41 | # returns 5 42 | m.data 43 | """ 44 | internal_name = "__" + name 45 | 46 | def getter(self): 47 | return getattr(self.__class__, internal_name) 48 | def setter(self, value): 49 | setattr(self.__class__, internal_name, value) 50 | def deleter(self): 51 | delattr(self.__class__, internal_name) 52 | prop = property(getter, setter, deleter, "monostate variable: " + name) 53 | # set the internal attribute 54 | setattr(cls, internal_name, initial_value) 55 | # set the accesser property 56 | setattr(cls, name, prop) 57 | 58 | #============================================================================== 59 | if (__name__ == "__main__"): 60 | print("Using a class:") 61 | class_1 = MonoState() 62 | print("First data: " + str(class_1.data)) 63 | class_1.data = 4 64 | class_2 = MonoState() 65 | print("Second data: " + str(class_2.data)) 66 | print("First instance: " + str(class_1)) 67 | print("Second instance: " + str(class_2)) 68 | print("These are not singletons, so these are different instances") 69 | 70 | print("") 71 | print("") 72 | 73 | print("Dynamically adding the property:") 74 | add_monostate_property(MonoState2, "data", 5) 75 | dynamic_1 = MonoState2() 76 | print("First data: " + str(dynamic_1.data)) 77 | dynamic_1.data = 4 78 | dynamic_2 = MonoState2() 79 | print("Second data: " + str(dynamic_2.data)) 80 | print("First instance: " + str(dynamic_1)) 81 | print("Second instance: " + str(dynamic_2)) 82 | print("These are not singletons, so these are different instances") 83 | -------------------------------------------------------------------------------- /Creational/Factory_Method.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Written by: DGC 3 | 4 | #============================================================================== 5 | class Line(object): 6 | """ A non-directed line. """ 7 | 8 | def __init__(self, point_1, point_2): 9 | self.point_1 = point_1 10 | self.point_2 = point_2 11 | 12 | def __eq__(self, line): 13 | """ Magic method to overide == operator. """ 14 | # if the lines are equal then the two points must be the same, but not 15 | # necessarily named the same i.e self.point_1 == line.point_2 and 16 | # self.point_2 == line.point_1 means that the lines are equal. 17 | if (type(line) != Line): 18 | return False 19 | if (self.point_1 == line.point_1): 20 | # line numbering matches 21 | return self.point_2 == line.point_2 22 | elif (self.point_1 == line.point_2): 23 | # line numbering does not match 24 | return self.point_2 == line.point_1 25 | else: 26 | # self.point_1 is not the start or end of the other line, not equal 27 | return False 28 | 29 | #============================================================================== 30 | class Vector(object): 31 | """ A directional vector. """ 32 | 33 | def __init__(self, x, y): 34 | self.x = x 35 | self.y = y 36 | 37 | def __eq__(self, vector): 38 | """ Magic method to overide == operator. """ 39 | if (type(vector) != Vector): 40 | return False 41 | return (self.x == vector.x) and (self.y == vector.y) 42 | 43 | #------------------------------------------------------------------------------ 44 | # Factory functions 45 | #------------------------------------------------------------------------------ 46 | 47 | class Factory(object): 48 | 49 | @classmethod 50 | def line_from_point_vector(self, point, vector): 51 | """ Returns the line from travelling vector from point. """ 52 | new_point = (point[0] + vector.x, point[1] + vector.y) 53 | return Line(point, new_point) 54 | 55 | @classmethod 56 | def vector_from_line(self, line): 57 | """ 58 | Returns the directional vector of the line. This is a vector v, such 59 | that line.point_1 + v == line.point_2 60 | """ 61 | return Vector( 62 | line.point_2.x - line.point_1.x, 63 | line.point_2.y - line.point_1.y 64 | ) 65 | 66 | #============================================================================== 67 | if (__name__ == "__main__"): 68 | # make a line from (1, 1) to (1, 0), check that the line made from the 69 | # point (1, 1) and the vector (0, -1) is the same line. 70 | constructor_line = Line((1, 1), (1, 0)) 71 | vector = Vector(0, -1); 72 | factory_line = Factory.line_from_point_vector( 73 | (1, 1), 74 | vector 75 | ) 76 | print(constructor_line == factory_line) 77 | 78 | -------------------------------------------------------------------------------- /Creational/Builder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Written by: DGC 3 | 4 | import abc 5 | 6 | #============================================================================== 7 | class Vehicle(object): 8 | 9 | def __init__(self, type_name): 10 | self.type = type_name 11 | self.wheels = None 12 | self.doors = None 13 | self.seats = None 14 | 15 | def view(self): 16 | print( 17 | "This vehicle is a " + 18 | self.type + 19 | " with; " + 20 | str(self.wheels) + 21 | " wheels, " + 22 | str(self.doors) + 23 | " doors, and " + 24 | str(self.seats) + 25 | " seats." 26 | ) 27 | 28 | #============================================================================== 29 | class VehicleBuilder(object): 30 | """ 31 | An abstract builder class, for concrete builders to be derived from. 32 | """ 33 | __metadata__ = abc.ABCMeta 34 | 35 | @abc.abstractmethod 36 | def make_wheels(self): 37 | raise 38 | 39 | @abc.abstractmethod 40 | def make_doors(self): 41 | raise 42 | 43 | @abc.abstractmethod 44 | def make_seats(self): 45 | raise 46 | 47 | #============================================================================== 48 | class CarBuilder(VehicleBuilder): 49 | 50 | def __init__(self): 51 | self.vehicle = Vehicle("Car ") 52 | 53 | def make_wheels(self): 54 | self.vehicle.wheels = 4 55 | 56 | def make_doors(self): 57 | self.vehicle.doors = 3 58 | 59 | def make_seats(self): 60 | self.vehicle.seats = 5 61 | 62 | #============================================================================== 63 | class BikeBuilder(VehicleBuilder): 64 | 65 | def __init__(self): 66 | self.vehicle = Vehicle("Bike") 67 | 68 | def make_wheels(self): 69 | self.vehicle.wheels = 2 70 | 71 | def make_doors(self): 72 | self.vehicle.doors = 0 73 | 74 | def make_seats(self): 75 | self.vehicle.seats = 2 76 | 77 | #============================================================================== 78 | class VehicleManufacturer(object): 79 | """ 80 | The director class, this will keep a concrete builder. 81 | """ 82 | 83 | def __init__(self): 84 | self.builder = None 85 | 86 | def create(self): 87 | """ 88 | Creates and returns a Vehicle using self.builder 89 | Precondition: not self.builder is None 90 | """ 91 | assert not self.builder is None, "No defined builder" 92 | self.builder.make_wheels() 93 | self.builder.make_doors() 94 | self.builder.make_seats() 95 | return self.builder.vehicle 96 | 97 | #============================================================================== 98 | if (__name__ == "__main__"): 99 | manufacturer = VehicleManufacturer() 100 | 101 | manufacturer.builder = CarBuilder() 102 | car = manufacturer.create() 103 | car.view() 104 | 105 | manufacturer.builder = BikeBuilder() 106 | bike = manufacturer.create() 107 | bike.view() 108 | -------------------------------------------------------------------------------- /Behavioural/Strategy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Written by: DGC 3 | 4 | #============================================================================== 5 | class PrimeFinder(object): 6 | 7 | def __init__(self, algorithm): 8 | """ 9 | Constructor, takes a callable object called algorithm. 10 | algorithm should take a limit argument and return an iterable of prime 11 | numbers below that limit. 12 | """ 13 | self.algorithm = algorithm 14 | self.primes = [] 15 | 16 | def calculate(self, limit): 17 | """ Will calculate all the primes below limit. """ 18 | self.primes = self.algorithm(limit) 19 | 20 | def out(self): 21 | """ Prints the list of primes prefixed with which algorithm made it """ 22 | print(self.algorithm.__name__) 23 | for prime in self.primes: 24 | print(prime) 25 | print("") 26 | 27 | #============================================================================== 28 | def hard_coded_algorithm(limit): 29 | """ 30 | Has hardcoded values for all the primes under 50, returns a list of those 31 | which are less than the given limit. 32 | """ 33 | hardcoded_primes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47] 34 | primes = [] 35 | for prime in hardcoded_primes: 36 | if (prime < limit): 37 | primes.append(prime) 38 | return primes 39 | 40 | #============================================================================== 41 | def standard_algorithm(limit): 42 | """ 43 | Not a great algorithm either, but it's the normal one to use. 44 | It puts 2 in a list, then for all the odd numbers less than the limit if 45 | none of the primes are a factor then add it to the list. 46 | """ 47 | primes = [2] 48 | # check only odd numbers. 49 | for number in range(3, limit, 2): 50 | is_prime = True 51 | # divide it by all our known primes, could limit by sqrt(number) 52 | for prime in primes: 53 | if (number % prime == 0): 54 | is_prime = False 55 | break 56 | if (is_prime): 57 | primes.append(number) 58 | return primes 59 | 60 | #============================================================================== 61 | class HardCodedClass(object): 62 | 63 | def __init__(self, limit): 64 | hardcoded_primes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47] 65 | self.primes = [] 66 | for prime in hardcoded_primes: 67 | if (prime < limit): 68 | self.primes.append(prime) 69 | 70 | def __iter__(self): 71 | return iter(self.primes) 72 | 73 | #============================================================================== 74 | if (__name__ == "__main__"): 75 | hardcoded_primes = PrimeFinder(hard_coded_algorithm) 76 | hardcoded_primes.calculate(50) 77 | hardcoded_primes.out() 78 | 79 | standard_primes = PrimeFinder(standard_algorithm) 80 | standard_primes.calculate(50) 81 | standard_primes.out() 82 | 83 | class_primes = PrimeFinder(HardCodedClass) 84 | class_primes.calculate(50) 85 | class_primes.out() 86 | 87 | print( 88 | "Do the two algorithms get the same result on 50 primes? %s" 89 | %(str(hardcoded_primes.primes == standard_primes.primes)) 90 | ) 91 | 92 | # the hardcoded algorithm only works on numbers under 50 93 | hardcoded_primes.calculate(100) 94 | standard_primes.calculate(100) 95 | 96 | print( 97 | "Do the two algorithms get the same result on 100 primes? %s" 98 | %(str(hardcoded_primes.primes == standard_primes.primes)) 99 | ) 100 | -------------------------------------------------------------------------------- /Behavioural/Strategy_old.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Written by: DGC 3 | 4 | import abc 5 | 6 | #============================================================================== 7 | class PrimeFinder(object): 8 | 9 | def __init__(self, algorithm): 10 | """ 11 | Constructor, takes a lass called algorithm. 12 | algorithm should have a function called calculate which will take a 13 | limit argument and return an iterable of prime numbers below that 14 | limit. 15 | """ 16 | self.algorithm = algorithm 17 | self.primes = [] 18 | 19 | def calculate(self, limit): 20 | """ Will calculate all the primes below limit. """ 21 | self.primes = self.algorithm.calculate(limit) 22 | 23 | def out(self): 24 | """ Prints the list of primes prefixed with which algorithm made it """ 25 | print(self.algorithm.name) 26 | for prime in self.primes: 27 | print(prime) 28 | print("") 29 | 30 | #============================================================================== 31 | class Algorithm(object): 32 | __metaclass__ = abc.ABCMeta 33 | 34 | @abc.abstractmethod 35 | def calculate(self, limit): 36 | raise 37 | 38 | #============================================================================== 39 | class HardCoded(Algorithm): 40 | """ 41 | Has hardcoded values for all the primes under 50, returns a list of those 42 | which are less than the given limit. 43 | """ 44 | 45 | def __init__(self): 46 | self.name = "hard_coded_algorithm" 47 | 48 | def calculate(self, limit): 49 | hardcoded_primes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47] 50 | primes = [] 51 | for prime in hardcoded_primes: 52 | if (prime < limit): 53 | primes.append(prime) 54 | return primes 55 | 56 | #============================================================================== 57 | class Standard(Algorithm): 58 | """ 59 | Not a great algorithm either, but it's the normal one to use. 60 | It puts 2 in a list, then for all the odd numbers less than the limit if 61 | none of the primes are a factor then add it to the list. 62 | """ 63 | 64 | def __init__(self): 65 | self.name = "standard_algorithm" 66 | 67 | def calculate(self, limit): 68 | primes = [2] 69 | # check only odd numbers. 70 | for number in range(3, limit, 2): 71 | is_prime = True 72 | # divide it by all our known primes, could limit by sqrt(number) 73 | for prime in primes: 74 | if (number % prime == 0): 75 | is_prime = False 76 | break 77 | if (is_prime): 78 | primes.append(number) 79 | return primes 80 | 81 | #============================================================================== 82 | if (__name__ == "__main__"): 83 | hard_coded_algorithm = HardCoded() 84 | hardcoded_primes = PrimeFinder(hard_coded_algorithm) 85 | hardcoded_primes.calculate(50) 86 | hardcoded_primes.out() 87 | 88 | standard_algorithm = Standard() 89 | standard_primes = PrimeFinder(standard_algorithm) 90 | standard_primes.calculate(50) 91 | standard_primes.out() 92 | 93 | print( 94 | "Do the two algorithms get the same result on 50 primes? %s" 95 | %(str(hardcoded_primes.primes == standard_primes.primes)) 96 | ) 97 | 98 | # the hardcoded algorithm only works on numbers under 50 99 | hardcoded_primes.calculate(100) 100 | standard_primes.calculate(100) 101 | 102 | print( 103 | "Do the two algorithms get the same result on 100 primes? %s" 104 | %(str(hardcoded_primes.primes == standard_primes.primes)) 105 | ) 106 | -------------------------------------------------------------------------------- /UI/utest_MVC.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Written by: DGC 3 | 4 | # python imports 5 | import unittest 6 | 7 | # local imports 8 | import MVC 9 | 10 | #============================================================================== 11 | class utest_MVC(unittest.TestCase): 12 | 13 | def test_model(self): 14 | model = MVC.Model() 15 | question, possible_answers = model.question_and_answers() 16 | 17 | # can't test what they are because they're random 18 | self.assertTrue( 19 | isinstance(question, str), 20 | "Question should be a string" 21 | ) 22 | self.assertTrue( 23 | isinstance(possible_answers, list), 24 | "Answers should be a list" 25 | ) 26 | 27 | for item in possible_answers: 28 | self.assertTrue( 29 | isinstance(item[0], str), 30 | "Elements of possible answer list should be strings" 31 | ) 32 | 33 | def test_controller(self): 34 | model = ModelMock() 35 | view = ViewMock() 36 | controller = MVC.Controller(model, view) 37 | controller.new_question() 38 | self.assertEqual( 39 | view.question, 40 | "Question", 41 | "Controller should pass the question to the view." 42 | ) 43 | controller.answer("Question", "correct") 44 | self.assertEqual( 45 | controller.view.mock_feedback, 46 | "That is correct.", 47 | "The feedback for a correct answer is wrong." 48 | ) 49 | controller.answer("Question", "incorrect") 50 | self.assertEqual( 51 | controller.view.mock_feedback, 52 | "Sorry that's wrong.", 53 | "The feedback for an incorrect answer is wrong." 54 | ) 55 | 56 | def test_view(self): 57 | view = MVC.View() 58 | controller = ControllerMock(view) 59 | view.register(controller) 60 | 61 | self.assertIs( 62 | view.answer_button, 63 | None, 64 | "The answer button should not be set." 65 | ) 66 | self.assertIs( 67 | view.continue_button, 68 | None, 69 | "The continue button should not be set." 70 | ) 71 | view.new_question("Test", ["correct", "incorrect"]) 72 | 73 | self.assertIsNot( 74 | view.answer_button, 75 | None, 76 | "The answer button should be set." 77 | ) 78 | self.assertIs( 79 | view.continue_button, 80 | None, 81 | "The continue button should not be set." 82 | ) 83 | # simulate a button press 84 | view.answer_button.invoke() 85 | self.assertIs( 86 | view.answer_button, 87 | None, 88 | "The answer button should not be set." 89 | ) 90 | self.assertIsNot( 91 | view.continue_button, 92 | None, 93 | "The continue button should be set." 94 | ) 95 | 96 | self.assertEqual( 97 | controller.question, 98 | "Test", 99 | "The question asked should be \"Test\"." 100 | ) 101 | self.assertEqual( 102 | controller.answer, 103 | "correct", 104 | "The answer given should be \"correct\"." 105 | ) 106 | 107 | # continue 108 | view.continue_button.invoke() 109 | self.assertIsNot( 110 | view.answer_button, 111 | None, 112 | "The answer button should be set." 113 | ) 114 | self.assertIs( 115 | view.continue_button, 116 | None, 117 | "The continue button should not be set." 118 | ) 119 | 120 | #============================================================================== 121 | class ViewMock(object): 122 | 123 | def new_question(self, question, answers): 124 | self.question = question 125 | self.answers = answers 126 | 127 | def register(self, controller): 128 | pass 129 | 130 | def feedback(self, feedback): 131 | self.mock_feedback = feedback 132 | 133 | #============================================================================== 134 | class ModelMock(object): 135 | 136 | def question_and_answers(self): 137 | return ("Question", ["correct", "incorrect"]) 138 | 139 | def is_correct(self, question, answer): 140 | correct = False 141 | if (answer == "correct"): 142 | correct = True 143 | return correct 144 | 145 | #============================================================================== 146 | class ControllerMock(object): 147 | 148 | def __init__(self, view): 149 | self.view = view 150 | 151 | def answer(self, question, answer): 152 | self.question = question 153 | self.answer = answer 154 | self.view.feedback("test") 155 | 156 | def next_question(self): 157 | self.view.new_question("Test", ["correct", "incorrect"]) 158 | 159 | 160 | #============================================================================== 161 | if (__name__ == "__main__"): 162 | unittest.main(verbosity=2) 163 | -------------------------------------------------------------------------------- /UI/MVC.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Written by: DGC 3 | 4 | from Tkinter import * 5 | import random 6 | 7 | #============================================================================== 8 | class Model(object): 9 | 10 | def __init__(self): 11 | # q_and_a is a dictionary where the key is a question and the entry is 12 | # a list of pairs, these pairs are an answer and whether it is correct 13 | self.q_and_a = { 14 | "How many wives did Henry VIII have?": [ 15 | ("Five", False), 16 | ("Six", True), 17 | ("Seven", False), 18 | ("Eight", False) 19 | ], 20 | "In which Italian city is Romeo and Juliet primarily set?": [ 21 | ("Verona", True), 22 | ("Naples", False), 23 | ("Milano", False), 24 | ("Pisa", False) 25 | ], 26 | "A light year is a measure of what?": [ 27 | ("Energy", False), 28 | ("Speed", False), 29 | ("Distance", True), 30 | ("Intensity", False) 31 | ] 32 | } 33 | 34 | def question_and_answers(self): 35 | """ 36 | Returns a randomly chosen question (string) and answers (list of 37 | strings) as a pair. 38 | """ 39 | key = random.choice(self.q_and_a.keys()) 40 | return (key, [x[0] for x in self.q_and_a[key]]) 41 | 42 | def is_correct(self, question, answer): 43 | answers = self.q_and_a[question] 44 | for ans in answers: 45 | if (ans[0] == answer): 46 | return ans[1] 47 | assert False, "Could not find answer." 48 | 49 | #============================================================================== 50 | class View(object): 51 | 52 | def __init__(self): 53 | self.parent = Tk() 54 | self.parent.title("Trivia") 55 | 56 | self.initialise_ui() 57 | 58 | self.controller = None 59 | 60 | def clear_screen(self): 61 | """ Clears the screen deleting all widgets. """ 62 | self.frame.destroy() 63 | self.initialise_ui() 64 | 65 | def initialise_ui(self): 66 | self.answer_button = None 67 | self.continue_button = None 68 | 69 | self.frame = Frame(self.parent) 70 | self.frame.pack() 71 | 72 | def new_question(self, question, answers): 73 | """ 74 | question is a string, answers is a list of strings 75 | e.g 76 | view.new_question( 77 | "Is the earth a sphere?", 78 | ["Yes", "No"] 79 | ) 80 | """ 81 | self.clear_screen() 82 | # put the question on as a label 83 | question_label = Label(self.frame, text=question) 84 | question_label.pack() 85 | 86 | # put the answers on as a radio buttons 87 | selected_answer = StringVar() 88 | selected_answer.set(answers[0]) 89 | 90 | for answer in answers: 91 | option = Radiobutton( 92 | self.frame, 93 | text=answer, 94 | variable=selected_answer, 95 | value=answer, 96 | ) 97 | option.pack() 98 | 99 | # button to confirm 100 | answer_function = lambda : self.controller.answer( 101 | question, 102 | selected_answer.get() 103 | ) 104 | self.answer_button = Button( 105 | self.frame, 106 | text="Answer", 107 | command=answer_function 108 | ) 109 | self.answer_button.pack() 110 | 111 | def main_loop(self): 112 | mainloop() 113 | 114 | def register(self, controller): 115 | """ Register a controller to give callbacks to. """ 116 | self.controller = controller 117 | 118 | def feedback(self, feedback): 119 | self.clear_screen() 120 | label = Label(self.frame, text=feedback) 121 | label.pack() 122 | 123 | self.continue_button = Button( 124 | self.frame, 125 | text="Continue", 126 | command=self.controller.next_question 127 | ) 128 | self.continue_button.pack() 129 | 130 | #============================================================================== 131 | class Controller(object): 132 | 133 | def __init__(self, model, view): 134 | self.model = model 135 | self.view = view 136 | 137 | self.view.register(self) 138 | self.new_question() 139 | 140 | def new_question(self): 141 | q_and_a = self.model.question_and_answers() 142 | self.view.new_question(q_and_a[0], q_and_a[1]) 143 | 144 | def next_question(self): 145 | self.new_question() 146 | 147 | def answer(self, question, answer): 148 | correct = self.model.is_correct(question, answer) 149 | feedback = "" 150 | if (correct): 151 | feedback = "That is correct." 152 | else: 153 | feedback = "Sorry that's wrong." 154 | 155 | self.view.feedback(feedback) 156 | 157 | #============================================================================== 158 | if (__name__ == "__main__"): 159 | # Note: The view should not send to the model but it is often useful 160 | # for the view to receive update event information from the model. 161 | # However you should not update the model from the view. 162 | 163 | view = View() 164 | model = Model() 165 | controller = Controller(model, view) 166 | 167 | view.main_loop() 168 | 169 | -------------------------------------------------------------------------------- /Design_Patterns_In_Python.md: -------------------------------------------------------------------------------- 1 | % Design Patterns in Python 2 | % or how I learned to stop worrying and love the gang of four 3 | % David Corne 4 | 5 | # Introduction # 6 | This is inspired by the aptly titled *Design Patterns: Elements of Reusable Object-Oriented Software* by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides who are collectively known as the Gang of Four (GOF). 7 | 8 | The content of this book is taken from my blog [here](http://davidcorne.com/category/design-patterns-in-python/). 9 | 10 | Why do I want to do this? I want to write this to pass on some knowledge which I have gained working as a software engineer, but also to learn a lot. I think of these as the twin goals of this blog because if I write about something I want to get it right. While I know quite a bit about some design patterns there are others I definitely don't. 11 | 12 | # Creational # 13 | 14 | ## Abstract factory ## 15 | ## Builder ## 16 | 17 | ### The Purpose ### 18 | The idea behind the builder pattern is to abstract away the construction of an object so that many implementations can use the same builder. This separates the construction logic of the desired class from it's representation. 19 | 20 | ### The Pattern ### 21 | Here is a general UML diagram for this 22 | 23 | ![General Builder Pattern](Images/UML/Builder_general.png "General Builder Pattern") 24 | 25 | So the object you are interested in creating is the class `Product` in this scenario. The class which you call to deal with this is the `Director`. 26 | 27 | The `Director` keeps a member which implements the `Builder` interface and calls this in it's `create()` method. This member is one of the `ConcreteBuilder` classes and has the logic needed for the creation of the specific `Product` you want. 28 | 29 | This abstracts the logic needed to create different types of `Product`. The `Product` may also be an interface and each `ConcreteBuilder` may return a different type. 30 | 31 | The `Builder` is typically set either with a function, where the `Director` class is responsible for the logic of the `ConcreteBuilder` creation, or by passing a `ConcreteBuilder` directly to the `Director`. 32 | 33 | ### An Example Usage ### 34 | This will use the example of building different types of vehicles. The code for this example is all contained in 35 | 36 | The Product here is a `Vehicle` which is defined as the following class. 37 | 38 | ```python 39 | #============================================================================== 40 | class Vehicle(object): 41 | 42 | def __init__(self, type_name): 43 | self.type = type_name 44 | self.wheels = None 45 | self.doors = None 46 | self.seats = None 47 | 48 | def view(self): 49 | print( 50 | "This vehicle is a " + 51 | self.type + 52 | " with; " + 53 | str(self.wheels) + 54 | " wheels, " + 55 | str(self.doors) + 56 | " doors, and " + 57 | str(self.seats) + 58 | " seats." 59 | ) 60 | ``` 61 | 62 | So different `Vehicles` can have different names, numbers of wheels doors and seats. The method `view()` will print a summery of what the `Vehicle` is. 63 | 64 | The construction of instances of `Vehicle` will be handles by the `ConcreteBuilder` classes. These should derive from a `Builder` interface (abstract class) so here is the base class `VehicleBuilder`. 65 | 66 | ```python 67 | #============================================================================== 68 | class VehicleBuilder(object): 69 | """ 70 | An abstract builder class, for concrete builders to be derived from. 71 | """ 72 | __metadata__ = abc.ABCMeta 73 | 74 | @abc.abstractmethod 75 | def make_wheels(self): 76 | raise 77 | 78 | @abc.abstractmethod 79 | def make_doors(self): 80 | raise 81 | 82 | @abc.abstractmethod 83 | def make_seats(self): 84 | raise 85 | ``` 86 | 87 | This uses the abc module just like the first version of PrimeFinder in my strategy pattern post. 88 | 89 | So with this `Builder` there are three functions for setting up the `Vehicle`. For the `ConcreteBuilders` there is a `CarBuilder` and a `BikeBuilder`, both of which inherit from `VehicleBuilder`. Here are the implementations of these classes. 90 | 91 | ```python 92 | #============================================================================== 93 | class CarBuilder(VehicleBuilder): 94 | 95 | def __init__(self): 96 | self.vehicle = Vehicle("Car ") 97 | 98 | def make_wheels(self): 99 | self.vehicle.wheels = 4 100 | 101 | def make_doors(self): 102 | self.vehicle.doors = 3 103 | 104 | def make_seats(self): 105 | self.vehicle.seats = 5 106 | 107 | #============================================================================== 108 | class BikeBuilder(VehicleBuilder): 109 | 110 | def __init__(self): 111 | self.vehicle = Vehicle("Bike") 112 | 113 | def make_wheels(self): 114 | self.vehicle.wheels = 2 115 | 116 | def make_doors(self): 117 | self.vehicle.doors = 0 118 | 119 | def make_seats(self): 120 | self.vehicle.seats = 2 121 | ``` 122 | 123 | The only logic in these is the creation of the `vehicle` member and setting the properties on it. Now to use these we need a Director class to call. For this example the Director is called `VehicleManufacturer`. 124 | 125 | ```python 126 | #============================================================================== 127 | class VehicleManufacturer(object): 128 | """ 129 | The director class, this will keep a concrete builder. 130 | """ 131 | 132 | def __init__(self): 133 | self.builder = None 134 | 135 | def create(self): 136 | """ 137 | Creates and returns a Vehicle using self.builder 138 | Precondition: not self.builder is None 139 | """ 140 | assert not self.builder is None, "No defined builder" 141 | self.builder.make_wheels() 142 | self.builder.make_doors() 143 | self.builder.make_seats() 144 | return self.builder.vehicle 145 | ``` 146 | 147 | `VehicleManufacturer` has a `create()` function which contains the calling code used with the Builder. However as you can see from the docstring and the code this has a precondition of self.builder being set. This means that if you have not given the Director a ConcreteBuilder to use, it cannot create a Vehicle. 148 | 149 | So this class is called like so. 150 | 151 | ```python 152 | #============================================================================== 153 | if (__name__ == "__main__"): 154 | manufacturer = VehicleManufacturer() 155 | 156 | manufacturer.builder = CarBuilder() 157 | car = manufacturer.create() 158 | car.view() 159 | 160 | manufacturer.builder = BikeBuilder() 161 | bike = manufacturer.create() 162 | bike.view() 163 | ``` 164 | 165 | The calling code uses one VehicleManufacturer to build both a car and a bike. The output of this code is given below. 166 | 167 | ```pycon 168 | This vehicle is a Car with; 4 wheels, 3 doors, and 5 seats. 169 | This vehicle is a Bike with; 2 wheels, 0 doors, and 2 seats. 170 | ``` 171 | 172 | The specific UML for this example is this. 173 | 174 | ![Specific Builder Pattern](Images/UML/Builder_specific.png "Specific Builder Pattern") 175 | 176 | ## Factory method ## 177 | ## Lazy initialization ## 178 | ## Multiton ## 179 | ## Object pool ## 180 | ## Prototype ## 181 | ## Resource acquisition is initialization ## 182 | ## Singleton ## 183 | 184 | # Structural # 185 | ## Adapter ## 186 | ## Bridge ## 187 | ## Composite ## 188 | ## Decorator ## 189 | ## Facade ## 190 | ### The Purpose ### 191 | 192 | The facade pattern is used to make one object with a simple interface represent a complicated system. The problem often occurs in programming where you have a series of interconnected classes where the functions must be called in a certain order or have complicated interdependencies. 193 | 194 | This pattern is to give a standard interface to such a system, so you don't have to rely on reading how you call the system in one of the files or look at example usage. 195 | 196 | ### The Pattern ### 197 | Here is a UML diagram representing the pattern. 198 | 199 | 202 | 203 | This shows that the pattern is just one class which groups together a lot of other objects and uses them in some way. 204 | 205 | ### An Example Usage### 206 | 207 | Here is an implementation of this in python. For this example I am using a car. As this is a small example I will not create a whole complex sub-system, just a few classes. 208 | 209 | Here are the three classes which make up the sub-system; Engine, StarterMotor and Battery. 210 | 211 | ```python 212 | #============================================================================== 213 | class Engine(object): 214 | 215 | def __init__(self): 216 | # how much the motor is spinning in revs per minute 217 | self.spin = 0 218 | 219 | def start(self, spin): 220 | if (spin > 2000): 221 | self.spin = spin // 15 222 | 223 | #============================================================================== 224 | class StarterMotor(object): 225 | 226 | def __init__(self): 227 | # how much the starter motor is spinning in revs per minute 228 | self.spin = 0 229 | 230 | def start(self, charge): 231 | # if there is enough power then spin fast 232 | if (charge > 50): 233 | self.spin = 2500 234 | 235 | #============================================================================== 236 | class Battery(object): 237 | 238 | def __init__(self): 239 | # % charged, starts flat 240 | self.charge = 0 241 | ``` 242 | 243 | So now we need our facade object which will work as a common interface for the car. 244 | 245 | ```python 246 | #============================================================================== 247 | class Car(object): 248 | # the facade object that deals with the battery, engine and starter motor. 249 | 250 | def __init__(self): 251 | self.battery = Battery() 252 | self.starter = StarterMotor() 253 | self.engine = Engine() 254 | 255 | def turn_key(self): 256 | # use the battery to turn the starter motor 257 | self.starter.start(self.battery.charge) 258 | 259 | # use the starter motor to spin the engine 260 | self.engine.start(self.starter.spin) 261 | 262 | # if the engine is spinning the car is started 263 | if (self.engine.spin > 0): 264 | print("Engine Started.") 265 | else: 266 | print("Engine Not Started.") 267 | 268 | def jump(self): 269 | self.battery.charge = 100 270 | print("Jumped") 271 | ``` 272 | 273 | This enables the user of Car to use the system as it was intended to be used. I include this at the bottom of the python module so that on running it makes a car, tries to start it, then jumps it to start it. 274 | 275 | ```python 276 | #============================================================================== 277 | if (__name__ == "__main__"): 278 | corsa = Car() 279 | corsa.turn_key() 280 | corsa.jump() 281 | corsa.turn_key() 282 | ``` 283 | 284 | The output of this program is the following; 285 | ```pycon 286 | Engine Not Started. 287 | Jumped 288 | Engine Started. 289 | ``` 290 | 291 | That is a simple example of the facade design pattern. The code for this is on my GitHub here. 292 | 293 | All of the code for this series can be found in this repository. 294 | ## Flyweight ## 295 | ## Front Controller ## 296 | ## Module ## 297 | ## Proxy ## 298 | ## Telescoping constructor ## 299 | 300 | # Behavioural # 301 | ## Blackboard ## 302 | ## Chain of Responsibility ## 303 | ## Command ## 304 | ## Data Caching ## 305 | ## Interpreter ## 306 | ## Iterator ## 307 | This post will be about the Iterator pattern which is a behavioural pattern. 308 | 309 | ###The Purpose### 310 | The idea behind this pattern is to have an object which you can loop over without needing to know the internal representation of the data. While in python nothing is private so you can find out the internals of the class, the iterator pattern gives you a standard interface. 311 | 312 | I think the best example of an iterator in python is using a list. As I'm sure you know this is how you would iterate over a list in python. 313 | ```python 314 | list = ["one", "two", "three"] 315 | for number in list: 316 | print(number) 317 | ``` 318 | 319 | Which gives the following output. 320 | ```pycon 321 | one 322 | two 323 | three 324 | ``` 325 | 326 | The goal of this pattern in python is to be able to do that with a user defined class. 327 | 328 | ### The Pattern ### 329 | 330 | This is quite a small pattern so I will focus more on the implementation detail than the design. 331 | 332 | But in the interest of consistency here is the UML diagram. 333 | 334 | ![General Iterator](Images/UML/Iterator_general.png "General Iterator") 335 | 336 | So the iterator holds a container by aggregation, so it doesn't own/delete the container, it just has a reference to it. 337 | 338 | You can see from this that we are talking about a general collection/data structure here so it is not specific to a list or dictionary for example. The methods on these classes are standard for defining a collection/iterator interface in python and I will go through these in more detail next. 339 | 340 | ###Python Protocols### 341 | 342 | Protocols are the name given in python, for the interface you need to make a certain kind of object. These are not a formal requirement like an interface, but more of a guide. 343 | 344 | They focus on pythons magic methods, which are those with double underscores surrounding the name. 345 | 346 | I'm going to briefly talk about the protocols for mutable and immutable containers, and iterators. 347 | 348 | ####Immutable Containers#### 349 | 350 | An immutable container is one where you cannot change the individual items. For this you only need to be able to get the length of it and access individual items. 351 | 352 | The python magic methods for an immutable container are. 353 | 354 | ```python 355 | def __len__(self): 356 | """ Returns the length of the container. Called like len(class) """ 357 | def __getitem__(self, key): 358 | """ 359 | Returns the keyth item in the container. 360 | Should raise TypeError if the type of the key is wrong. 361 | Should raise KeyError if there is no item for the key. 362 | Called like class[key] 363 | """ 364 | ``` 365 | 366 | Again the exceptions mentioned in __getitem__ are convention, but it's important for writing idiomatic python. 367 | 368 | ####Mutable Containers#### 369 | As you might expect a mutable container has the same methods for accessing items as an immutable container, but adds ways of setting or adding them. 370 | 371 | Here are the magic methods. 372 | 373 | ```python 374 | def __len__(self): 375 | """ 376 | Returns the length of the container. 377 | Called like len(class) 378 | """ 379 | def __getitem__(self, key): 380 | """ 381 | Returns the keyth item in the container. 382 | Should raise TypeError if the type of the key is wrong. 383 | Should raise KeyError if there is no item for the key. 384 | Called like class[key] 385 | """ 386 | 387 | def __setitem__(self, key, value): 388 | """ 389 | Sets the keyth item in the container. 390 | Should raise TypeError if the type of the key is wrong. 391 | Should raise KeyError if there is no item for the key. 392 | Called like class[key] = value 393 | """ 394 | 395 | def __delitem__(self, key): 396 | """ 397 | Deletes an item from the collection. 398 | Should raise TypeError if the type of the key is wrong. 399 | Should raise KeyError if there is no item for the key. 400 | Called like del class[key] 401 | """ 402 | ``` 403 | 404 | For an immutable container you probably want to have some way of adding elements too. For a list/array style container this might be in the form of a function append(self, key) or for a dictionary/table type container it might be using the __setitem__(self, key, value) function. 405 | 406 | There are other functions you can add such as __reversed__(self) and __contains__(self, item) but they are not needed for core functionality. They are very well described here. 407 | 408 | ####Iterators#### 409 | 410 | The protocol for an iterator is very simple. 411 | 412 | ```python 413 | def __iter__(self): 414 | """ 415 | Returns an iterator for the collection. 416 | Called like iter(class) or for item in class: 417 | """ 418 | def next(self): 419 | """ 420 | Returns the next item in the collection. 421 | Called in a for loop, or manually. 422 | Should raise StopIteration on the last item. 423 | """ 424 | ``` 425 | The __iter__ function will typically return another iterator object or return itself. Note in python 3 next() is renamed __next__(). 426 | 427 | ###An Example Usage### 428 | Here is an example of how you might implement a simple iterator. My iterator will loop over a collection in reverse. 429 | 430 | ```python 431 | #============================================================================== 432 | class ReverseIterator(object): 433 | """ 434 | Iterates the object given to it in reverse so it shows the difference. 435 | """ 436 | 437 | def __init__(self, iterable_object): 438 | self.list = iterable_object 439 | # start at the end of the iterable_object 440 | self.index = len(iterable_object) 441 | 442 | def __iter__(self): 443 | # return an iterator 444 | return self 445 | 446 | def next(self): 447 | """ Return the list backwards so it's noticeably different.""" 448 | if (self.index == 0): 449 | # the list is over, raise a stop index exception 450 | raise StopIteration 451 | self.index = self.index - 1 452 | return self.list[self.index] 453 | ``` 454 | 455 | Note this only has the two functions needed for the iterator protocol, as these are all that are needed. 456 | 457 | Hopefully it is fairly obvious what these functions do. The constructor takes some iterable and caches it's length as an index. The __iter__ returns the ReverseIterator as it has a next() function. The next function decrements the index and returns that item, unless there is no item to return at which point it raise StopIteration. 458 | 459 | In my example the ReverseIterator is used by a Days class, given here. 460 | 461 | ```python 462 | #============================================================================== 463 | class Days(object): 464 | 465 | def __init__(self): 466 | self.days = [ 467 | "Monday", 468 | "Tuesday", 469 | "Wednesday", 470 | "Thursday", 471 | "Friday", 472 | "Saturday", 473 | "Sunday" 474 | ] 475 | 476 | def reverse_iter(self): 477 | return ReverseIterator(self.days) 478 | ``` 479 | 480 | This is just a wrapper for a list of the days of the week, which has a function to return an iterator. 481 | 482 | This is how these classes are used. 483 | 484 | ```python 485 | #============================================================================== 486 | if (__name__ == "__main__"): 487 | days = Days() 488 | for day in days.reverse_iter(): 489 | print(day) 490 | ``` 491 | 492 | Note I could have used a __iter__() function in Days instead of reverse_iter(). This would then have been used like this. 493 | 494 | ```python 495 | #============================================================================== 496 | if (__name__ == "__main__"): 497 | days = Days() 498 | for day in days: 499 | print(day) 500 | ``` 501 | 502 | My reason for not doing this is that this does not make it clear that you are reversing it. 503 | 504 | The output from either of these is. 505 | 506 | ```pycon 507 | Sunday 508 | Saturday 509 | Friday 510 | Thursday 511 | Wednesday 512 | Tuesday 513 | Monday 514 | ``` 515 | 516 | This code for this can be found in this file. 517 | 518 | Here is the specific UML for these classes. 519 | 520 | ![Specific Iterator](Images/UML/Iterator_specific.png "Specific Iterator") 521 | 522 | All of the code for the design patterns can be found here. 523 | 524 | ## Mediator ## 525 | ## Memento ## 526 | ## Null object ## 527 | ## Observer (Publish/Subscribe) ## 528 | ## State ## 529 | ## Servant ## 530 | ## Specification ## 531 | ## Strategy ## 532 | The strategy pattern is a behavioural design pattern. This means that it is a common communication method between objects and not concerned with how those objects are created or structured. 533 | 534 | ### The Purpose ### 535 | 536 | The idea behind the strategy pattern is to encapsulate the implementation details of an algorithm and make them interchangeable. This gives more flexibility in how an object can be used and enables each algorithm to be tested independently of the calling object. 537 | 538 | ### The Pattern ### 539 | 540 | Here is a UML diagram of a typical use of the strategy pattern. 541 | 542 | ![Strategy Pattern](Images/UML/Strategy_general.jpg "Strategy Pattern") 543 | 544 | This shows an interface (an unimplemented class denoted by <<>>) called IAlgorithm. The caller class will contain an instance of an object which implements this interface using aggregation. This means that the lifetime of the algorithms is not necessarily tied to the caller. The implemented algorithm classes derive from the interface. 545 | 546 | ### An Example Usage### 547 | 548 | I will give two implementations of this pattern; one which is more standard and will closely match the above UML and one which I believe is more pythonic. 549 | 550 | #### First Example #### 551 | 552 | My example will be using two different algorithms to calculate prime numbers. 553 | 554 | Here is the caller class PrimeFinder. 555 | 556 | ```python 557 | #============================================================================== 558 | class PrimeFinder(object): 559 | 560 | def __init__(self, algorithm): 561 | """ 562 | Constructor, takes a lass called algorithm. 563 | algorithm should have a function called calculate which will take a 564 | limit argument and return an iterable of prime numbers below that 565 | limit. 566 | """ 567 | self.algorithm = algorithm 568 | self.primes = [] 569 | 570 | def calculate(self, limit): 571 | """ Will calculate all the primes below limit. """ 572 | self.primes = self.algorithm.calculate(limit) 573 | 574 | def out(self): 575 | """ Prints the list of primes prefixed with which algorithm made it """ 576 | print(self.algorithm.name) 577 | for prime in self.primes: 578 | print(prime) 579 | print("") 580 | ``` 581 | 582 | This will be given an algorithm on construction, the contract for the algorithm is that it must have a function called calculate which takes a limit. This is explained in the docstring for __init__ but in a statically typed language such as c++ the constructor would look something like this. 583 | 584 | ```python 585 | PrimeFinder(const IAlgorithm& algorithm); 586 | ``` 587 | 588 | We can not (and should not) enforce what is passed into the constructor in python. However we can pass in objects derived from an abstract base class so that our algorithms will definitely work. The functionality of abstract base classes is importable in python from the module abc. So we need this at the top of the file. 589 | 590 | ```python 591 | import abc 592 | ``` 593 | 594 | Here is the base class I will be using in this example. 595 | 596 | ```python 597 | #============================================================================== 598 | class Algorithm(object): 599 | __metaclass__ = abc.ABCMeta 600 | 601 | @abc.abstractmethod 602 | def calculate(self, limit): 603 | raise 604 | ``` 605 | 606 | This defines an abstract base class Algorithm. The __metaclass__ line changes the way this class is constructed. If you derive from it and you have not implemented the functions decorated with @abc.abstractmethod the following error will be raised. 607 | 608 | ```python 609 | TypeError: Can't instantiate abstract class [class] with abstract methods [methods] 610 | ``` 611 | 612 | This enforces the contract that any class deriving from Algorithm will be able to be used in PrimeFinder. So now we can derive our algorithms from this with the following two classes. 613 | 614 | ```python 615 | #============================================================================== 616 | class HardCoded(Algorithm): 617 | """ 618 | Has hardcoded values for all the primes under 50, returns a list of those 619 | which are less than the given limit. 620 | """ 621 | 622 | def __init__(self): 623 | self.name = "hard_coded_algorithm" 624 | 625 | def calculate(self, limit): 626 | hardcoded_primes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47] 627 | primes = [] 628 | for prime in hardcoded_primes: 629 | if (prime < limit): 630 | primes.append(prime) 631 | return primes 632 | 633 | #============================================================================== 634 | class Standard(Algorithm): 635 | """ 636 | Not a great algorithm either, but it's the normal one to use. 637 | It puts 2 in a list, then for all the odd numbers less than the limit if 638 | none of the primes are a factor then add it to the list. 639 | """ 640 | 641 | def __init__(self): 642 | self.name = "standard_algorithm" 643 | 644 | def calculate(self, limit): 645 | primes = [2] 646 | # check only odd numbers. 647 | for number in range(3, limit, 2): 648 | is_prime = True 649 | # divide it by all our known primes, could limit by sqrt(number) 650 | for prime in primes: 651 | if (number % prime == 0): 652 | is_prime = False 653 | break 654 | if (is_prime): 655 | primes.append(number) 656 | return primes 657 | ``` 658 | 659 | I am not going to go through all the details of these two classes as that is not what this post is about, I'll just give an overview. 660 | 661 | The first has a hard-coded list and returns all the primes it knows about below the limit you give. 662 | 663 | The second makes a list of primes then iterates over all the odd numbers less than the limit and if none of the primes on the list divide the number it adds it to the list of primes. 664 | 665 | The following is the calling code included at the bottom of this file 666 | 667 | ```python 668 | #============================================================================== 669 | if (__name__ == "__main__"): 670 | hard_coded_algorithm = HardCoded() 671 | hardcoded_primes = PrimeFinder(hard_coded_algorithm) 672 | hardcoded_primes.calculate(50) 673 | hardcoded_primes.out() 674 | 675 | standard_algorithm = Standard() 676 | standard_primes = PrimeFinder(standard_algorithm) 677 | standard_primes.calculate(50) 678 | standard_primes.out() 679 | ``` 680 | 681 | So you instantiate an algorithm, pass it to the prime finder then call the methods on it and the algorithm you gave it will be used. This code does not show that, as these algorithms give the same results on a limit of 50. The code afterwards shows that they are different. 682 | 683 | ```python 684 | print( 685 | "Do the two algorithms get the same result on 50 primes? %s" 686 | %(str(hardcoded_primes.primes == standard_primes.primes)) 687 | ) 688 | 689 | # the hardcoded algorithm only works on numbers under 50 690 | hardcoded_primes.calculate(100) 691 | standard_primes.calculate(100) 692 | 693 | print( 694 | "Do the two algorithms get the same result on 100 primes? %s" 695 | %(str(hardcoded_primes.primes == standard_primes.primes)) 696 | ) 697 | ``` 698 | 699 | Which prints the following 700 | 701 | ```python 702 | Do the two algorithms get the same result on 50 primes? True 703 | Do the two algorithms get the same result on 100 primes? False 704 | ``` 705 | 706 | Here is the specific UML diagram for these. 707 | 708 | ![Specific Strategy Pattern](Images/UML/Strategy_general.jpg "Specific Strategy") 709 | 710 | The code for this implementation is found in this file. 711 | 712 | __Second Example__ 713 | 714 | The previous example is more how you would implement this pattern in a statically typed language such as C++ or C#. This is a more pythonic approach which will take some advise I heard in a talk; classes with only a __init__ function and one other function are functions in disguise. As functions are first class objects in python there is a lot we can do with them and I believe this gives a more pythonic variant of the strategy pattern. 715 | 716 | Here is the calling class. 717 | 718 | ```python 719 | #============================================================================== 720 | class PrimeFinder(object): 721 | 722 | def __init__(self, algorithm): 723 | """ 724 | Constructor, takes a callable object called algorithm. 725 | algorithm should take a limit argument and return an iterable of prime 726 | numbers below that limit. 727 | """ 728 | self.algorithm = algorithm 729 | self.primes = [] 730 | 731 | def calculate(self, limit): 732 | """ Will calculate all the primes below limit. """ 733 | self.primes = self.algorithm(limit) 734 | 735 | def out(self): 736 | """ Prints the list of primes prefixed with which algorithm made it """ 737 | print(self.algorithm.__name__) 738 | for prime in self.primes: 739 | print(prime) 740 | print("") 741 | ``` 742 | 743 | There are a few differences between this and the class PrimeFinder in the previous example. This one asks for a callable object rather than an object with a calculate method, this is reflected in the implementation of calculate where it calls the passed in object. The other difference is that it does not need a property name it uses the python built in __name__. 744 | 745 | This means we can pass functions into the class. Here are two functions which correspond to the previous classes. 746 | 747 | ```python 748 | #============================================================================== 749 | def hard_coded_algorithm(limit): 750 | """ 751 | Has hardcoded values for all the primes under 50, returns a list of those 752 | which are less than the given limit. 753 | """ 754 | hardcoded_primes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47] 755 | primes = [] 756 | for prime in hardcoded_primes: 757 | if (prime < limit): 758 | primes.append(prime) 759 | return primes 760 | 761 | #============================================================================== 762 | def standard_algorithm(limit): 763 | """ 764 | Not a great algorithm either, but it's the normal one to use. 765 | It puts 2 in a list, then for all the odd numbers less than the limit if 766 | none of the primes are a factor then add it to the list. 767 | """ 768 | primes = [2] 769 | # check only odd numbers. 770 | for number in range(3, limit, 2): 771 | is_prime = True 772 | # divide it by all our known primes, could limit by sqrt(number) 773 | for prime in primes: 774 | if (number % prime == 0): 775 | is_prime = False 776 | break 777 | if (is_prime): 778 | primes.append(number) 779 | return primes 780 | ``` 781 | 782 | These are called in almost the same way as using the classes. 783 | 784 | ```python 785 | #============================================================================== 786 | if (__name__ == "__main__"): 787 | hardcoded_primes = PrimeFinder(hard_coded_algorithm) 788 | hardcoded_primes.calculate(50) 789 | hardcoded_primes.out() 790 | 791 | standard_primes = PrimeFinder(standard_algorithm) 792 | standard_primes.calculate(50) 793 | standard_primes.out() 794 | ``` 795 | 796 | As this can take any callable object this can also be a class. A class in python is also a callable object. When a class is called an object is made so the function in PrimeFinder 797 | 798 | ```python 799 | def calculate(self, limit): 800 | """ Will calculate all the primes below limit. """ 801 | self.primes = self.algorithm(limit) 802 | ``` 803 | 804 | will set self.primes to an instance of the class. As out() needs only that the object self.primes is iterable we can use the python magic method __iter__ to make that so. 805 | 806 | Here is the implementation of a class which can be passed in to PrimeFinder. 807 | 808 | ```python 809 | #============================================================================== 810 | class HardCodedClass(object): 811 | 812 | def __init__(self, limit): 813 | hardcoded_primes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47] 814 | self.primes = [] 815 | for prime in hardcoded_primes: 816 | if (prime < limit): 817 | self.primes.append(prime) 818 | 819 | def __iter__(self): 820 | return iter(self.primes) 821 | ``` 822 | 823 | This is called in the following way. 824 | 825 | ```python 826 | class_primes = PrimeFinder(HardCodedClass) 827 | class_primes.calculate(50) 828 | class_primes.out() 829 | ``` 830 | 831 | Note that HardCodedClass has no brackets after it, this is because the class is being passed in as an object not an instance of the class. 832 | 833 | Another thing to note is that although I have used different instances of PrimeFinder in my calling code I could have just set the property algorithm in the following way. 834 | 835 | ```python 836 | #============================================================================== 837 | if (__name__ == "__main__"): 838 | prime_finder = PrimeFinder(hard_coded_algorithm) 839 | prime_finder.calculate(50) 840 | prime_finder.out() 841 | 842 | prime_finder.algorithm = standard_algorithm 843 | prime_finder.calculate(50) 844 | prime_finder.out() 845 | 846 | prime_finder.algorithm = HardCodedClass 847 | prime_finder.calculate(50) 848 | prime_finder.out() 849 | ``` 850 | 851 | The code for this specific file can be found here. All of the code for the design patterns can be found here. 852 | ## Template ## 853 | ## Visitor ## 854 | 855 | # UI Patterns # 856 | ## Model View Presenter ## 857 | ## Model View Controller ## 858 | ## Model View View-Model ## 859 | ## Model View Adapter ## 860 | ## Presentation Abstraction Control ## 861 | 862 | --------------------------------------------------------------------------------