Aufgabe 08 - Jahresprojekt Sprint 2 - SRP, OCP und LSP
Lernziele
Abschnitt betitelt „Lernziele“- Du benennst die Gründe zur Änderung einer Klasse und teilst sie nach dem Single Responsibility Principle so, dass die Logik ohne Infrastruktur testbar ist.
- Du erkennst eine
if-Kette über einen Typ als Verstoß gegen das Open-Closed-Prinzip und ersetzt sie durch eine Strategie hinter einem Interface. - Du schreibst den Vertrag eines Obertyps auf und erkennst Untertypen, die mehr verlangen oder weniger liefern.
- Du setzt SRP, OCP und LSP im Code deines Jahresprojekts um und dokumentierst die Entscheidungen.
Ausgangslage
Abschnitt betitelt „Ausgangslage“Zweite Woche von Sprint 2. Kapitel 12 und 13 behandeln die ersten drei Buchstaben von SOLID: S (ein Grund zur Änderung), O (Neues als neue Klasse, nicht als neuer Zweig) und L (Untertypen halten das Versprechen ihres Obertyps). In Aufgabe 07 habt ihr im Design-Review Stellen gefunden, an denen eine Änderung Kreise zieht. Heute räumt ihr die erste davon auf, mit einem Test, der zeigt, dass es danach leichter ist.
Die Kata: Hörbuch-Abo-Abrechnung. Der Startcode:
interface Track { title: string; minutes: number;}
class SubscriptionBill { constructor(private readonly tracks: Track[], private readonly plan: string) {}
totalCents(): number { const minutes = this.tracks.reduce((sum, e) => sum + e.minutes, 0); if (this.plan === "basic") return minutes * 2; if (this.plan === "pro") return Math.min(minutes * 2, 900); if (this.plan === "school") return 0; throw new Error(`unknown plan ${this.plan}`); }
toHtml(): string { return `<h1>Rechnung</h1><p>${(this.totalCents() / 100).toFixed(2)} EUR</p>`; }
sendTo(address: string): void { console.log(`mail to ${address}: ${this.toHtml()}`); }}
class Player { // contract: never throws; returns false if the track cannot be played play(track: Track): boolean { return track.minutes > 0; }}
class OfflinePlayer extends Player { play(track: Track): boolean { if (!track.title.endsWith(".mp3")) throw new Error("only downloaded mp3 files"); return true; }}Dafür brauchst du:
- Kapitel 12 Entwurfsmuster I – SOLID: S und O und Kapitel 13 Entwurfsmuster I – SOLID: L.
- Ein TypeScript-Projekt, das mit
npx tsxläuft (für die Kata),node:assertgenügt für die Prüfungen. - Euer Design-Review aus Aufgabe 07.
Allgemein
Abschnitt betitelt „Allgemein“Projektordner anlegen siehe https://qiz.at/projektordner
- Lies vorher kurz https://qiz.at/assignmentsabgabe durch.
- Nur vollständige Abgaben (siehe https://qiz.at/rules) können positiv bewertet werden.
- Abgabe- und Testdetails siehe https://qiz.at/assignmentsabgabe
- Abgegeben wird dein Projektordner mit der Kata (Code und
README.md) und den Links auf Issues, Merge Requests und Abschnitte imdocs/design-review.mddes Jahresprojekts. Bezeichner und Kommentare im Code sind Englisch. - Wer was beigetragen hat, zeigen Board, Merge Requests und
/spend-Einträge (Kapitel 07).
Wesentlich_überwiegend
Abschnitt betitelt „Wesentlich_überwiegend“Aufgabe
Abschnitt betitelt „Aufgabe“- Kata: Nenne in der
README.mddie Gründe, aus denen sichSubscriptionBilländern müsste, mit je einer konkreten Änderungsanfrage. Teile die Klasse so, dass jede Klasse genau einen Grund zur Änderung hat, und ersetze dieif-Kette über den Tarif durch ein InterfacePricingPlanmit einer Klasse je Tarif. Prüfe die Berechnung mit zweiassert-Aufrufen, ohne dass dabei HTML entsteht oder etwas ausgegeben wird. - Schreibe den Vertrag von
Player.play(Vorbedingung, Nachbedingung) auf und begründe in einem Satz, obOfflinePlayermehr verlangt oder weniger liefert.
Tipp: SRP heißt „ein Grund zur Änderung“, nicht „eine Methode“. Committe vor dem neuen Tarif, dann zeigt
git status, ob ein neuer Tarif wirklich nur eine neue Datei braucht.
Bewertungskriterien
Abschnitt betitelt „Bewertungskriterien“Vollständig, wenn:
- mindestens drei Gründe zur Änderung mit je einer Änderungsanfrage genannt sind und nach der Aufteilung jede Klasse genau einen Grund hat,
- die Tarife hinter
PricingPlanstehen und keine Verzweigung über den Tarif mehr existiert, - die Berechnung mit zwei Prüfungen ohne HTML und ohne Ausgabe getestet ist,
- der Vertrag von
playsteht undOfflinePlayerrichtig als Untertyp eingeordnet ist, der mehr verlangt.
Wie unter Allgemein.
Wesentlich_vollständig
Abschnitt betitelt „Wesentlich_vollständig“Für alle, die in der Einheit mit den Schritten oben fertig sind bzw. optional (= freiwillig), wenn du in der Einheit nicht fertig wirst.
Aufgabe
Abschnitt betitelt „Aufgabe“Erweitere (siehe Erweitern in https://qiz.at/rules) den vollständigen Teil der vorherigen Stufe dieser Datei (ändere oder lösche nur, was für diese Aufgabe unbedingt notwendig ist):
- SRP im Projekt: Wähle eine Klasse, Funktion oder Komponente eures Projekts mit mindestens zwei Gründen zur Änderung (zum Beispiel eine Komponente, die Daten lädt, prüft und anzeigt, oder eine Route, die rechnet und speichert). Lege dafür ein Issue an und teile sie in einem Merge Request so, dass die Fachlogik ohne Oberfläche, Netzwerk und Datenbank testbar wird. Schreibe mindestens einen Test, der genau diese Logik prüft.
- LSP im Projekt: Suche alle Stellen mit
extendsoderimplementsund alleinstanceof-Prüfungen. Beurteile jede Stelle in einer Tabelle im Design-Review (Datei und Zeile, Vertrag des Obertyps, verlangt mehr oder liefert weniger?). Hat euer Projekt keine Vererbung, prüfe zwei Implementierungen desselben Interfaces oder Typs gegen dessen Erwartungen. - Ergänze im Design-Review einen Abschnitt SRP, OCP, LSP: Vorher-Nachher der geteilten Stelle (Gründe zur Änderung vorher und nachher), Link auf den Merge Request.
Tipp: Für Logik, die ihr aus einer Komponente herauslöst, eignet sich eine Datei in eurer Fachlogik-Schicht. Wenn der Test die Oberfläche starten muss, ist die Trennung noch nicht gelungen.
Bewertungskriterien
Abschnitt betitelt „Bewertungskriterien“Vollständig, wenn:
- der Merge Request eine Stelle mit mindestens zwei Gründen zur Änderung so teilt, dass jede neue Einheit einen Grund hat, und mindestens ein Test die herausgelöste Logik ohne Infrastruktur prüft,
- jede Stelle mit
extends,implementsoderinstanceofmit Datei und Zeile gegen den Vertrag beurteilt ist, - der Abschnitt im Design-Review Vorher und Nachher nennt und den Merge Request verlinkt.
Wie unter Allgemein.
PlusPlus Aufgabe
Abschnitt betitelt „PlusPlus Aufgabe“Für alle, die in der Einheit mit den Schritten oben fertig sind bzw. optional (= freiwillig), wenn du in der Einheit nicht fertig wirst.
Aufgabe
Abschnitt betitelt „Aufgabe“Erweitere (siehe Erweitern in https://qiz.at/rules) den vollständigen Teil der vorherigen Stufe dieser Datei (ändere oder lösche nur, was für diese Aufgabe unbedingt notwendig ist):
- OCP im Projekt: Suche eine
if- oderswitch-Kette über einen Typ oder eine Art (Medientyp, Rolle, Status, Format). Ersetze sie durch eine Strategie hinter einem Interface, sodass eine neue Variante nur eine neue Klasse und ihre Auswahl braucht. Weise mitgit diff --statnach, dass eine zusätzliche Variante keine bestehende Variante berührt. Gibt es keine solche Kette, begründe im Design-Review, warum, und nenne die Stelle, an der sie als Nächstes entstehen würde. - Beantworte im Design-Review in zwei Sätzen: Wer wählt jetzt die passende Strategie aus, und wo steht das im Code?
Tipp: Die offene Frage „Wer wählt aus?“ ist kein Fehler deiner Lösung. Kapitel 16 beantwortet sie mit der Factory.
Bewertungskriterien
Abschnitt betitelt „Bewertungskriterien“Vollständig, wenn:
- die Kette durch Strategien hinter einem Interface ersetzt ist und der Diff einer zusätzlichen Variante nur neue Dateien und die Auswahlstelle zeigt (oder das Fehlen einer Kette begründet ist),
- die Auswahlstelle mit Datei benannt ist.
Wie unter Allgemein.
MandalorianPlus Aufgabe
Abschnitt betitelt „MandalorianPlus Aufgabe“Für alle, die in der Einheit mit den Schritten oben fertig sind bzw. optional (= freiwillig), wenn du in der Einheit nicht fertig wirst.
Aufgabe
Abschnitt betitelt „Aufgabe“Erweitere (siehe Erweitern in https://qiz.at/rules) den vollständigen Teil der vorherigen Stufe dieser Datei (ändere oder lösche nur, was für diese Aufgabe unbedingt notwendig ist):
- Schreibe einen Vertragstest für ein Interface oder einen Obertyp eures Projekts: eine Testfunktion, die gegen den Obertyp geschrieben ist und mit jeder Implementierung laufen muss. Lass sie gegen alle Implementierungen laufen und behebe einen gefundenen Vertragsbruch, ohne den Vertrag aufzuweichen.
- Nimm eine echte Änderungsanfrage aus eurem Backlog, die eure Strategie aus PlusPlus (oder eine andere Abstraktion) nicht abfängt, zum Beispiel weil die Signatur die nötige Information gar nicht bekommt. Erkläre in drei bis vier Sätzen, warum das Open-Closed-Prinzip nicht jede Änderung abfangen kann und was das für die Wahl der Abstraktion bedeutet.
Tipp: Kapitel 13 weist darauf hin, dass auch Attrappen in Tests den Vertrag halten müssen. Ein Vertragstest ist der einfachste Weg, das zu prüfen.
Bewertungskriterien
Abschnitt betitelt „Bewertungskriterien“Vollständig, wenn:
- der Vertragstest gegen den Obertyp geschrieben ist, mit allen Implementierungen läuft und ein Vertragsbruch behoben oder begründet ausgeschlossen ist,
- die Erklärung an einer echten Änderungsanfrage zeigt, welche Information der Abstraktion fehlt.
Wie unter Allgemein.
Zeit dafür
Abschnitt betitelt „Zeit dafür“Durchschnittlich bis Wesentlich_vollständig: ca. 70 min (5 min Board-Check, 25 min Kata, 40 min SRP und LSP im Projekt)
Autoren
Abschnitt betitelt „Autoren“GAM
CC-BY-SA