Tilbake
2.4
Designmønstre

2.4 Designmønstre

Vanlige designmønstre som Singleton, Observer og Factory.

65 min
6 oppgaver
DesignmønstreSingletonObserverFactory
Du leser den tradisjonelle versjonen
Din fremgang i kapitlet
0 / 6 oppgaver

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.

Definisjon
Designmønster er en generell, gjenbrukbar løsning på et vanlig problem i programvaredesign. Det er en mal eller oppskrift, ikke ferdig kode.

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

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

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!

Definisjon
Strategy-mønsteret lar deg definere en familie av algoritmer, innkapsle hver av dem, og gjøre dem utskiftbare.

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

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

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!

Definisjon
Factory-mønsteret brukes til å opprette objekter uten å spesifisere den eksakte klassen.

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

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

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 Q4

Enkelt å 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.