Zum Hauptinhalt springen

Design 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.