Zum Inhalt springen

Aufgabe 10 - Jahresprojekt Sprint 3 - Singleton und Factory

Zu Zen-Modus wechseln
  • Du löst die Testfalle eines Singletons aus, erklärst sie über geteilten Zustand und behebst sie mit Dependency Injection.
  • Du planst Sprint 3 auf Basis von Velocity und Retro-Maßnahme.
  • Du findest globalen Zustand in deinem Projekt und entscheidest, ob er ein bewusst gewähltes Singleton oder zufällig entstanden ist.
  • Du ordnest die Erzeugung von Varianten in deinem Projekt mit Factory und Registry oder begründest, warum hier kein Muster nötig ist.
  • Du dokumentierst ein Muster in einem einheitlichen Steckbrief.

Erste Woche von Sprint 3. Kapitel 15 und 16 behandeln die zwei Erzeugungsmuster des Jahres: das Singleton (genau eine Instanz, aber Vorsicht mit globalem Zustand) und die Factory mit Registry (die Entscheidung, welche Klasse entsteht, steht an genau einer Stelle). In Aufgabe 08 blieb die Frage offen, wer die passende Strategie auswählt. Heute bekommt sie im Projekt eine Antwort, oder eine begründete Absage.

Ab dieser Aufgabe dokumentiert ihr jedes Muster, das ihr einsetzt oder bewusst weglasst, im Design-Review als Muster-Steckbrief mit immer denselben fünf Feldern. Die Steckbriefe sind später der Beleg „Muster im Code markiert und begründet“ für das Projektende (Kapitel 21).

FeldInhalt
ProblemWelche Änderung oder welcher Fehler war ohne Muster zu erwarten? Ein Satz.
Rollen im CodeWer spielt welche Rolle (Interface, Variante, Fabrik, Tabelle, Zugriffspunkt …), mit Datei.
ErkennungsmerkmalDie Zeile oder das Zeilenpaar, an der man das Muster erkennt, nicht der Klassenname.
Alternative oder VerzichtWas wäre der einfachere Weg gewesen, und warum wurde er (nicht) gewählt?
Trade-offEin echter Nachteil an genau dieser Stelle, kein verkapptes Lob.

Die Kata: Zähler für Downloads. Der Startcode:

class DownloadCounter {
private static instance: DownloadCounter | null = null;
private count = 0;
private constructor() {}
static getInstance(): DownloadCounter {
if (DownloadCounter.instance === null) DownloadCounter.instance = new DownloadCounter();
return DownloadCounter.instance;
}
add(): void { this.count++; }
total(): number { return this.count; }
}
function downloadAll(files: string[]): number {
for (const _ of files) DownloadCounter.getInstance().add();
return files.length;
}

Dafür brauchst du:

  • Kapitel 15 Entwurfsmuster I – Singleton (Modul- und Klassen-Form, Kritik am globalen Zustand, Dependency Injection als Alternative) und Kapitel 16 Entwurfsmuster I – Factory und Registry (gemeinsames Interface, eine Klasse je Variante, Registry, switch gegen Registry, Muster in freier Wildbahn).
  • Euer Design-Review mit den Musterkandidaten aus Aufgabe 07 und der Strategie aus Aufgabe 08.

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 im docs/design-review.md des Jahresprojekts. Bezeichner und Kommentare im Code sind Englisch.
  • Wer was beigetragen hat, zeigen Board, Merge Requests und /spend-Einträge (Kapitel 07).
  • Kata: Schreibe zwei Testfunktionen, die nacheinander laufen: testA ruft downloadAll mit zwei Dateien auf und erwartet danach total() gleich 2, testB erwartet zu Beginn 0 und ruft dann downloadAll mit einer Datei auf. Halte fest, welcher Test scheitert, und erkläre den Mechanismus in zwei Sätzen.
  • Baue die Kata mit Dependency Injection um: ein kleines Interface Counter mit add(), downloadAll bekommt den Zähler als Parameter, die Produktion übergibt DownloadCounter.getInstance(), jeder Test eine frische Instanz einer eigenen Klasse. Zeige, dass beide Tests in beiden Reihenfolgen grün sind.
  • Planning von Sprint 3: Retro-Maßnahme aus Sprint 2 prüfen, Milestone Sprint 3, Planung mit der Velocity der bisherigen Sprints, Sprint-Ziel als ein Satz, Prozessrolle wechseln.

Tipp: Wenn testB grün ist, läuft es vermutlich vor testA oder setzt den Zustand zurück. Eine Methode resetForTests() würde den Test grün machen, aber schon ihre Notwendigkeit ist ein Warnzeichen.

Vollständig, wenn:

  • der scheiternde Test benannt und über den geteilten Zustand erklärt ist, der den Test überlebt,
  • downloadAll den Zähler hereingereicht bekommt, nicht mehr selbst getInstance() aufruft und beide Tests in beiden Reihenfolgen grün sind,
  • die Retro-Maßnahme überprüft, der Milestone Sprint 3 angelegt und das Sprint-Ziel ein Satz über einen Zustand des Produkts ist.

Wie unter Allgemein.

Für alle, die in der Einheit mit den Schritten oben fertig sind bzw. optional (= freiwillig), wenn du in der Einheit nicht fertig wirst.

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):

  • Globaler Zustand im Projekt: Sucht nach getInstance(), veränderlichen Variablen auf Modulebene und exportierten Objekten, die von mehreren Dateien verändert werden. Haltet im Design-Review jede Fundstelle mit Datei und Zeile fest und ordnet sie ein: bewusst gewähltes Singleton oder zufällig entstanden, Modul- oder Klassen-Form, kann ein Test den Zustand überleben? Gibt es keine Fundstelle, haltet fest, wo ihr gesucht habt.
  • Erzeugung von Varianten: Nehmt die Auswahlstelle aus Aufgabe 08 oder einen Musterkandidaten aus der Kategorie Erzeugung. Entscheidet mit der Reihenfolge aus Kapitel 16 („erst das Problem, dann das Muster“): Gibt es mehrere Varianten, und kommen absehbar weitere dazu?
    • Wenn ja: Baut in einem Merge Request eine Factory mit Registry (gemeinsames Interface, eine Klasse je Variante, eine Map von Schlüssel auf Erzeugungsfunktion, Aufrufer kennen nur Factory und Interface).
    • Wenn nein: Begründet den Verzicht.
  • Schreibt in beiden Fällen den Muster-Steckbrief im Design-Review.

Tipp: Ein fehlender Eintrag in der Registry muss mit einer klaren Fehlermeldung scheitern, nicht still. Ein Verzicht ist ein vollwertiges Ergebnis, wenn er ein Problem benennt, das es (noch) nicht gibt.

Vollständig, wenn:

  • jede Fundstelle globalen Zustands mit Datei und Zeile nach Absicht, Form und Testrisiko eingeordnet ist (oder die Suche dokumentiert ist),
  • die Entscheidung für oder gegen eine Factory mit dem Problem begründet ist und bei einer Factory die Aufrufer keine konkrete Variante mehr kennen,
  • der Muster-Steckbrief alle fünf Felder mit Bezug auf den eigenen Code enthält.

Wie unter Allgemein.

Für alle, die in der Einheit mit den Schritten oben fertig sind bzw. optional (= freiwillig), wenn du in der Einheit nicht fertig wirst.

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):

  • Baue für die problematischste Fundstelle globalen Zustands den Umbau mit Dependency Injection: Signatur vorher und nachher im Design-Review, der Composition Root aus Aufgabe 09 übergibt die Instanz, ein Test läuft mit einer frischen Instanz.
  • Hat euer Projekt eine Factory mit Registry: Ergänze eine fehlende Fehlerklasse für unbekannte Schlüssel samt Test, und weise mit einem eigenen Commit nach, dass eine neue Variante nur eine neue Klasse und eine Zeile in der Registry braucht (git diff --stat, die Factory-Funktion kommt im Diff nicht vor). Ohne Factory: Zeige an einer switch-Stelle eures Projekts, welche bestehenden Stellen eine neue Variante heute anfassen müsste.

Tipp: Formatiere im Nachweis-Commit nichts um, sonst geht die eigentliche Änderung im Diff unter.

Vollständig, wenn:

  • der Umbau Signatur vorher und nachher zeigt, die Instanz im Composition Root übergeben wird und ein Test mit frischer Instanz grün ist,
  • die Fehlerklasse getestet ist und der Diff der neuen Variante nur neue Klasse und Registrierungszeile zeigt (bzw. die anzufassenden Stellen der switch-Stelle vollständig genannt sind).

Wie unter Allgemein.

Für alle, die in der Einheit mit den Schritten oben fertig sind bzw. optional (= freiwillig), wenn du in der Einheit nicht fertig wirst.

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):

  • Muster in freier Wildbahn: Finde in der Dokumentation oder im Code einer Bibliothek, die euer Projekt verwendet, eine Stelle mit Singleton- oder Factory-Charakter (Erkennungszeichen aus Kapitel 16: getInstance(), eine Erzeugungsfunktion je nach Typ, eine Tabelle von Namen auf Klassen). Schreibe dazu einen Steckbrief und gib die Quelle an.
  • Beurteile in drei bis vier Sätzen, ob die Stelle das Muster aus einem echten Problem heraus verwendet und welche Folge sie für euren Code hat (zum Beispiel geteilter Zustand in Tests oder eine Erweiterungsstelle, die ihr nutzen könnt).

Tipp: Frameworks reden offen über ihre Muster. Suche in der Dokumentation nach Wörtern wie „instance“, „create“, „register“ oder „factory“.

Vollständig, wenn:

  • der Steckbrief alle fünf Felder mit Bezug auf die fremde Stelle enthält und die Quelle nachvollziehbar ist,
  • die Beurteilung das Problem hinter dem Muster und eine konkrete Folge für das eigene Projekt nennt.

Wie unter Allgemein.

Durchschnittlich bis Wesentlich_vollständig: ca. 70 min (5 min Board-Check, 15 min Kata, 20 min Planning, 30 min globaler Zustand und Factory im Projekt)

GAM

CC-BY-SA

https://qiz.at/rules