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

Definisjon
Designmønster er ei generell, gjenbrukbar løysing på eit vanleg problem i programvaredesign. Det er ein mal eller oppskrift, ikkje ferdig kode.

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

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

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!

Definisjon
Strategy-mønsteret lèt deg definere ein familie av algoritmar, kapsle inn kvar av dei, og gjere dei utskiftbare.

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

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

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!

Definisjon
Factory-mønsteret vert brukt til å opprette objekt utan å spesifisere den eksakte klassen.

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

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

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