Design Patterns
- with short self explanatory code examples
- alternative for golang examples see here : https://github.com/tmrts/go-patterns
Creational Patterns
- Concern the process of object creation
Factory
class Burger:
def __init__(self, ingredients):
self.ingredients = ingredients
def make(self):
print(self.ingredients)
class BurgerFactory:
def createcheeseBurger(self):
ingredients = ["patty", "cheese", "patty"]
return Burger(ingredients)
def createVeganBurger(self):
ingredients = ["patty", "lettuce", "vegan-patty"]
return Burger(ingredients)
def createDeluxeBurger(self):
ingredients = ["patty", "cheese", "patty", "secret-sauce"]
return Burger(ingredients)
BuFa = BurgerFactory()
BuFa.createCheeseBurger().make()
BuFa.createDeluxeBurger().make()
Builder Patterns
class Burger:
def __init__(self):
self.buns = None
self.patty = None
self.cheese = None
def setBuns(self, bunStyle):
self.buns = bunStyle
def setPatty(self, pattyStyle):
self.buns = pattyStyle
def setCheese(self, cheeseStyle):
self.buns = cheeseStyle
class BurgerBuilder:
def __init__(self):
self.burger = Burger()
def addBuns(self, bunStyle):
self.burger.setBuns(bunStyle)
return self
def addPatty(self, pattyStyle):
self.burger.setPatty(pattyStyle)
return self
def addCheese(self, cheeseStyle):
self.burger.setCheese(cheeseyStyle)
return self
def build(self):
return self.burger
burger = BurgerBuilder()
.addBuns("sesame")
.addPatty("vegan-patty")
.addCheese("american cheese")
.build()
Singleton
- ex shared "global space"
- or how one could implement shared mutable state like react
[count, setCount] = useState(0)
behind the scenes
class ApplicationState:
instance = None
def __init(self):
self.isLoggedIn = False
@staticmethod
def getAppState():
# check if there already is another Instance running:
if not ApplicationState.instance:
# if not we create one
ApplicationState.instance = ApplicationState()
# then we create the already existing/freshly created Instance
return ApplicationState.instance
appState1 = ApplicationState.getAppState()
print(appState1.isLoggedIn) # prints False
appState2 = ApplicationState.getAppState()
appState1.isLoggedIn = True
print(appState1.isLoggedIn) # prints True
print(appState2.isLoggedIn) # prints True
# since appState 1 and 2 both "share" the one truth of the isLoggedIn state
Behavioral Patterns
- Characterize the ways in which classes or objects interact and distribute responsibility.
Observer
- aka Publisher & Subscriber - Pattern
- example Youtube subscriber notifications
# the Publisher
class YoutubeChannel:
def __init__(self, name):
self.name = name
self.subscribers = []
def subscribe (self, sub):
self.subscribers.append(sub)
def notify(self, event):
for sub in self.subscribers:
sub.sendNotification(self.name, event)
# define the Subscriber interface:
from abc import ABC, abstractmethod
class YoutubeSubscribers(ABC):
@abstractmethod
def sendNotification(self, event):
pass
# One of the Subscribers using the above interface
class YoubeUser(YoutubeSubscriber):
def __init__(self, name):
self.name = name
def sendNotification(self, channel, event):
print(f"User {self.name} received notification from {channel}: new {event} is available!")
# example
channel = YoutubeChannel("Cats'nDogs")
channel.subscribe(YoutubeUser("Paul#123"))
channel.subscribe(YoutubeUser("Dan#653"))
channel.subscribe(YoutubeUser("Lana#574"))
channel.notify("A new Cat-Video")
Iterator
- example "linked list" or "binary search tree"
class ListNode:
def __init__(self, val):
self.val = val
self.next = None
class LinkedList:
def __init__(self, head):
self.head = head
self.cur = None
# define Iterator
def __iter__(self):
self.cur = self.head
return self
# Iterate to next
def __next__(self):
if self.cur:
val = self.cur.val
self.cur = self.cur.next
return val
else:
raise StopIteration
# fill a list
head = ListNode(1)
head.next = ListNode(2)
head.next.next = ListNode(3)
myList = LinkedList(head)
# iterate trough said list
for n in myList:
print(n)
Strategy Pattern - Open-Closed Principle
- Basically all it says make the "thing" an abstractmehod or interface. And do the Logic below/trough that interface.
- open for extension & closed for modification
- ex. filter functionality. Easy to add additional functinaly (ex. filter by isPrime() or by isSmallerAbs100() etc...)
for abc import ABC, abstractmethod
class FilterStrategy(ABC):
@abstractmethod
def removeValue(self, val):
pass
class RemoveNegativeStrategy(FilterStrategy):
def removeValue(self, val):
return val < 0
class RemoveOddStrategy(FilterStrategy):
def removeValue(self, val):
return abs(val)%2
class Values:
def __init__(self, vals):
if not strategy.removeValue(n):
res.appen(n)
return res
values = Values({-7, -4, -1, 0, 2, 6, 9})
print(values.filter(RemoveNegativeStrategy())) # [ 0, 2, 6, 9]
print(values.filter(RemoveOddStrategy())) # [-4, 0, 2, 6]
Structural Patterns
- Deal with the composition of objects or classes.
Adapter
class UsbCable:
def __init__(self):
self.isPlugged = False
def plugUsb(self):
self.isPlugged = True
class UsbPort:
def __init__(self):
self.portAvailable = True
def plug(self, usb):
if self.portvailable:
usb.plugUsb()
self.portAvailable = False
# usbcables can plug directly only into usb ports
usbCable = UsbCable()
usbPort1 = UsbPort()
usbPort1.plug(usbCable)
class MicroUsbCable:
def __init__(self):
self.isPlugged = False
def plugMicroUsb(self):
self.isPlugged = True
class MicroToUsbAdapter(UsbCable):
def __init__(self, microUsbCable):
self.microUsbCable = microUsbCable
self.microUsbCable.plugMicroUsb()
# could override Usb.Cble.plugUsb() if needed, but not needed in this case
# mow MicroUsb and Usb can connect via an adapter
microToUsbAdapter = MicroToUsbAdapter(MicroUsbCable())
usbPort2 = UsbPort()
usbPort2.plug(microToUsbAdapter)
Facade
- wrapper class to abstract lower level details away from the user.
- ex. API that is exposed over some http requests
- the SQL and whatever else is hidden behind those simple API calls.
Dependency Injection
Not a design pattern
initself but worth a sidenote.
Injecting Classes that provide (additional) functionality. For example add a optional Sorting Algorithm to some class that deals with some data stored in tables.
This is useful because we can decouple dependencies. For example some handlerFunction could use the Logger class
in itself. But now it would be dependent to any chances happening there. Instead we can just decide, pass in any optional Logger class that implements some fitting Interface and we use that for logging.