Vanlige designmønstre som Singleton, Observer og Factory.
Designmønster
Mange programmeringsproblem dukkar opp att og att. I staden for å finne opp løysinga kvar gong, kan vi bruke designmønster – bevisste oppskrifter for vanlege utfordringar.
Designmønster er ikkje ferdig kode du kan kopiere, men konseptuelle løysingar du tilpassar din situasjon. Vi skal sjå på tre grunnleggjande mønster: Observer, Strategy og Factory.
Fordelar med designmønster:
- Gir eit felles språk for utviklarar
- Dokumenterer beste praksis
- Gjer kode meir vedlikehaldbar og fleksibel
- Løyser vanlege problem effektivt
Designmønster vart populære gjennom boka "Design Patterns" (1994) av "Gang of Four".
Eksempel: Observer-mønsteret
Problem: Du vil at fleire objekt skal varslast automatisk når noko vert endra.
Løysing: Observer-mønsteret lèt objekt "abonnere" på hendingar.
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 vert brukt i GUI-frameworks, hendingssystem og reaktiv 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!Fleire displaylar vert oppdaterte automatisk når temperaturen vert endra!
Problem: Du har forskjellige måtar å gjere noko på, og vil enkelt kunne byte mellom dei.
Løysing: Lag ein strategy-klasse for kvar algoritme, og lat konteksten bruke den valde strategien.
Dette er komposisjon i aksjon: i staden for å arve oppførsel, "har" objektet ein 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 gjer det enkelt å leggje til nye algoritmar utan å endre eksisterande kode.
Eksempel: Strategy med praktisk bruk – Betalingsmetodar
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 å leggje til nye betalingsmåtar utan å endre Handlekurv!
Problem: Du vil skjule kompleks objektoppretting eller bestemme kva klasse som skal instansierast basert på input.
Løysing: Lag ein factory-funksjon eller -klasse som tek imot parametrar og returnerer rett objekttype.
Dette gjer kode meir fleksibel og enklare å 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 ved 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 å leggje til nye rapportformat ved å oppdatere formater-ordboka!
Oppsummering
I dette kapittelet har du lært:
- Designmønster er bevisste løysingar på vanlege problem
- Observer lèt objekt abonnere på hendingar (laus kopling)
- Strategy gjer algoritmar utskiftbare ved runtime (fleksibilitet)
- Factory skjuler objektopprettingslogikk (innkapsling)
Desse mønstera er fundamentale i profesjonell programvareutvikling og dukkar opp i nesten alle store system.
Neste kapittel: Vi lærer UML for å visualisere desse mønstera!
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.