🏗️ Objektorientierte Programmierung

Informatik - Teil 2: Klassen, Objekte, Vererbung und Polymorphie

1. Was ist OOP?

📖 Definition

Objektorientierte Programmierung (OOP) ist ein Programmierparadigma, das auf dem Konzept von "Objekten" basiert. Objekte sind Einheiten, die Daten (Attribute) und Funktionen (Methoden) zusammenfassen. OOP ermöglicht es, komplexe Systeme durch die Modellierung realer Objekte und deren Beziehungen zu strukturieren.

💡 Warum OOP?

  • Modellierung der realen Welt: Objekte im Code spiegeln Objekte der Realität wider
  • Wiederverwendbarkeit: Einmal geschriebener Code kann mehrfach genutzt werden
  • Wartbarkeit: Code ist leichter zu verstehen und zu ändern
  • Erweiterbarkeit: Neue Funktionen können einfach hinzugefügt werden

2. Klassen und Objekte

📖 Was ist eine Klasse?

Eine Klasse ist ein Bauplan oder eine Vorlage, die beschreibt, welche Eigenschaften (Attribute) und Fähigkeiten (Methoden) ihre Objekte haben werden. Eine Klasse definiert die Struktur, aber enthält selbst keine konkreten Daten.

📖 Was ist ein Objekt?

Ein Objekt ist eine konkrete Instanz einer Klasse. Es wird nach dem Bauplan der Klasse erstellt und enthält tatsächliche Datenwerte. Von einer Klasse können beliebig viele Objekte erstellt werden.
Klasse vs. Objekt:
🏭 Klasse = Bauplan für ein Auto (beschreibt, dass ein Auto Räder, Motor, Farbe hat)
🚗 Objekt = Dein konkretes Auto (roter BMW mit 150 PS)

📝 Eine Klasse erstellen

public class Auto {
    // Attribute (Eigenschaften)
    String marke;
    String farbe;
    int baujahr;
    int kmStand;
    
    // Methoden (Fähigkeiten)
    void fahren(int kilometer) {
        kmStand += kilometer;
        System.out.println("Das Auto fährt " + kilometer + " km.");
    }
    
    void hupen() {
        System.out.println("Huuup!");
    }
    
    void infoAusgeben() {
        System.out.println(marke + ", " + farbe + ", Baujahr: " + baujahr);
    }
}

🔨 Objekte erstellen und verwenden

public class Main {
    public static void main(String[] args) {
        // Objekt erstellen mit new
        Auto meinAuto = new Auto();
        
        // Attribute setzen
        meinAuto.marke = "BMW";
        meinAuto.farbe = "Rot";
        meinAuto.baujahr = 2020;
        meinAuto.kmStand = 15000;
        
        // Methoden aufrufen
        meinAuto.infoAusgeben();    // BMW, Rot, Baujahr: 2020
        meinAuto.fahren(100);        // Das Auto fährt 100 km.
        meinAuto.hupen();            // Huuup!
        
        // Zweites Objekt erstellen
        Auto zweitesAuto = new Auto();
        zweitesAuto.marke = "VW";
        zweitesAuto.farbe = "Blau";
    }
}

3. Konstruktoren

📖 Was ist ein Konstruktor?

Ein Konstruktor ist eine spezielle Methode, die automatisch aufgerufen wird, wenn ein neues Objekt erstellt wird (new). Der Konstruktor initialisiert die Attribute des Objekts. Er hat den gleichen Namen wie die Klasse und keinen Rückgabetyp.

🔧 Arten von Konstruktoren

public class Auto {
    String marke;
    String farbe;
    int baujahr;
    
    // 1. Standard-Konstruktor (ohne Parameter)
    public Auto() {
        marke = "Unbekannt";
        farbe = "Weiß";
        baujahr = 2000;
    }
    
    // 2. Parametrisierter Konstruktor
    public Auto(String marke, String farbe, int baujahr) {
        this.marke = marke;       // this unterscheidet Attribut vom Parameter
        this.farbe = farbe;
        this.baujahr = baujahr;
    }
    
    // 3. Konstruktor mit weniger Parametern
    public Auto(String marke) {
        this.marke = marke;
        this.farbe = "Schwarz";     // Standardwert
        this.baujahr = 2023;        // Standardwert
    }
}

📞 Konstruktor aufrufen

// Standard-Konstruktor verwenden
Auto auto1 = new Auto();

// Parametrisierter Konstruktor
Auto auto2 = new Auto("BMW", "Rot", 2022);

// Konstruktor mit einem Parameter
Auto auto3 = new Auto("Mercedes");

🔑 Das Schlüsselwort this

this verweist auf das aktuelle Objekt und wird verwendet, um:

  • Attribute von gleichnamigen Parametern zu unterscheiden
  • Andere Konstruktoren der gleichen Klasse aufzurufen
  • Das aktuelle Objekt an eine Methode zu übergeben
public Auto(String marke, String farbe) {
    this.marke = marke;  // this.marke = Attribut, marke = Parameter
    this.farbe = farbe;
}

// Konstruktor-Verkettung
public Auto(String marke) {
    this(marke, "Schwarz");  // Ruft anderen Konstruktor auf
}

4. Die vier Säulen der OOP

🎭

Abstraktion

Komplexität verbergen, nur Wesentliches zeigen

🔒

Kapselung

Daten schützen durch Zugriffsmodifizierer

👨‍👧

Vererbung

Eigenschaften von Oberklasse übernehmen

🔄

Polymorphie

Gleiche Schnittstelle, verschiedenes Verhalten

4.1 Abstraktion

📖 Was ist Abstraktion?

Abstraktion bedeutet, nur die wesentlichen Eigenschaften eines Objekts zu modellieren und unwichtige Details zu verbergen. Der Benutzer muss nicht wissen, WIE etwas funktioniert, nur DASS es funktioniert.

Beispiel: Auto fahren

Du musst nicht wissen, wie der Motor funktioniert – du trittst aufs Gas und das Auto fährt. Die komplexe Technik ist abstrahiert.

// Der Benutzer ruft nur fahren() auf
// Die komplexe Motorsteuerung ist verborgen
auto.fahren(100);  // Einfach zu benutzen!

4.2 Kapselung (Encapsulation)

📖 Was ist Kapselung?

Kapselung bedeutet, dass die Daten (Attribute) einer Klasse vor direktem Zugriff von außen geschützt werden. Der Zugriff erfolgt nur über kontrollierte Methoden (Getter und Setter).

🔐 Zugriffsmodifizierer

Modifizierer Klasse Paket Unterklasse Überall
private
(default)
protected
public

📥📤 Getter und Setter

public class Konto {
    // Private Attribute - nicht direkt zugänglich
    private String inhaber;
    private double kontostand;
    
    // Konstruktor
    public Konto(String inhaber) {
        this.inhaber = inhaber;
        this.kontostand = 0.0;
    }
    
    // Getter - Wert auslesen
    public String getInhaber() {
        return inhaber;
    }
    
    public double getKontostand() {
        return kontostand;
    }
    
    // Setter - Wert setzen (mit Validierung!)
    public void setInhaber(String inhaber) {
        if (inhaber != null && !inhaber.isEmpty()) {
            this.inhaber = inhaber;
        }
    }
    
    // Methoden für kontrollierte Änderungen
    public void einzahlen(double betrag) {
        if (betrag > 0) {
            kontostand += betrag;
        }
    }
    
    public boolean abheben(double betrag) {
        if (betrag > 0 && kontostand >= betrag) {
            kontostand -= betrag;
            return true;
        }
        return false;
    }
}
Warum Kapselung?
Ohne Kapselung könnte jemand konto.kontostand = -1000000; setzen!
Mit Kapselung wird im Setter geprüft, ob der Wert gültig ist.

4.3 Vererbung (Inheritance)

📖 Was ist Vererbung?

Vererbung ermöglicht es, eine neue Klasse (Unterklasse/Subklasse) auf Basis einer bestehenden Klasse (Oberklasse/Superklasse) zu erstellen. Die Unterklasse erbt alle Attribute und Methoden der Oberklasse und kann diese erweitern oder überschreiben.

👨‍👧 Vererbung mit extends

// Oberklasse (Superklasse)
public class Fahrzeug {
    protected String marke;
    protected int baujahr;
    
    public Fahrzeug(String marke, int baujahr) {
        this.marke = marke;
        this.baujahr = baujahr;
    }
    
    public void starten() {
        System.out.println("Fahrzeug startet");
    }
}

// Unterklasse (Subklasse) - erbt von Fahrzeug
public class Auto extends Fahrzeug {
    private int anzahlTueren;
    
    public Auto(String marke, int baujahr, int anzahlTueren) {
        super(marke, baujahr);  // Konstruktor der Oberklasse aufrufen
        this.anzahlTueren = anzahlTueren;
    }
    
    // Eigene Methode
    public void hupen() {
        System.out.println("Huuup!");
    }
}

// Weitere Unterklasse
public class Motorrad extends Fahrzeug {
    private boolean hatBeiwagen;
    
    public Motorrad(String marke, int baujahr) {
        super(marke, baujahr);
        this.hatBeiwagen = false;
    }
}

🔑 Das Schlüsselwort super

super verweist auf die Oberklasse und wird verwendet für:

  • super() – Konstruktor der Oberklasse aufrufen (muss erste Zeile sein!)
  • super.methode() – Methode der Oberklasse aufrufen
  • super.attribut – Attribut der Oberklasse zugreifen

4.4 Polymorphie

📖 Was ist Polymorphie?

Polymorphie (griechisch: "Vielgestaltigkeit") bedeutet, dass Objekte verschiedener Klassen über die gleiche Schnittstelle angesprochen werden können, aber unterschiedliches Verhalten zeigen. Eine Methode kann in Unterklassen überschrieben werden, um spezifisches Verhalten zu implementieren.

🔄 Methoden überschreiben (Override)

public class Tier {
    protected String name;
    
    public Tier(String name) {
        this.name = name;
    }
    
    public void lautGeben() {
        System.out.println("Das Tier macht ein Geräusch");
    }
}

public class Hund extends Tier {
    public Hund(String name) {
        super(name);
    }
    
    @Override  // Kennzeichnet überschriebene Methode
    public void lautGeben() {
        System.out.println(name + " sagt: Wuff!");
    }
}

public class Katze extends Tier {
    public Katze(String name) {
        super(name);
    }
    
    @Override
    public void lautGeben() {
        System.out.println(name + " sagt: Miau!");
    }
}

✨ Polymorphie in Aktion

public class Main {
    public static void main(String[] args) {
        // Array vom Typ Tier kann alle Unterklassen halten
        Tier[] tiere = new Tier[3];
        tiere[0] = new Hund("Bello");
        tiere[1] = new Katze("Minka");
        tiere[2] = new Tier("Unbekannt");
        
        // Gleicher Methodenaufruf, unterschiedliches Verhalten!
        for (Tier tier : tiere) {
            tier.lautGeben();
        }
    }
}

// Ausgabe:
// Bello sagt: Wuff!
// Minka sagt: Miau!
// Das Tier macht ein Geräusch

5. Statische Elemente (static)

📖 Was bedeutet static?

Statische Attribute und Methoden gehören zur Klasse selbst, nicht zu einzelnen Objekten. Es gibt nur eine Kopie, die von allen Objekten geteilt wird. Statische Elemente können ohne Objekt-Erstellung aufgerufen werden.

📊 Statische Attribute und Methoden

public class Schueler {
    // Statisches Attribut - wird von allen Objekten geteilt
    private static int anzahlSchueler = 0;
    
    // Instanz-Attribute - jedes Objekt hat eigene Werte
    private String name;
    private int alter;
    
    public Schueler(String name, int alter) {
        this.name = name;
        this.alter = alter;
        anzahlSchueler++;  // Zähler erhöhen bei jedem neuen Objekt
    }
    
    // Statische Methode - ohne Objekt aufrufbar
    public static int getAnzahlSchueler() {
        return anzahlSchueler;
    }
    
    // Normale Methode - braucht ein Objekt
    public String getName() {
        return name;
    }
}

// Verwendung:
System.out.println(Schueler.getAnzahlSchueler());  // 0

Schueler s1 = new Schueler("Max", 16);
Schueler s2 = new Schueler("Anna", 17);

System.out.println(Schueler.getAnzahlSchueler());  // 2
Wichtige Regel:
Statische Methoden können nicht auf nicht-statische Attribute zugreifen!
(Sie wissen nicht, zu welchem Objekt die Attribute gehören würden.)

📋 Zusammenfassung: OOP Grundlagen

  • Klasse: Bauplan für Objekte (definiert Attribute und Methoden)
  • Objekt: Konkrete Instanz einer Klasse (erstellt mit new)
  • Konstruktor: Spezielle Methode zur Initialisierung neuer Objekte
  • this: Verweist auf das aktuelle Objekt
  • Abstraktion: Komplexität verbergen, nur Wesentliches zeigen
  • Kapselung: Attribute private, Zugriff über Getter/Setter
  • Vererbung: Klassen erweitern mit extends, super für Oberklasse
  • Polymorphie: Methoden überschreiben (@Override) für spezifisches Verhalten
  • static: Gehört zur Klasse, nicht zum Objekt

UML-Sichtbarkeit (für Klassendiagramme):

  • + = public
  • - = private
  • # = protected
  • ~ = package (default)