Objektorientierte Programmierung: Kurzeinführung

Diese Kurzeinführung erläutert die Grundlagen des objektorientierten Programmierens (OOP) in Python und dient zur Unterstützung bei der Bearbeitung der Übungsaufgaben. Entsprechende Short-Videos werden noch bereitgestellt.

Von unstrukturiertem zu strukturiertem Code

Betrachten wir zunächst ein Beispiel für unstrukturierten Code zur Verwaltung von Studierendendaten:

# Unstrukturiert: Getrennte Datenstrukturen und Funktionen
student_grades = {}
student_courses = {}

def add_grade(student_name, course, grade):
    # Speichere Note für Student
    if student_name not in student_grades:
        student_grades[student_name] = {}
    student_grades[student_name][course] = grade

def calculate_average(student_name):
    # Berechne Durchschnitt
    if student_name not in student_grades:
        return 0
    grades = student_grades[student_name].values()
    return sum(grades) / len(grades) if grades else 0

# Verwendung - fehleranfällig!
add_grade("Alice", "Python", 85)
print(calculate_average("Alice"))  # 85.0
print(calculate_average("Alise"))  # 0.0 - Ups, Tippfehler!

Das Problem: Daten (student_grades, student_courses) und Funktionen sind getrennt. Ein einfacher Tippfehler im Namen führt zu falschen Ergebnissen, ohne dass das System den Fehler erkennen kann.

Die objektorientierte Lösung bündelt zusammengehörige Daten und Funktionen in einer Klasse:

class Student:
    def __init__(self, name):
        self.name = name
        self.grades = {}     # Noten pro Kurs
        self.courses = []    # Belegte Kurse
    
    def add_grade(self, course, grade):
        # Prüfe ob Kurs belegt ist
        if course not in self.courses:
            print(f"Fehler: {self.name} ist nicht für {course} eingeschrieben")
            return False
        # Prüfe ob Note gültig ist
        if not (0 <= grade <= 100):
            print(f"Fehler: Note {grade} ist ungültig (0-100 erlaubt)")
            return False
        self.grades[course] = grade
        return True
        
    def enroll_course(self, course):
        if course in self.courses:
            print(f"Hinweis: {self.name} ist bereits für {course} eingeschrieben")
            return False
        self.courses.append(course)
        return True

# Verwendung - sicherer und klarer
alice = Student("Alice")
alice.enroll_course("Python")   # Erst einschreiben
alice.add_grade("Python", 85)   # Dann Note vergeben

Die Klasse Student ist wie ein Bauplan, der festlegt, welche Daten (Attribute wie name, grades, courses) und welche Funktionen (Methoden wie add_grade, enroll_course) zu einem Studierenden gehören.

Klassen und Objekte

Eine Klasse definiert einen neuen Typ mit Attributen (Daten) und Methoden (Funktionen). Ein einfaches Beispiel ist eine Klasse für geometrische Formen:

class Circle:
    def __init__(self, radius):
        # Initialisierung der Attribute
        self.radius = radius
    
    def area(self):
        # Berechne Fläche
        return 3.14159 * self.radius ** 2
    
    def perimeter(self):
        # Berechne Umfang
        return 2 * 3.14159 * self.radius

# Objekte erstellen und verwenden
small_circle = Circle(3)
big_circle = Circle(10)

print(small_circle.area())      # 28.27431
print(big_circle.perimeter())   # 62.8318

Wichtige Konzepte:

  • Die spezielle Methode __init__ ist der Initialisierer - sie wird automatisch aufgerufen, wenn ein neues Objekt erstellt wird
  • self ist eine Referenz auf das aktuelle Objekt. Über self haben wir Zugriff auf die Attribute und Methoden des Objekts
  • Jedes Objekt hat seinen eigenen Zustand (hier: verschiedene Radien)
  • Methoden operieren auf den Daten des Objekts (hier: Berechnung von Fläche und Umfang)

Vererbung

Mit Vererbung können wir Klassen erstellen, die die Eigenschaften einer anderen Klasse übernehmen und erweitern:

class Shape:
    def area(self):
        pass  # Platzhalter - wird von konkreten Formen implementiert
    
    def perimeter(self):
        pass  # Platzhalter - wird von konkreten Formen implementiert

class Rectangle(Shape):  # Rectangle erbt von Shape
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def area(self):
        return self.width * self.height
    
    def perimeter(self):
        return 2 * (self.width + self.height)

# Beide Klassen gleich behandeln
shapes = [Circle(5), Rectangle(3, 4)]
for shape in shapes:
    print(f"Fläche: {shape.area()}")

Hier erben sowohl Circle als auch Rectangle von Shape. Sie können unterschiedliche Implementierungen der gleichen Methoden haben, aber einheitlich verwendet werden.

Dies war eine erste Einführung in die wichtigsten Konzepte der objektorientierten Programmierung. Die Konzepte werden in den Videos und Übungen weiter vertieft.