OOP Python – Compositie¶
In deze laatste paragraaf aandacht voor nog een OOP-techniek, compositie (composition). Compositie wil zeggen dat een object samengesteld wordt aan de hand van verschillende andere objecten. Het samengesteld object kan op die manier taken delegeren aan die samenstellende objecten. Op zich lijkt compositie wel wat op overerving, maar waar overerving impliciet is, is compositie expliciet (en daarmee ook schaalbaarder, testbaarder en overdraagbaarder).
Compositie biedt een superieure manier om delegatie te beheren, aangezien het selectief de toegang kan delegeren, zelfs sommige attributen of methoden kan maskeren, terwijl overerving dit alles moet ontberen.
Aan het eind van deze paragraaf maken we oefening 3.
Een webshop-voorbeeld¶
Als voorbeeld wordt hier een klasse Bestelling toegevoegd die gebruik maakt van de klassen Klant en Winkelwagen die we eerder hebben gemaakt. Een bestelling is namelijk een compositie van een klant, een winkelwagen met producten, en een betaalmethode.
Eerst maken we een eenvoudige klasse voor een betaalmethode (betaalmethode.py):
class Betaalmethode:
"""Klasse voor betaalmethoden"""
def __init__(self, type_betaling, kosten=0.0):
self.type = type_betaling
self.kosten = kosten # Eventuele transactiekosten
def verwerk_betaling(self, bedrag):
"""Verwerk een betaling"""
totaal = bedrag + self.kosten
print(f"Betaling van €{totaal:.2f} wordt verwerkt via {self.type}")
if self.kosten > 0:
print(f" (inclusief €{self.kosten:.2f} transactiekosten)")
return totaal
De volgende stap is om de klasse Bestelling te maken die gebruik maakt van Klant, Winkelwagen en Betaalmethode:
import datetime
class Bestelling:
"""Klasse voor bestellingen in de webshop
Een bestelling is samengesteld uit:
- Een klant
- Een winkelwagen met producten
- Een betaalmethode
- Verzendkosten
"""
def __init__(self, klant, winkelwagen, betaalmethode, verzendkosten=5.95):
self.klant = klant
self.winkelwagen = winkelwagen
self.betaalmethode = betaalmethode
self.verzendkosten = verzendkosten
self.besteldatum = datetime.datetime.now()
self.status = "In behandeling"
def bereken_totaal(self):
"""Berekent totaalbedrag inclusief verzendkosten"""
subtotaal = self.winkelwagen.bereken_totaal()
# Gratis verzending boven €50
if subtotaal >= 50:
verzending = 0.0
else:
verzending = self.verzendkosten
return subtotaal + verzending
def plaats_bestelling(self):
"""Plaatst de bestelling en verwerkt de betaling"""
if not self.winkelwagen.items:
print("Kan geen lege bestelling plaatsen!")
return False
print(f"\n{'='*50}")
print(f"BESTELLING VOOR {self.klant._naam}")
print(f"{'='*50}")
# Toon producten
print("\nProducten:")
for product in self.winkelwagen.items:
print(f" - {product}")
# Bereken kosten
subtotaal = self.winkelwagen.bereken_totaal()
print(f"\nSubtotaal: €{subtotaal:.2f}")
if subtotaal >= 50:
print("Verzendkosten: €0.00 (GRATIS verzending!)")
verzending = 0.0
else:
print(f"Verzendkosten: €{self.verzendkosten:.2f}")
verzending = self.verzendkosten
totaal = subtotaal + verzending
print(f"Totaal: €{totaal:.2f}")
# Verwerk betaling
print(f"\nBetaalmethode: {self.betaalmethode.type}")
eindtotaal = self.betaalmethode.verwerk_betaling(totaal)
# Bevestiging
self.status = "Bevestigd"
print(f"\nBestelling bevestigd!")
print(f"Bevestiging verzonden naar {self.klant._email}")
print(f"Verwachte levering: 2-3 werkdagen")
print(f"{'='*50}\n")
return True
def toon_status(self):
"""Toont de status van de bestelling"""
print(f"\nBestellingsnummer: #{id(self)}")
print(f"Klant: {self.klant._naam}")
print(f"Datum: {self.besteldatum.strftime('%d-%m-%Y %H:%M')}")
print(f"Status: {self.status}")
print(f"Aantal producten: {len(self.winkelwagen.items)}")
print(f"Totaalbedrag: €{self.bereken_totaal():.2f}")
De compositie in actie¶
Laten we kijken hoe een bestelling werkt. We gebruiken de klassen die we al hebben (Product, Klant, Winkelwagen) en combineren ze:
from product import Product
from klant import Klant
from winkelwagen import Winkelwagen
# Maak producten aan
laptop = Product("Laptop", 799.99, 5)
muis = Product("Draadloze muis", 25.50, 20)
toetsenbord = Product("Mechanisch toetsenbord", 89.99, 15)
# Maak een klant aan
anna = Klant("Anna de Vries", "anna@email.nl")
# Maak een winkelwagen en voeg producten toe
wagen = Winkelwagen(anna)
wagen.voeg_toe(laptop)
wagen.voeg_toe(muis)
wagen.voeg_toe(toetsenbord)
# Kies een betaalmethode
ideal = Betaalmethode("iDEAL", kosten=0.0) # Geen transactiekosten
# Maak een bestelling (compositie!)
bestelling = Bestelling(
klant=anna,
winkelwagen=wagen,
betaalmethode=ideal,
verzendkosten=5.95
)
# Plaats de bestelling
bestelling.plaats_bestelling()
# Toon status
bestelling.toon_status()
Resultaat:
==================================================
BESTELLING VOOR Anna de Vries
==================================================
Producten:
- Laptop (€799.99)
- Draadloze muis (€25.50)
- Mechanisch toetsenbord (€89.99)
Subtotaal: €915.48
Verzendkosten: €0.00 (GRATIS verzending!)
Totaal: €915.48
Betaalmethode: iDEAL
Betaling van €915.48 wordt verwerkt via iDEAL
Bestelling bevestigd!
Bevestiging verzonden naar anna@email.nl
Verwachte levering: 2-3 werkdagen
==================================================
Bestellingsnummer: #4426417040
Klant: Anna de Vries
Datum: 31-01-2026 12:45
Status: Bevestigd
Aantal producten: 3
Totaalbedrag: €915.48
Compositie vs. Overerving¶
Het verschil tussen compositie en overerving:
Overerving (is-een relatie):
- Een FysiekProduct is een Product
- Een DigitaalProduct is een Product
- Gebruikt super() om functionaliteit over te nemen
Compositie (heeft-een relatie):
- Een Bestelling heeft een Klant
- Een Bestelling heeft een Winkelwagen
- Een Bestelling heeft een Betaalmethode
- Gebruikt instantievariabelen om andere objecten te bevatten
Wanneer compositie gebruiken?¶
Gebruik compositie wanneer:
- Flexibiliteit belangrijker is: Je kunt tijdens runtime eenvoudig de samenstelling wijzigen (bijvoorbeeld een andere betaalmethode kiezen)
- Meerdere bronnen: Een object functionaliteit nodig heeft van meerdere verschillende klassen
- Geen "is-een" relatie: De objecten zijn niet hetzelfde type, maar werken samen
Voorbeeld waar we de betaalmethode kunnen wijzigen:
# Maak meerdere betaalmethodes
ideal = Betaalmethode("iDEAL", kosten=0.0)
creditcard = Betaalmethode("Creditcard", kosten=2.50)
paypal = Betaalmethode("PayPal", kosten=1.00)
# Maak bestelling met iDEAL
bestelling = Bestelling(anna, wagen, ideal)
# Wijzig de betaalmethode voor het plaatsen
bestelling.betaalmethode = paypal
# Nu wordt PayPal gebruikt
bestelling.plaats_bestelling()
Voordelen van compositie¶
- Flexibiliteit: Objecten kunnen tijdens runtime worden vervangen
- Loskoppeling: Klassen zijn minder afhankelijk van elkaar
- Herbruikbaarheid: Componenten kunnen in verschillende contexten worden gebruikt
- Testbaarheid: Componenten kunnen afzonderlijk getest worden
- Onderhoudbaarheid: Wijzigingen in één component hebben minder impact
Samenvatting¶
In dit deel hebben we geleerd over:
- Compositie als alternatief voor overerving
- Het verschil tussen "is-een" (overerving) en "heeft-een" (compositie) relaties
- Het samenstellen van complexe objecten uit eenvoudigere objecten
- Het delegeren van functionaliteit naar component-objecten
- Wanneer compositie geschikter is dan overerving
Afsluiting OOP¶
Je hebt nu alle belangrijke OOP-concepten in Python gezien:
- Klassen en objecten: Het maken van sjablonen en instanties
- Inkapseling: Het afschermen van data met private attributen
- Overerving: Het hergebruiken van code door overerving
- Polymorfisme: Dezelfde interface, verschillende implementaties
- Compositie: Het samenstellen van objecten uit andere objecten
Deze concepten vormen de basis voor het bouwen van complexe, onderhoudbare applicaties zoals webshops, games en nog veel meer!