Was ist objektorientierte Programmierung (OOP)? Die 4 Grundprinzipien mit Codebeispielen

Objektorientierte Programmierung ist ein Ansatz, bei dem Sie Software aus Objekten aufbauen, die Daten und das passende Verhalten bündeln. Ich betreue seit 2012 Webprojekte, und genau diese Denkweise verhindert, dass eine wachsende Codebasis zerfällt. Deshalb erkläre ich hier die vier Grundprinzipien mit konkreten Codebeispielen in Python.
Die vier Prinzipien heißen Kapselung, Vererbung, Polymorphie und Abstraktion. Ich nutze Python, denn die Syntax ist schlank und auch Einsteiger lesen den Code ohne Mühe. Allerdings gelten alle Ideen genauso für Java, C#, PHP oder TypeScript. Die SOLID Prinzipien behandle ich hier bewusst nicht, dafür gibt es einen eigenen Beitrag. Weitere Artikel finden Sie in der Kategorie Software.
Was ist objektorientierte Programmierung (OOP)?
Objektorientierte Programmierung ist ein Programmierparadigma, das ein Programm als System von Objekten beschreibt, die einander Nachrichten schicken. Jedes Objekt trägt seine eigenen Daten, also seinen Zustand, und dazu die Methoden, die diese Daten verändern. So zerfällt der Code in kleine Teile mit klarer Verantwortung.
Denken Sie an einen Onlineshop. Warenkorb, Produkt, Kunde und Bestellung sind getrennte Begriffe. OOP verlangt, dass Sie jeden davon als Klasse beschreiben. Der Warenkorb weiß also, wie er Artikel aufnimmt und die Summe bildet. Die Bestellung verfolgt dagegen ihren eigenen Status. Tritt dann ein Fehler auf, wissen Sie sofort, wo Sie suchen müssen.
Die Wurzeln des Paradigmas reichen zurück bis zur Sprache Simula in den 1960er Jahren. Danach verfeinerte Smalltalk die Idee, und C++ sowie Java machten sie massentauglich. Heute unterstützen zum Beispiel Python, PHP, C#, Kotlin und Swift Klassen und Objekte. Was Sie hier lernen, bleibt also auch beim Sprachwechsel wertvoll.
Was unterscheidet eine Klasse von einem Objekt?
Eine Klasse ist eine Vorlage, ein Objekt ist ein konkretes Exemplar dieser Vorlage. Denken Sie an den Bauplan eines Architekten. Der Plan selbst ist kein Haus, trotzdem können Sie hundert Häuser danach bauen. Ebenso beschreibt die Klasse Daten und Verhalten, und das Objekt ist die echte Ausprägung im Speicher.
class Produkt:
def __init__(self, name, preis):
self.name = name
self.preis = preis
def rabattpreis(self, satz):
return self.preis * (1 - satz)
stift = Produkt("Stift", 4)
heft = Produkt("Heft", 12)
print(heft.rabattpreis(0.25)) # 9.0
Hier ist Produkt die Klasse. Dagegen sind stift und heft zwei getrennte Objekte dieser Klasse. Beide teilen dieselben Methoden, allerdings hat jedes seinen eigenen Namen und Preis. Die Methode __init__ ist der Konstruktor, den Python beim Erzeugen des Objekts aufruft. Zudem zeigt der Parameter self, auf welchem Objekt eine Methode arbeitet.
Wenn Sie die Rechnung von Hand prüfen wollen, nutzt mein Rabattrechner dieselbe Formel. Code und Werkzeug zu vergleichen ist also ein netter erster Test für Ihre Klasse.
Warum ist objektorientierte Programmierung entstanden?
Im prozeduralen Code stehen Daten und Funktionen getrennt. Für ein kleines Skript ist das kein Problem, denn niemand sonst arbeitet daran. Wächst das Projekt allerdings, ändern Hunderte Funktionen dieselben gemeinsamen Daten. Folglich lässt sich kaum noch absehen, was eine Änderung auslöst. OOP reduziert dieses Chaos, weil die Daten bei ihrem Besitzer liegen.
| Kriterium | Prozeduraler Ansatz | Objektorientierter Ansatz |
|---|---|---|
| Grundeinheit | Funktion | Klasse und Objekt |
| Wo liegen die Daten? | Oft gemeinsam oder global | Im Objekt, dem sie gehören |
| Wiederverwendung | Funktionen kopieren oder Bibliotheken | Vererbung und Komposition |
| Wirkung einer Änderung | Kann sich weit ausbreiten | Bleibt eher innerhalb der Klasse |
| Passt gut zu | Kurzen Skripten, einmaligen Aufgaben | Langlebigen Projekten mit mehreren Personen |
Die Tabelle sagt nicht, dass prozeduraler Code schlecht ist. Zum Beispiel wäre eine Klasse für ein Skript mit zwanzig Zeilen, das eine CSV Datei bereinigt, reine Zeremonie. Andererseits profitiert ein Adminbereich, den Sie jahrelang pflegen, enorm von einer klaren Objektstruktur.
Meine Beobachtung ist einfach. Meist merken Sie den Bedarf an Objekten, sobald der dritte Entwickler ins Team kommt. Die ersten beiden behalten den Code im Kopf. Der Neue verliert dagegen Tage, bis er versteht, welche Funktion welche Daten ändert. Klassen schreiben dieses Wissen also direkt in den Code.
Was ist Kapselung und welches Problem löst sie?
Kapselung bedeutet, die Daten eines Objekts nach außen zu verbergen und den Zugriff nur über kontrollierte Methoden zu erlauben. Es geht dabei nicht um Geheimhaltung als Selbstzweck. Stattdessen soll das Objekt stets in einem gültigen Zustand bleiben.
Nehmen Sie ein Bankkonto. Könnte jeder den Kontostand direkt ändern, dann könnte jemand minus fünftausend eintragen, ohne dass das System es bemerkt. Kapselung schließt diese Tür. Nur die Methoden für Einzahlung und Abhebung ändern den Stand, und genau diese Methoden prüfen die Regeln.
In der Praxis bringt Ihnen Kapselung drei Vorteile:
- Sie ändern das Innere einer Klasse, ohne den aufrufenden Code zu beschädigen.
- Sie stoppen ungültige Daten an einer einzigen Stelle, statt überall Prüfungen zu schreiben.
- Sie finden beim Debuggen schneller heraus, auf welchem Weg sich ein Wert geändert hat.
Kurz gesagt macht Kapselung das Objekt zum Wächter seiner eigenen Regeln. Sobald das zur Gewohnheit wird, tauchen Überraschungen wie ein plötzlich negativer Wert deutlich seltener auf.
Wie sieht Kapselung in einem Codebeispiel aus?
Die folgende Klasse speichert den Kontostand in einem Feld mit führendem Unterstrich und bietet nach außen nur eine lesbare Eigenschaft an:
class Bankkonto:
def __init__(self, inhaber):
self.inhaber = inhaber
self._stand = 0
@property
def stand(self):
return self._stand
def einzahlen(self, betrag):
if betrag <= 0:
raise ValueError("Betrag muss positiv sein")
self._stand += betrag
def abheben(self, betrag):
if betrag > self._stand:
raise ValueError("Deckung reicht nicht")
self._stand -= betrag
konto = Bankkonto("Anna")
konto.einzahlen(500)
konto.abheben(200)
print(konto.stand) # 300
Sie können stand lesen, doch konto.stand = 1000 löst einen Fehler aus, weil die Eigenschaft keinen Setter hat. Der Kontostand ändert sich also nur über einzahlen und abheben. Somit leben Regeln wie „kein negativer Betrag“ an genau einem Ort.
Vielleicht laden Sie den Stand morgen aus einer Datenbank. Dann ändern Sie nur das Innere der Klasse. Der übrige Code schreibt weiterhin konto.stand wie bisher. Genau das ist der greifbarste Nutzen der Kapselung.
Dieselbe Logik passt auch zu Lagerbeständen, Gutscheinen oder Treuepunkten. Eine Gutscheinklasse hält zum Beispiel Ablaufdatum und Nutzungszähler intern und verweigert sich, sobald sie ungültig ist.
Gibt es in Python wirklich private Attribute?
Nein, Python kennt kein exaktes Gegenstück zum Schlüsselwort private aus Java. Das offizielle Python Tutorial sagt es deutlich: Private Instanzvariablen, auf die nur das Objekt selbst zugreifen kann, gibt es in Python nicht. Stattdessen verlassen Sie sich auf eine Namenskonvention.
Ein Name mit einem führenden Unterstrich, etwa _stand, signalisiert: internes Detail, bitte nicht anfassen. Bei zwei führenden Unterstrichen wendet Python Name Mangling an und verknüpft den Attributnamen mit dem Klassennamen. Dieser Mechanismus soll Namenskonflikte in Unterklassen vermeiden. Er ist also keine Sicherheitsfunktion.
class Beispiel:
def __init__(self):
self.__geheim = 42
b = Beispiel()
print(b._Beispiel__geheim) # 42, trotzdem erreichbar
Kapselung und damit objektorientierte Programmierung ist in Python daher eine Frage der Disziplin. Ihr Team hält sich an die Konvention, und niemand greift von außen auf diese Felder zu. In Java oder C# erzwingt dagegen der Compiler die Regel. Beide Wege verfolgen somit dasselbe Ziel: eine klare Grenze um den inneren Zustand eines Objekts.
Was ist Vererbung und wann sollten Sie sie nutzen?
Vererbung bedeutet, dass eine Klasse die Attribute und Methoden einer anderen Klasse übernimmt. Die erbende Klasse heißt Unterklasse, die vererbende heißt Oberklasse. Die Unterklasse schreibt gemeinsames Verhalten nicht neu. Stattdessen ergänzt oder ändert sie nur das, was sie besonders macht.
Für den richtigen Einsatz gibt es einen einfachen Test. Passt zwischen beiden Begriffen die Aussage „ist ein“? Eine Katze ist ein Tier, deshalb ist eine Klasse Katze als Unterklasse von Tier sinnvoll. Ein Auto ist dagegen kein Motor, sondern es hat einen Motor. Im zweiten Fall ist Vererbung also das falsche Werkzeug.
Typische Situationen, in denen Vererbung gut passt:
- Mehrere Klassen teilen dieselben Felder und den größten Teil ihres Verhaltens.
- Ein Framework wie Django erwartet, dass Sie von einer seiner Klassen erben.
- Unterklassen erweitern die Oberklasse, ohne deren Zusagen zu brechen.
Allerdings wird eine Vererbungskette ab drei oder vier Ebenen schwer lesbar. Sie öffnen dann fünf Dateien, nur um die Herkunft einer Methode zu finden. Daher empfehle ich jedem Team flache Hierarchien.
Wie funktioniert Vererbung in einem Codebeispiel?
Nehmen wir das Benutzersystem einer Website. Jeder Benutzer hat einen Namen und eine Mailadresse, und ein Administrator braucht zusätzlich Rechte:
class Benutzer:
def __init__(self, name, mail):
self.name = name
self.mail = mail
def vorstellen(self):
return f"{self.name} ({self.mail})"
def darf(self, aktion):
return False
class Admin(Benutzer):
def __init__(self, name, mail, rechte):
super().__init__(name, mail)
self.rechte = set(rechte)
def darf(self, aktion):
return aktion in self.rechte
a = Admin("Jonas", "jonas@example.com", ["loeschen", "bearbeiten"])
print(a.vorstellen()) # geerbte Methode
print(a.darf("loeschen")) # True
Admin nutzt vorstellen, ohne die Methode selbst zu schreiben, denn sie stammt aus der Oberklasse. Der Aufruf super().__init__ startet den Konstruktor der Oberklasse, also sparen Sie sich die doppelte Zuweisung von Name und Mail. Die Methode darf definieren Sie dagegen neu. Das nennt man Überschreiben, und es öffnet die Tür zum nächsten Prinzip.
Eine starke Passwortrichtlinie gehört ebenfalls in solche Klassen. Für Testkonten erzeugen Sie zufällige Werte mit meinem Passwortgenerator.
Wann ist Komposition besser als Vererbung?
Komposition bedeutet, dass ein Objekt andere Objekte als Felder hält und einen Teil der Arbeit an sie abgibt. Sie beschreibt eine „hat ein“ Beziehung statt „ist ein“. Die Literatur zu Entwurfsmustern wiederholt deshalb immer wieder denselben Rat: Bevorzugen Sie Komposition, wo es geht.
class Motor:
def starten(self):
return "Motor läuft"
class Auto:
def __init__(self, motor):
self.motor = motor
def losfahren(self):
return self.motor.starten() + ", Auto fährt"
auto = Auto(Motor())
Mit dieser Struktur schreiben Sie morgen eine Klasse für einen Elektromotor und geben sie dem Auto. Zudem ändert sich in Auto keine einzige Zeile. Mit Vererbung bräuchten Sie für dieselbe Flexibilität eine eigene Unterklasse pro Motortyp.
Meine praktische Regel lautet: Sprechen Sie die Beziehung laut aus. Klingt „ein Admin ist ein Benutzer“ richtig, dann nutze ich Vererbung. Klingt „eine Rechnung ist ein PDF“ seltsam, dann bekommt die Rechnung stattdessen ein Objekt, das PDFs erzeugt. Diese kleine Gewohnheit erspart Ihnen später den Abriss fragiler Hierarchien.
Was bedeutet Polymorphie in der objektorientierten Programmierung?
Polymorphie bedeutet, dass Objekte verschiedener Klassen auf denselben Methodenaufruf jeweils auf ihre eigene Art antworten. Das Wort stammt aus dem Griechischen und heißt „Vielgestaltigkeit“. Der aufrufende Code braucht deshalb den genauen Typ nicht. Er muss nur wissen, dass die erwartete Methode existiert.
Ein Beispiel aus dem Alltag: Sagen Sie einer Gitarre, einem Klavier und einem Schlagzeug „spielen“, dann erklingen alle drei, jedes anders. Wer den Befehl gibt, kennt das Innenleben der Instrumente nicht. Ebenso begegnet Ihnen diese Situation in Software ständig, etwa bei Zahlungen, Benachrichtigungen oder Exporten.
Polymorphie tritt in zwei gängigen Formen auf. Erstens gibt es Polymorphie zur Laufzeit, bei der Unterklassen eine Methode der Oberklasse überschreiben. Zweitens erlauben Java und C# Methoden mit gleichem Namen und anderen Parametern, das heißt Überladen. Python unterstützt Überladen nicht direkt, deshalb nutzen Sie dort Standardwerte für Parameter.
Der eigentliche Gewinn: Ein neuer Typ lässt bestehenden Code unberührt. Folglich weichen lange Ketten aus if und elif kleinen, unabhängigen Klassen.
Wie setzen Sie Polymorphie im Code um?
Nehmen wir den Bezahlschritt eines Onlineshops. Kartenzahlung, Überweisung und Nachnahme erledigen unterschiedliche Aufgaben. Trotzdem möchte der Bestellcode alle gleich ansprechen:
class Kartenzahlung:
def zahlen(self, betrag):
return f"{betrag} EUR von der Karte abgebucht"
class Ueberweisung:
def zahlen(self, betrag):
return f"IBAN für {betrag} EUR verschickt"
class Nachnahme:
def zahlen(self, betrag):
return f"{betrag} EUR bei Lieferung fällig"
def bestellung_abschliessen(methode, betrag):
print(methode.zahlen(betrag))
for methode in [Kartenzahlung(), Ueberweisung(), Nachnahme()]:
bestellung_abschliessen(methode, 75)
Die Funktion bestellung_abschliessen fragt nie nach der Zahlungsart. Stattdessen ruft sie zahlen auf, und jedes Objekt liefert seine eigene Antwort. Kommt nächsten Monat eine Wallet dazu, schreiben Sie eine neue Klasse und lassen den Bestellcode in Ruhe.
Der Bezahlschritt hängt allerdings genauso stark von der Nutzerführung ab wie von der Architektur. Auf meiner Seite zur E-Commerce Beratung beschreibe ich, worauf ich dort achte.
Ist Duck Typing eine Form der Polymorphie?
Ja, in Python läuft der Großteil der Polymorphie über Duck Typing. Im vorigen Beispiel hatten die drei Zahlungsklassen keine gemeinsame Oberklasse. Trotzdem hat dieselbe Funktion alle drei verarbeitet. Python prüfte also nicht den Typ, sondern nur, ob die erwartete Methode vorhanden war.
Die Definition im Python Glossar fasst es zusammen: Statt den Typ eines Objekts zu prüfen, rufen Sie direkt seine Methode auf oder nutzen sein Attribut. Der Name geht auf ein altes Sprichwort zurück: Wenn es watschelt wie eine Ente und quakt wie eine Ente, dann ist es eine Ente.
Diese Freiheit ist stark, birgt allerdings ein Risiko. Schreibt eine Klasse zahlung statt zahlen, zeigt sich der Fehler erst, wenn genau diese Zeile läuft. Deshalb setze ich in großen Projekten eine von zwei Absicherungen ein:
- Die erwartete Schnittstelle mit Typhinweisen und typing.Protocol beschreiben und dann mit einem Werkzeug wie mypy prüfen.
- Eine gemeinsame abstrakte Klasse definieren und die Unterklassen zwingen, die Methode umzusetzen.
Die zweite Variante führt direkt zum vierten Prinzip, der Abstraktion.
Was ist Abstraktion und wie unterscheidet sie sich von Kapselung?
Abstraktion stellt in den Vordergrund, was ein Objekt tut, und schiebt das Wie in den Hintergrund. Sie zeigen Nutzern also nur die Schnittstelle, die sie brauchen. Beim Autofahren nutzen Sie Lenkrad, Gaspedal und Bremse. Den Zeitpunkt der Einspritzung müssen Sie dagegen nicht kennen.
Viele Entwickler verwechseln beide Begriffe, denn beide scheinen Details zu verstecken. Ihr Fokus ist allerdings verschieden. Abstraktion ist eine Entwurfsentscheidung und beantwortet die Frage, welche Fähigkeiten ein Objekt anbieten soll. Kapselung ist dagegen eine Technik der Umsetzung und beantwortet die Frage, wie Sie den inneren Zustand schützen.
| Frage | Abstraktion | Kapselung |
|---|---|---|
| Worauf zielt sie? | Was das Objekt tut | Wie die Daten sicher bleiben |
| Wann denken Sie daran? | Im Entwurf | In der Umsetzung |
| Werkzeug in Python | Modul abc, Protocol | Unterstrich, property |
| Nutzen | Verbirgt Komplexität | Verhindert ungültige Zustände |
Kurz gesagt fragt Abstraktion „was zeige ich?“ und Kapselung „was schütze ich?“. Eine gute Klasse erledigt somit beides.
Wie schreiben Sie eine abstrakte Klasse mit Codebeispiel?
In Python schreiben Sie abstrakte Klassen mit dem Modul abc aus der Standardbibliothek. Laut Dokumentation des Moduls abc können Sie keine Instanz einer Klasse erzeugen, deren abstrakte Methoden noch nicht überschrieben sind. Diese Regel zwingt Unterklassen, den Vertrag einzuhalten:
from abc import ABC, abstractmethod
class Benachrichtigung(ABC):
def __init__(self, empfaenger):
self.empfaenger = empfaenger
@abstractmethod
def senden(self, nachricht):
...
def senden_und_loggen(self, nachricht):
ergebnis = self.senden(nachricht)
print(f"Log: {self.empfaenger} / {ergebnis}")
class MailBenachrichtigung(Benachrichtigung):
def senden(self, nachricht):
return f"Mail verschickt: {nachricht}"
class SmsBenachrichtigung(Benachrichtigung):
def senden(self, nachricht):
return f"SMS verschickt: {nachricht[:160]}"
Versuchen Sie, Benachrichtigung() direkt zu erzeugen, meldet Python einen TypeError. Der Grund: senden ist noch abstrakt. Dagegen ist senden_und_loggen eine konkrete Methode, und jede Unterklasse bekommt sie geschenkt.
Um Längengrenzen wie die 160 Zeichen einer SMS zu testen, hilft Ihnen mein Wörterzähler. Messen Sie Ihre Textvorlagen zunächst dort und übernehmen Sie sie dann in den Code.
Wie arbeiten die vier Prinzipien in einem Projekt zusammen?
Jedes Prinzip einzeln zu lernen ist leicht, die eigentliche Kunst liegt in der Kombination. Schauen Sie zum Beispiel noch einmal auf das Benachrichtigungssystem. Alle vier Prinzipien spielen darin eine Rolle:
- Abstraktion: Die Klasse Benachrichtigung beschreibt nur die Fähigkeit zu senden und verbirgt Details des Kanals.
- Vererbung: Die Klassen für Mail und SMS erben das Feld für den Empfänger und die Logmethode.
- Polymorphie: Derselbe Aufruf von senden_und_loggen liefert auf jedem Kanal ein anderes Ergebnis.
- Kapselung: Jeder Kanal hält seine Zugangsdaten intern und gibt sie nie preis.
kanaele = [MailBenachrichtigung("anna@example.com"),
SmsBenachrichtigung("+4915100000000")]
for kanal in kanaele:
kanal.senden_und_loggen("Ihre Bestellung ist unterwegs")
Diese Schleife läuft auch dann weiter, wenn Sie einen WhatsApp Kanal ergänzen. Sie schreiben lediglich eine neue Unterklasse von Benachrichtigung. So wächst das System, ohne dass Sie bereits getesteten Code anfassen. Dieselbe Denkweise auf das Frontend übertragen beschreibe ich im Beitrag über Micro Frontends.
Welche Fehler passieren in der objektorientierten Programmierung am häufigsten?
In Projekten, die ich über die Jahre übernommen habe, sehe ich immer wieder dieselben Fehler. Die meisten entstehen nicht aus Unwissen. Vielmehr wenden Teams die Prinzipien zu stark oder an der falschen Stelle an:
- Gottklasse: eine Klasse, die alles weiß und auf Tausende Zeilen anwächst.
- Tiefe Vererbung: Hierarchien mit fünf oder sechs Ebenen, die jede Suche zur Qual machen.
- Sinnlose Getter und Setter: ungeprüfte Zugriffsmethoden für jedes Feld, also Zeremonie statt Kapselung.
- Ketten von Typprüfungen: lange Blöcke mit isinstance, wo Polymorphie genügen würde.
- Verfrühte Abstraktion: drei Schichten von Schnittstellen für einen einzigen Anwendungsfall.
Allen diesen Fehlern ist gemeinsam, dass sich der Code gegen Änderungen sträubt. Zudem zeigt sich schlechte Struktur oft in der Performance. Unnötige Objekte und doppelte Abfragen bremsen eine Seite aus. Wie Sie das Tempo messen, erkläre ich im Leitfaden zum Google Lighthouse Test.
Sind OOP und funktionale Programmierung Konkurrenten?
Nein, die meisten modernen Sprachen unterstützen beide Paradigmen nebeneinander. Funktionale Programmierung setzt auf unveränderliche Daten und Funktionen ohne Seiteneffekte. Objektorientierte Programmierung sammelt dagegen den Zustand in Objekten. Beide bieten somit starke Werkzeuge für unterschiedliche Probleme.
Zum Beispiel wirken map, filter oder List Comprehensions innerhalb einer Python Klasse völlig natürlich. In JavaScript und TypeScript sind React Komponenten mit der Zeit von Klassen zu Funktionen und Hooks gewandert. Trotzdem leben Geschäftsregeln, Datenmodelle und Serviceschichten in den meisten Projekten weiterhin in Klassen.
Mein Ansatz ist pragmatisch, denn objektorientierte Programmierung ist kein Glaubensbekenntnis. Bei Datentransformationen denke ich funktional, bei Fachmodellen in Objekten. Entscheidend ist also die nächste Person, die den Code liest, und nicht die Treue zu einem Paradigma.
Eine schöne Parallele gibt es auch im Web. Strukturierte Daten, die eine Seite für Suchmaschinen beschreiben, bilden selbst ein Modell mit Typen, Eigenschaften und verschachtelten Objekten. Ein Produkt enthält etwa eine Marke und ein Angebot. Mehr dazu lesen Sie in meinem Leitfaden zu Schema Markup; mit dem Klassendenken lesen Sie das Markup deutlich leichter.
In welcher Reihenfolge lernen Sie objektorientierte Programmierung am besten?
Der häufigste Fehler von Einsteigern: Sie lernen die vier Prinzipien auswendig und schreiben keinen Code. Die Ideen sitzen allerdings erst, wenn Sie sie in kleinen Projekten ausprobieren. Aus meiner Praxis empfehle ich die folgende Reihenfolge. Betrachten Sie sie als Startpunkt, nicht als festen Lehrplan:
- Machen Sie sich mit Klassen, Objekten, Konstruktoren und self anhand winziger Beispiele vertraut.
- Probieren Sie Kapselung mit einem Bankkonto oder einer Lagerklasse aus.
- Wenden Sie Vererbung mit einer zweistufigen Hierarchie wie Benutzer und Admin an.
- Schreiben Sie dieselbe Hierarchie mit Komposition neu und vergleichen Sie das Ergebnis.
- Bauen Sie Polymorphie in ein Szenario für Zahlungen oder Benachrichtigungen ein.
- Schreiben Sie eine abstrakte Klasse mit dem Modul abc und lassen Sie Unterklassen ihr folgen.
- Gehen Sie schließlich zu den SOLID Prinzipien und grundlegenden Entwurfsmustern über.
Außerdem rate ich zu einem kleinen Test bei jedem Schritt. Konkret lehrt Sie der Nachweis, dass ein negativer Betrag einen Fehler auslösen muss, den Sinn der Kapselung besser als jede Definition.
Kleine Übung: Wie schreiben Sie eine Klasse, die Slugs erzeugt?
Zum Üben nehmen wir eine vertraute Aufgabe aus dem Web: aus einem Titel einen Slug für die URL bauen. Sie eignet sich gut für Kapselung, denn die Umwandlungsregeln sollen für Aufrufer unsichtbar bleiben:
import re
class SlugErzeuger:
_tabelle = str.maketrans({"ä": "ae", "ö": "oe", "ü": "ue", "ß": "ss"})
def __init__(self, trenner="_"):
self._trenner = trenner
def erzeugen(self, titel):
text = titel.lower().translate(self._tabelle)
woerter = re.findall(r"\w+", text)
return self._trenner.join(woerter)
print(SlugErzeuger().erzeugen("Was ist objektorientierte Programmierung?"))
Die Ausgabe lautet was_ist_objektorientierte_programmierung. Für echte URLs setzen Sie als Trenner dann einen Bindestrich. Die Erweiterung liegt danach bei Ihnen. Vergleichen Sie Ihr Ergebnis mit meinem Slug Generator, dann entdecken Sie schnell übersehene Sonderfälle.
Die Übung vermittelt zudem ein wenig SEO. Eine saubere URL Struktur hilft Suchmaschinen, eine Seite zu verstehen. Diese kleine Klasse leistet also echte Arbeit in einem echten Webprojekt.
Was bringt Ihnen OOP Wissen in einem Webprojekt?
Eine Unternehmenswebsite oder Shopplattform lebt jahrelang. In dieser Zeit kommen zudem neue Zahlungsarten, neue Sprachen und neue Integrationen hinzu. Eine Codebasis, die objektorientierten Prinzipien folgt, nimmt solche Änderungen als kleine, unabhängige Klassen auf. Eine nachlässige Struktur bricht dagegen bei jeder Änderung an anderer Stelle.
In meinen Webdesign Projekten stelle ich dem Entwicklerteam im ersten Termin zwei Fragen. Wo liegen die Geschäftsregeln, und wie viele Dateien berührt ein neues Feature? Diese zwei Antworten zeigen schnell, wie treu der Code den objektorientierten Prinzipien bleibt.
Zusammengefasst ist objektorientierte Programmierung keine Checkliste, und die vier Prinzipien sind Gewohnheiten, die Änderungen billiger machen. Kapselung schützt Daten, Vererbung reduziert Wiederholung, Polymorphie erleichtert neue Typen, und Abstraktion verbirgt Komplexität. Der nächste Schritt sind die SOLID Prinzipien, die ich in einem eigenen Beitrag der Kategorie Software behandle.




