Zum Inhalt springen

Aufgabe 12 - Formen und Flächen

Zu Zen-Modus wechseln

Der Klassiker des Polymorphismus: verschiedene geometrische Formen, ein gemeinsamer Vertrag als abstrakte Basisklasse, und ein Programm, das erweiterbar bleibt, ohne dass bestehender Code angefasst wird (siehe Kapitel Polymorphismus und Interfaces). Der Erweiterbarkeits-Beweis in Teil C ist das Herzstück der Übung: Sie erleben das Open-Closed-Prinzip nicht als Vokabel, sondern als überprüfbare Eigenschaft Ihres eigenen Codes.

  • Kapitel Polymorphismus und Interfaces (abc, @abstractmethod, Open-Closed-Prinzip) und Kapitel Vererbung.
  • Python (abc, math), VS Code.
  • Sie definieren Verträge als abstrakte Basisklassen und implementieren sie.
  • Sie schreiben Funktionen, die ausschließlich gegen den Vertrag programmiert sind.
  • Sie weisen die Erweiterbarkeit eines Entwurfs experimentell nach.
  • Reproduktion: die abc-Syntax wiedergeben und den Vertrag nach Vorgabe erstellen (Teil A).
  • Reorganisation und Transfer: den Vertrag auf mehrere konkrete Klassen übertragen und polymorphe Funktionen entwickeln (Teile B und C).
  • Reflexion, Problemlösung und Urteilsbildung: eine heikle Vererbungsfrage (Quadrat und Rechteck) untersuchen und ein begründetes Urteil fällen (Teil D).

Die Übung ist auf etwa zwei Stunden ausgelegt. Teil D ist der Expertenteil.

  1. Definieren Sie die abstrakte Klasse Shape (erbt von ABC) mit den abstrakten Methoden area() und perimeter() sowie der konkreten Methode describe(), die etwa "Circle: area 12.57, perimeter 12.57" liefert und dafür die abstrakten Methoden aufruft (type(self).__name__ liefert den Klassennamen).
  2. Versuchen Sie, Shape() direkt zu instanzieren, und notieren Sie die exakte Fehlermeldung. Erklären Sie in einem Satz, warum dieses Verbot erwünscht ist.
  3. Legen Sie eine leere Subklasse Circle(Shape) an (noch ohne Methoden) und versuchen Sie, sie zu instanzieren. Was meldet Python, und was sagt Ihnen der Zeitpunkt der Meldung?
  1. Implementieren Sie Circle(radius), Rectangle(width, height) und Triangle(a, b, c). Für die Dreiecksfläche recherchieren Sie die Formel von Heron.
  2. Ungültige Maße lehnen die Konstruktoren ab: negative oder Null-Werte sowie eine verletzte Dreiecksungleichung führen zu einem raise ValueError(...) mit aussagekräftiger Meldung.
  3. Prüfen Sie jede Klasse mit einem bekannten Wert (etwa Kreis mit Radius 1: Fläche rund 3.1416; Rechteck 3 mal 4: Fläche 12, Umfang 14).
  1. Schreiben Sie total_area(shapes) und largest_shape(shapes). Beide Funktionen kennen ausschließlich den Vertrag Shape; kein isinstance, keine Klassennamen.
  2. Demonstrieren Sie beide mit einer gemischten Liste aus mindestens fünf Formen.
  3. Der Erweiterbarkeits-Beweis: Ergänzen Sie jetzt eine Klasse RegularHexagon(side) (regelmäßiges Sechseck; Formeln recherchieren). Dabei gilt die Sperrklausel: Ausschließlich neue Zeilen sind erlaubt, keine bestehende Zeile darf sich ändern. Funktionieren total_area und largest_shape sofort? Protokollieren Sie das Ergebnis als Kommentar; es ist der Kern der Abgabe.

Die naheliegende Idee Square(Rectangle) ist das Lehrbuchbeispiel für eine problematische Vererbung. Untersuchen Sie sie selbst:

  1. Implementieren Sie Square einmal als Subklasse von Rectangle (Konstruktor reicht side zweimal weiter) und einmal als direkte Subklasse von Shape. Beide Varianten müssen den Vertrag erfüllen.
  2. Ergänzen Sie in Rectangle eine Methode scale_width(factor), die nur die Breite ändert. Was soll Square damit tun? Spielen Sie die Möglichkeiten durch (Breite ändern und kein Quadrat mehr sein; beide Seiten ändern und den Aufrufer überraschen; die Methode verweigern) und zeigen Sie an einem kurzen Codebeispiel, wie jede Variante berechtigte Erwartungen verletzt.
  3. Fällen Sie Ihr Urteil in vier bis sechs Sätzen entlang des Substitutionsprinzips: Unter welcher Bedingung ist Square(Rectangle) vertretbar (Stichwort: unveränderliche Objekte), und wann ist die Variante neben dem Rechteck die ehrlichere?
  4. Zeichnen Sie das Klassendiagramm Ihrer endgültigen Hierarchie: abstrakte Klasse und Methoden gekennzeichnet, Vererbungspfeile korrekt.
  1. Was erzwingt @abstractmethod, und zu welchem Zeitpunkt schlägt eine unvollständige Subklasse fehl?
  2. Warum darf describe() in der abstrakten Klasse konkret sein, obwohl area() abstrakt ist?
  3. Woran erkennen Sie, dass total_area gegen den Vertrag und nicht gegen konkrete Klassen programmiert ist?
  4. Formulieren Sie das Open-Closed-Prinzip in einem Satz und nennen Sie die Stelle dieser Übung, die es belegt.
  5. Warum ist “jedes Quadrat ist ein Rechteck” mathematisch wahr, als Klassenbeziehung aber heikel?

shapes.py mit Vertrag, Implementierungen, polymorphen Funktionen und dem dokumentierten Erweiterbarkeits-Beweis; für Teil D beide Square-Varianten, das Urteil und das Diagramm.