Vanlige designmønstre som Singleton, Observer og Factory.
Designmønstre
Mange programmeringsproblemer dukker opp igjen og igjen. I stedet for å finne opp løsningen hver gang, kan vi bruke designmønstre – beprøvde oppskrifter for vanlige utfordringer.
Designmønstre er ikke ferdig kode du kan kopiere, men konseptuelle løsninger du tilpasser din situasjon. Vi skal se på tre grunnleggende mønstre: Observer, Strategy og Factory.
Fordeler med designmønstre:
- Gir et felles språk for utviklere
- Dokumenterer beste praksis
- Gjør kode mer vedlikeholdbar og fleksibel
- Løser vanlige problemer effektivt
Designmønstre ble populære gjennom boken "Design Patterns" (1994) av "Gang of Four".
Eksempel: Observer-mønsteret
Problem: Du vil at flere objekter skal varsles automatisk når noe endres.
Løsning: Observer-mønsteret lar objekter "abonnere" på hendelser.
class Subject:
"""Den som observeres."""
def __init__(self):
self._observers = []
def attach(self, observer):
"""Legger til en observer."""
self._observers.append(observer)
def detach(self, observer):
"""Fjerner en observer."""
self._observers.remove(observer)
def notify(self, data):
"""Varsler alle observers."""
for observer in self._observers:
observer.update(data)
class Observer:
"""Baseklasse for observers."""
def update(self, data):
"""Kalles når subject endres."""
pass
# Konkrete observers
class EmailObserver(Observer):
def update(self, data):
print(f"📧 Sender e-post: {data}")
class SMSObserver(Observer):
def update(self, data):
print(f"📱 Sender SMS: {data}")
# Bruk
subject = Subject()
subject.attach(EmailObserver())
subject.attach(SMSObserver())
subject.notify("Ny melding mottatt!")
# Output:
# 📧 Sender e-post: Ny melding mottatt!
# 📱 Sender SMS: Ny melding mottatt!Observer-mønsteret brukes i GUI-frameworks, hendelsessystemer og reaktive programmering.
Eksempel: Observer med praktisk bruk – Værvarsel
class Værstasjon(Subject):
def __init__(self):
super().__init__()
self._temperatur = 0
def sett_temperatur(self, temp):
self._temperatur = temp
self.notify(f"Ny temperatur: {temp}°C")
def get_temperatur(self):
return self._temperatur
class TermometerDisplay(Observer):
def __init__(self, navn):
self.navn = navn
def update(self, data):
print(f"{self.navn}: {data}")
class VarslingSystem(Observer):
def update(self, data):
if "temperatur" in data.lower():
temp = int(data.split(": ")[1].split("°")[0])
if temp < 0:
print("⚠️ VARSEL: Fare for glatt vei!")
# Bruk
stasjon = Værstasjon()
stasjon.attach(TermometerDisplay("Hjem"))
stasjon.attach(TermometerDisplay("Skole"))
stasjon.attach(VarslingSystem())
stasjon.sett_temperatur(-5)
# Output:
# Hjem: Ny temperatur: -5°C
# Skole: Ny temperatur: -5°C
# ⚠️ VARSEL: Fare for glatt vei!Flere displayer oppdateres automatisk når temperaturen endres!
Problem: Du har forskjellige måter å gjøre noe på, og vil enkelt kunne bytte mellom dem.
Løsning: Lag en strategy-klasse for hver algoritme, og la konteksten bruke den valgte strategy.
Dette er komposisjon i aksjon: i stedet for å arve oppførsel, "har" objektet en strategy det delegerer til.
Eksempel: Strategy-mønsteret – Sortering
class SorteringsStrategy:
"""Baseklasse for sorteringsstrategier."""
def sorter(self, data):
pass
class BubbleSortStrategy(SorteringsStrategy):
def sorter(self, data):
print("Sorterer med Bubble Sort")
return sorted(data) # Forenklet
class QuickSortStrategy(SorteringsStrategy):
def sorter(self, data):
print("Sorterer med Quick Sort")
return sorted(data) # Forenklet
class DataSorter:
"""Kontekst som bruker en strategy."""
def __init__(self, strategy):
self._strategy = strategy
def sett_strategy(self, strategy):
"""Bytt strategy ved runtime!"""
self._strategy = strategy
def sorter_data(self, data):
return self._strategy.sorter(data)
# Bruk
tall = [5, 2, 8, 1, 9]
sorter = DataSorter(BubbleSortStrategy())
print(sorter.sorter_data(tall))
# Output: Sorterer med Bubble Sort
# [1, 2, 5, 8, 9]
# Bytt strategi ved runtime
sorter.sett_strategy(QuickSortStrategy())
print(sorter.sorter_data(tall))
# Output: Sorterer med Quick Sort
# [1, 2, 5, 8, 9]Strategy gjør det enkelt å legge til nye algoritmer uten å endre eksisterende kode.
Eksempel: Strategy med praktisk bruk – Betalingsmetoder
class BetalingsStrategy:
def betal(self, beløp):
pass
class KredittkortStrategy(BetalingsStrategy):
def __init__(self, kortnummer):
self.kortnummer = kortnummer
def betal(self, beløp):
print(f"💳 Betaler {beløp} kr med kredittkort ****{self.kortnummer[-4:]}")
class VippsStrategy(BetalingsStrategy):
def __init__(self, telefon):
self.telefon = telefon
def betal(self, beløp):
print(f"📱 Betaler {beløp} kr med Vipps ({self.telefon})")
class KontantStrategy(BetalingsStrategy):
def betal(self, beløp):
print(f"💵 Betaler {beløp} kr kontant")
class Handlekurv:
def __init__(self):
self._varer = []
self._betalingsstrategy = None
def legg_til(self, vare, pris):
self._varer.append((vare, pris))
def sett_betalingsmåte(self, strategy):
self._betalingsstrategy = strategy
def betal(self):
total = sum(pris for _, pris in self._varer)
self._betalingsstrategy.betal(total)
# Bruk
kurv = Handlekurv()
kurv.legg_til("Bok", 299)
kurv.legg_til("Penn", 19)
kurv.sett_betalingsmåte(VippsStrategy("12345678"))
kurv.betal() # 📱 Betaler 318 kr med Vipps (12345678)Enkelt å legge til nye betalingsmåter uten å endre Handlekurv!
Problem: Du vil skjule kompleks objektoppretting eller bestemme hvilken klasse som skal instansieres basert på input.
Løsning: Lag en factory-funksjon eller -klasse som tar imot parametere og returnerer riktig objekttype.
Dette gjør kode mer fleksibel og enklere å utvide.
Eksempel: Factory-mønsteret – Dyrefabrikk
class Dyr:
def lag_lyd(self):
pass
class Hund(Dyr):
def lag_lyd(self):
return "Voff!"
class Katt(Dyr):
def lag_lyd(self):
return "Mjau!"
class Ku(Dyr):
def lag_lyd(self):
return "Mø!"
# Factory-funksjon
def dyrefabrikk(dyretype):
"""Fabrikk som lager riktig dyreobject."""
if dyretype == "hund":
return Hund()
elif dyretype == "katt":
return Katt()
elif dyretype == "ku":
return Ku()
else:
raise ValueError(f"Ukjent dyretype: {dyretype}")
# Bruk
dyr1 = dyrefabrikk("hund")
print(dyr1.lag_lyd()) # Voff!
dyr2 = dyrefabrikk("katt")
print(dyr2.lag_lyd()) # Mjau!
# Klienten trenger ikke å vite om Hund-, Katt- eller Ku-klassene!
# Bare dyrefabrikk() trenger å oppdateres hvis nye dyr legges til.Factory skjuler kompleksiteten av objektoppretting.
Eksempel: Factory med praktisk bruk – Rapportgenerator
class Rapport:
def generer(self, data):
pass
class PDFRapport(Rapport):
def generer(self, data):
return f"📄 PDF-rapport: {data}"
class ExcelRapport(Rapport):
def generer(self, data):
return f"📊 Excel-rapport: {data}"
class HTMLRapport(Rapport):
def generer(self, data):
return f"🌐 HTML-rapport: {data}"
class RapportFabrikk:
@staticmethod
def lag_rapport(format):
"""Statisk factory-metode."""
formater = {
"pdf": PDFRapport,
"excel": ExcelRapport,
"html": HTMLRapport
}
rapport_klasse = formater.get(format.lower())
if rapport_klasse:
return rapport_klasse()
else:
raise ValueError(f"Ukjent format: {format}")
# Bruk
data = "Salgsdata for Q4"
rapport = RapportFabrikk.lag_rapport("pdf")
print(rapport.generer(data)) # 📄 PDF-rapport: Salgsdata for Q4
rapport = RapportFabrikk.lag_rapport("excel")
print(rapport.generer(data)) # 📊 Excel-rapport: Salgsdata for Q4Enkelt å legge til nye rapportformater ved å oppdatere formater-ordboken!
Oppsummering
I dette kapittelet har du lært:
- Designmønstre er beprøvde løsninger på vanlige problemer
- Observer lar objekter abonnere på hendelser (løs kobling)
- Strategy gjør algoritmer utskiftbare ved runtime (fleksibilitet)
- Factory skjuler objektopprettingslogikk (enkapsulering)
Disse mønstrene er fundamentale i profesjonell programvareutvikling og dukker opp i nesten alle store systemer.
Neste kapittel: Vi lærer UML for å visualisere disse mønstrene!
Dette kapitlet er skrevet av Anthropics toppmodeller (Claude Opus og Claude Fable) og er foreløpig ikke manuelt gjennomgått — kvalitetskontrollen gjøres av uavhengige KI-agenter, og innmeldte feil rettes fortløpende. Funnet en feil? Meld fra, så retter vi den. Les mer om hvordan innholdet lages.