Assignment Chef icon Assignment Chef
All German tutorials

Programming lesson

ECSE250 Assignment 1: Objektorientierte Modellierung eines Convenience Stores – Schritt-für-Schritt-Tutorial

Lerne, wie du mit abstrakten Klassen, Vererbung und Datenstrukturen einen KI-Assistenten für einen Campus-Store programmierst – inklusive Praxisbeispielen zu Buzziness und Happiness-Index.

ECSE250 Assignment 1 Objektorientierte Programmierung Java abstrakte Klasse Java Beispiel Vererbung Java Tutorial Convenience Store Programmierung Campus Store KI Happiness Index Java Buzziness Java eigene ArrayList implementieren ItemList ohne Collections UML Diagramm OOP Java equals Methode double Vergleich Studenten Projekt OOP Java Datenstrukturen selbst gebaut ECSE250 Lösungshilfe OOP Praxisbeispiel Uni

Einleitung: Warum OOP im Alltag (und im Campus-Store) unverzichtbar ist

Stell dir vor, du betreibst einen kleinen Convenience Store auf dem Campus – genau wie die fiktive McGill-Filiale in Trottier. Dein Sortiment umfasst Getränke, Snacks und spezielle Produkte wie FizzWiz oder SnoozeJuice. Um den Überblick zu behalten, brauchst du ein intelligentes System, das Bestände verwaltet, Preise kalkuliert und sogar die „Happiness“ der Kunden misst. Genau hier setzt die ECSE250 Assignment 1 an: Du programmierst einen KI-Assistenten, der auf objektorientierten Prinzipien basiert. Dieses Tutorial führt dich durch die Kernkonzepte – ohne die Lösung zu verraten, aber mit vielen praktischen Tipps.

Grundlagen: Abstrakte Klassen und Vererbung

Die Aufgabenstellung verlangt, dass du eine abstrakte Klasse StoreItem erstellst. Warum abstrakt? Weil du nie ein allgemeines „StoreItem“ instanziieren wirst, sondern konkrete Produkte wie Drink oder Snack. Abstrakte Klassen dienen als Blaupause: Sie definieren gemeinsame Eigenschaften (z. B. price und happinessIndex) und Methoden, die in Unterklassen überschrieben werden.

Ein aktuelles Beispiel: Denk an eine KI-gesteuerte Playlist-App, die Songs nach Stimmung kategorisiert. Ein abstrakter „Song“ könnte Felder wie tempo und genre haben. Konkrete Unterklassen wie PopSong oder JazzSong überschreiben dann die Methode getMoodBoost(). Genauso funktioniert es bei deinem Store: Jedes Produkt hat einen Preis und einen Happiness-Index, aber die Berechnung des Happiness-Index variiert (z. B. bei „buzzy“ Drinks).

Schritt 1: Die Basisklasse StoreItem

Definiere die Klasse im Package assignment1.items. Private Felder: price (double) und happinessIndex (int). Der Konstruktor soll bei negativen Werten eine IllegalArgumentException werfen. Wichtig: Beim Vergleich von price in der equals()-Methode darfst du nicht == verwenden, sondern prüfst, ob die Differenz kleiner als 0.001 ist – das verhindert Rundungsfehler.

public abstract class StoreItem {
    private double price;
    private int happinessIndex;

    public StoreItem(double price, int happinessIndex) {
        if (price < 0 || happinessIndex < 0) {
            throw new IllegalArgumentException("Preis und Happiness-Index müssen nicht-negativ sein.");
        }
        this.price = price;
        this.happinessIndex = happinessIndex;
    }

    public final double getPrice() { return price; }
    public int getHappinessIndex() { return happinessIndex; }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        StoreItem other = (StoreItem) obj;
        return Math.abs(this.price - other.price) < 0.001
                && this.happinessIndex == other.happinessIndex;
    }
}

Beachte: getPrice() ist final, damit Unterklassen den Preis nicht überschreiben können – der Preis eines Drinks bleibt pro Flasche gleich, egal wie viele Flaschen im Pack sind.

Schritt 2: Abstrakte Klasse Drink mit Buzziness

Die Klasse Drink erbt von StoreItem und fügt Felder wie numOfBottles (protected int) und buzzy (private boolean) hinzu. Statische Felder wie MAX_PACK_SIZE = 6 und BUZZY_HAPPINESS_BOOST = 1 sind praktisch, um Konstanten zu definieren, die für alle Drinks gelten.

Die Methode getHappinessIndex() wird überschrieben: Bei buzzy Drinks wird der Happiness-Index um BUZZY_HAPPINESS_BOOST erhöht. Hier ein Beispiel, wie du die Superklassenmethode nutzt:

@Override
public int getHappinessIndex() {
    int base = super.getHappinessIndex();
    return buzzy ? base + BUZZY_HAPPINESS_BOOST : base;
}

Die equals()-Methode in Drink sollte die equals() von StoreItem aufrufen und zusätzlich die Buzziness vergleichen. Die Anzahl der Flaschen wird nicht verglichen – das ist sinnvoll, denn zwei Drinks sind gleich, wenn sie denselben Preis, Happiness-Index und Buzziness haben, unabhängig von der Packungsgröße.

Die combine()-Methode nimmt ein anderes Drink-Objekt und prüft, ob es „equal“ ist (nach deiner Definition). Wenn ja, kombiniere die Flaschenanzahlen (z. B. beim Auffüllen des Bestands). Achte darauf, dass das kombinierte Ergebnis die maximale Packungsgröße nicht überschreitet.

Schritt 3: Konkrete Produkte: FizzWiz, SnoozeJuice und Snack

Jetzt erstellst du konkrete Unterklassen. FizzWiz und SnoozeJuice erben von Drink, Snack von StoreItem. Jede Klasse hat einen eigenen Konstruktor, der die Parameter an die Superklasse weitergibt. Du könntest auch zusätzliche Felder hinzufügen – zum Beispiel bei Snack eine nutrientScore. Denk daran, dass du für jede Klasse eine toString()-Methode schreiben kannst, um das Debugging zu erleichtern.

Ein Tipp aus der Praxis: In der Spieleentwicklung (z. B. bei Minecraft) werden oft ähnliche Hierarchien verwendet: Ein abstrakter „Item“ hat Schaden, Haltbarkeit etc., und konkrete Items wie „Schwert“ oder „Bogen“ erben davon. Genauso baust du deinen Store auf.

Schritt 4: Datumsklasse MyDate

Die Klasse MyDate speichert Tag, Monat, Jahr. Sie wird für Verfallsdaten oder Lieferdaten benötigt. Implementiere Methoden wie isBefore() oder daysUntil(). Achte darauf, dass du keine importierten Klassen wie java.util.Date verwendest – du schreibst alles selbst. Das ist eine gute Übung, um zu verstehen, wie Datumsberechnungen intern funktionieren.

Schritt 5: Datenstruktur ItemList (ohne ArrayList oder LinkedList)

Die Aufgabenstellung verbietet die Verwendung von ArrayList oder LinkedList. Du musst deine eigene Liste implementieren – zum Beispiel mit einem Array, das bei Bedarf wächst. Das ist ähnlich wie eine manuelle Implementierung einer ArrayList. Du brauchst Methoden wie add(), remove(), get(), size() und indexOf(). Achte auf die Effizienz: Beim Hinzufügen eines Elements, wenn das Array voll ist, erstellst du ein neues, doppelt so großes Array und kopierst die Elemente.

Warum ist das relevant? Stell dir vor, du entwickelst eine KI für eine E-Commerce-Plattform, die Tausende von Produkten in Echtzeit verwalten muss. Eine eigene Datenstruktur gibt dir die Kontrolle über Speicher und Performance – ähnlich wie bei der Entwicklung von Datenbanken oder Caching-Systemen.

Schritt 6: Store-Klasse als zentrale Steuerung

Die Klasse Store kombiniert alles: Sie enthält eine ItemList für das Inventar und Methoden wie addItem(), removeItem(), findItem() und getTotalHappiness(). Hier kommt die KI ins Spiel: Du könntest eine Methode suggestPurchase() implementieren, die basierend auf dem Happiness-Index und dem Preis das beste Angebot vorschlägt. Denk an eine Empfehlungs-Engine wie bei Spotify oder Netflix – nur für Snacks und Drinks.

Ein aktuelles Beispiel: Der Hype um KI-Assistenten wie ChatGPT hat gezeigt, wie wichtig gut strukturierter Code ist. Dein Store-Assistent ist ein Mini-KI-System, das Entscheidungen trifft: Welches Produkt hat das beste Preis-Happiness-Verhältnis? Soll ich einen Snack mit einem Drink kombinieren? Das sind Fragen, die du mit OOP elegant lösen kannst.

Häufige Fehler und wie du sie vermeidest

  • Fehler bei equals(): Vergiss nicht, den Typ zu prüfen (getClass() != obj.getClass()). Sonst kann ein Drink mit einem Snack verglichen werden – das ergibt keinen Sinn.
  • Flüchtige Kopien: Die Anweisung sagt explizit, dass du keine Kopien von Objekten erstellen darfst, die Methoden zurückgeben oder empfangen. Gib direkt die Referenz zurück, es sei denn, es ist anders angegeben.
  • Package-Struktur: Alle Klassen müssen im Package assignment1.items sein. Achte auf die korrekte Verzeichnisstruktur, sonst kompiliert der Code nicht auf Ed.
  • Testen auf Ed: Lade regelmäßig hoch und prüfe die Ergebnisse. Wenn dein Code nicht kompiliert, bekommst du 0 Punkte – auch wenn die Logik stimmt.

Zusammenfassung und nächste Schritte

Mit diesem Tutorial hast du einen Fahrplan für die ECSE250 Assignment 1. Du hast gelernt, wie abstrakte Klassen, Vererbung, Polymorphie und eigene Datenstrukturen zusammenspielen. Die Fähigkeit, solche Systeme zu entwerfen, ist nicht nur für die Uni wichtig – sie ist die Grundlage für modernes Software Engineering, sei es in der Spieleentwicklung, bei KI-Apps oder in der Finanzbranche.

Setze dich jetzt an die Tastatur und zeichne zuerst ein UML-Diagramm. Das hilft dir, die Beziehungen zwischen den Klassen zu visualisieren. Und denk dran: Der erste Late-Penalty wird dir am Semesterende automatisch erlassen – aber versuche trotzdem, pünktlich abzugeben. Viel Erfolg!