Home

06 Übung: MVC-Refactoring

exercises chapter-06 architecture mvc refactoring separation-of-concerns

Einführung

Das Model-View-Controller (MVC)-Muster trennt Code in drei Komponenten:

Zusätzlich behandelt in Schichtenarchitekturen eine Repository/Datenzugriffsschicht Datenbankoperationen getrennt vom Model.

In dieser Übung analysieren Sie “Spaghetti-Code”, der alle Belange vermischt, und refaktorieren ihn in ordnungsgemäß getrennte Komponenten.

Lernziele:

Anweisungen:

  1. Studieren Sie die Referenz zur Separation of Concerns
  2. Analysieren Sie die bereitgestellte monolithische Funktion
  3. Bearbeiten Sie alle vier Aufgaben
  4. Überprüfen Sie Ihre Antworten anhand der Lösung

Gesamtpunkte: 20 Zeit: ~25 Minuten


Referenz: Separation of Concerns

Komponente Verantwortung Sollte NICHT enthalten
Model Geschäftsentitäten, Berechnungen, Regeln SQL, HTML, HTTP-Verarbeitung
View HTML-Generierung, Formatierung Geschäftslogik, SQL
Controller Anfrageverarbeitung, Koordination Geschäftslogik, SQL, HTML
Repository Datenbankabfragen, Persistenz Geschäftslogik, HTML

Zu analysierender Code

Die folgende Funktion vermischt Datenzugriff, Geschäftslogik und Präsentation in einer einzigen Funktion. Zeilennummern sind als Referenz angegeben.

 1  def show_order_details(order_id):
 2      # Connect to database and fetch order
 3      conn = sqlite3.connect('shop.db')
 4      cursor = conn.cursor()
 5      order_row = cursor.execute(
 6          "SELECT * FROM orders WHERE id = ?", (order_id,)
 7      ).fetchone()
 8
 9      if not order_row:
10          return "<h1>Order Not Found</h1>"
11
12      # Fetch order items
13      items = cursor.execute(
14          "SELECT * FROM order_items WHERE order_id = ?", (order_id,)
15      ).fetchall()
16
17      # Calculate totals (business logic)
18      subtotal = sum(item['price'] * item['quantity'] for item in items)
19      tax = subtotal * 0.19  # 19% VAT
20      shipping = 0 if subtotal > 50 else 4.99
21      total = subtotal + tax + shipping
22
23      # Check if eligible for discount
24      if order_row['customer_type'] == 'premium':
25          total = total * 0.9  # 10% premium discount
26
27      # Generate HTML
28      html = f"""
29      <div class="order">
30          <h1>Order #{order_id}</h1>
31          <table>
32              <tr><th>Item</th><th>Qty</th><th>Price</th></tr>
33      """
34      for item in items:
35          html += f"<tr><td>{item['name']}</td><td>{item['quantity']}</td>"
36          html += f"<td>€{item['price']:.2f}</td></tr>"
37
38      html += f"""
39          </table>
40          <p>Subtotal: €{subtotal:.2f}</p>
41          <p>Tax (19%): €{tax:.2f}</p>
42          <p>Shipping: €{shipping:.2f}</p>
43          <p><strong>Total: €{total:.2f}</strong></p>
44      </div>
45      """
46      return html

Aufgabe A: Code-Abschnitte klassifizieren (6 Punkte)

Identifizieren Sie für jeden unten aufgeführten Code-Abschnitt, ob er zu M (Model), V (View), C (Controller) oder D (Datenzugriff/Repository) gehört.

Zeilen Klassifikation (M/V/C/D) Kurze Begründung
3-7
9-10
13-15
17-21
24-25
27-45

(1 Punkt pro korrekter Klassifikation)


Aufgabe B: Das Model entwerfen (5 Punkte)

Schreiben Sie eine Python-Klasse Order, die:

  1. Die Bestelldaten speichert (id, customer_type, items)
  2. Die Geschäftslogik für die Berechnung von Zwischensumme, Steuer, Versand und Gesamtsumme enthält
  3. @property-Dekoratoren für berechnete Werte verwendet

Ihre Antwort:

# Schreiben Sie hier Ihre Order-Klasse






(Bewertung: 1 Pkt. Struktur, 2 Pkt. Properties, 2 Pkt. korrekte Logik)


Aufgabe C: Das Repository entwerfen (4 Punkte)

Schreiben Sie eine Python-Klasse OrderRepository mit einer Methode find_by_id(order_id), die:

  1. Die Datenbankabfragen ausführt
  2. Ein Order-Objekt zurückgibt (oder None, wenn nicht gefunden)
  3. KEINE Geschäftslogik oder HTML enthält

Ihre Antwort:

# Schreiben Sie hier Ihre OrderRepository-Klasse






(Bewertung: 2 Pkt. korrekte Abfragen, 2 Pkt. korrekter Rückgabetyp)


Aufgabe D: Das Komponentendiagramm zeichnen (5 Punkte)

Zeichnen Sie ein Diagramm, das zeigt:

  1. Die vier Komponenten (Controller, Repository, Model, View)
  2. Pfeile, die zeigen, welche Komponente welche aufruft
  3. Beschriftungen an den Pfeilen, die beschreiben, was übergeben wird

Verwenden Sie ASCII-Art oder einfache Kästen und Pfeile.

Ihre Antwort:

(Zeichnen Sie hier Ihr Diagramm)






(Bewertung: 1 Pkt. pro korrekt dargestellter Komponente, 1 Pkt. für korrekte Pfeilrichtungen)


Lösung

Lösung anzeigen

Aufgabe A: Code-Klassifikation

Zeilen Klassifikation Begründung
3-7 D (Datenzugriff) Datenbankverbindung und SQL-Abfrage
9-10 C (Controller) Ablaufsteuerung für Nicht-gefunden-Fall
13-15 D (Datenzugriff) SQL-Abfrage für Items
17-21 M (Model/Geschäftslogik) Berechnungslogik (Zwischensumme, Steuer, Versand)
24-25 M (Model/Geschäftslogik) Geschäftsregel (Premium-Rabatt)
27-45 V (View) HTML-Generierung

Aufgabe B: Model-Klasse

@dataclass
class OrderItem:
    name: str
    quantity: int
    price: float

@dataclass
class Order:
    id: str
    customer_type: str
    items: list[OrderItem]

    @property
    def subtotal(self) -> float:
        return sum(item.price * item.quantity for item in self.items)

    @property
    def tax(self) -> float:
        return self.subtotal * 0.19

    @property
    def shipping(self) -> float:
        return 0 if self.subtotal > 50 else 4.99

    @property
    def total(self) -> float:
        base_total = self.subtotal + self.tax + self.shipping
        if self.customer_type == 'premium':
            return base_total * 0.9
        return base_total

Bewertungshinweise:

  • 1 Punkt: Hat id, customer_type, items Attribute
  • 1 Punkt: Verwendet @property für subtotal
  • 1 Punkt: Verwendet @property für tax und/oder shipping
  • 1 Punkt: Korrekte subtotal-Berechnung
  • 1 Punkt: Korrektes total mit Premium-Rabatt-Logik

Aufgabe C: Repository-Klasse

class OrderRepository:
    def __init__(self, db_path: str):
        self.db_path = db_path

    def find_by_id(self, order_id: str) -> Order | None:
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()

        order_row = cursor.execute(
            "SELECT * FROM orders WHERE id = ?", (order_id,)
        ).fetchone()

        if not order_row:
            return None

        item_rows = cursor.execute(
            "SELECT * FROM order_items WHERE order_id = ?", (order_id,)
        ).fetchall()

        items = [OrderItem(
            name=row['name'],
            quantity=row['quantity'],
            price=row['price']
        ) for row in item_rows]

        return Order(
            id=order_id,
            customer_type=order_row['customer_type'],
            items=items
        )

Bewertungshinweise:

  • 1 Punkt: Korrekte Order-Abfrage
  • 1 Punkt: Korrekte Items-Abfrage
  • 1 Punkt: Gibt None zurück, wenn nicht gefunden
  • 1 Punkt: Konstruiert und gibt Order-Objekt zurück

Aufgabe D: Komponentendiagramm

    Benutzeranfrage
         │
         ▼
┌─────────────────┐
│   Controller    │  ← Koordiniert Ablauf
│ OrderController │
└────────┬────────┘
         │ ruft find_by_id() auf
         ▼
┌─────────────────┐
│   Repository    │  ← Datenzugriff
│ OrderRepository │
└────────┬────────┘
         │ gibt Order zurück
         ▼
┌─────────────────┐
│     Model       │  ← Geschäftslogik
│     Order       │    (Berechnungen)
└────────┬────────┘
         │ wird an render() übergeben
         ▼
┌─────────────────┐
│      View       │  ← Präsentation
│ render_order()  │
└────────┬────────┘
         │
         ▼
    HTML-Antwort

Bewertungshinweise:

  • 1 Punkt: Controller oben dargestellt (empfängt Anfrage)
  • 1 Punkt: Repository dargestellt (vom Controller aufgerufen)
  • 1 Punkt: Model dargestellt (vom Repository zurückgegeben)
  • 1 Punkt: View dargestellt (erhält Model)
  • 1 Punkt: Korrekte Pfeilrichtungen (Abwärtsfluss)

Alternative akzeptable Diagrammstile:

Controller → Repository → (gibt zurück) Model
    ↓                          ↓
   View ←──────────────────────┘

Oder horizontal:

Anfrage → Controller → Repository → DB
              ↓              ↓
            View ← ← ← ← Order
© 2026 Dominik Mueller   •  Powered by Soopr   •  Theme  Moonwalk