327 lines
20 KiB
HTML
327 lines
20 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Kapitel 16: Master-Abschlussprojekt – PetCare & Tierheim-Manager</title>
|
||
<link rel="stylesheet" href="../assets/style.css">
|
||
<style>
|
||
.gold-header {
|
||
background: linear-gradient(135deg, #4338ca 0%, #6366f1 50%, #8b5cf6 100%);
|
||
}
|
||
.mvc-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||
gap: 16px;
|
||
margin: 20px 0;
|
||
}
|
||
.mvc-card {
|
||
background: #ffffff;
|
||
border-radius: var(--radius-md);
|
||
padding: 18px;
|
||
border: 2px solid var(--border-color);
|
||
box-shadow: var(--shadow-sm);
|
||
transition: transform 0.2s, box-shadow 0.2s;
|
||
}
|
||
.mvc-card:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: var(--shadow-md);
|
||
}
|
||
.mvc-card.model { border-top: 5px solid #10b981; }
|
||
.mvc-card.view { border-top: 5px solid #3b82f6; }
|
||
.mvc-card.controller { border-top: 5px solid #8b5cf6; }
|
||
.badge-master {
|
||
background: linear-gradient(90deg, #f59e0b, #ec4899);
|
||
color: white;
|
||
font-weight: 700;
|
||
padding: 4px 14px;
|
||
border-radius: 999px;
|
||
display: inline-block;
|
||
font-size: 0.85rem;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.05em;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<header class="gold-header">
|
||
<div class="header-container">
|
||
<nav class="nav-links">
|
||
<a href="../index.html">🏠 Zur Kursübersicht</a>
|
||
<span>›</span>
|
||
<a href="../15_parameter_und_container/index.html">Kapitel 15</a>
|
||
<span>›</span>
|
||
<span style="color: white; font-weight: 600;">Kapitel 16 (Masterprojekt)</span>
|
||
</nav>
|
||
<div class="header-badge">🏆 Kapitel 16 • Das Große Meisterstück</div>
|
||
<h1>PetCare & Tierheim-Manager Pro 🐾🏥🖥️</h1>
|
||
<p>Die Krönung aller 3 Lehrpfade: Model-View-Controller (MVC), Vererbung, Polymorphie, JSON- & CSV-Persistenz, robuste Exceptions und eine vollwertige Tkinter Desktop-Applikation!</p>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="container">
|
||
|
||
<!-- 1. Motivierendes Intro & Lehrpfad-Konsolidierung -->
|
||
<section class="card">
|
||
<h2>1. 🏆 Dein Meisterstück: Alle 3 Lehrpfade vereint!</h2>
|
||
<p>
|
||
Herzlichen Glückwunsch zu diesem Meilenstein! Du hast gelernt, wie man Variablen deklariert, Schleifen baut, Klassen als Baupläne entwirft, Datenstrukturen serialisiert und echte Desktop-Fenster mit Tkinter zum Leben erweckt.
|
||
</p>
|
||
<p>
|
||
In diesem <strong>Master-Abschlussprojekt</strong> baust du eine vollwertige, professionelle Software für ein Tierheim: Den <strong>PetCare- & Tierheim-Manager</strong>. Hier greifen alle Rädchen nahtlos ineinander:
|
||
</p>
|
||
|
||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 15px; margin: 20px 0;">
|
||
<div style="background: #f0fdf4; border-left: 4px solid #10b981; padding: 14px; border-radius: var(--radius-sm);">
|
||
<h4 style="color: #166534; margin-bottom: 6px;">🛣️ Lehrpfad 1: Grundlagen</h4>
|
||
<p style="font-size: 0.88rem; color: var(--text-muted); margin: 0;">
|
||
Typsichere Datenverarbeitung, String-Formatierung für Berichte, Rechnen von Statistiken (Mittelwert, Summen) und sauberer CSV-Export.
|
||
</p>
|
||
</div>
|
||
<div style="background: #eff6ff; border-left: 4px solid #3b82f6; padding: 14px; border-radius: var(--radius-sm);">
|
||
<h4 style="color: #1e40af; margin-bottom: 6px;">🛣️ Lehrpfad 2: OOP & Clean Code</h4>
|
||
<p style="font-size: 0.88rem; color: var(--text-muted); margin: 0;">
|
||
Objekt-Instanziierung, Kapselung, <code>self</code>, <code>__str__</code>, <code>__repr__</code>, 1:n Beziehungen und modulare Methoden.
|
||
</p>
|
||
</div>
|
||
<div style="background: #f5f3ff; border-left: 4px solid #8b5cf6; padding: 14px; border-radius: var(--radius-sm);">
|
||
<h4 style="color: #5b21b6; margin-bottom: 6px;">🛣️ Lehrpfad 3: Professionelle Software</h4>
|
||
<p style="font-size: 0.88rem; color: var(--text-muted); margin: 0;">
|
||
Vererbung (<code>super()</code>), Polymorphie (Tierlaute), eigene Exceptions, JSON-Savegames, Tkinter Desktop-GUI und 100% Unittest-Abdeckung!
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 2. MVC Architektur -->
|
||
<section class="card">
|
||
<h2>2. 🏛️ Die Architektur: Model-View-Controller (MVC)</h2>
|
||
<p>
|
||
In professionellen Software-Unternehmen schreibt man niemals Geschäftslogik und Benutzeroberfläche in einen einzigen großen Code-Klotz ("Spaghetti-Code"). Stattdessen trennt man das System strikt nach dem <strong>MVC-Muster</strong>:
|
||
</p>
|
||
|
||
<div class="mvc-grid">
|
||
<div class="mvc-card model">
|
||
<span class="badge-master" style="background: #10b981;">1. Das Model (Daten & Logik)</span>
|
||
<h3 style="margin-top: 10px; color: #065f46;">Klassen <code>Tier</code> & <code>Tierheim</code></h3>
|
||
<p style="font-size: 0.9rem;">
|
||
Verwaltet alle Tiere, berechnet Statistiken (Ø Alter, Gewicht), filtert Bestände und speichert/lädt JSON & CSV.
|
||
</p>
|
||
<div class="box box-tipp" style="margin-top: 10px; font-size: 0.82rem; padding: 8px;">
|
||
💡 <strong>100% Headless testbar:</strong> Weiß nichts von Tkinter und läuft komplett ohne Bildschirm!
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mvc-card view">
|
||
<span class="badge-master" style="background: #3b82f6;">2. Die View (Anzeige)</span>
|
||
<h3 style="margin-top: 10px; color: #1e40af;">Tkinter Widgets</h3>
|
||
<p style="font-size: 0.9rem;">
|
||
Das visuelle Fenster: Header, Formular-Eingabefelder, Listbox für den Tierbestand, Aktionsbuttons und die dynamische Statusleiste.
|
||
</p>
|
||
<div class="box box-tipp" style="margin-top: 10px; font-size: 0.82rem; padding: 8px;">
|
||
👀 <strong>Dumb View:</strong> Zeigt nur an, was das Model ihr mitteilt.
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mvc-card controller">
|
||
<span class="badge-master" style="background: #8b5cf6;">3. Der Controller (Steuerung)</span>
|
||
<h3 style="margin-top: 10px; color: #5b21b6;">Klasse <code>TierheimApp</code></h3>
|
||
<p style="font-size: 0.9rem;">
|
||
Reagiert auf Benutzerklicks, validiert Eingaben mit <code>try-except</code>, ruft Methoden des Tierheims auf und aktualisiert die Anzeige.
|
||
</p>
|
||
<div class="box box-tipp" style="margin-top: 10px; font-size: 0.82rem; padding: 8px;">
|
||
🛡️ <strong>Fehler-Abfangjäger:</strong> Verhindert App-Abstürze bei Fehleingaben.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="box box-analogie">
|
||
<div class="box-title">🍽️ Die Restaurant-Analogie für MVC:</div>
|
||
<ul>
|
||
<li><strong>Model (Die Küche):</strong> Kocht das Essen, verwaltet die Zutaten und Rezepte (Logik & Daten).</li>
|
||
<li><strong>View (Die Speisekarte & Teller):</strong> Das, was der Gast sieht und liest (Grafik).</li>
|
||
<li><strong>Controller (Der Kellner):</strong> Nimmt die Bestellung vom Gast auf, gibt sie an die Küche weiter und bringt das fertige Gericht an den Tisch!</li>
|
||
</ul>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 3. Klassendiagramm & Vererbung -->
|
||
<section class="card">
|
||
<h2>3. 🧬 Das Datenmodell: Vererbung & Polymorphie</h2>
|
||
<p>
|
||
Ein Tierheim beherbergt verschiedene Tierarten: <strong>Hunde</strong>, <strong>Katzen</strong> und <strong>Vögel</strong>.
|
||
Sie alle teilen gemeinsame Eigenschaften (Name, Alter, Gewicht, Impfstatus, Hunger), besitzen jedoch jeweils eigene Spezialmerkmale und Verhaltensweisen:
|
||
</p>
|
||
|
||
<div class="code-container">
|
||
<div class="code-header">
|
||
<div class="code-dots">
|
||
<span class="code-dot dot-red"></span>
|
||
<span class="code-dot dot-yellow"></span>
|
||
<span class="code-dot dot-green"></span>
|
||
</div>
|
||
<span>Python (Vererbungshierarchie & super())</span>
|
||
</div>
|
||
<pre><span class="kw">class</span> <span class="cls">Tier</span>:
|
||
<span class="kw">def</span> <span class="fn">__init__</span>(<span class="var">self</span>, <span class="var">name</span>: <span class="cls">str</span>, <span class="var">alter</span>: <span class="cls">int</span>, <span class="var">gewicht</span>: <span class="cls">float</span>, <span class="var">geimpft</span>: <span class="cls">bool</span> = <span class="kw">False</span>, <span class="var">hunger</span>: <span class="cls">int</span> = <span class="num">50</span>):
|
||
<span class="kw">if</span> <span class="kw">not</span> <span class="var">name</span>.<span class="fn">strip</span>():
|
||
<span class="kw">raise</span> <span class="cls">ValidierungsFehler</span>(<span class="str">"Name darf nicht leer sein!"</span>)
|
||
<span class="var">self</span>.<span class="attr">name</span> = <span class="var">name</span>.<span class="fn">strip</span>()
|
||
<span class="var">self</span>.<span class="attr">alter</span> = <span class="var">alter</span>
|
||
<span class="var">self</span>.<span class="attr">gewicht</span> = <span class="var">gewicht</span>
|
||
<span class="var">self</span>.<span class="attr">geimpft</span> = <span class="var">geimpft</span>
|
||
<span class="var">self</span>.<span class="attr">hunger</span> = <span class="fn">max</span>(<span class="num">0</span>, <span class="fn">min</span>(<span class="num">100</span>, <span class="var">hunger</span>))
|
||
|
||
<span class="kw">def</span> <span class="fn">mache_laut</span>(<span class="var">self</span>) -> <span class="cls">str</span>:
|
||
<span class="kw">return</span> <span class="str">f"{self.name} macht ein Geräusch."</span>
|
||
|
||
<span class="kw">class</span> <span class="cls">Hund</span>(<span class="cls">Tier</span>):
|
||
<span class="kw">def</span> <span class="fn">__init__</span>(<span class="var">self</span>, <span class="var">name</span>: <span class="cls">str</span>, <span class="var">alter</span>: <span class="cls">int</span>, <span class="var">gewicht</span>: <span class="cls">float</span>, <span class="var">rasse</span>: <span class="cls">str</span> = <span class="str">"Mischling"</span>, ...):
|
||
<span class="fn">super</span>().<span class="fn">__init__</span>(<span class="var">name</span>, <span class="var">alter</span>, <span class="var">gewicht</span>, ...)
|
||
<span class="var">self</span>.<span class="attr">rasse</span> = <span class="var">rasse</span>
|
||
<span class="var">self</span>.<span class="attr">gassigegangen</span> = <span class="kw">False</span>
|
||
|
||
<span class="kw">def</span> <span class="fn">mache_laut</span>(<span class="var">self</span>) -> <span class="cls">str</span>:
|
||
<span class="kw">return</span> <span class="str">f"{self.name} bellt: Wuff! Wuff! 🐶"</span></pre>
|
||
</div>
|
||
|
||
<div class="box box-merke">
|
||
<div class="box-title">🎭 Polymorphie in der Praxis:</div>
|
||
Wenn wir im Tierheim <code>for tier in self.tiere: print(tier.mache_laut())</code> aufrufen, weiß Python dank Polymorphie zur Laufzeit automatisch, welches Tier welchen Laut von sich gibt – ohne eine einzige <code>if/elif</code>-Typabfrage!
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 4. Persistenz & Factory-Muster -->
|
||
<section class="card">
|
||
<h2>4. 💾 Savegame-Persistenz mit dem Factory-Muster</h2>
|
||
<p>
|
||
Beim Speichern als JSON-Datei wandeln wir jedes Tier-Objekt in ein Dictionary um und ergänzen den Schlüssel <code>"art": "Hund"</code>.
|
||
Beim Laden liest die <strong>Factory-Methode</strong> <code>Tier.from_dict(data)</code> diesen Schlüssel aus und erzeugt automatisch die exakte Unterklasse:
|
||
</p>
|
||
|
||
<div class="code-container">
|
||
<div class="code-header">
|
||
<div class="code-dots">
|
||
<span class="code-dot dot-red"></span>
|
||
<span class="code-dot dot-yellow"></span>
|
||
<span class="code-dot dot-green"></span>
|
||
</div>
|
||
<span>Python (Factory-Methode mit Type-Discriminator)</span>
|
||
</div>
|
||
<pre><span class="kw">@classmethod</span>
|
||
<span class="kw">def</span> <span class="fn">from_dict</span>(<span class="var">cls</span>, <span class="var">data</span>: <span class="cls">dict</span>) -> <span class="cls">Tier</span>:
|
||
<span class="var">art</span> = <span class="var">data</span>.<span class="fn">get</span>(<span class="str">"art"</span>, <span class="str">"Tier"</span>)
|
||
<span class="kw">if</span> <span class="var">art</span> == <span class="str">"Hund"</span>:
|
||
<span class="kw">return</span> <span class="cls">Hund</span>(<span class="var">name</span>=<span class="var">data</span>[<span class="str">"name"</span>], <span class="var">alter</span>=<span class="var">data</span>[<span class="str">"alter"</span>], <span class="var">gewicht</span>=<span class="var">data</span>[<span class="str">"gewicht"</span>], <span class="var">rasse</span>=<span class="var">data</span>.<span class="fn">get</span>(<span class="str">"rasse"</span>, <span class="str">"Mischling"</span>), ...)
|
||
<span class="kw">elif</span> <span class="var">art</span> == <span class="str">"Katze"</span>:
|
||
<span class="kw">return</span> <span class="cls">Katze</span>(...)
|
||
<span class="kw">elif</span> <span class="var">art</span> == <span class="str">"Vogel"</span>:
|
||
<span class="kw">return</span> <span class="cls">Vogel</span>(...)
|
||
<span class="kw">else</span>:
|
||
<span class="kw">return</span> <span class="cls">Tier</span>(...)</pre>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 5. Projekt-Checkliste -->
|
||
<section class="card">
|
||
<h2>5. ✅ Deine Projekt-Checkliste</h2>
|
||
<p>Arbeite die folgenden 5 Abschnitte in der Datei <code>aufgabe.py</code> der Reihe nach ab:</p>
|
||
|
||
<ul class="checklist">
|
||
<li class="check-item">
|
||
<strong>1. Eigene Exceptions:</strong> <code>TierheimFehler</code>, <code>ValidierungsFehler</code>, <code>TierNichtGefundenFehler</code>, <code>KapazitaetUeberschrittenFehler</code>.
|
||
</li>
|
||
<li class="check-item">
|
||
<strong>2. Basisklasse <code>Tier</code>:</strong> Validierung von Name, Alter, Gewicht im <code>__init__</code>, Methoden <code>fuettern()</code>, <code>impfen()</code>, <code>to_dict()</code> und Factory-Methode <code>from_dict()</code>.
|
||
</li>
|
||
<li class="check-item">
|
||
<strong>3. Kindklassen <code>Hund</code>, <code>Katze</code>, <code>Vogel</code>:</strong> Vererbung mit <code>super()</code>, artspezifische Attribute & Methoden (<code>gassi_gehen()</code>, <code>kratzen()</code>, <code>fliegen()</code>) und polymorphes <code>mache_laut()</code>.
|
||
</li>
|
||
<li class="check-item">
|
||
<strong>4. Geschäftslogik <code>Tierheim</code>:</strong> Tieraufnahme mit Kapazitätsprüfung, Vermittlung/Entlassen, Filter nach Art/Impfung/Hunger, Statistiken (Ø Alter, Gewicht) und JSON-/CSV-Persistenz.
|
||
</li>
|
||
<li class="check-item">
|
||
<strong>5. Benutzeroberfläche <code>TierheimApp</code>:</strong> Tkinter-Events mit dem Model verknüpfen, try-except Fehlerabfang und dynamische Statusleisten-Meldungen.
|
||
</li>
|
||
</ul>
|
||
</section>
|
||
|
||
<!-- 6. Lösungstipps & Details -->
|
||
<section class="card">
|
||
<h2>6. 💡 Ausklappbare Hilfestellungen & Tipps</h2>
|
||
|
||
<details>
|
||
<summary>🔍 Tipp zu Schritt 1: Eigene Exceptions definieren</summary>
|
||
<div class="details-content">
|
||
<p>Eigene Exception-Klassen erben in Python einfach von <code>Exception</code> oder einer übergeordneten Basis-Exception:</p>
|
||
<div class="code-container">
|
||
<pre><span class="kw">class</span> <span class="cls">TierheimFehler</span>(<span class="cls">Exception</span>):
|
||
<span class="kw">pass</span>
|
||
|
||
<span class="kw">class</span> <span class="cls">ValidierungsFehler</span>(<span class="cls">TierheimFehler</span>):
|
||
<span class="kw">pass</span></pre>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
|
||
<details>
|
||
<summary>🔍 Tipp zu Schritt 4: CSV-Export mit dem csv-Modul</summary>
|
||
<div class="details-content">
|
||
<p>Nutze <code>csv.writer</code> mit <code>newline=""</code> für saubere Tabellenausgabe:</p>
|
||
<div class="code-container">
|
||
<pre><span class="kw">with</span> <span class="fn">open</span>(<span class="var">dateipfad</span>, <span class="str">"w"</span>, <span class="var">newline</span>=<span class="str">""</span>, <span class="var">encoding</span>=<span class="str">"utf-8"</span>) <span class="kw">as</span> <span class="var">f</span>:
|
||
<span class="var">writer</span> = <span class="var">csv</span>.<span class="fn">writer</span>(<span class="var">f</span>)
|
||
<span class="var">writer</span>.<span class="fn">writerow</span>([<span class="str">"Art"</span>, <span class="str">"Name"</span>, <span class="str">"Alter"</span>, <span class="str">"Gewicht"</span>, <span class="str">"Geimpft"</span>, <span class="str">"Hunger"</span>, <span class="str">"Details"</span>])
|
||
<span class="kw">for</span> <span class="var">t</span> <span class="kw">in</span> <span class="var">self</span>.<span class="attr">tiere</span>:
|
||
<span class="var">writer</span>.<span class="fn">writerow</span>([<span class="var">t</span>.<span class="var">__class__</span>.<span class="var">__name__</span>, <span class="var">t</span>.<span class="attr">name</span>, <span class="var">t</span>.<span class="attr">alter</span>, <span class="str">f"{t.gewicht:.1f}"</span>, <span class="str">"Ja"</span> <span class="kw">if</span> <span class="var">t</span>.<span class="attr">geimpft</span> <span class="kw">else</span> <span class="str">"Nein"</span>, <span class="str">f"{t.hunger}%"</span>, <span class="var">t</span>.<span class="fn">get_details</span>()])</pre>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
|
||
<details>
|
||
<summary>🔍 Tipp zu Schritt 5: Fehler im GUI-Controller sanft abfangen</summary>
|
||
<div class="details-content">
|
||
<p>Damit die Desktop-App bei einer Fehleingabe nicht abstürzt, fange Exceptions im Event-Handler ab:</p>
|
||
<div class="code-container">
|
||
<pre><span class="kw">try</span>:
|
||
<span class="var">self</span>.<span class="attr">tierheim</span>.<span class="fn">tier_aufnehmen</span>(<span class="var">neues_tier</span>)
|
||
<span class="var">self</span>.<span class="fn">aktualisiere_ansicht</span>()
|
||
<span class="var">self</span>.<span class="fn">setze_status</span>(<span class="str">f"{neues_tier.name} aufgenommen! 🎉"</span>, <span class="var">typ</span>=<span class="str">"success"</span>)
|
||
<span class="kw">except</span> <span class="cls">TierheimFehler</span> <span class="kw">as</span> <span class="var">e</span>:
|
||
<span class="var">self</span>.<span class="fn">setze_status</span>(<span class="fn">str</span>(<span class="var">e</span>), <span class="var">typ</span>=<span class="str">"warning"</span>)</pre>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
</section>
|
||
|
||
<!-- 7. Testen & Starten -->
|
||
<section class="card">
|
||
<h2>7. 🧪 Lösung überprüfen & App starten</h2>
|
||
|
||
<h3>1. Automatische Unittests ausführen:</h3>
|
||
<div class="code-container">
|
||
<div class="code-header">
|
||
<span>Terminal (im Ordner 16_master_abschlussprojekt)</span>
|
||
</div>
|
||
<pre><code>python3 test_aufgabe.py</code></pre>
|
||
</div>
|
||
|
||
<h3>2. Grafische Desktop-Applikation starten:</h3>
|
||
<div class="code-container">
|
||
<div class="code-header">
|
||
<span>Terminal</span>
|
||
</div>
|
||
<pre><code>python3 app.py</code></pre>
|
||
</div>
|
||
|
||
<div class="footer-nav">
|
||
<a href="../15_parameter_und_container/index.html" class="btn btn-secondary">← Zurück zu Kapitel 15</a>
|
||
<a href="../index.html" class="btn">🏆 Kurs abgeschlossen – Zur Kursübersicht →</a>
|
||
</div>
|
||
</section>
|
||
|
||
</main>
|
||
|
||
</body>
|
||
</html>
|