diff --git a/CREDITS.md b/CREDITS.md index 25b698b05..85f31d975 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1,42 +1,38 @@ --- no_beamer: true +no_pdf: true --- - # Credits This is a list of external projects used to build the lecture slides and the lecture notes for the teaching material. These are licensed under their own licences and are not part of the CC BY-SA 4.0 licence of this project. - ## Building the Lecture Slides -* [Pandoc](https://github.com/jgm/pandoc) -* [Pandoc-Lecture](https://github.com/cagix/pandoc-lecture) -* [Pandoc-Lecture-Zen](https://github.com/cagix/pandoc-lecture-zen) -* [TeX Live](http://tug.org/texlive/) -* [Beamer](https://github.com/josephwright/beamer) -* [Metropolis](https://github.com/matze/mtheme) - +- [Pandoc](https://github.com/jgm/pandoc) +- [Pandoc-Lecture](https://github.com/cagix/pandoc-lecture) +- [Pandoc-Lecture-Zen](https://github.com/cagix/pandoc-lecture-zen) +- [TeX Live](http://tug.org/texlive/) +- [Beamer](https://github.com/josephwright/beamer) +- [Metropolis](https://github.com/matze/mtheme) ## Building the Lecture GitHub Preview -* [Pandoc](https://github.com/jgm/pandoc) -* [Pandoc-Lecture](https://github.com/cagix/pandoc-lecture) - +- [Pandoc](https://github.com/jgm/pandoc) +- [Pandoc-Lecture](https://github.com/cagix/pandoc-lecture) ## Further Tools used -* [GNU Make](https://www.gnu.org/software/make/) -* [Pandoc Dockerfiles](https://github.com/pandoc/dockerfiles) -* [Docker](https://www.docker.com/) -* [GitHub](https://github.com/) - +- [GNU Make](https://www.gnu.org/software/make/) +- [Pandoc Dockerfiles](https://github.com/pandoc/dockerfiles) +- [Docker](https://www.docker.com/) +- [GitHub](https://github.com/) ## Contributors [This project](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture) -has been created and is being maintained by the author -[Carsten Gips](https://github.com/cagix) and various +has been created and is being maintained by the author [Carsten +Gips](https://github.com/cagix) and various [contributors](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/graphs/contributors). diff --git a/admin/exams.md b/admin/exams.md index 5e0bc3679..396344863 100644 --- a/admin/exams.md +++ b/admin/exams.md @@ -1,244 +1,243 @@ --- -title: "Prüfungsvorbereitung" -author: "Carsten Gips (HSBI)" -tldr: | - **Durchführung: Präsenz oder Open-Book (je nach Corona-Lage)** +author: Carsten Gips (HSBI) +no_beamer: true +title: Prüfungsvorbereitung +--- - Die Klausur wird dieses Semester elektronisch stattfinden. Dazu werden wir den Prüfungs-ILIAS der - HSBI nutzen. +::: tldr +**Durchführung: Präsenz oder Open-Book (je nach Corona-Lage)** - Sofern die Situation dies zulässt, wird die Klausur in den Räumen der HSBI am Campus Minden unter - Aufsicht durchgeführt. Hier werden Ihnen Rechner für den Zugang zum Prüfungs-ILIAS zur Verfügung - gestellt, Sie benötigen nur Ihre HSBI-Zugangsdaten (User, Passwort), einen Studierendenausweis und - Personalausweis sowie Ihren DIN-A4-Spickzettel. +Die Klausur wird dieses Semester elektronisch stattfinden. Dazu werden wir den +Prüfungs-ILIAS der HSBI nutzen. - Wenn die Corona-Lage eine Durchführung in Präsenz nicht erlaubt, wird die Klausur stattdessen als - _Open-Book-Ausarbeitung_ aus dem Home-Office durchgeführt. Sie benötigen dazu einen normalen Rechner - oder Laptop mit einem Standardbrowser. Tablets und Handys können wg. der Mobil-Version der Browser - problematisch sein. Sie müssen JavaScript aktivieren und Cookies zulassen, der Privacy-Modus ist - bitte ebenfalls zu deaktivieren. Sie erreichen den Prüfungs-ILIAS - [eassessment.hsbi.de](https://eassessment.hsbi.de) nur über VPN. +Sofern die Situation dies zulässt, wird die Klausur in den Räumen der HSBI am Campus +Minden unter Aufsicht durchgeführt. Hier werden Ihnen Rechner für den Zugang zum +Prüfungs-ILIAS zur Verfügung gestellt, Sie benötigen nur Ihre HSBI-Zugangsdaten +(User, Passwort), einen Studierendenausweis und Personalausweis sowie Ihren +DIN-A4-Spickzettel. - Die Entscheidung über die konkrete Durchführung wird spätestens zwei Wochen vor der Prüfung getroffen - und Ihnen per EMail über das LSF mitgeteilt. +Wenn die Corona-Lage eine Durchführung in Präsenz nicht erlaubt, wird die Klausur +stattdessen als *Open-Book-Ausarbeitung* aus dem Home-Office durchgeführt. Sie +benötigen dazu einen normalen Rechner oder Laptop mit einem Standardbrowser. Tablets +und Handys können wg. der Mobil-Version der Browser problematisch sein. Sie müssen +JavaScript aktivieren und Cookies zulassen, der Privacy-Modus ist bitte ebenfalls zu +deaktivieren. Sie erreichen den Prüfungs-ILIAS +[eassessment.hsbi.de](https://eassessment.hsbi.de) nur über VPN. - **Ablauf der Klausur** +Die Entscheidung über die konkrete Durchführung wird spätestens zwei Wochen vor der +Prüfung getroffen und Ihnen per EMail über das LSF mitgeteilt. - Die Prüfung (das ILIAS-Objekt) selbst schalte ich erst zum Start der Prüfung online. Bei der Durchführung - als Open-Book-Ausarbeitung wird parallel zur Prüfung eine Zoom-Sitzung laufen, in der Sie Fragen stellen - können. +**Ablauf der Klausur** - **Hilfsmittel und Themen** +Die Prüfung (das ILIAS-Objekt) selbst schalte ich erst zum Start der Prüfung online. +Bei der Durchführung als Open-Book-Ausarbeitung wird parallel zur Prüfung eine +Zoom-Sitzung laufen, in der Sie Fragen stellen können. - Bei der Durchführung in Präsenz am Campus Minden ist ein Spickzettel (DIN A4, beidseitig beschrieben) - als Hilfsmittel zugelassen. +**Hilfsmittel und Themen** - Bei der Durchführung als "Open-Book-Ausarbeitung" im Home-Office sind alle Hilfsmittel zugelassen. +Bei der Durchführung in Präsenz am Campus Minden ist ein Spickzettel (DIN A4, +beidseitig beschrieben) als Hilfsmittel zugelassen. - Die Unterstützung durch Dritte bzw. jegliche Kommunikation mit Dritten ist in keinem Fall zugelassen. - Sie sollen die Prüfung selbstständig bearbeiten. +Bei der Durchführung als "Open-Book-Ausarbeitung" im Home-Office sind alle +Hilfsmittel zugelassen. - Es wird keines der behandelten Themen ausgeschlossen, allerdings eignen sich manche Themen etwas - besser für Klausurfragen als andere ;-) -youtube: - - link: "https://youtu.be/warjJ9ZXvEM" - name: "Hinweise zur Prüfung: Fragetypen-Demo" - - link: "https://youtu.be/_cVhJX-D6zM" - name: "Hinweise zur Prüfung: Technische Vorbereitung" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/4f7c425ae13cec9dc1b3bed28c1cadd666e4ba87c4afd9fe131913187fe110d2296ef62b36696efc3f19e151c7f18fe1bd4b83aad9f86eb9a5f384d38f2a7fbf" - name: "Hinweise zur Prüfung: Fragetypen-Demo" - - link: "https://www.hsbi.de/medienportal/m/66febead31c0b01deec2ffb47adf8abfa247b51a76a539b6681423d9206f819e4df8c93ee7affd4be3f7144e035dfbaf997af8cd9314d4dbc65244bf11cd9de8" - name: "Hinweise zur Prüfung: Technische Vorbereitung" -no_beamer: true ---- +Die Unterstützung durch Dritte bzw. jegliche Kommunikation mit Dritten ist in keinem +Fall zugelassen. Sie sollen die Prüfung selbstständig bearbeiten. + +Es wird keines der behandelten Themen ausgeschlossen, allerdings eignen sich manche +Themen etwas besser für Klausurfragen als andere ;-) +::: +::: youtube +- [Hinweise zur Prüfung: Fragetypen-Demo](https://youtu.be/warjJ9ZXvEM) +- [Hinweise zur Prüfung: Technische Vorbereitung](https://youtu.be/_cVhJX-D6zM) +::: # Elektronische Klausur: Termin, Materialien ## Termin -Die schriftliche Prüfung erfolgt durch eine Klausur, die als digitale Prüfung auf einem -Prüfungs-ILIAS durchgeführt wird. +Die schriftliche Prüfung erfolgt durch eine Klausur, die als digitale Prüfung auf +einem Prüfungs-ILIAS durchgeführt wird. -Es wird angestrebt, die Klausur in Präsenz in den Rechnerpools am Campus Minden durchzuführen. -Falls dies wegen der Corona-Situation oder anderer Umstände nicht möglich sein sollte, wird -die Klausur als "Open-Book-Ausarbeitung" im Home-Office durchgeführt. +Es wird angestrebt, die Klausur in Präsenz in den Rechnerpools am Campus Minden +durchzuführen. Falls dies wegen der Corona-Situation oder anderer Umstände nicht +möglich sein sollte, wird die Klausur als "Open-Book-Ausarbeitung" im Home-Office +durchgeführt. -Es wird in beiden Prüfungszeiträumen ein Termin angeboten. Die Termine werden vom Prüfungsamt -bekannt gegeben. +Es wird in beiden Prüfungszeiträumen ein Termin angeboten. Die Termine werden vom +Prüfungsamt bekannt gegeben. Dauer jeweils 90 Minuten. -* Die konkrete Durchführungsform [(in Präsenz am Campus Minden oder im Home-Office)]{.notes} - wird Ihnen [spätestens]{.notes} zwei Wochen vor der Prüfung über das LSF bekanntgegeben +- Die konkrete Durchführungsform [(in Präsenz am Campus Minden oder im + Home-Office)]{.notes} wird Ihnen [spätestens]{.notes} zwei Wochen vor der + Prüfung über das LSF bekanntgegeben ## Zugelassene Hilfsmittel ::: {.details title="Präsenz (in Minden)"} - **Zugelassene Materialien**: **DIN-A4-Spickzettel (beidseitig)** -Sie dürfen **einen** Spickzettel im **DIN-A4**-Format benutzen, der beidseitig beschrieben -sein kann. - -Ich möchte Sie hier noch einmal ermuntern, diesen Zettel tatsächlich manuell zu erstellen -(also ganz traditionell zu **schreiben**), da bereits der Schreibvorgang einen gewissen -Lerneffekt bewirkt! +Sie dürfen **einen** Spickzettel im **DIN-A4**-Format benutzen, der beidseitig +beschrieben sein kann. +Ich möchte Sie hier noch einmal ermuntern, diesen Zettel tatsächlich manuell zu +erstellen (also ganz traditionell zu **schreiben**), da bereits der Schreibvorgang +einen gewissen Lerneffekt bewirkt! ::: ::: {.details title="Open-Book-Ausarbeitung (Homeoffice)"} +Falls die Prüfung als Open-Book-Ausarbeitung im Home-Office durchgeführt werden +sollte, dürfen Sie alle Unterlagen benutzen. -Falls die Prüfung als Open-Book-Ausarbeitung im Home-Office durchgeführt werden sollte, dürfen -Sie alle Unterlagen benutzen. - -* Ausnahme: **Keine Hilfe durch Dritte!** (insbesondere keine Zusammenarbeit, keine Kommunikation) - - Sie sollen die Prüfung eigenständig bearbeiten. Hilfe von Dritten sowie jegliche Kommunikation - mit Dritten ist in keinem Fall zugelassen und wird als Täuschungsversuch gewertet. +- Ausnahme: **Keine Hilfe durch Dritte!** (insbesondere keine Zusammenarbeit, + keine Kommunikation) + Sie sollen die Prüfung eigenständig bearbeiten. Hilfe von Dritten sowie jegliche + Kommunikation mit Dritten ist in keinem Fall zugelassen und wird als + Täuschungsversuch gewertet. ::: ## Einsicht -* Prüfungseinsicht: Zeitnah; Bekanntgabe per Mail - +- Prüfungseinsicht: Zeitnah; Bekanntgabe per Mail # Technische Vorbereitungen ::: {.details title="Präsenz (in Minden)"} +Diese Bemerkungen betreffen die Durchführung als Präsenzprüfung in den Räumen am +Campus Minden. -Diese Bemerkungen betreffen die Durchführung als Präsenzprüfung in den Räumen am Campus Minden. - -* **HSBI-Zugangsdaten**: Username, Passwort - - Bei der Durchführung der Prüfung am Campus Minden wird Ihnen ein Rechner zur Verfügung - gestellt. Dort läuft voraussichtlich ein Browser im Kiosk-Mode, wo Sie sich am Prüfungs-ILIAS - anmelden. Dazu benötigen Sie ihre HSBI-Zugangsdaten, mit denen Sie sich auch im "normalen" - ILIAS anmelden. +- **HSBI-Zugangsdaten**: Username, Passwort -* **Studierendenausweis** und Personalausweis + Bei der Durchführung der Prüfung am Campus Minden wird Ihnen ein Rechner zur + Verfügung gestellt. Dort läuft voraussichtlich ein Browser im Kiosk-Mode, wo Sie + sich am Prüfungs-ILIAS anmelden. Dazu benötigen Sie ihre HSBI-Zugangsdaten, mit + denen Sie sich auch im "normalen" ILIAS anmelden. - An der Prüfung dürfen nur Personen teilnehmen, die dafür im LSF angemeldet sind. Es findet - eine entsprechende Kontrolle statt. Halten Sie Ihren Studierendenausweis und Personalausweis - bereit. +- **Studierendenausweis** und Personalausweis + An der Prüfung dürfen nur Personen teilnehmen, die dafür im LSF angemeldet sind. + Es findet eine entsprechende Kontrolle statt. Halten Sie Ihren + Studierendenausweis und Personalausweis bereit. ::: ::: {.details title="Open-Book-Ausarbeitung (Homeoffice)"} +Diese Bemerkungen betreffen die Durchführung aus dem Home-Office mit Ihrer Hardware. +Bei der Durchführung in Präsenz in den Räumen am Campus Minden werden die +technischen Details von uns für Sie vorbereitet sein. -Diese Bemerkungen betreffen die Durchführung aus dem Home-Office mit Ihrer Hardware. Bei der -Durchführung in Präsenz in den Räumen am Campus Minden werden die technischen Details von uns -für Sie vorbereitet sein. +- **Rechner**: Nutzen Sie für die Prüfung einen stationären Rechner oder ein + Notebook. -* **Rechner**: Nutzen Sie für die Prüfung einen stationären Rechner oder ein Notebook. + Vermeiden Sie die Verwendung von Tablets und Smartphones! Bei der Verwendung von + Tablets kann es unter Umständen zu Darstellungsproblemen kommen. Smartphones + sind aufgrund des kleinen Bildschirms für die Prüfungsdurchführung schlicht + ungeeignet. - Vermeiden Sie die Verwendung von Tablets und Smartphones! Bei der Verwendung von Tablets - kann es unter Umständen zu Darstellungsproblemen kommen. Smartphones sind aufgrund des kleinen - Bildschirms für die Prüfungsdurchführung schlicht ungeeignet. + Bei fehlendem Zugang zu einem entsprechenden Endgerät kontaktieren Sie bitte + frühzeitig die Prüfungsverantwortlichen. - Bei fehlendem Zugang zu einem entsprechenden Endgerät kontaktieren Sie bitte frühzeitig die - Prüfungsverantwortlichen. +- **Netz**: Stabil genug? Belastbar genug? -* **Netz**: Stabil genug? Belastbar genug? + Wenn Sie keinen Zugang zu einer ausreichend stabilen Internetverbindung haben, + setzen Sie sich frühzeitig mit Ihren Prüfungsverantwortlichen in Verbindung. - Wenn Sie keinen Zugang zu einer ausreichend stabilen Internetverbindung haben, setzen Sie sich - frühzeitig mit Ihren Prüfungsverantwortlichen in Verbindung. +- **VPN**: Der Prüfungs-ILIAS ist nur im HSBI-VPN erreichbar. -* **VPN**: Der Prüfungs-ILIAS ist nur im HSBI-VPN erreichbar. + Installieren Sie den VPN-Client (Anleitung: + [hsbi.de/dvz/faq/cat/7](https://www.hsbi.de/dvz/faq/cat/7)) und testen Sie im + Vorfeld der Prüfung bei aktivierter VPN-Verbindung den Zugang zur + Prüfungsplattform [eassessment.hsbi.de](https://eassessment.hsbi.de). + Zugangsdaten wie im normalen ILIAS. - Installieren Sie den VPN-Client (Anleitung: [hsbi.de/dvz/faq/cat/7](https://www.hsbi.de/dvz/faq/cat/7)) - und testen Sie im Vorfeld der Prüfung bei aktivierter VPN-Verbindung den Zugang zur Prüfungsplattform - [eassessment.hsbi.de](https://eassessment.hsbi.de). Zugangsdaten wie im normalen - ILIAS. + Achtung: Auch wenn Sie sich in den Räumen der HSBI befinden, müssen Sie oft die + VPN-Verbindung aktivieren, um Zugang zur Prüfungsplattform zu erhalten. - Achtung: Auch wenn Sie sich in den Räumen der HSBI befinden, müssen Sie oft die VPN-Verbindung aktivieren, - um Zugang zur Prüfungsplattform zu erhalten. +- **Browser**: [Nutzen Sie einen der]{.notes} Standardbrowser (Edge, Firefox, + Safari, Chrome/Chromium) in der Standardeinstellung: insbesondere JavaScript und + Cookies müssen aktiviert/erlaubt sein. -* **Browser**: [Nutzen Sie einen der]{.notes} Standardbrowser (Edge, Firefox, Safari, Chrome/Chromium) - in der Standardeinstellung: insbesondere JavaScript und Cookies müssen aktiviert/erlaubt sein. - - Deaktivieren Sie sämtliche Browser-Erweiterungen wie z.B. Ad-Blocker (AdBlockPlus, uBlock, ...) oder - JavaScript-Blocker (No-Script, Ghostery, ...) für den Prüfungszeitraum. + Deaktivieren Sie sämtliche Browser-Erweiterungen wie z.B. Ad-Blocker + (AdBlockPlus, uBlock, ...) oder JavaScript-Blocker (No-Script, Ghostery, ...) + für den Prüfungszeitraum. **Nutzen Sie Ihren Browser nicht im Privacy-Modus!** -* **HSBI-Zugangsdaten**: Username, Passwort - - Bei der Durchführung der Prüfung als Open-Book-Ausarbeitung führen Sie die Prüfung auf Ihrer - eigenen Hardware im Home-Office durch. Auch hier müssen Sie sich am Prüfungs-ILIAS anmelden. - Dazu benötigen Sie ihre HSBI-Zugangsdaten, mit denen Sie sich auch im "normalen" ILIAS anmelden. +- **HSBI-Zugangsdaten**: Username, Passwort + Bei der Durchführung der Prüfung als Open-Book-Ausarbeitung führen Sie die + Prüfung auf Ihrer eigenen Hardware im Home-Office durch. Auch hier müssen Sie + sich am Prüfungs-ILIAS anmelden. Dazu benötigen Sie ihre HSBI-Zugangsdaten, mit + denen Sie sich auch im "normalen" ILIAS anmelden. ::: - # Bearbeitung des E-Assessment 1. Lesen Sie sich die Hinweise auf der Startseite durch 2. Bearbeiten Sie die Aufgaben in **einem einzigen** Browser-Tab - **Öffnen Sie die Aufgaben _NICHT_ in parallelen Tabs!** - Es kann sonst zu Fehlfunktionen von ILIAS kommen. + **Öffnen Sie die Aufgaben *NICHT* in parallelen Tabs!** Es kann sonst zu + Fehlfunktionen von ILIAS kommen. - Bewegen Sie sich nicht per Browser-Navigation ("vor", "zurück" im Browser) - durch die Aufgaben, sondern nutzen Sie dafür die Buttons "nächste Frage", - "Weiter" oder "Zurück" vom ILIAS! + Bewegen Sie sich nicht per Browser-Navigation ("vor", "zurück" im Browser) durch + die Aufgaben, sondern nutzen Sie dafür die Buttons "nächste Frage", "Weiter" + oder "Zurück" vom ILIAS! 3. Hinweis zu Anzeige der restlichen Bearbeitungsdauer - Wenn Sie den Browser bzw. das Tab mit der Prüfung im Laufe der Prüfung verlassen, - wird Ihnen bei der Rückkehr unter Umständen eine falsche restliche Bearbeitungsdauer - angezeigt. Sie können die Anzeige korrigieren/aktualisieren, indem Sie einfach zu einer - vorigen oder nächsten Aufgabe navigieren. + Wenn Sie den Browser bzw. das Tab mit der Prüfung im Laufe der Prüfung + verlassen, wird Ihnen bei der Rückkehr unter Umständen eine falsche restliche + Bearbeitungsdauer angezeigt. Sie können die Anzeige korrigieren/aktualisieren, + indem Sie einfach zu einer vorigen oder nächsten Aufgabe navigieren. - Hinweis: Die restliche Bearbeitungsdauer wird im Test nur dann angezeigt, wenn diese - Funktion von den Prüfenden aktiviert wurde. + Hinweis: Die restliche Bearbeitungsdauer wird im Test nur dann angezeigt, wenn + diese Funktion von den Prüfenden aktiviert wurde. -4. Parallel zum E-Assessment läuft eine Zoom-Session, dort können Sie Fragen stellen +4. Parallel zum E-Assessment läuft eine Zoom-Session, dort können Sie Fragen + stellen 5. Verbindungsprobleme (Home-Office): - * Bei kurzzeitigen Verbindungsabbrüchen loggen Sie sich einfach wieder ein - * Wenn die Probleme länger dauern, gilt der Versuch als nicht unternommen + + - Bei kurzzeitigen Verbindungsabbrüchen loggen Sie sich einfach wieder ein + - Wenn die Probleme länger dauern, gilt der Versuch als nicht unternommen [(außer Sie haben die Probleme aktiv herbeigeführt, dann kann das als Täuschungsversuch gewertet werden, vgl. RPO §22a (4))]{.notes} - # Fragetypen-Demo In Ihrem ILIAS-Kurs finden Sie eine [**Fragetypen-Demo**](https://www.hsbi.de/elearning/goto.php?target=tst_1352273&client_id=FH-Bielefeld) -mit den wichtigsten Fragetypen. Machen Sie sich mit der Mechanik der Fragetypen vertraut und schauen -Sie sich die Kommentare bei den einzelnen Aufgaben an. Sie können die Demo bei Bedarf beliebig oft -wiederholen. - +mit den wichtigsten Fragetypen. Machen Sie sich mit der Mechanik der Fragetypen +vertraut und schauen Sie sich die Kommentare bei den einzelnen Aufgaben an. Sie +können die Demo bei Bedarf beliebig oft wiederholen. # Hinweise zu den Inhalten -* Klausurrelevant: Vorlesung und Praktikum -* Für Verständnis u.U. hilfreich: Studium der vertiefenden Literaturangaben +- Klausurrelevant: Vorlesung und Praktikum +- Für Verständnis u.U. hilfreich: Studium der vertiefenden Literaturangaben \smallskip -* **Fragen**: - * Schauen Sie sich die Challenges und/oder Quizzes an ... - * Schauen Sie sich die Praktikumsaufgaben an ... - * Überlegen Sie sich, was zu einem Themengebiet im Rahmen einer Prüfung +- **Fragen**: + - Schauen Sie sich die Challenges und/oder Quizzes an ... + - Schauen Sie sich die Praktikumsaufgaben an ... + - Überlegen Sie sich, was zu einem Themengebiet im Rahmen einer Prüfung möglich ist und (wie) gefragt werden könnte :) ::: center **Können vor Kennen :-)** ::: - # Beispiele für mögliche Fragen ## Vererbung und Polymorphie Betrachten Sie den folgenden Java-Code: -```java +``` java public class Person { public String getInfo(Person p) { return "Person"; } } @@ -256,12 +255,12 @@ public class Studi extends Person { Geben Sie alle Ausgaben, die das obige Programm produziert, an. -Begründen Sie Ihre Antwort kurz und stichhaltig (für *jede* Ausgabe!). -Was geschieht, bzw. wieso kommt es zu der jeweiligen Ausgabe? +Begründen Sie Ihre Antwort kurz und stichhaltig (für *jede* Ausgabe!). Was +geschieht, bzw. wieso kommt es zu der jeweiligen Ausgabe? ## Multithreading und Synchronisierung -```java +``` java public class StaffelKaputt extends Thread { private Object stab; StaffelKaputt(Object stab) { this.stab = stab; } @@ -282,24 +281,24 @@ public class StaffelKaputt extends Thread { }} ``` -Das Programm enthält einen Fehler, der sich zur Laufzeit offenbart. -Welche Ausgabe erwarten Sie (angenommen, das Programm wäre fehlerfrei; eine -mögliche Variante reicht)? Welche Ausgabe erhalten Sie stattdessen? Korrigieren -Sie den Fehler. +Das Programm enthält einen Fehler, der sich zur Laufzeit offenbart. Welche Ausgabe +erwarten Sie (angenommen, das Programm wäre fehlerfrei; eine mögliche Variante +reicht)? Welche Ausgabe erhalten Sie stattdessen? Korrigieren Sie den Fehler. ## Reguläre Ausdrücke -Auf welche Strings passt (im Sinne von "match") der folgende reguläre -Ausdruck: `\s*([a-zA-Z0-9_.\-]+)\s*=\s*(-?\d+\.?\d*)\s;?\s*` +Auf welche Strings passt (im Sinne von "match") der folgende reguläre Ausdruck: +`\s*([a-zA-Z0-9_.\-]+)\s*=\s*(-?\d+\.?\d*)\s;?\s*` ## Versionieren mit Git -* Erklären Sie, wie man mit Git die Unterschiede zwischen zwei - bestimmten Versionsständen einer Datei herausfindet. +- Erklären Sie, wie man mit Git die Unterschiede zwischen zwei bestimmten + Versionsständen einer Datei herausfindet. + +- Was ist der Unterschied zwischen einer Workingcopy und einem Repository? -* Was ist der Unterschied zwischen einer Workingcopy und einem Repository? +- Worin liegt der Unterschied zwischen folgenden Arbeitsschritten: -* Worin liegt der Unterschied zwischen folgenden Arbeitsschritten: a. Editieren von Datei `A.txt` b. `git add A.txt` c. Editieren von Datei `A.txt` @@ -312,39 +311,38 @@ Ausdruck: `\s*([a-zA-Z0-9_.\-]+)\s*=\s*(-?\d+\.?\d*)\s;?\s*` c. `git add A.txt` d. `git commit` -* Was würde `git diff` jeweils nach Schritt 2 anzeigen? +- Was würde `git diff` jeweils nach Schritt 2 anzeigen? ## Kommandozeilenparameter -Schreiben Sie ein Programm, welches auf zwei Kommandozeilenparameter reagieren -kann. Die erkannten Parameter sollen auf der Konsole ausgegeben werden. Nutzen -Sie Apache Commons CLI (API siehe Anhang). +Schreiben Sie ein Programm, welches auf zwei Kommandozeilenparameter reagieren kann. +Die erkannten Parameter sollen auf der Konsole ausgegeben werden. Nutzen Sie Apache +Commons CLI (API siehe Anhang). -* Beim Aufruf ohne Parameter soll eine Hilfe zum korrekten Aufruf ausgegeben +- Beim Aufruf ohne Parameter soll eine Hilfe zum korrekten Aufruf ausgegeben werden und das Programm soll sich anschließend beenden. -* Das Programm soll den Parameter `-debug` erkennen. -* Das Programm soll den Parameter `-x=10` erkennen, wobei der Wert beim Aufruf +- Das Programm soll den Parameter `-debug` erkennen. +- Das Programm soll den Parameter `-x=10` erkennen, wobei der Wert beim Aufruf variieren kann (Integer). -* Die Parameter können in unterschiedlicher Reihenfolge auftreten. -* Es kann auch nur ein Parameter angegeben werden. +- Die Parameter können in unterschiedlicher Reihenfolge auftreten. +- Es kann auch nur ein Parameter angegeben werden. ## Build mit Ant -* Was ist der Unterschied zwischen Ant-Targets und Ant-Tasks? -* Wie kann man Ant-Properties von außen (beim Aufruf) setzen? -* Schreiben Sie ein Ant-Target, welches alle `.class`-Dateien in einem Ordner +- Was ist der Unterschied zwischen Ant-Targets und Ant-Tasks? +- Wie kann man Ant-Properties von außen (beim Aufruf) setzen? +- Schreiben Sie ein Ant-Target, welches alle `.class`-Dateien in einem Ordner umbenennt. -* Schreiben Sie ein Ant-Target, mit dem Sie die Javadoc-Dokumentation - erzeugen, packen und das resultierende `.zip`-File in den Ordner `dist/` - verschieben. -* Schreiben Sie Ant-Targets, mit denen Sie JUnit-Testfälle ausführen und - auswerten können. +- Schreiben Sie ein Ant-Target, mit dem Sie die Javadoc-Dokumentation erzeugen, + packen und das resultierende `.zip`-File in den Ordner `dist/` verschieben. +- Schreiben Sie Ant-Targets, mit denen Sie JUnit-Testfälle ausführen und auswerten + können. ## Generics Was kommt hier raus? Und warum? -```java +``` java public class X { void methode(int a) { System.out.println("non-generic"); @@ -365,7 +363,7 @@ public class X { Erklären Sie den Code. Was passiert? -```java +``` java class MyFormatter extends SimpleFormatter { public String format(LogRecord record) { return super.format(record) + "---- FAKE ----\n"; @@ -391,9 +389,9 @@ public class MoreLogging { ## Methodenreferenzen -* Was bedeutet der folgende Code? +- Was bedeutet der folgende Code? - ```java + ``` java List str = Arrays.asList("a", "b", "A", "B"); str.sort(String::compareToIgnoreCase); ``` diff --git a/admin/readme.md b/admin/readme.md index b7eade73d..6543335de 100644 --- a/admin/readme.md +++ b/admin/readme.md @@ -1,5 +1,6 @@ --- -title: "Organisatorisches" -no_pdf: true no_beamer: true +no_pdf: true +title: Organisatorisches --- + diff --git a/homework/b01.md b/homework/b01.md index bf8efdb33..8f822f6f5 100644 --- a/homework/b01.md +++ b/homework/b01.md @@ -1,60 +1,63 @@ --- author: Carsten Gips (HSBI) -title: "Blatt 01: Hangman (Wiederholung Swing)" no_beamer: true +title: "Blatt 01: Hangman (Wiederholung Swing)" --- - - # Zusammenfassung -Dieses Blatt ist bewusst einfach gestaltet und dient der Wiederholung Ihrer Kenntnisse aus der -LV "Programmieren 1". +Dieses Blatt ist bewusst einfach gestaltet und dient der Wiederholung Ihrer +Kenntnisse aus der LV "Programmieren 1". -Implementieren Sie in Java das Spiel [Hangman]. +Implementieren Sie in Java das Spiel +[Hangman](https://en.wikipedia.org/wiki/Hangman_(game)). # Aufgaben ## Installation JDK und IDE, Deaktivierung AI-Support -Sie benötigen für die Bearbeitung der Übungsaufgaben ein *Java Development Kit* (JDK). Wir -verwenden in der Lehrveranstaltung "Programmieren 2" aus verschiedenen Gründen die *Long-Term -Support (LTS)*-Variante, d.h. aktuell das "Java SE Development Kit 21 (LTS)" (JDK 21). +Sie benötigen für die Bearbeitung der Übungsaufgaben ein *Java Development Kit* +(JDK). Wir verwenden in der Lehrveranstaltung "Programmieren 2" aus verschiedenen +Gründen die *Long-Term Support (LTS)*-Variante, d.h. aktuell das "Java SE +Development Kit 21 (LTS)" (JDK 21). -Sofern noch nicht geschehen, installieren Sie bitte auf Ihrem Rechner **Java SE 21 (LTS)** in -einer *64-bit Version*. Wenn Sie mehrere JDKs installiert haben sollten, stellen Sie bitte -sicher, dass Sie für "Programmieren 2" tatsächlich das Java SE 21 (LTS) verwenden. Der -Anbieter des JDKs sollte keine Rolle spielen. +Sofern noch nicht geschehen, installieren Sie bitte auf Ihrem Rechner **Java SE 21 +(LTS)** in einer *64-bit Version*. Wenn Sie mehrere JDKs installiert haben sollten, +stellen Sie bitte sicher, dass Sie für "Programmieren 2" tatsächlich das Java SE 21 +(LTS) verwenden. Der Anbieter des JDKs sollte keine Rolle spielen. -Installieren Sie auf Ihrem Rechner eine IDE für Java Ihrer Wahl. Machen Sie sich mit der -Arbeitsweise Ihrer IDE vertraut. Wenn Sie im Verlauf des Praktikums feststellen, dass die -gewählte IDE nicht für Sie gemacht ist, können Sie jederzeit auf eine andere IDE wechseln. +Installieren Sie auf Ihrem Rechner eine IDE für Java Ihrer Wahl. Machen Sie sich mit +der Arbeitsweise Ihrer IDE vertraut. Wenn Sie im Verlauf des Praktikums feststellen, +dass die gewählte IDE nicht für Sie gemacht ist, können Sie jederzeit auf eine +andere IDE wechseln. -Da Sie das Programmierhandwerk erlernen und üben und vertiefen sollen, dürfen Sie im Rahmen -dieser Lehrveranstaltung noch keine KI-gestützten Assistenten benutzen. Bitte schalten Sie -sämtliche KI-Unterstützung wie beispielsweise Copilot, JetBrains AI Assistant, Cursor, -CodeGPT, Codeium, Tabnine, Windsurf, ... (Liste nicht vollständig) für die Bearbeitung der -Übungsaufgaben in dieser Lehrveranstaltung ab. +Da Sie das Programmierhandwerk erlernen und üben und vertiefen sollen, dürfen Sie im +Rahmen dieser Lehrveranstaltung noch keine KI-gestützten Assistenten benutzen. Bitte +schalten Sie sämtliche KI-Unterstützung wie beispielsweise Copilot, JetBrains AI +Assistant, Cursor, CodeGPT, Codeium, Tabnine, Windsurf, ... (Liste nicht +vollständig) für die Bearbeitung der Übungsaufgaben in dieser Lehrveranstaltung ab. ## Anlegen eines Java-Projektes -Legen Sie für die Bearbeitung der Aufgabe ein neues Java-Projekt in Ihrer IDE an. Achten Sie -bitte darauf, dass im Projektpfad **keine Leerzeichen** und **keine Sonderzeichen** (Umlaute -o.ä.) vorkommen! Dies kann zu teilweise seltsamen Fehler führen. +Legen Sie für die Bearbeitung der Aufgabe ein neues Java-Projekt in Ihrer IDE an. +Achten Sie bitte darauf, dass im Projektpfad **keine Leerzeichen** und **keine +Sonderzeichen** (Umlaute o.ä.) vorkommen! Dies kann zu teilweise seltsamen Fehler +führen. -Wir werden in dieser Lehrveranstaltung das Build-Tool **Gradle** verwenden, dieses aber erst -später im Verlauf der Lehrveranstaltung besprechen. Für dieses erste Übungsblatt können Sie -Ihr Projekt noch so konfigurieren, dass es ohne Build-Tool arbeitet und direkt in der IDE -kompiliert und gestartet wird. +Wir werden in dieser Lehrveranstaltung das Build-Tool **Gradle** verwenden, dieses +aber erst später im Verlauf der Lehrveranstaltung besprechen. Für dieses erste +Übungsblatt können Sie Ihr Projekt noch so konfigurieren, dass es ohne Build-Tool +arbeitet und direkt in der IDE kompiliert und gestartet wird. Testen Sie bitte die genutzte Java-Version: -1. Konsole: Geben Sie den Befehl `java -version` auf der Konsole ein. Die Ausgabe sollte - `java version "21.0.6" 2025-01-21 LTS` (oder ähnlich) ergeben. Wichtig sind die "21" und - "LTS". +1. Konsole: Geben Sie den Befehl `java -version` auf der Konsole ein. Die Ausgabe + sollte `java version "21.0.6" 2025-01-21 LTS` (oder ähnlich) ergeben. Wichtig + sind die "21" und "LTS". 2. IDE: Erstellen Sie ein Programm, welches die Anweisung - `System.out.println(System.getProperty("java.version"));` ausführt. Beim Start über die - IDE sollte dabei die Ausgabe `21.0.6` (oder ähnlich) herauskommen. Wichtig ist die "21". + `System.out.println(System.getProperty("java.version"));` ausführt. Beim Start + über die IDE sollte dabei die Ausgabe `21.0.6` (oder ähnlich) herauskommen. + Wichtig ist die "21". Korrigieren Sie Ihr Setup, wenn Sie andere Ausgaben erhalten. @@ -71,26 +74,30 @@ public class Main { } ``` -Worin besteht der Unterschied zwischen dem Übersetzen und dem Starten des obigen Programms? -Wie unterscheiden Sie das in Ihrer IDE? Wie können Sie das Programm manuell auf der Konsole -übersetzen und starten? Demonstrieren Sie das live im Praktikum. +Worin besteht der Unterschied zwischen dem Übersetzen und dem Starten des obigen +Programms? Wie unterscheiden Sie das in Ihrer IDE? Wie können Sie das Programm +manuell auf der Konsole übersetzen und starten? Demonstrieren Sie das live im +Praktikum. -Worin besteht der Unterschied zwischen Compiler-Warnungen/-Fehlern und Laufzeit-Fehlern? -Erklären Sie das im Praktikum an selbst gewählten Beispielen am obigen Programm. +Worin besteht der Unterschied zwischen Compiler-Warnungen/-Fehlern und +Laufzeit-Fehlern? Erklären Sie das im Praktikum an selbst gewählten Beispielen am +obigen Programm. -Starten Sie das obige Programm im Debug-Modus Ihrer IDE. Halten Sie die Ausführung am -`System.out.println` an, verändern Sie den Wert der Variablen `version` und führen Sie dann -die nächste Anweisung (das `System.out.println`) aus. Wie beenden Sie das Programm? -Demonstrieren Sie das live im Praktikum. +Starten Sie das obige Programm im Debug-Modus Ihrer IDE. Halten Sie die Ausführung +am `System.out.println` an, verändern Sie den Wert der Variablen `version` und +führen Sie dann die nächste Anweisung (das `System.out.println`) aus. Wie beenden +Sie das Programm? Demonstrieren Sie das live im Praktikum. ## Swing und Java2D -Das Spiel soll vollständig über eine in Swing und Java2D realisierte GUI bedient werden: +Das Spiel soll vollständig über eine in Swing und Java2D realisierte GUI bedient +werden: 1. Visualisieren Sie den Zustand des Galgenmännchens per Java2D. 2. Visualisieren Sie den Zustand des zu ratenden Wortes geeignet. 3. Es muss ein Eingabefeld geben, um den nächsten Buchstaben eingeben zu können. -4. Die bisher eingegebenen Buchstaben sollen in der Reihenfolge der Eingabe angezeigt werden. +4. Die bisher eingegebenen Buchstaben sollen in der Reihenfolge der Eingabe + angezeigt werden. 5. Das Spiel soll per Knopfdruck abgebrochen und neu gestartet werden können. ## Einlesen von Textdateien @@ -101,11 +108,11 @@ Es soll die Möglichkeit geben, eine Textdatei mit zu ratenden Wörtern einzules 2. Startverzeichnis ist der Ordner, in dem das Spiel gestartet wurde. 3. Es sollen nur Textdateien mit der Endung `.txt` ausgewählt werden können. 4. Es soll nur eine Datei ausgewählt werden können (also keine Mehrfachselektion). -5. Die ausgewählte Textdatei soll eingelesen werden (Format: pro Zeile ein Wort). Alle - eingelesenen Wörter sollen in eine Menge überführt und für das Spiel nutzbar gemacht - werden. -6. Es soll für jedes neue Spiel ein zufälliges Wort aus der Menge der eingelesenen Wörter zum - Raten ausgewählt werden. +5. Die ausgewählte Textdatei soll eingelesen werden (Format: pro Zeile ein Wort). + Alle eingelesenen Wörter sollen in eine Menge überführt und für das Spiel + nutzbar gemacht werden. +6. Es soll für jedes neue Spiel ein zufälliges Wort aus der Menge der eingelesenen + Wörter zum Raten ausgewählt werden. ## Dokumentation @@ -113,38 +120,41 @@ Erstellen Sie ein UML-Klassendiagramm für Ihre Lösung. ## Ausprobieren von fortgeschrittenen Widgets -1. Heben Sie in der Anzeige der eingegebenen Buchstaben die korrekt geratenen Buchstaben in - grüner Farbe hervor. +1. Heben Sie in der Anzeige der eingegebenen Buchstaben die korrekt geratenen + Buchstaben in grüner Farbe hervor. 2. Passen Sie die Visualisierung des Galgenmännchens farblich an die Tageszeit an, - beispielsweise könnte es in den Nachtstunden eine Art Dark-Mode geben und tagsüber einen - Light-Mode. Alternativ könnten Sie auch die Farbe der Zeichnung mit der Uhrzeit variieren. -3. Lassen Sie den Nutzer per Slider die Schwierigkeit variieren: Steuerung der Länge des zu - ratenden Wortes und/oder Steuerung der Anzahl der erlaubten Fehlversuche. + beispielsweise könnte es in den Nachtstunden eine Art Dark-Mode geben und + tagsüber einen Light-Mode. Alternativ könnten Sie auch die Farbe der Zeichnung + mit der Uhrzeit variieren. +3. Lassen Sie den Nutzer per Slider die Schwierigkeit variieren: Steuerung der + Länge des zu ratenden Wortes und/oder Steuerung der Anzahl der erlaubten + Fehlversuche. # Bearbeitung und Abgabe -- Bearbeitung: Einzelbearbeitung oder bis zu 3er Teams -- Abgabe: - - Post Mortem [im ILIAS] eintragen: - - Verfassen Sie im ILIAS pro Blatt und pro Team-Mitglied ein aussagekräftiges und - nachvollziehbares "*Post Mortem*". Gehen Sie dabei auf folgende Punkte ein: +- Bearbeitung: Einzelbearbeitung oder bis zu 3er Teams +- Abgabe: + - Post Mortem [im + ILIAS](https://www.hsbi.de/elearning/goto.php?target=exc_1514856&client_id=FH-Bielefeld) + eintragen: - 1. Zusammenfassung: Was wurde gemacht? - 2. Implementierungsdetails: Kurze Beschreibung besonders interessanter Aspekte der - Umsetzung. - 3. Was war der schwierigste Teil bei der Bearbeitung? Wie haben Sie dieses Problem - gelöst? - 4. Was haben Sie gelernt oder (besser) verstanden? - 5. Team: Mit wem haben Sie zusammengearbeitet? + Verfassen Sie im ILIAS pro Blatt und pro Team-Mitglied ein aussagekräftiges + und nachvollziehbares "*Post Mortem*". Gehen Sie dabei auf folgende Punkte + ein: - Das Post Mortem muss von **jeder Person** im Team **individuell** verfasst und abgegeben - werden. Der Umfang des Textes soll zwischen 200 und 400 Wörtern liegen. + 1. Zusammenfassung: Was wurde gemacht? + 2. Implementierungsdetails: Kurze Beschreibung besonders interessanter + Aspekte der Umsetzung. + 3. Was war der schwierigste Teil bei der Bearbeitung? Wie haben Sie dieses + Problem gelöst? + 4. Was haben Sie gelernt oder (besser) verstanden? + 5. Team: Mit wem haben Sie zusammengearbeitet? - Laden Sie hier bitte **nicht** Ihre Lösungen hoch! + Das Post Mortem muss von **jeder Person** im Team **individuell** verfasst + und abgegeben werden. Der Umfang des Textes soll zwischen 200 und 400 + Wörtern liegen. - - Deadline: 25. April, 08:00 Uhr -- Vorstellung im Praktikum: 25. April + Laden Sie hier bitte **nicht** Ihre Lösungen hoch! - [Hangman]: https://en.wikipedia.org/wiki/Hangman_(game) - [im ILIAS]: https://www.hsbi.de/elearning/goto.php?target=exc_1514856&client_id=FH-Bielefeld + - Deadline: 25. April, 08:00 Uhr +- Vorstellung im Praktikum: 25. April diff --git a/homework/b02.md b/homework/b02.md index 96e756df7..7dacd870a 100644 --- a/homework/b02.md +++ b/homework/b02.md @@ -1,15 +1,14 @@ --- author: Carsten Gips (HSBI) -title: "Blatt 02: Git-Quest & Calculator (Git Basics, Lambda-Ausdrücke, Gradle)" no_beamer: true +title: "Blatt 02: Git-Quest & Calculator (Git Basics, Lambda-Ausdrücke, Gradle)" --- - - # Zusammenfassung -Auf diesem Blatt üben Sie den Umgang mit Git (Repo und Commits - zunächst auf der Konsole) -sowie den Einsatz von Lambda-Ausdrücken und das Schreiben von Gradle-Build-Skripten. +Auf diesem Blatt üben Sie den Umgang mit Git (Repo und Commits - zunächst auf der +Konsole) sowie den Einsatz von Lambda-Ausdrücken und das Schreiben von +Gradle-Build-Skripten. # Aufgaben @@ -37,131 +36,138 @@ Betrachten Sie die folgende Ausgabe von `git status` in einer lokalen Workingcop Erklären Sie die Ausgabe. -Geben Sie eine Befehlssequenz an, mit der Sie nur die Änderungen in `foo.java` committen -können. +Geben Sie eine Befehlssequenz an, mit der Sie nur die Änderungen in `foo.java` +committen können. ### Git-Spiel -Klonen Sie die [Vorgaben "Git-Quest"]. Sie finden die Geschichte des Helden Markus im -Dungeon.[^1] - -1. Öffnen Sie eine Konsole und beantworten Sie mit Hilfe der Befehle `git checkout`, - `git log` und `git show` sowie `git diff` folgende Fragen: - - - Was passierte an `tag 01`? - - Wann hat der Held zum ersten Mal 4 `experience` Punkte? - - Wann hat der Held zum ersten Mal 10 `hunger` Punkte? - - Wie viele Heiltränke hat der Held insgesamt in seinem Rucksack gehabt? - - Was hat der Held im Shop gekauft? Und wie viel Gold hat er dafür bezahlt? - - Was passierte zwischen `tag 03` und `tag 04`, d.h. was änderte sich zwischen diesen - Commits? - - Hat der Held etwas gegessen? Falls ja, was und wann? - -2. Beim letzten Commit (`tag 04.5`) ist etwas schief gelaufen, es wurden versehentlich zu - wenig `experience` Punkte eingestellt. Ändern Sie diesen letzten Commit und passen Sie die - `experience` Punkte auf 42 an. - -3. Schreiben Sie die Geschichte in der Datei `questlog.md` fort und erzeugen Sie einen neuen - Commit für `tag 04.6`. Ändern Sie bitte hierzu nur die eine Datei `questlog.md`. - -4. Schreiben Sie die Geschichte noch weiter fort (`tag 04.7`), aber ändern Sie diesmal - mehrere Dateien, die an diesem Tag (neuer Commit) gemeinsam eingecheckt werden sollen. - -5. Fälschlicherweise wurden die Statuspunkte und die Ausrüstung bisher gemeinsam in der Datei - `stats.md` geführt. Korrigieren Sie das und verschieben Sie die Ausrüstungsgegenstände aus - der Datei `stats.md` in eine neue Datei `gear.md`. Checken Sie Ihre Änderungen als - `tag 04.8` (neuer Commit) gemeinsam ein. (*Hinweis*: Es reicht, wenn diese Änderung als - letzter Commit auf der Spitze des `master`-Branches existiert. Sie brauchen/sollen die - Trennung von Statuspunkten und Ausrüstung **nicht rückwirkend** in die Historie einbauen!) +Klonen Sie die [Vorgaben +"Git-Quest"](https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_gitquest). +Sie finden die Geschichte des Helden Markus im Dungeon.[^1] + +1. Öffnen Sie eine Konsole und beantworten Sie mit Hilfe der Befehle + `git checkout`, `git log` und `git show` sowie `git diff` folgende Fragen: + + - Was passierte an `tag 01`? + - Wann hat der Held zum ersten Mal 4 `experience` Punkte? + - Wann hat der Held zum ersten Mal 10 `hunger` Punkte? + - Wie viele Heiltränke hat der Held insgesamt in seinem Rucksack gehabt? + - Was hat der Held im Shop gekauft? Und wie viel Gold hat er dafür bezahlt? + - Was passierte zwischen `tag 03` und `tag 04`, d.h. was änderte sich zwischen + diesen Commits? + - Hat der Held etwas gegessen? Falls ja, was und wann? + +2. Beim letzten Commit (`tag 04.5`) ist etwas schief gelaufen, es wurden + versehentlich zu wenig `experience` Punkte eingestellt. Ändern Sie diesen + letzten Commit und passen Sie die `experience` Punkte auf 42 an. + +3. Schreiben Sie die Geschichte in der Datei `questlog.md` fort und erzeugen Sie + einen neuen Commit für `tag 04.6`. Ändern Sie bitte hierzu nur die eine Datei + `questlog.md`. + +4. Schreiben Sie die Geschichte noch weiter fort (`tag 04.7`), aber ändern Sie + diesmal mehrere Dateien, die an diesem Tag (neuer Commit) gemeinsam eingecheckt + werden sollen. + +5. Fälschlicherweise wurden die Statuspunkte und die Ausrüstung bisher gemeinsam in + der Datei `stats.md` geführt. Korrigieren Sie das und verschieben Sie die + Ausrüstungsgegenstände aus der Datei `stats.md` in eine neue Datei `gear.md`. + Checken Sie Ihre Änderungen als `tag 04.8` (neuer Commit) gemeinsam ein. + (*Hinweis*: Es reicht, wenn diese Änderung als letzter Commit auf der Spitze des + `master`-Branches existiert. Sie brauchen/sollen die Trennung von Statuspunkten + und Ausrüstung **nicht rückwirkend** in die Historie einbauen!) Demonstrieren Sie Ihr Vorgehen im Praktikum jeweils live. ### Commit-Meldungen Gute Commit-Meldungen schreiben erfordert Übung. Schauen Sie sich die beiden Commits -[Dungeon-CampusMinden/Dungeon/commit/46530b6] und -[Dungeon-CampusMinden/Dungeon/commit/3e37472] an. +[Dungeon-CampusMinden/Dungeon/commit/46530b6](https://github.com/Dungeon-CampusMinden/Dungeon/commit/46530b6dc970a8cedb0610b92268b9c78345e067) +und +[Dungeon-CampusMinden/Dungeon/commit/3e37472](https://github.com/Dungeon-CampusMinden/Dungeon/commit/3e3747220ade538b4c974a520cc9104121789aa1) +an. -Diskutieren Sie jeweils, was Ihnen an den Commits auffällt: Was gefällt Ihnen, was stört Sie? -Schlagen Sie Verbesserungen vor. +Diskutieren Sie jeweils, was Ihnen an den Commits auffällt: Was gefällt Ihnen, was +stört Sie? Schlagen Sie Verbesserungen vor. ## Gradle -Folgen Sie der Anleitung auf [gradle.org] und installieren Sie Gradle auf Ihrem Rechner. Legen -Sie in der Konsole ein neues Gradle-Projekt für eine Java-Applikation an (ohne IDE!). Das -Build-Script soll in Groovy erzeugt und als Test-API soll JUnit4 verwendet werden. +Folgen Sie der Anleitung auf [gradle.org](https://gradle.org/) und installieren Sie +Gradle auf Ihrem Rechner. Legen Sie in der Konsole ein neues Gradle-Projekt für eine +Java-Applikation an (ohne IDE!). Das Build-Script soll in Groovy erzeugt und als +Test-API soll JUnit4 verwendet werden. -Wie finden Sie auf der Konsole heraus, welche Tasks es gibt? Erklären Sie das Projektlayout, -d.h. wo kommen beispielsweise die Java-Dateien hin? +Wie finden Sie auf der Konsole heraus, welche Tasks es gibt? Erklären Sie das +Projektlayout, d.h. wo kommen beispielsweise die Java-Dateien hin? -Erklären Sie, in welche Abschnitte das generierte Buildskript unterteilt ist und welche -Aufgaben diese Abschnitte jeweils erfüllen. Gehen Sie dabei im *Detail* auf das Plugin -`application` und die dort bereitgestellten Tasks und deren Abhängigkeiten untereinander ein. +Erklären Sie, in welche Abschnitte das generierte Buildskript unterteilt ist und +welche Aufgaben diese Abschnitte jeweils erfüllen. Gehen Sie dabei im *Detail* auf +das Plugin `application` und die dort bereitgestellten Tasks und deren +Abhängigkeiten untereinander ein. -Öffnen Sie das Projekt in Ihrer IDE. Wie können Sie hier die verschiedenen Tasks ansteuern? +Öffnen Sie das Projekt in Ihrer IDE. Wie können Sie hier die verschiedenen Tasks +ansteuern? -Machen Sie sich Notizen, welche Sie im Praktikum nutzen dürfen, um dort das Buildskript zu -erklären. +Machen Sie sich Notizen, welche Sie im Praktikum nutzen dürfen, um dort das +Buildskript zu erklären. ## Calculator: Anonyme Klassen und Lambda-Ausdrücke -Klonen Sie die [Vorgaben "Calculator"] und laden Sie das Projekt als Gradle-Projekt in Ihre -IDE. - -In den Vorgaben wird das Package `calculator` verwendet. Recherchieren Sie und erklären Sie im -Praktikum, was Packages in Java sind und wie man damit die Code-Basis strukturieren kann. -Gehen Sie dabei auch auf Sichtbarkeiten ein. - -Im Package `calculator` finden Sie einige Interfaces und Klassen, mit denen man einen -einfachen Taschenrechner modellieren kann: Dieser kann einfache mathematische Operationen auf -zwei Integern ausführen. - -In der Klasse `calculator.Calculator` finden Sie vier mit `TODO` markierte Stellen in der -Methode `setupOperationSelector`: - -1. Erstellen Sie eine neue **Java-Klasse** `Sub`, die das Interface `Operation` implementiert - und eine Subtraktion bereitstellt. Erweitern Sie den `Calculator` und binden Sie eine - **Instanz dieser Klasse** ein. Nutzen Sie hier keine anonymen Klassen oder - Lambda-Ausdrücke. -2. Erstellen Sie eine weitere Operation "Mul" (Multiplikation von zwei Integern). Nutzen Sie - dazu eine passende **anonyme Klasse**. -3. Erstellen Sie eine weitere Operation "Div" (Integerdivision). Erstellen Sie einen - passenden **Lambda-Ausdruck**. -4. Für die `JComboBox` `operationSelector` wird ein `ActionListener` mit Hilfe einer - *anonymen Klasse* definiert. Konvertieren Sie dies in einen entsprechenden +Klonen Sie die [Vorgaben +"Calculator"](https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_calculator) +und laden Sie das Projekt als Gradle-Projekt in Ihre IDE. + +In den Vorgaben wird das Package `calculator` verwendet. Recherchieren Sie und +erklären Sie im Praktikum, was Packages in Java sind und wie man damit die +Code-Basis strukturieren kann. Gehen Sie dabei auch auf Sichtbarkeiten ein. + +Im Package `calculator` finden Sie einige Interfaces und Klassen, mit denen man +einen einfachen Taschenrechner modellieren kann: Dieser kann einfache mathematische +Operationen auf zwei Integern ausführen. + +In der Klasse `calculator.Calculator` finden Sie vier mit `TODO` markierte Stellen +in der Methode `setupOperationSelector`: + +1. Erstellen Sie eine neue **Java-Klasse** `Sub`, die das Interface `Operation` + implementiert und eine Subtraktion bereitstellt. Erweitern Sie den `Calculator` + und binden Sie eine **Instanz dieser Klasse** ein. Nutzen Sie hier keine + anonymen Klassen oder Lambda-Ausdrücke. +2. Erstellen Sie eine weitere Operation "Mul" (Multiplikation von zwei Integern). + Nutzen Sie dazu eine passende **anonyme Klasse**. +3. Erstellen Sie eine weitere Operation "Div" (Integerdivision). Erstellen Sie + einen passenden **Lambda-Ausdruck**. +4. Für die `JComboBox` `operationSelector` wird ein `ActionListener` mit Hilfe + einer *anonymen Klasse* definiert. Konvertieren Sie dies in einen entsprechenden **Lambda-Ausdruck**. # Bearbeitung und Abgabe -- Bearbeitung: Einzelbearbeitung oder bis zu 3er Teams -- Abgabe: - - Post Mortem [im ILIAS] eintragen: - - Verfassen Sie im ILIAS pro Blatt und pro Team-Mitglied ein aussagekräftiges und - nachvollziehbares "*Post Mortem*". Gehen Sie dabei auf folgende Punkte ein: +- Bearbeitung: Einzelbearbeitung oder bis zu 3er Teams +- Abgabe: + - Post Mortem [im + ILIAS](https://www.hsbi.de/elearning/goto.php?target=exc_1514856&client_id=FH-Bielefeld) + eintragen: - 1. Zusammenfassung: Was wurde gemacht? - 2. Implementierungsdetails: Kurze Beschreibung besonders interessanter Aspekte der - Umsetzung. - 3. Was war der schwierigste Teil bei der Bearbeitung? Wie haben Sie dieses Problem - gelöst? - 4. Was haben Sie gelernt oder (besser) verstanden? - 5. Team: Mit wem haben Sie zusammengearbeitet? + Verfassen Sie im ILIAS pro Blatt und pro Team-Mitglied ein aussagekräftiges + und nachvollziehbares "*Post Mortem*". Gehen Sie dabei auf folgende Punkte + ein: - Das Post Mortem muss von **jeder Person** im Team **individuell** verfasst und abgegeben - werden. Der Umfang des Textes soll zwischen 200 und 400 Wörtern liegen. + 1. Zusammenfassung: Was wurde gemacht? + 2. Implementierungsdetails: Kurze Beschreibung besonders interessanter + Aspekte der Umsetzung. + 3. Was war der schwierigste Teil bei der Bearbeitung? Wie haben Sie dieses + Problem gelöst? + 4. Was haben Sie gelernt oder (besser) verstanden? + 5. Team: Mit wem haben Sie zusammengearbeitet? - Laden Sie hier bitte **nicht** Ihre Lösungen hoch! + Das Post Mortem muss von **jeder Person** im Team **individuell** verfasst + und abgegeben werden. Der Umfang des Textes soll zwischen 200 und 400 + Wörtern liegen. - - Deadline: 02. Mai, 08:00 Uhr -- Vorstellung im Praktikum: 02. Mai + Laden Sie hier bitte **nicht** Ihre Lösungen hoch! -[^1]: Für alle, die schon mit Branches umgehen können: Betrachten Sie auf diesem Blatt bitte - nur den Branch `master`. + - Deadline: 02. Mai, 08:00 Uhr +- Vorstellung im Praktikum: 02. Mai - [Vorgaben "Git-Quest"]: https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_gitquest - [Dungeon-CampusMinden/Dungeon/commit/46530b6]: https://github.com/Dungeon-CampusMinden/Dungeon/commit/46530b6dc970a8cedb0610b92268b9c78345e067 - [Dungeon-CampusMinden/Dungeon/commit/3e37472]: https://github.com/Dungeon-CampusMinden/Dungeon/commit/3e3747220ade538b4c974a520cc9104121789aa1 - [gradle.org]: https://gradle.org/ - [Vorgaben "Calculator"]: https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_calculator - [im ILIAS]: https://www.hsbi.de/elearning/goto.php?target=exc_1514856&client_id=FH-Bielefeld +[^1]: Für alle, die schon mit Branches umgehen können: Betrachten Sie auf diesem + Blatt bitte nur den Branch `master`. diff --git a/homework/b03.md b/homework/b03.md index 4333e63e5..59ed8fdd6 100644 --- a/homework/b03.md +++ b/homework/b03.md @@ -1,130 +1,138 @@ --- author: Carsten Gips (HSBI) -title: "Blatt 03: Git-Quest & LSF-Contact (Git Branches, Methoden-Referenzen, Logging)" no_beamer: true +title: "Blatt 03: Git-Quest & LSF-Contact (Git Branches, Methoden-Referenzen, + Logging)" --- - - # Zusammenfassung -Auf diesem Blatt üben Sie den Umgang mit Git (Branches und Mergen - zunächst auf der Konsole) -sowie den Einsatz von Methoden-Referenzen und den Einsatz von Logging. +Auf diesem Blatt üben Sie den Umgang mit Git (Branches und Mergen - zunächst auf der +Konsole) sowie den Einsatz von Methoden-Referenzen und den Einsatz von Logging. # Aufgaben ## Git-Spiel -Betrachten Sie erneut die [Vorgaben zur "Git-Quest"]. Die Geschichte des Helden Markus findet -im `master`-Branch kein Ende, sondern erst im Hilfsbranch `end`. - -Machen Sie nun verschiedene Experimente mit Branches in Git, und starten Sie dabei jeweils mit -einem frischen Klon der Vorgaben. - -1. Ändern Sie eine Datei, die im Branch `end` nicht verändert wurde. Erzeugen Sie mit diesen - Änderungen auf dem `master` einen neuen Commit. Mergen Sie danach den Branch `end` in den - `master`-Branch. -2. Ändern Sie nun eine Datei, die auch im Branch `end` verändert wurde. Achten Sie dabei - darauf, die Änderung an einer anderen Stelle in der Datei vorzunehmen. Erzeugen Sie mit - diesen Änderungen auf dem `master` einen neuen Commit. Mergen Sie danach den Branch `end` - in den `master`-Branch. -3. Wie (2), aber ändern Sie nun eine Stelle, die auch im Branch `end` verändert wurde. - Erzeugen Sie mit diesen Änderungen auf dem `master` einen neuen Commit. Mergen Sie danach - den Branch `end` in den `master`-Branch. Was passiert, wenn die Änderung im `master` - identisch zu der in `end` ist? Was passiert, wenn die Änderung im `master` anders ist als - in `end`? -4. Wie (2), aber setzen Sie bitte den Branch `end` auf die Spitze von `master`, bevor Sie - `end` in `master` mergen. - -Was beobachten Sie jeweils? Erklären Sie Ihre Beobachtungen. Wenn es Konflikte gibt: Wie lösen -Sie diese auf? Demonstrieren Sie das Vorgehen im Praktikum live. +Betrachten Sie erneut die [Vorgaben zur +"Git-Quest"](https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_gitquest). +Die Geschichte des Helden Markus findet im `master`-Branch kein Ende, sondern erst +im Hilfsbranch `end`. + +Machen Sie nun verschiedene Experimente mit Branches in Git, und starten Sie dabei +jeweils mit einem frischen Klon der Vorgaben. + +1. Ändern Sie eine Datei, die im Branch `end` nicht verändert wurde. Erzeugen Sie + mit diesen Änderungen auf dem `master` einen neuen Commit. Mergen Sie danach den + Branch `end` in den `master`-Branch. +2. Ändern Sie nun eine Datei, die auch im Branch `end` verändert wurde. Achten Sie + dabei darauf, die Änderung an einer anderen Stelle in der Datei vorzunehmen. + Erzeugen Sie mit diesen Änderungen auf dem `master` einen neuen Commit. Mergen + Sie danach den Branch `end` in den `master`-Branch. +3. Wie (2), aber ändern Sie nun eine Stelle, die auch im Branch `end` verändert + wurde. Erzeugen Sie mit diesen Änderungen auf dem `master` einen neuen Commit. + Mergen Sie danach den Branch `end` in den `master`-Branch. Was passiert, wenn + die Änderung im `master` identisch zu der in `end` ist? Was passiert, wenn die + Änderung im `master` anders ist als in `end`? +4. Wie (2), aber setzen Sie bitte den Branch `end` auf die Spitze von `master`, + bevor Sie `end` in `master` mergen. + +Was beobachten Sie jeweils? Erklären Sie Ihre Beobachtungen. Wenn es Konflikte gibt: +Wie lösen Sie diese auf? Demonstrieren Sie das Vorgehen im Praktikum live. ## LSF-Contact -Betrachten Sie die [Vorgaben "LSF-Contact"]. Klonen Sie das Repo und laden Sie das Projekt als -Gradle-Projekt in Ihre IDE. +Betrachten Sie die [Vorgaben +"LSF-Contact"](https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_lsfcontact). +Klonen Sie das Repo und laden Sie das Projekt als Gradle-Projekt in Ihre IDE. ### Methoden-Referenzen -Sie finden im Package `lsfcontact` eine Klasse `Student`. Jede Instanz dieser Klasse hat -mindestens einen Namen (`String`), und man kann verschiedene Konktaktmöglichkeiten per Setter -setzen: EMail-Adresse, Telefonnummer, Post-Adresse (alle `String`). +Sie finden im Package `lsfcontact` eine Klasse `Student`. Jede Instanz dieser Klasse +hat mindestens einen Namen (`String`), und man kann verschiedene +Konktaktmöglichkeiten per Setter setzen: EMail-Adresse, Telefonnummer, Post-Adresse +(alle `String`). Die Klasse `LsfContactUtil` soll ein Hilfsmodul im LSF simulieren, mit der man die -Studierenden kontaktieren kann. Es gibt drei verschiedene Methoden, die jeweils mit einer -Liste mit `Student`-Objekten aufgerufen werden und die alle Studierenden mit der entsprechend -gesetzten Kontaktoption über diesen Kontaktweg ansprechen. *Beispiel*: Die Methode -`emailStudents` filtert alle Studierenden, deren EMail-Adresse gesetzt ist (d.h. deren -EMail-Adresse ein nicht-leerer String ist) und "schickt" diesen Studierenden eine "EMail" über -den Aufruf der privaten Hilfsmethode `email`. +Studierenden kontaktieren kann. Es gibt drei verschiedene Methoden, die jeweils mit +einer Liste mit `Student`-Objekten aufgerufen werden und die alle Studierenden mit +der entsprechend gesetzten Kontaktoption über diesen Kontaktweg ansprechen. +*Beispiel*: Die Methode `emailStudents` filtert alle Studierenden, deren +EMail-Adresse gesetzt ist (d.h. deren EMail-Adresse ein nicht-leerer String ist) und +"schickt" diesen Studierenden eine "EMail" über den Aufruf der privaten Hilfsmethode +`email`. Die Klasse `Main` erzeugt einige `Student`-Objekte, gruppiert sie in einer Liste und demonstriert die Aufrufe der Methoden in `LsfContactUtil`. -Es fällt auf, dass die drei Methoden `emailStudents`, `phoneStudents` und `writeStudents` -algorithmisch identisch sind und sich nur in der Abfrage der entsprechenden Kontaktoption und -dem Aufruf der internen Kontakt-Methode unterscheiden. Auch die internen Kontakt-Methoden -`email`, `phone` und `write` sind recht einfallslose Code-Duplikate. - -Schreiben Sie die Klasse `LsfContactUtil` so um, dass es nur noch eine `public` Methode für -das Kontaktieren einer Liste von `Student`-Objekten gibt. Fassen Sie ebenfalls die drei -`private` Hilfsmethoden zu einer neuen Hilfsmethode zusammen - dabei soll es inhaltlich bei -dem `System.out.println()` mit den aktuell verwendeten Informationen bleiben. Überlegen Sie, -wie Sie die Abfrage der Kontaktmöglichkeit und auch die Kriterien für die Prüfung der Strings -von außen als Parameter in die Methode hineingeben können. Passen Sie die Schnittstellen an, -so dass der neuen `public` Methode zusätzlich zur `List` passende Methodenreferenzen -übergeben werden können. Ändern Sie die Demo-Aufrufe in `Main` entsprechend. Die Klasse -`Student` verändern Sie bitte nicht. - -*Tipp*: Gehen Sie schrittweise vor und starten zunächst mit geeigneten Lambda-Ausdrücken. -Schaffen Sie es, diese durch Methodenreferenzen zu ersetzen? - -Achten Sie darauf, alle Schritte nachvollziehbar in Ihrer Arbeitskopie per Git Commit -festzuhalten. Demonstrieren Sie dies im Praktikum. +Es fällt auf, dass die drei Methoden `emailStudents`, `phoneStudents` und +`writeStudents` algorithmisch identisch sind und sich nur in der Abfrage der +entsprechenden Kontaktoption und dem Aufruf der internen Kontakt-Methode +unterscheiden. Auch die internen Kontakt-Methoden `email`, `phone` und `write` sind +recht einfallslose Code-Duplikate. + +Schreiben Sie die Klasse `LsfContactUtil` so um, dass es nur noch eine `public` +Methode für das Kontaktieren einer Liste von `Student`-Objekten gibt. Fassen Sie +ebenfalls die drei `private` Hilfsmethoden zu einer neuen Hilfsmethode zusammen - +dabei soll es inhaltlich bei dem `System.out.println()` mit den aktuell verwendeten +Informationen bleiben. Überlegen Sie, wie Sie die Abfrage der Kontaktmöglichkeit und +auch die Kriterien für die Prüfung der Strings von außen als Parameter in die +Methode hineingeben können. Passen Sie die Schnittstellen an, so dass der neuen +`public` Methode zusätzlich zur `List` passende Methodenreferenzen +übergeben werden können. Ändern Sie die Demo-Aufrufe in `Main` entsprechend. Die +Klasse `Student` verändern Sie bitte nicht. + +*Tipp*: Gehen Sie schrittweise vor und starten zunächst mit geeigneten +Lambda-Ausdrücken. Schaffen Sie es, diese durch Methodenreferenzen zu ersetzen? + +Achten Sie darauf, alle Schritte nachvollziehbar in Ihrer Arbeitskopie per Git +Commit festzuhalten. Demonstrieren Sie dies im Praktikum. ### Logging -Bauen Sie für das `LsfContactUtil` ein Logging auf der Basis von `java.util.logging` ein: Jede -Benachrichtigung von Studierenden soll in ein gemeinsames CSV-File geloggt werden. Dabei soll -pro Logging-Vorgang eine neue Zeile mit den folgenden Informationen angehängt werden: +Bauen Sie für das `LsfContactUtil` ein Logging auf der Basis von `java.util.logging` +ein: Jede Benachrichtigung von Studierenden soll in ein gemeinsames CSV-File geloggt +werden. Dabei soll pro Logging-Vorgang eine neue Zeile mit den folgenden +Informationen angehängt werden: -- Log-Level, -- Name der den Log-Vorgang auslösenden Methode, -- Name der Klasse, in der die den Log-Vorgang auslösenden Methode angesiedelt ist, -- Log-Meldung, bestehend aus - - Name des kontaktierten Studierenden, - - genutzte Adresse des Studierenden (Mail- oder Postadresse oder Telefonnummer), - - Kontaktmodus (wird eine Mail geschickt oder ein Brief geschrieben oder ein Anruf - getätigt). +- Log-Level, +- Name der den Log-Vorgang auslösenden Methode, +- Name der Klasse, in der die den Log-Vorgang auslösenden Methode angesiedelt ist, +- Log-Meldung, bestehend aus + - Name des kontaktierten Studierenden, + - genutzte Adresse des Studierenden (Mail- oder Postadresse oder + Telefonnummer), + - Kontaktmodus (wird eine Mail geschickt oder ein Brief geschrieben oder ein + Anruf getätigt). -Demonstrieren Sie in der Abgabe, wie Sie im Test oder im Hauptprogramm den Logger steuern -können, beispielsweise Änderung der Log-Level oder Abschalten des Loggings. +Demonstrieren Sie in der Abgabe, wie Sie im Test oder im Hauptprogramm den Logger +steuern können, beispielsweise Änderung der Log-Level oder Abschalten des Loggings. # Bearbeitung und Abgabe -- Bearbeitung: Einzelbearbeitung oder bis zu 3er Teams -- Abgabe: - - Post Mortem [im ILIAS] eintragen: - - Verfassen Sie im ILIAS pro Blatt und pro Team-Mitglied ein aussagekräftiges und - nachvollziehbares "*Post Mortem*". Gehen Sie dabei auf folgende Punkte ein: +- Bearbeitung: Einzelbearbeitung oder bis zu 3er Teams +- Abgabe: + - Post Mortem [im + ILIAS](https://www.hsbi.de/elearning/goto.php?target=exc_1514856&client_id=FH-Bielefeld) + eintragen: - 1. Zusammenfassung: Was wurde gemacht? - 2. Implementierungsdetails: Kurze Beschreibung besonders interessanter Aspekte der - Umsetzung. - 3. Was war der schwierigste Teil bei der Bearbeitung? Wie haben Sie dieses Problem - gelöst? - 4. Was haben Sie gelernt oder (besser) verstanden? - 5. Team: Mit wem haben Sie zusammengearbeitet? + Verfassen Sie im ILIAS pro Blatt und pro Team-Mitglied ein aussagekräftiges + und nachvollziehbares "*Post Mortem*". Gehen Sie dabei auf folgende Punkte + ein: - Das Post Mortem muss von **jeder Person** im Team **individuell** verfasst und abgegeben - werden. Der Umfang des Textes soll zwischen 200 und 400 Wörtern liegen. + 1. Zusammenfassung: Was wurde gemacht? + 2. Implementierungsdetails: Kurze Beschreibung besonders interessanter + Aspekte der Umsetzung. + 3. Was war der schwierigste Teil bei der Bearbeitung? Wie haben Sie dieses + Problem gelöst? + 4. Was haben Sie gelernt oder (besser) verstanden? + 5. Team: Mit wem haben Sie zusammengearbeitet? - Laden Sie hier bitte **nicht** Ihre Lösungen hoch! + Das Post Mortem muss von **jeder Person** im Team **individuell** verfasst + und abgegeben werden. Der Umfang des Textes soll zwischen 200 und 400 + Wörtern liegen. - - Deadline: 09. Mai, 08:00 Uhr -- Vorstellung im Praktikum: 09. Mai + Laden Sie hier bitte **nicht** Ihre Lösungen hoch! - [Vorgaben zur "Git-Quest"]: https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_gitquest - [Vorgaben "LSF-Contact"]: https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_lsfcontact - [im ILIAS]: https://www.hsbi.de/elearning/goto.php?target=exc_1514856&client_id=FH-Bielefeld + - Deadline: 09. Mai, 08:00 Uhr +- Vorstellung im Praktikum: 09. Mai diff --git a/homework/b04.md b/homework/b04.md index 49605b323..bb4a6251a 100644 --- a/homework/b04.md +++ b/homework/b04.md @@ -1,26 +1,27 @@ --- author: Carsten Gips (HSBI) -title: "Blatt 04: Stream-API & Damaged Bridge (Git Remote, Streams, Lambda-Ausdrücke)" no_beamer: true +title: "Blatt 04: Stream-API & Damaged Bridge (Git Remote, Streams, + Lambda-Ausdrücke)" --- - - # Zusammenfassung -Auf diesem Blatt üben Sie den Umgang mit Git Remotes sowie die Erstellung von Pull-Requests -auf GitHub. Darüber hinaus üben wir den Umgang mit der Java-Stream-API und noch einmal mit -Lambda-Ausdrücken. +Auf diesem Blatt üben Sie den Umgang mit Git Remotes sowie die Erstellung von +Pull-Requests auf GitHub. Darüber hinaus üben wir den Umgang mit der Java-Stream-API +und noch einmal mit Lambda-Ausdrücken. ::: important -**Hinweis**: Bitte denken Sie daran, dass Sie spätestens ab diesem Blatt die Bearbeitung der -Aufgaben in Ihren öffentlich sichtbaren Git-Repos durchführen sollen. +**Hinweis**: Bitte denken Sie daran, dass Sie spätestens ab diesem Blatt die +Bearbeitung der Aufgaben in Ihren öffentlich sichtbaren Git-Repos durchführen +sollen. -Erstellen Sie ab diesem Blatt für **alle** Ihre Lösungen passende Pull-Requests gegen **Ihre -eigenen Repos** (auch wenn dies nicht explizit in den Aufgaben gefordert wird). +Erstellen Sie ab diesem Blatt für **alle** Ihre Lösungen passende Pull-Requests +gegen **Ihre eigenen Repos** (auch wenn dies nicht explizit in den Aufgaben +gefordert wird). -Die Links zu Ihren Pull-Requests mit den Lösungen geben Sie bitte ab sofort immer in Ihrem -*Post Mortem* mit an. +Die Links zu Ihren Pull-Requests mit den Lösungen geben Sie bitte ab sofort immer in +Ihrem *Post Mortem* mit an. ::: # Aufgaben @@ -29,92 +30,107 @@ Die Links zu Ihren Pull-Requests mit den Lösungen geben Sie bitte ab sofort imm ### Git: Pull-Requests (und Code-Formatierung und -Dokumentation) -Forken Sie das ["Stream-API"]-Repo und erzeugen Sie eine lokale Arbeitskopie von Ihrem Fork. +Forken Sie das +["Stream-API"](https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_streamapi)-Repo +und erzeugen Sie eine lokale Arbeitskopie von Ihrem Fork. -Sie finden die Vorgaben für jede Teilaufgabe in einem eigenen Branch. Checken Sie diese -Branches lokal als Tracking-Branches aus und bearbeiten Sie die Teilaufgaben jeweils in ihrem -eigenen Branch. Pushen Sie Ihre Änderungen in Ihren Fork zurück und erstellen Sie dort je -einen Pull-Request auf **Ihren** eigenen `master`-Branch. +Sie finden die Vorgaben für jede Teilaufgabe in einem eigenen Branch. Checken Sie +diese Branches lokal als Tracking-Branches aus und bearbeiten Sie die Teilaufgaben +jeweils in ihrem eigenen Branch. Pushen Sie Ihre Änderungen in Ihren Fork zurück und +erstellen Sie dort je einen Pull-Request auf **Ihren** eigenen `master`-Branch. **Bitte lassen Sie die Pull-Requests bis zur Vorstellung im Praktikum offen.** -Achten Sie darauf, alle Schritte nachvollziehbar in Ihrer Arbeitskopie per Git-Commit -festzuhalten. Demonstrieren Sie im Praktikum, wie Sie mit den Pull-Requests arbeiten. +Achten Sie darauf, alle Schritte nachvollziehbar in Ihrer Arbeitskopie per +Git-Commit festzuhalten. Demonstrieren Sie im Praktikum, wie Sie mit den +Pull-Requests arbeiten. ### Stream-API: Task I -Betrachten Sie den Branch `task_i`. Sie finden im Package `streamapi` einige Hilfsklassen -sowie in der Datei [`Main.java`] einen Starter für diese erste Teilaufgabe. +Betrachten Sie den Branch `task_i`. Sie finden im Package `streamapi` einige +Hilfsklassen sowie in der Datei +[`Main.java`](https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_streamapi/blob/task_i/src/main/java/streamapi/Main.java) +einen Starter für diese erste Teilaufgabe. -In der Funktion `Main#students` wird für eine Liste von `Student`-Objekten die Summe der -gesammelten ECTS berechnet. +In der Funktion `Main#students` wird für eine Liste von `Student`-Objekten die Summe +der gesammelten ECTS berechnet. -Schreiben Sie den Body dieser Methode so um, dass die selbe Funktionalität unter Nutzung der -[Java-Stream-API] erreicht wird. Bevorzugen Sie dabei nach Möglichkeit Methoden-Referenzen vor -Lambda-Ausdrücken. +Schreiben Sie den Body dieser Methode so um, dass die selbe Funktionalität unter +Nutzung der [Java-Stream-API](https://dev.java/learn/api/streams/) erreicht wird. +Bevorzugen Sie dabei nach Möglichkeit Methoden-Referenzen vor Lambda-Ausdrücken. ### Stream-API: Task II -Betrachten Sie nun den Branch `task_ii`. Sie finden wieder im Package `streamapi` einige -Hilfsklassen sowie in der Datei [`Main.java`][1] einen Starter für diese zweite Teilaufgabe. +Betrachten Sie nun den Branch `task_ii`. Sie finden wieder im Package `streamapi` +einige Hilfsklassen sowie in der Datei +[`Main.java`](https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_streamapi/blob/task_ii/src/main/java/streamapi/Main.java) +einen Starter für diese zweite Teilaufgabe. -In der Funktion `Main#ifmCps` wird für eine Liste von `Student`-Objekten die Menge der -gesammelten ECTS bestimmt. Dabei werden nur Studierende des Informatik-Studiengangs -berücksichtigt. +In der Funktion `Main#ifmCps` wird für eine Liste von `Student`-Objekten die Menge +der gesammelten ECTS bestimmt. Dabei werden nur Studierende des +Informatik-Studiengangs berücksichtigt. Was bedeutet "die Menge der gesammelten ECTS", was ist die Mengen-Eigenschaft? -Schreiben Sie den Body dieser Methode so um, dass die selbe Funktionalität unter Nutzung der -[Java-Stream-API] erreicht wird. Bevorzugen Sie dabei nach Möglichkeit Methoden-Referenzen vor -Lambda-Ausdrücken. +Schreiben Sie den Body dieser Methode so um, dass die selbe Funktionalität unter +Nutzung der [Java-Stream-API](https://dev.java/learn/api/streams/) erreicht wird. +Bevorzugen Sie dabei nach Möglichkeit Methoden-Referenzen vor Lambda-Ausdrücken. ### Stream-API: Task III -Betrachten Sie nun den Branch `task_iii`. Sie finden wieder im Package `streamapi` einige -Hilfsklassen sowie in der Datei [`Main.java`][2] einen Starter für diese dritte Teilaufgabe. +Betrachten Sie nun den Branch `task_iii`. Sie finden wieder im Package `streamapi` +einige Hilfsklassen sowie in der Datei +[`Main.java`](https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_streamapi/blob/task_iii/src/main/java/streamapi/Main.java) +einen Starter für diese dritte Teilaufgabe. -In der Funktion `Main#random` werden zunächst zehn zufällige Integerwerte im Bereich zwischen -0 (inklusive) und 10 (exklusive) berechnet. Anschließend werden diese Zahlen weiter -verarbeitet und das Ergebnis zurückgeliefert. +In der Funktion `Main#random` werden zunächst zehn zufällige Integerwerte im Bereich +zwischen 0 (inklusive) und 10 (exklusive) berechnet. Anschließend werden diese +Zahlen weiter verarbeitet und das Ergebnis zurückgeliefert. -Schreiben Sie den Body dieser Methode so um, dass die selbe Funktionalität unter Nutzung der -[Java-Stream-API] erreicht wird. Bevorzugen Sie dabei nach Möglichkeit Methoden-Referenzen vor -Lambda-Ausdrücken. +Schreiben Sie den Body dieser Methode so um, dass die selbe Funktionalität unter +Nutzung der [Java-Stream-API](https://dev.java/learn/api/streams/) erreicht wird. +Bevorzugen Sie dabei nach Möglichkeit Methoden-Referenzen vor Lambda-Ausdrücken. ### Stream-API: Task IV+V -Betrachten Sie nun den Branch `task_iv_v`. Sie finden wieder im Package `streamapi` einige -Hilfsklassen sowie in der Datei [`Main.java`][3] einen Starter für diese vierte Teilaufgabe. +Betrachten Sie nun den Branch `task_iv_v`. Sie finden wieder im Package `streamapi` +einige Hilfsklassen sowie in der Datei +[`Main.java`](https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_streamapi/blob/task_iv_v/src/main/java/streamapi/Main.java) +einen Starter für diese vierte Teilaufgabe. 1. Ressourcen in Java - In der Funktion `Main#getResourceAsStream` soll eine Textdatei als `InputStream` zum - Einlesen geöffnet werden. Die Datei soll dabei im Ressourcen-Ordner des Projekts gesucht - werden. + In der Funktion `Main#getResourceAsStream` soll eine Textdatei als `InputStream` + zum Einlesen geöffnet werden. Die Datei soll dabei im Ressourcen-Ordner des + Projekts gesucht werden. - Informieren Sie sich im Web über den Umgang in Java mit Ressourcen. Welcher Ordner wurde - in der vorgegebenen Gradle-Konfiguration als Ressourcen-Ordner für das Projekt definiert? + Informieren Sie sich im Web über den Umgang in Java mit Ressourcen. Welcher + Ordner wurde in der vorgegebenen Gradle-Konfiguration als Ressourcen-Ordner für + das Projekt definiert? - Schreiben Sie den Body dieser Methode so um, dass die zu dem übergebenen Dateinamen - passende Ressource im Kontext der aktuellen Klasse als `InputStream` geöffnet wird und - geben Sie diesen als Ergebnis zurück. + Schreiben Sie den Body dieser Methode so um, dass die zu dem übergebenen + Dateinamen passende Ressource im Kontext der aktuellen Klasse als `InputStream` + geöffnet wird und geben Sie diesen als Ergebnis zurück. - *Hinweis*: Diese Teilaufgabe ist die Voraussetzung für die Bearbeitung der nächsten - Teilaufgabe. Wenn Sie diese Aufgabe nicht hinbekommen sollten, dann definieren Sie sich - ersatzweise einen *Text Block* (Multi-line String) mit dem Inhalt der Textdatei und - erzeugen daraus einen `InputStream`. Das zählt dann aber nicht als "bearbeitet". + *Hinweis*: Diese Teilaufgabe ist die Voraussetzung für die Bearbeitung der + nächsten Teilaufgabe. Wenn Sie diese Aufgabe nicht hinbekommen sollten, dann + definieren Sie sich ersatzweise einen *Text Block* (Multi-line String) mit dem + Inhalt der Textdatei und erzeugen daraus einen `InputStream`. Das zählt dann + aber nicht als "bearbeitet". 2. Einlesen von Textdateien - In der Funktion `Main#resources` wird eine Textdatei im Ressourcen-Ordner über eine - interne Hilfsfunktion (siehe vorige Teilaufgabe) als `InputStream` zum Einlesen geöffnet. - Über einen `BufferedReader` werden alle Zeilen eingelesen und anschließend alle Zeilen, - die mit dem Buchstaben "a" beginnen und mindestens zwei Zeichen lang sind, wieder zusammen - gefügt (mit einem Zeilenumbruch `\n` als Trenner). + In der Funktion `Main#resources` wird eine Textdatei im Ressourcen-Ordner über + eine interne Hilfsfunktion (siehe vorige Teilaufgabe) als `InputStream` zum + Einlesen geöffnet. Über einen `BufferedReader` werden alle Zeilen eingelesen und + anschließend alle Zeilen, die mit dem Buchstaben "a" beginnen und mindestens + zwei Zeichen lang sind, wieder zusammen gefügt (mit einem Zeilenumbruch `\n` als + Trenner). - Schreiben Sie den Body dieser Methode so um, dass die selbe Funktionalität unter Nutzung - der [Java-Stream-API] erreicht wird. Bevorzugen Sie dabei nach Möglichkeit - Methoden-Referenzen vor Lambda-Ausdrücken. + Schreiben Sie den Body dieser Methode so um, dass die selbe Funktionalität unter + Nutzung der [Java-Stream-API](https://dev.java/learn/api/streams/) erreicht + wird. Bevorzugen Sie dabei nach Möglichkeit Methoden-Referenzen vor + Lambda-Ausdrücken. ## Record-Klassen @@ -122,93 +138,94 @@ Machen Sie aus der Klasse `streamapi.Student` eine Record-Klasse. ## DevDungeon: Zerbrechende Tiles und Speed Potions (Lambda-Ausdrücke) -Klonen Sie das Projekt [DevDungeon] und laden Sie es in Ihrer IDE als Gradle-Projekt. -Betrachten Sie das Sub-Projekt[^1] "devDungeon". Dies ist ein von einem Studierenden -([\@Flamtky]) erstelltes Spiel mit mehreren Leveln, in denen Sie spielerisch verschiedene -Aufgaben *in-game* und *ex-game* lösen müssen. - -Starten Sie den DevDungeon mit `./gradlew devDungeon:runDevDungeon`. Spielen Sie sich für -diese Aufgabe durch das **erste Level** ("Damaged Bridge")[^2]. - -Ziel ist es, die mysteriöse Brücke in der Mitte des ersten Levels lebendig zu überqueren. -Beobachten Sie die Startsequenz: Was fällt ihnen an dem Monster auf, dass Sie ganz am Anfang -angreifen will? Sie finden weitere Hinweise in den Briefkästen und über die Popups ... -Beachten Sie auch die Hinweise am versteckten Item. Um dieses nutzbar zu machen, müssen Sie in -den Java-Code des Spiels gehen (im `src/`-Unterordner im Sub-Projekt "devDungeon") und den -Effekt für das Item reparieren (implementieren). Analysieren Sie den Code für das Item und -seinen Effekt, und schauen Sie sich die anderen Effekte im selben Package an. Schreiben Sie -nun Code für die mit "TODO" markierte Methode des Effekts. Starten Sie dann das Spiel neu und -schauen Sie, ob das Item nun funktioniert. - -**WICHTIG**: **Bevor** Sie mit der Implementierung beginnen, schauen Sie sich bitte die -Einführung in die Programmierung des Dungeons und die verwendete -*Entity-Component-System*-Architektur in der Lektion [Intro Dungeon] an. Dort werden Ihnen -Hintergründe zum Dungeon und der für diese Aufgabe relevanten Component erklärt. +Klonen Sie das Projekt +[DevDungeon](https://github.com/Dungeon-CampusMinden/dev-dungeon) und laden Sie es +in Ihrer IDE als Gradle-Projekt. Betrachten Sie das Sub-Projekt[^1] "devDungeon". +Dies ist ein von einem Studierenden ([\@Flamtky](https://github.com/Flamtky)) +erstelltes Spiel mit mehreren Leveln, in denen Sie spielerisch verschiedene Aufgaben +*in-game* und *ex-game* lösen müssen. + +Starten Sie den DevDungeon mit `./gradlew devDungeon:runDevDungeon`. Spielen Sie +sich für diese Aufgabe durch das **erste Level** ("Damaged Bridge")[^2]. + +Ziel ist es, die mysteriöse Brücke in der Mitte des ersten Levels lebendig zu +überqueren. Beobachten Sie die Startsequenz: Was fällt ihnen an dem Monster auf, +dass Sie ganz am Anfang angreifen will? Sie finden weitere Hinweise in den +Briefkästen und über die Popups ... Beachten Sie auch die Hinweise am versteckten +Item. Um dieses nutzbar zu machen, müssen Sie in den Java-Code des Spiels gehen (im +`src/`-Unterordner im Sub-Projekt "devDungeon") und den Effekt für das Item +reparieren (implementieren). Analysieren Sie den Code für das Item und seinen +Effekt, und schauen Sie sich die anderen Effekte im selben Package an. Schreiben Sie +nun Code für die mit "TODO" markierte Methode des Effekts. Starten Sie dann das +Spiel neu und schauen Sie, ob das Item nun funktioniert. + +**WICHTIG**: **Bevor** Sie mit der Implementierung beginnen, schauen Sie sich bitte +die Einführung in die Programmierung des Dungeons und die verwendete +*Entity-Component-System*-Architektur in der Lektion [Intro +Dungeon](../lecture/misc/dungeon.md) an. Dort werden Ihnen Hintergründe zum Dungeon +und der für diese Aufgabe relevanten Component erklärt. **Hinweis**: Sie können das Demo-Level deaktivieren, indem Sie in der Klasse -`starter.DevDungeon` das Flag `SKIP_TUTORIAL` auf den Wert `true` setzen. Damit gelangen Sie -direkt in das in dieser Aufgabe relevante Level. - -**Hinweis**: Aktuell ist das Projekt DevDungeon an einigen Stellen noch *Work-in-Progress*, -beispielsweise fehlt häufig noch die Javadoc. Alle Gradle-Tasks, die von Checkstyle-Tasks -abhängen (`checkstyleMain`, `check`, `build`, ...) werden deshalb fehlschlagen. Sie können den -DevDungeon aber wie oben beschrieben mit `./gradlew devDungeon:runDevDungeon` (bzw. über den -Task `devDungeon:runDevDungeon` aus der IDE heraus) starten. - -**WICHTIG**: Achten Sie bitte darauf, dass im Projektpfad **keine Leerzeichen** und keine -Sonderzeichen (Umlaute o.ä.) vorkommen! Dies kann zu seltsamen Fehler führen. Bitte auch -darauf achten, dass Sie als JDK ein **Java SE 21 (LTS)** verwenden. Unter Windows ist der -Einsatz von [WSL] empfehlenswert. +`starter.DevDungeon` das Flag `SKIP_TUTORIAL` auf den Wert `true` setzen. Damit +gelangen Sie direkt in das in dieser Aufgabe relevante Level. + +**Hinweis**: Aktuell ist das Projekt DevDungeon an einigen Stellen noch +*Work-in-Progress*, beispielsweise fehlt häufig noch die Javadoc. Alle Gradle-Tasks, +die von Checkstyle-Tasks abhängen (`checkstyleMain`, `check`, `build`, ...) werden +deshalb fehlschlagen. Sie können den DevDungeon aber wie oben beschrieben mit +`./gradlew devDungeon:runDevDungeon` (bzw. über den Task `devDungeon:runDevDungeon` +aus der IDE heraus) starten. + +**WICHTIG**: Achten Sie bitte darauf, dass im Projektpfad **keine Leerzeichen** und +keine Sonderzeichen (Umlaute o.ä.) vorkommen! Dies kann zu seltsamen Fehler führen. +Bitte auch darauf achten, dass Sie als JDK ein **Java SE 21 (LTS)** verwenden. Unter +Windows ist der Einsatz von +[WSL](https://learn.microsoft.com/en-us/windows/wsl/install) empfehlenswert. # Bearbeitung und Abgabe -- Bearbeitung: Einzelbearbeitung oder bis zu 3er Teams -- Abgabe: - - Post Mortem [im ILIAS] eintragen: - - Verfassen Sie im ILIAS pro Blatt und pro Team-Mitglied ein aussagekräftiges und - nachvollziehbares "*Post Mortem*". Gehen Sie dabei auf folgende Punkte ein: - - 1. Zusammenfassung: Was wurde gemacht? - 2. Implementierungsdetails: Kurze Beschreibung besonders interessanter Aspekte der - Umsetzung. - 3. Was war der schwierigste Teil bei der Bearbeitung? Wie haben Sie dieses Problem - gelöst? - 4. Was haben Sie gelernt oder (besser) verstanden? - 5. Team: Mit wem haben Sie zusammengearbeitet? - 6. Links zu Ihren Pull-Requests mit der Lösung. - - Das Post Mortem muss von **jeder Person** im Team **individuell** verfasst und abgegeben - werden. Der Umfang des Textes soll zwischen 200 und 400 Wörtern liegen. - - Laden Sie hier bitte **nicht** Ihre Lösungen hoch! - - - Deadline: 23. Mai, 08:00 Uhr -- Vorstellung im Praktikum: 23. Mai - -[^1]: Gradle-Subprojekte sind im Prinzip mehrere Java-Projekte in einem gemeinsamen Repository - mit einer gemeinsamen Gradle-Basiskonfiguration. Jedes Sub-Projekt hat dann noch einmal - eine eigene, die Basiskonfiguration verfeinernde Gradle-Konfiguration. Da jedes - Sub-Projekt eigene Tasks mitbringen kann, muss denn der Name des Sub-Projekts dem - Tasknamen vorangestellt werden: Beispielsweise muss statt `./gradlew runDevDungeon` nun - `./gradlew devDungeon:runDevDungeon` aufgerufen werden. Siehe auch [Multi-Project Build - Basics] oder [Structuring Projects with Gradle]. - -[^2]: Das erste richtige Level, also das erste Level *nach* dem Demo-Level. Das Demo-Level - zeigt Ihnen, wie Sie das Spiel bedienen können. Zusätzlich gibt es die kurze [Anleitung - "How to play"] ... - - ["Stream-API"]: https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_streamapi - [`Main.java`]: https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_streamapi/blob/task_i/src/main/java/streamapi/Main.java - [Java-Stream-API]: https://dev.java/learn/api/streams/ - [1]: https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_streamapi/blob/task_ii/src/main/java/streamapi/Main.java - [2]: https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_streamapi/blob/task_iii/src/main/java/streamapi/Main.java - [3]: https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_streamapi/blob/task_iv_v/src/main/java/streamapi/Main.java - [DevDungeon]: https://github.com/Dungeon-CampusMinden/dev-dungeon - [\@Flamtky]: https://github.com/Flamtky - [Intro Dungeon]: ../lecture/misc/dungeon.md - [WSL]: https://learn.microsoft.com/en-us/windows/wsl/install - [im ILIAS]: https://www.hsbi.de/elearning/goto.php?target=exc_1514856&client_id=FH-Bielefeld - [Multi-Project Build Basics]: https://docs.gradle.org/current/userguide/intro_multi_project_builds.html - [Structuring Projects with Gradle]: https://docs.gradle.org/current/userguide/multi_project_builds.html - [Anleitung "How to play"]: https://github.com/Dungeon-CampusMinden/Dungeon/blob/master/dungeon/doc/how_to_play.md +- Bearbeitung: Einzelbearbeitung oder bis zu 3er Teams +- Abgabe: + - Post Mortem [im + ILIAS](https://www.hsbi.de/elearning/goto.php?target=exc_1514856&client_id=FH-Bielefeld) + eintragen: + + Verfassen Sie im ILIAS pro Blatt und pro Team-Mitglied ein aussagekräftiges + und nachvollziehbares "*Post Mortem*". Gehen Sie dabei auf folgende Punkte + ein: + + 1. Zusammenfassung: Was wurde gemacht? + 2. Implementierungsdetails: Kurze Beschreibung besonders interessanter + Aspekte der Umsetzung. + 3. Was war der schwierigste Teil bei der Bearbeitung? Wie haben Sie dieses + Problem gelöst? + 4. Was haben Sie gelernt oder (besser) verstanden? + 5. Team: Mit wem haben Sie zusammengearbeitet? + 6. Links zu Ihren Pull-Requests mit der Lösung. + + Das Post Mortem muss von **jeder Person** im Team **individuell** verfasst + und abgegeben werden. Der Umfang des Textes soll zwischen 200 und 400 + Wörtern liegen. + + Laden Sie hier bitte **nicht** Ihre Lösungen hoch! + + - Deadline: 23. Mai, 08:00 Uhr +- Vorstellung im Praktikum: 23. Mai + +[^1]: Gradle-Subprojekte sind im Prinzip mehrere Java-Projekte in einem gemeinsamen + Repository mit einer gemeinsamen Gradle-Basiskonfiguration. Jedes Sub-Projekt + hat dann noch einmal eine eigene, die Basiskonfiguration verfeinernde + Gradle-Konfiguration. Da jedes Sub-Projekt eigene Tasks mitbringen kann, muss + denn der Name des Sub-Projekts dem Tasknamen vorangestellt werden: + Beispielsweise muss statt `./gradlew runDevDungeon` nun + `./gradlew devDungeon:runDevDungeon` aufgerufen werden. Siehe auch + [Multi-Project Build + Basics](https://docs.gradle.org/current/userguide/intro_multi_project_builds.html) + oder [Structuring Projects with + Gradle](https://docs.gradle.org/current/userguide/multi_project_builds.html). + +[^2]: Das erste richtige Level, also das erste Level *nach* dem Demo-Level. Das + Demo-Level zeigt Ihnen, wie Sie das Spiel bedienen können. Zusätzlich gibt es + die kurze [Anleitung "How to + play"](https://github.com/Dungeon-CampusMinden/Dungeon/blob/master/dungeon/doc/how_to_play.md) + ... diff --git a/homework/b05.md b/homework/b05.md index 1fe4c16d9..b49dfb8c9 100644 --- a/homework/b05.md +++ b/homework/b05.md @@ -1,154 +1,160 @@ --- author: Carsten Gips (HSBI) -title: "Blatt 05: Torch Riddle & Katzen-Café (Streams, JUnit, Optional<>, Visitor)" no_beamer: true +title: "Blatt 05: Torch Riddle & Katzen-Café (Streams, JUnit, Optional\\<\\>, + Visitor)" --- - - # Zusammenfassung -Auf diesem Blatt üben Sie den Umgang mit der Java-Stream-API und `Optional<>`. Sie erstellen -erste JUnit-Tests und implementieren das Visitor-Pattern für ein einfaches Beispiel. +Auf diesem Blatt üben Sie den Umgang mit der Java-Stream-API und `Optional<>`. Sie +erstellen erste JUnit-Tests und implementieren das Visitor-Pattern für ein einfaches +Beispiel. ::: important -**Hinweis**: Bitte denken Sie daran, dass Sie spätestens seit Blatt 04 die Bearbeitung der -Aufgaben in Ihren öffentlich sichtbaren Git-Repos durchführen sollen. +**Hinweis**: Bitte denken Sie daran, dass Sie spätestens seit Blatt 04 die +Bearbeitung der Aufgaben in Ihren öffentlich sichtbaren Git-Repos durchführen +sollen. -Erstellen Sie für **alle** Ihre Lösungen passende Pull-Requests gegen **Ihre eigenen Repos** -(auch wenn dies nicht explizit in den Aufgaben gefordert wird). +Erstellen Sie für **alle** Ihre Lösungen passende Pull-Requests gegen **Ihre eigenen +Repos** (auch wenn dies nicht explizit in den Aufgaben gefordert wird). -Die Links zu Ihren Pull-Requests mit den Lösungen geben Sie bitte immer in Ihrem *Post Mortem* -mit an. +Die Links zu Ihren Pull-Requests mit den Lösungen geben Sie bitte immer in Ihrem +*Post Mortem* mit an. ::: # Aufgaben ## DevDungeon: Fackeln im Sturm (Umgang mit `Optional<>` und Streams) -Klonen Sie das Projekt [DevDungeon] und laden Sie es in Ihrer IDE als Gradle-Projekt. -Betrachten Sie das Sub-Projekt "devDungeon". Dies ist ein von einem Studierenden ([\@Flamtky]) -erstelltes Spiel mit mehreren Leveln, in denen Sie spielerisch verschiedene Aufgaben *in-game* +Klonen Sie das Projekt +[DevDungeon](https://github.com/Dungeon-CampusMinden/dev-dungeon) und laden Sie es +in Ihrer IDE als Gradle-Projekt. Betrachten Sie das Sub-Projekt "devDungeon". Dies +ist ein von einem Studierenden ([\@Flamtky](https://github.com/Flamtky)) erstelltes +Spiel mit mehreren Leveln, in denen Sie spielerisch verschiedene Aufgaben *in-game* und *ex-game* lösen müssen. -Starten Sie den DevDungeon mit `./gradlew devDungeon:runDevDungeon`. Spielen Sie sich für -diese Aufgabe durch das **zweite Level** ("Torch Riddle")[^1]. - -Sie befinden sich in einem Raum mit Fackeln, welche Sie per Interaktion an- und ausschalten -können. Neben jeder Fackel ist ein Briefkasten, der der Fackel einen Zahlenwert zuordnet. -Irgendwo führt eine Tür zu einem zunächst versteckten Raum mit einer Belohnung - aber diese -Tür geht erst auf, wenn Sie (a) die richtigen Fackeln an- bzw. ausgeschaltet haben, und wenn -Sie (b) die defekte Methode `TorchRiddleRiddleHandler#getSumOfLitTorches` (im Package -`level.devlevel.riddleHandler`) korrekt implementiert haben. Beachten Sie die entsprechenden -Hinweise im Javadoc der Methode. - -Das Tor zum nächsten Level geht unabhängig davon erst auf, wenn Sie den Boss-Gegner[^2] in -diesem Level besiegt haben ... Hierzu ist *keine* Programmierung notwendig, lediglich -geschicktes Spielen und gegebenenfalls rechtzeitiges Trinken von (dann hoffentlich -vorhandenen) Heil-Tränken. - -**Hinweis**: Aktuell ist das Projekt DevDungeon an einigen Stellen noch *Work-in-Progress*, -beispielsweise fehlt häufig noch die Javadoc. Alle Gradle-Tasks, die von Checkstyle-Tasks -abhängen (`checkstyleMain`, `check`, `build`, ...) werden deshalb fehlschlagen. Sie können den -DevDungeon aber wie oben beschrieben mit `./gradlew devDungeon:runDevDungeon` (bzw. über den -Task `devDungeon:runDevDungeon` aus der IDE heraus) starten. - -**WICHTIG**: Achten Sie bitte darauf, dass im Projektpfad **keine Leerzeichen** und keine -Sonderzeichen (Umlaute o.ä.) vorkommen! Dies kann zu seltsamen Fehler führen. Bitte auch -darauf achten, dass Sie als JDK ein **Java SE 21 (LTS)** verwenden. Unter Windows ist der -Einsatz von [WSL] empfehlenswert. +Starten Sie den DevDungeon mit `./gradlew devDungeon:runDevDungeon`. Spielen Sie +sich für diese Aufgabe durch das **zweite Level** ("Torch Riddle")[^1]. + +Sie befinden sich in einem Raum mit Fackeln, welche Sie per Interaktion an- und +ausschalten können. Neben jeder Fackel ist ein Briefkasten, der der Fackel einen +Zahlenwert zuordnet. Irgendwo führt eine Tür zu einem zunächst versteckten Raum mit +einer Belohnung - aber diese Tür geht erst auf, wenn Sie (a) die richtigen Fackeln +an- bzw. ausgeschaltet haben, und wenn Sie (b) die defekte Methode +`TorchRiddleRiddleHandler#getSumOfLitTorches` (im Package +`level.devlevel.riddleHandler`) korrekt implementiert haben. Beachten Sie die +entsprechenden Hinweise im Javadoc der Methode. + +Das Tor zum nächsten Level geht unabhängig davon erst auf, wenn Sie den +Boss-Gegner[^2] in diesem Level besiegt haben ... Hierzu ist *keine* Programmierung +notwendig, lediglich geschicktes Spielen und gegebenenfalls rechtzeitiges Trinken +von (dann hoffentlich vorhandenen) Heil-Tränken. + +**Hinweis**: Aktuell ist das Projekt DevDungeon an einigen Stellen noch +*Work-in-Progress*, beispielsweise fehlt häufig noch die Javadoc. Alle Gradle-Tasks, +die von Checkstyle-Tasks abhängen (`checkstyleMain`, `check`, `build`, ...) werden +deshalb fehlschlagen. Sie können den DevDungeon aber wie oben beschrieben mit +`./gradlew devDungeon:runDevDungeon` (bzw. über den Task `devDungeon:runDevDungeon` +aus der IDE heraus) starten. + +**WICHTIG**: Achten Sie bitte darauf, dass im Projektpfad **keine Leerzeichen** und +keine Sonderzeichen (Umlaute o.ä.) vorkommen! Dies kann zu seltsamen Fehler führen. +Bitte auch darauf achten, dass Sie als JDK ein **Java SE 21 (LTS)** verwenden. Unter +Windows ist der Einsatz von +[WSL](https://learn.microsoft.com/en-us/windows/wsl/install) empfehlenswert. ## Katzen-Café -Forken Sie das ["Cat-Cafe"]-Repo und erzeugen Sie sich eine lokale Arbeitskopie von Ihrem -Fork. +Forken Sie das +["Cat-Cafe"](https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_catcafe)-Repo +und erzeugen Sie sich eine lokale Arbeitskopie von Ihrem Fork. ### Code-Analyse -Analysieren Sie die Modellierung des Binärbaums (`Tree`, `Empty`, `Node`) und erklären Sie die -Funktionsweise: +Analysieren Sie die Modellierung des Binärbaums (`Tree`, `Empty`, `Node`) und +erklären Sie die Funktionsweise: -- Was sind Vorteile, was sind Nachteile dieser Modellierung? -- Was musste getan werden, um die selbst implementierten Bäume in Schleifen - (`Tree mytree; for (Tree t: mytree) {...}`) und in Streams - `Tree mytree; mytree.stream(). ...` nutzen zu können? -- Wie funktioniert der `TreeIterator`? +- Was sind Vorteile, was sind Nachteile dieser Modellierung? +- Was musste getan werden, um die selbst implementierten Bäume in Schleifen + (`Tree mytree; for (Tree t: mytree) {...}`) und in Streams + `Tree mytree; mytree.stream(). ...` nutzen zu können? +- Wie funktioniert der `TreeIterator`? ### Umgang mit `Optional<>` -Bauen Sie die beiden Methoden `CatCafe#getCatByName` und `CatCafe#getCatByWeight` so um, dass -ein passendes `Optional<>` zurückgeliefert wird. Passen Sie die entsprechenden Methodenaufrufe -in `Main#main` entsprechend an. +Bauen Sie die beiden Methoden `CatCafe#getCatByName` und `CatCafe#getCatByWeight` so +um, dass ein passendes `Optional<>` zurückgeliefert wird. Passen Sie die +entsprechenden Methodenaufrufe in `Main#main` entsprechend an. -*Tipp*: Stellen Sie in den beiden Methoden auf die [Java-Stream-API] um, dann ergibt sich die +*Tipp*: Stellen Sie in den beiden Methoden auf die +[Java-Stream-API](https://dev.java/learn/api/streams/) um, dann ergibt sich die Nutzung von `Optional<>` fast von selbst. ### JUnit -Erstellen Sie mit JUnit 4 oder 5 mindestens 10 unterschiedliche Testfälle für die Klasse -`CatCafe`. +Erstellen Sie mit JUnit 4 oder 5 mindestens 10 unterschiedliche Testfälle für die +Klasse `CatCafe`. -*Tipp*: In der Gradle-Konfiguration der Vorgabe ist bereits JUnit5 konfiguriert, d.h. die -entsprechenden Abhängigkeiten werden durch Gradle aufgelöst. Wenn Sie die Vorgaben als -Gradle-Projekt in Ihrer IDE öffnen, dann steht Ihnen dort auch die JUnit5-Bibliothek -automatisch zur Verfügung. Wenn Sie JUnit4 nutzen möchten, müssten Sie bitte die -Gradle-Konfiguration entsprechend anpassen. Mit `./gradlew test` können Sie entsprechende -Testfälle ausführen. +*Tipp*: In der Gradle-Konfiguration der Vorgabe ist bereits JUnit5 konfiguriert, +d.h. die entsprechenden Abhängigkeiten werden durch Gradle aufgelöst. Wenn Sie die +Vorgaben als Gradle-Projekt in Ihrer IDE öffnen, dann steht Ihnen dort auch die +JUnit5-Bibliothek automatisch zur Verfügung. Wenn Sie JUnit4 nutzen möchten, müssten +Sie bitte die Gradle-Konfiguration entsprechend anpassen. Mit `./gradlew test` +können Sie entsprechende Testfälle ausführen. ### Visitor-Pattern Die Klasse `CatCafe` hat eine Methode `CatCafe#accept`, die einen Visitor mit dem -parametrischen Typ `TreeVisitor` an das intern genutzte Feld `Tree clowder` -weiterleitet. +parametrischen Typ `TreeVisitor` an das intern genutzte Feld +`Tree clowder` weiterleitet. -Implementieren Sie das Visitor-Pattern für den Baum (`Tree`), indem Sie das Interface -`TreeVisitor` implementieren: +Implementieren Sie das Visitor-Pattern für den Baum (`Tree`), indem Sie das +Interface `TreeVisitor` implementieren: 1. Erstellen Sie einen konkreten Visitor `InOrderVisitor`, der den Baum **inorder** traversiert. 2. Erstellen Sie einen weiteren konkreten Visitor `PostOrderVisitor`, der den Baum **postorder** traversiert. -Beim Besuch eines Knotens soll jeweils die Methode `toString()` für den Datenanteil aufgerufen -werden und passend mit den Ergebnissen der Traversierung der linken und rechten Teilbäume -konkateniert werden und der resultierende String zurückgeben werden. +Beim Besuch eines Knotens soll jeweils die Methode `toString()` für den Datenanteil +aufgerufen werden und passend mit den Ergebnissen der Traversierung der linken und +rechten Teilbäume konkateniert werden und der resultierende String zurückgeben +werden. Fügen Sie passende Aufrufe der beiden Visitoren in `Main#main` hinzu. # Bearbeitung und Abgabe -- Bearbeitung: Einzelbearbeitung oder bis zu 3er Teams -- Abgabe: - - Post Mortem [im ILIAS] eintragen: +- Bearbeitung: Einzelbearbeitung oder bis zu 3er Teams +- Abgabe: + - Post Mortem [im + ILIAS](https://www.hsbi.de/elearning/goto.php?target=exc_1514856&client_id=FH-Bielefeld) + eintragen: - Verfassen Sie im ILIAS pro Blatt und pro Team-Mitglied ein aussagekräftiges und - nachvollziehbares "*Post Mortem*". Gehen Sie dabei auf folgende Punkte ein: + Verfassen Sie im ILIAS pro Blatt und pro Team-Mitglied ein aussagekräftiges + und nachvollziehbares "*Post Mortem*". Gehen Sie dabei auf folgende Punkte + ein: - 1. Zusammenfassung: Was wurde gemacht? - 2. Implementierungsdetails: Kurze Beschreibung besonders interessanter Aspekte der - Umsetzung. - 3. Was war der schwierigste Teil bei der Bearbeitung? Wie haben Sie dieses Problem - gelöst? - 4. Was haben Sie gelernt oder (besser) verstanden? - 5. Team: Mit wem haben Sie zusammengearbeitet? - 6. Links zu Ihren Pull-Requests mit der Lösung. + 1. Zusammenfassung: Was wurde gemacht? + 2. Implementierungsdetails: Kurze Beschreibung besonders interessanter + Aspekte der Umsetzung. + 3. Was war der schwierigste Teil bei der Bearbeitung? Wie haben Sie dieses + Problem gelöst? + 4. Was haben Sie gelernt oder (besser) verstanden? + 5. Team: Mit wem haben Sie zusammengearbeitet? + 6. Links zu Ihren Pull-Requests mit der Lösung. - Das Post Mortem muss von **jeder Person** im Team **individuell** verfasst und abgegeben - werden. Der Umfang des Textes soll zwischen 200 und 400 Wörtern liegen. + Das Post Mortem muss von **jeder Person** im Team **individuell** verfasst + und abgegeben werden. Der Umfang des Textes soll zwischen 200 und 400 + Wörtern liegen. - Laden Sie hier bitte **nicht** Ihre Lösungen hoch! + Laden Sie hier bitte **nicht** Ihre Lösungen hoch! - - Deadline: 30. Mai, 08:00 Uhr -- Vorstellung im Praktikum: 30. Mai + - Deadline: 30. Mai, 08:00 Uhr +- Vorstellung im Praktikum: 30. Mai -[^1]: Das zweite richtige Level, also das zweite Level *nach* dem Demo-Level. Oder eben das - dritte Level, wenn man das Demo-Level mitzählt :-) +[^1]: Das zweite richtige Level, also das zweite Level *nach* dem Demo-Level. Oder + eben das dritte Level, wenn man das Demo-Level mitzählt :-) [^2]: ... sieht aus wie eine wandelnde Kerze ... - - [DevDungeon]: https://github.com/Dungeon-CampusMinden/dev-dungeon - [\@Flamtky]: https://github.com/Flamtky - [WSL]: https://learn.microsoft.com/en-us/windows/wsl/install - ["Cat-Cafe"]: https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_catcafe - [Java-Stream-API]: https://dev.java/learn/api/streams/ - [im ILIAS]: https://www.hsbi.de/elearning/goto.php?target=exc_1514856&client_id=FH-Bielefeld diff --git a/homework/b06.md b/homework/b06.md index b7f7ca43b..763d3f8de 100644 --- a/homework/b06.md +++ b/homework/b06.md @@ -1,32 +1,33 @@ --- author: Carsten Gips (HSBI) -title: "Blatt 06: Cycle Chronicles (ÄK&GW, Mocking)" no_beamer: true +title: "Blatt 06: Cycle Chronicles (ÄK&GW, Mocking)" --- - - # Zusammenfassung -Auf diesem Blatt üben Sie die Erstellung von Testfällen mit der Äquivalenzklassenbildung und -Grenzwertanalyse. Sie üben den Einsatz mit Mockito. Zusätzlich haben Sie die Gelegenheit, noch -einmal mit Logging und Record-Klassen zu arbeiten. +Auf diesem Blatt üben Sie die Erstellung von Testfällen mit der +Äquivalenzklassenbildung und Grenzwertanalyse. Sie üben den Einsatz mit Mockito. +Zusätzlich haben Sie die Gelegenheit, noch einmal mit Logging und Record-Klassen zu +arbeiten. ::: important -**Hinweis**: Bitte denken Sie daran, dass Sie spätestens seit Blatt 04 die Bearbeitung der -Aufgaben in Ihren öffentlich sichtbaren Git-Repos durchführen sollen. +**Hinweis**: Bitte denken Sie daran, dass Sie spätestens seit Blatt 04 die +Bearbeitung der Aufgaben in Ihren öffentlich sichtbaren Git-Repos durchführen +sollen. -Erstellen Sie für **alle** Ihre Lösungen passende Pull-Requests gegen **Ihre eigenen Repos** -(auch wenn dies nicht explizit in den Aufgaben gefordert wird). +Erstellen Sie für **alle** Ihre Lösungen passende Pull-Requests gegen **Ihre eigenen +Repos** (auch wenn dies nicht explizit in den Aufgaben gefordert wird). -Die Links zu Ihren Pull-Requests mit den Lösungen geben Sie bitte immer in Ihrem *Post Mortem* -mit an. +Die Links zu Ihren Pull-Requests mit den Lösungen geben Sie bitte immer in Ihrem +*Post Mortem* mit an. ::: # Aufgaben -Forken Sie das ["Cycle Chronicles"]-Repo und erzeugen Sie sich eine lokale Arbeitskopie von -Ihrem Fork. +Forken Sie das ["Cycle +Chronicles"](https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_cyclechronicles)-Repo +und erzeugen Sie sich eine lokale Arbeitskopie von Ihrem Fork. ## Analyse: Äquivalenzklassen & Grenzwerte @@ -34,16 +35,16 @@ Die Methode `Shop#accept` dient zur Annahme eines neuen Auftrags eines Kunden. Neue Aufträge sollen nur unter bestimmten Bedingungen angenommen werden: -- Es darf sich nicht um ein E-Bike handeln. -- Es darf sich nicht um ein Gravel-Bike handeln. -- Der Kunde darf nicht noch andere offene Aufträge beim Shop haben (es kann pro Kunden immer - nur maximal einen offenen Auftrag in der Warteschlange geben). -- Es sind aktuell höchstens vier andere offene Aufträge vorhanden (es dürfen zu jeder Zeit - maximal fünf offene Aufträge in der Warteschlange sein). +- Es darf sich nicht um ein E-Bike handeln. +- Es darf sich nicht um ein Gravel-Bike handeln. +- Der Kunde darf nicht noch andere offene Aufträge beim Shop haben (es kann pro + Kunden immer nur maximal einen offenen Auftrag in der Warteschlange geben). +- Es sind aktuell höchstens vier andere offene Aufträge vorhanden (es dürfen zu + jeder Zeit maximal fünf offene Aufträge in der Warteschlange sein). -Der Rückgabewert der Methode signalisiert, ob der Auftrag angenommen wurde und in die -Warteschlange der offenen Aufträge eingereiht wurde (`true`) oder ob er abgelehnt wurde -(`false`). +Der Rückgabewert der Methode signalisiert, ob der Auftrag angenommen wurde und in +die Warteschlange der offenen Aufträge eingereiht wurde (`true`) oder ob er +abgelehnt wurde (`false`). Aufgaben: @@ -53,96 +54,103 @@ Aufgaben: ## Mocking I -Implementieren Sie nun die in der vorigen Aufgabe ermittelten Testfälle für die Methode -`Shop#accept` mit Hilfe von JUnit (Version 4 oder 5). +Implementieren Sie nun die in der vorigen Aufgabe ermittelten Testfälle für die +Methode `Shop#accept` mit Hilfe von JUnit (Version 4 oder 5). Leider gibt es beim Ausführen vieler Ihrer JUnit-Testmethoden eine -`UnsupportedOperationException`-Exception, da die Klasse `Order` bisher nur unvollständig -implementiert ist: Es existieren praktisch nur die Methodensignaturen, der Aufruf der Methoden -liefert nur eine `UnsupportedOperationException`. +`UnsupportedOperationException`-Exception, da die Klasse `Order` bisher nur +unvollständig implementiert ist: Es existieren praktisch nur die Methodensignaturen, +der Aufruf der Methoden liefert nur eine `UnsupportedOperationException`. -Setzen Sie aktiv Mocking mit Mockito ein, um Ihre JUnit-Tests für `Shop#accept` ausführbar zu -machen. Begründen Sie die Anwendung von Mockito. +Setzen Sie aktiv Mocking mit Mockito ein, um Ihre JUnit-Tests für `Shop#accept` +ausführbar zu machen. Begründen Sie die Anwendung von Mockito. -**Wichtig**: Die zu testende Methode `Shop#accept` soll in der vorliegenden Implementierung im -Test genutzt werden, d.h. sie darf nicht "weg-gemockt" werden! +**Wichtig**: Die zu testende Methode `Shop#accept` soll in der vorliegenden +Implementierung im Test genutzt werden, d.h. sie darf nicht "weg-gemockt" werden! *Tipp*: In der Gradle-Konfiguration der Vorgabe ist bereits JUnit5 und Mockito -vorkonfiguriert, d.h. die entsprechenden Abhängigkeiten werden durch Gradle aufgelöst. Wenn -Sie die Vorgaben als Gradle-Projekt in Ihrer IDE öffnen, dann steht Ihnen dort auch die -JUnit5-Bibliothek automatisch zur Verfügung. Wenn Sie JUnit4 nutzen möchten, müssten Sie bitte -die Gradle-Konfiguration entsprechend anpassen. Mit `./gradlew test` können Sie Ihre Testfälle -ausführen. +vorkonfiguriert, d.h. die entsprechenden Abhängigkeiten werden durch Gradle +aufgelöst. Wenn Sie die Vorgaben als Gradle-Projekt in Ihrer IDE öffnen, dann steht +Ihnen dort auch die JUnit5-Bibliothek automatisch zur Verfügung. Wenn Sie JUnit4 +nutzen möchten, müssten Sie bitte die Gradle-Konfiguration entsprechend anpassen. +Mit `./gradlew test` können Sie Ihre Testfälle ausführen. ## Mocking II -Die Methoden `Shop#repair` und `Shop#deliver` sind auch noch nicht implementiert. Nutzen Sie -geeignetes Mocking, um für diese beiden Methoden Tests in JUnit zu implementieren. Begründen -Sie die Anwendung von Mockito. +Die Methoden `Shop#repair` und `Shop#deliver` sind auch noch nicht implementiert. +Nutzen Sie geeignetes Mocking, um für diese beiden Methoden Tests in JUnit zu +implementieren. Begründen Sie die Anwendung von Mockito. -*Hinweis*: Sie *müssen* hier keine ÄK/GW-Analyse machen, können das aber natürlich gern tun. +*Hinweis*: Sie *müssen* hier keine ÄK/GW-Analyse machen, können das aber natürlich +gern tun. ## Record-Klassen -Die Klasse `Order` ist zwar bisher nur unvollständig implementiert, aber Sie können bereits -deutlich erkennen, dass es zwei Attribute geben muss (welche?). +Die Klasse `Order` ist zwar bisher nur unvollständig implementiert, aber Sie können +bereits deutlich erkennen, dass es zwei Attribute geben muss (welche?). -Bauen Sie die Klasse in eine passende Record-Klasse mit den entsprechenden Attributen um. Sie -dürfen die beiden Methoden in `Order` auch geeignet "implementieren" und umbenennen - die -Umbenennung muss dann aber auch in den Aufrufen in `Shop` und in Ihren JUnit-Tests passieren! +Bauen Sie die Klasse in eine passende Record-Klasse mit den entsprechenden +Attributen um. Sie dürfen die beiden Methoden in `Order` auch geeignet +"implementieren" und umbenennen - die Umbenennung muss dann aber auch in den +Aufrufen in `Shop` und in Ihren JUnit-Tests passieren! ## Logging -Bauen Sie für den `Shop` ein Logging auf der Basis von `java.util.logging` ein: Jede Änderung -an der Auftrags-Warteschlange `pendingOrders` und auch an der Menge der fertigen Aufträge -`completedOrders` soll in ein gemeinsames CSV-File geloggt werden. Dabei soll pro -Logging-Vorgang eine neue Zeile mit den folgenden Informationen angehängt werden: +Bauen Sie für den `Shop` ein Logging auf der Basis von `java.util.logging` ein: Jede +Änderung an der Auftrags-Warteschlange `pendingOrders` und auch an der Menge der +fertigen Aufträge `completedOrders` soll in ein gemeinsames CSV-File geloggt werden. +Dabei soll pro Logging-Vorgang eine neue Zeile mit den folgenden Informationen +angehängt werden: -- Log-Level, -- Name der den Log-Vorgang auslösenden Methode, -- Name der Klasse, in der die den Log-Vorgang auslösenden Methode angesiedelt ist, -- Log-Meldung, bestehend aus - - den beiden Details der Order (Fahrradtyp, Name des Kunden), und - - dem Namen der betroffenen Datenstruktur ("pendingOrders" oder "completedOrders"). +- Log-Level, +- Name der den Log-Vorgang auslösenden Methode, +- Name der Klasse, in der die den Log-Vorgang auslösenden Methode angesiedelt ist, +- Log-Meldung, bestehend aus + - den beiden Details der Order (Fahrradtyp, Name des Kunden), und + - dem Namen der betroffenen Datenstruktur ("pendingOrders" oder + "completedOrders"). -Demonstrieren Sie in der Abgabe, wie Sie im Test oder im Hauptprogramm den Logger steuern -können, beispielsweise Änderung der Log-Level oder Abschalten des Loggings. +Demonstrieren Sie in der Abgabe, wie Sie im Test oder im Hauptprogramm den Logger +steuern können, beispielsweise Änderung der Log-Level oder Abschalten des Loggings. *Tipp*: Der Aufruf von `Shop#repair` ändert sowohl `pendingOrders` als auch -`completedOrders` - hier müssen also beim Logging zwei neue Zeilen im Logfile angelegt werden. +`completedOrders` - hier müssen also beim Logging zwei neue Zeilen im Logfile +angelegt werden. -**Hinweis**: Da in den Klassen der Vorgabe die meisten Methoden nicht implementiert sind, -müssen Sie dies für diese Aufgabe selbst flink erledigen. Es handelt sich um die vier Methoden -`Shop#repair` und `Shop#deliver` sowie `Order#getBicycleType` und `Order#getBicycleType`.[^1] +**Hinweis**: Da in den Klassen der Vorgabe die meisten Methoden nicht implementiert +sind, müssen Sie dies für diese Aufgabe selbst flink erledigen. Es handelt sich um +die vier Methoden `Shop#repair` und `Shop#deliver` sowie `Order#getBicycleType` und +`Order#getBicycleType`.[^1] # Bearbeitung und Abgabe -- Bearbeitung: Einzelbearbeitung oder bis zu 3er Teams -- Abgabe: - - Post Mortem [im ILIAS] eintragen: - - Verfassen Sie im ILIAS pro Blatt und pro Team-Mitglied ein aussagekräftiges und - nachvollziehbares "*Post Mortem*". Gehen Sie dabei auf folgende Punkte ein: +- Bearbeitung: Einzelbearbeitung oder bis zu 3er Teams +- Abgabe: + - Post Mortem [im + ILIAS](https://www.hsbi.de/elearning/goto.php?target=exc_1514856&client_id=FH-Bielefeld) + eintragen: - 1. Zusammenfassung: Was wurde gemacht? - 2. Implementierungsdetails: Kurze Beschreibung besonders interessanter Aspekte der - Umsetzung. - 3. Was war der schwierigste Teil bei der Bearbeitung? Wie haben Sie dieses Problem - gelöst? - 4. Was haben Sie gelernt oder (besser) verstanden? - 5. Team: Mit wem haben Sie zusammengearbeitet? - 6. Links zu Ihren Pull-Requests mit der Lösung. + Verfassen Sie im ILIAS pro Blatt und pro Team-Mitglied ein aussagekräftiges + und nachvollziehbares "*Post Mortem*". Gehen Sie dabei auf folgende Punkte + ein: - Das Post Mortem muss von **jeder Person** im Team **individuell** verfasst und abgegeben - werden. Der Umfang des Textes soll zwischen 200 und 400 Wörtern liegen. + 1. Zusammenfassung: Was wurde gemacht? + 2. Implementierungsdetails: Kurze Beschreibung besonders interessanter + Aspekte der Umsetzung. + 3. Was war der schwierigste Teil bei der Bearbeitung? Wie haben Sie dieses + Problem gelöst? + 4. Was haben Sie gelernt oder (besser) verstanden? + 5. Team: Mit wem haben Sie zusammengearbeitet? + 6. Links zu Ihren Pull-Requests mit der Lösung. - Laden Sie hier bitte **nicht** Ihre Lösungen hoch! + Das Post Mortem muss von **jeder Person** im Team **individuell** verfasst + und abgegeben werden. Der Umfang des Textes soll zwischen 200 und 400 + Wörtern liegen. - - Deadline: 06. Juni, 08:00 Uhr -- Vorstellung im Praktikum: 06. Juni + Laden Sie hier bitte **nicht** Ihre Lösungen hoch! -[^1]: Die Methoden `Order#getBicycleType` und `Order#getBicycleType` haben Sie sogar schon bei - der Umsetzung der Record-Klassen-Aufgaben "implementiert" :-) + - Deadline: 06. Juni, 08:00 Uhr +- Vorstellung im Praktikum: 06. Juni - ["Cycle Chronicles"]: https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_cyclechronicles - [im ILIAS]: https://www.hsbi.de/elearning/goto.php?target=exc_1514856&client_id=FH-Bielefeld +[^1]: Die Methoden `Order#getBicycleType` und `Order#getBicycleType` haben Sie sogar + schon bei der Umsetzung der Record-Klassen-Aufgaben "implementiert" :-) diff --git a/homework/b07.md b/homework/b07.md index 46b78dcc3..93bb15c33 100644 --- a/homework/b07.md +++ b/homework/b07.md @@ -1,25 +1,24 @@ --- author: Carsten Gips (HSBI) -title: "Blatt 07: Bike-Shop & Illusion Riddle (Refactoring, Javadoc)" no_beamer: true +title: "Blatt 07: Bike-Shop & Illusion Riddle (Refactoring, Javadoc)" --- - - # Zusammenfassung -Auf diesem Blatt üben Sie den Einsatz von Refactoring und das Dokumentieren von Code mit -Javadoc. +Auf diesem Blatt üben Sie den Einsatz von Refactoring und das Dokumentieren von Code +mit Javadoc. ::: important -**Hinweis**: Bitte denken Sie daran, dass Sie spätestens seit Blatt 04 die Bearbeitung der -Aufgaben in Ihren öffentlich sichtbaren Git-Repos durchführen sollen. +**Hinweis**: Bitte denken Sie daran, dass Sie spätestens seit Blatt 04 die +Bearbeitung der Aufgaben in Ihren öffentlich sichtbaren Git-Repos durchführen +sollen. -Erstellen Sie für **alle** Ihre Lösungen passende Pull-Requests gegen **Ihre eigenen Repos** -(auch wenn dies nicht explizit in den Aufgaben gefordert wird). +Erstellen Sie für **alle** Ihre Lösungen passende Pull-Requests gegen **Ihre eigenen +Repos** (auch wenn dies nicht explizit in den Aufgaben gefordert wird). -Die Links zu Ihren Pull-Requests mit den Lösungen geben Sie bitte immer in Ihrem *Post Mortem* -mit an. +Die Links zu Ihren Pull-Requests mit den Lösungen geben Sie bitte immer in Ihrem +*Post Mortem* mit an. ::: # Aufgaben @@ -27,171 +26,184 @@ mit an. ## Javadoc-Kommentare Gute Javadoc-Kommentare schreiben erfordert Übung. Schauen Sie sich die in Commit -[Dungeon-CampusMinden/Dungeon/commit/46530b6] neu hinzugefügte Datei -[code/core/src/controller/ControllerLayer.java] an. +[Dungeon-CampusMinden/Dungeon/commit/46530b6](https://github.com/Dungeon-CampusMinden/Dungeon/commit/46530b6dc970a8cedb0610b92268b9c78345e067) +neu hinzugefügte Datei +[code/core/src/controller/ControllerLayer.java](https://github.com/Dungeon-CampusMinden/Dungeon/blob/46530b6dc970a8cedb0610b92268b9c78345e067/code/core/src/controller/ControllerLayer.java) +an. -Diskutieren Sie jeweils, was Ihnen an der Dokumentation dieser Klasse auffällt: Was gefällt -Ihnen, was stört Sie? Schlagen Sie Verbesserungen vor. +Diskutieren Sie jeweils, was Ihnen an der Dokumentation dieser Klasse auffällt: Was +gefällt Ihnen, was stört Sie? Schlagen Sie Verbesserungen vor. ## DevDungeon: Illusion Riddle -Klonen Sie das Projekt [DevDungeon] und laden Sie es in Ihrer IDE als Gradle-Projekt. -Betrachten Sie das Sub-Projekt "devDungeon". Dies ist ein von einem Studierenden ([\@Flamtky]) -erstelltes Spiel mit mehreren Leveln, in denen Sie spielerisch verschiedene Aufgaben *in-game* +Klonen Sie das Projekt +[DevDungeon](https://github.com/Dungeon-CampusMinden/dev-dungeon) und laden Sie es +in Ihrer IDE als Gradle-Projekt. Betrachten Sie das Sub-Projekt "devDungeon". Dies +ist ein von einem Studierenden ([\@Flamtky](https://github.com/Flamtky)) erstelltes +Spiel mit mehreren Leveln, in denen Sie spielerisch verschiedene Aufgaben *in-game* und *ex-game* lösen müssen. ### DevDungeon: Lösen des Illusion Riddle -Starten Sie den DevDungeon mit `./gradlew devDungeon:runDevDungeon`. Spielen Sie sich für -diese Aufgabe durch das **dritte Level** ("Illusion Riddle")[^1]. - -In diesem Level gibt es mehrere Räume, die durch Teleporter (statt Türen) miteinander -verbunden sind. Beim Betreten dieser nur schwer erkennbaren dunkleren Bodenkacheln werden Sie -in den nächsten Raum transportiert. Während dieser Mechanismus deterministisch ist, gibt es -zusätzlich eine neue Monster-Art, die Sie mit fischähnlich aussehenden Geschossen angreift und -bei einem Treffer in einen zufälligen Raum transportiert. - -Im Level sind insgesamt drei Schalter verborgen, von denen Sie mindestens zwei finden und -betätigen müssen, damit die weiteren Übergänge freigeschaltet werden. Die Reihenfolge spielt -dabei keine Rolle. - -Leider ist durch den starken *Fog of War* kaum etwas zu sehen. Auch die vielen Fackeln -verbessern die Sicht nicht wirklich. Können Sie mit diesen Fackeln irgendwie interagieren? - -Ziel ist es, den Weg zum letzten Raum des Levels zu finden und den dort wartenden Boss-Gegner -zu besiegen. - -*Tipp*: Es könnte hilfreich sein, sich eine Skizze der Räume anzufertigen. Diese sind in -diesem Level bei jedem Start identisch. - -*Tipp*: Es könnte in einem bestimmten Raum hilfreich sein, mehrfach im Kreis zu laufen ... - -*Tipp*: Eine Code-Analyse könnte helfen. Vielleicht können Sie durch Anpassungen im Code die -Sicht verbessern oder die Gesundheit Ihres Helden verbessern oder die Teleportationsgeschosse -unschädlich machen? Streng genommen ist das natürlich *cheaten*, aber da Sie ja Code lesen und -anpassen üben, können wir im Rahmen dieser Lehrveranstaltung darüber hinwegsehen. Erklären Sie -im Praktikum, welche Änderungen Sie wo und warum vorgenommen haben und was das bewirkt. - -**Hinweis**: Aktuell ist das Projekt DevDungeon an einigen Stellen noch *Work-in-Progress*, -beispielsweise fehlt häufig noch die Javadoc. Alle Gradle-Tasks, die von Checkstyle-Tasks -abhängen (`checkstyleMain`, `check`, `build`, ...) werden deshalb fehlschlagen. Sie können den -DevDungeon aber wie oben beschrieben mit `./gradlew devDungeon:runDevDungeon` (bzw. über den -Task `devDungeon:runDevDungeon` aus der IDE heraus) starten. - -**WICHTIG**: Achten Sie bitte darauf, dass im Projektpfad **keine Leerzeichen** und keine -Sonderzeichen (Umlaute o.ä.) vorkommen! Dies kann zu seltsamen Fehler führen. Bitte auch -darauf achten, dass Sie als JDK ein **Java SE 21 (LTS)** verwenden. Unter Windows ist der -Einsatz von [WSL] empfehlenswert. +Starten Sie den DevDungeon mit `./gradlew devDungeon:runDevDungeon`. Spielen Sie +sich für diese Aufgabe durch das **dritte Level** ("Illusion Riddle")[^1]. + +In diesem Level gibt es mehrere Räume, die durch Teleporter (statt Türen) +miteinander verbunden sind. Beim Betreten dieser nur schwer erkennbaren dunkleren +Bodenkacheln werden Sie in den nächsten Raum transportiert. Während dieser +Mechanismus deterministisch ist, gibt es zusätzlich eine neue Monster-Art, die Sie +mit fischähnlich aussehenden Geschossen angreift und bei einem Treffer in einen +zufälligen Raum transportiert. + +Im Level sind insgesamt drei Schalter verborgen, von denen Sie mindestens zwei +finden und betätigen müssen, damit die weiteren Übergänge freigeschaltet werden. Die +Reihenfolge spielt dabei keine Rolle. + +Leider ist durch den starken *Fog of War* kaum etwas zu sehen. Auch die vielen +Fackeln verbessern die Sicht nicht wirklich. Können Sie mit diesen Fackeln irgendwie +interagieren? + +Ziel ist es, den Weg zum letzten Raum des Levels zu finden und den dort wartenden +Boss-Gegner zu besiegen. + +*Tipp*: Es könnte hilfreich sein, sich eine Skizze der Räume anzufertigen. Diese +sind in diesem Level bei jedem Start identisch. + +*Tipp*: Es könnte in einem bestimmten Raum hilfreich sein, mehrfach im Kreis zu +laufen ... + +*Tipp*: Eine Code-Analyse könnte helfen. Vielleicht können Sie durch Anpassungen im +Code die Sicht verbessern oder die Gesundheit Ihres Helden verbessern oder die +Teleportationsgeschosse unschädlich machen? Streng genommen ist das natürlich +*cheaten*, aber da Sie ja Code lesen und anpassen üben, können wir im Rahmen dieser +Lehrveranstaltung darüber hinwegsehen. Erklären Sie im Praktikum, welche Änderungen +Sie wo und warum vorgenommen haben und was das bewirkt. + +**Hinweis**: Aktuell ist das Projekt DevDungeon an einigen Stellen noch +*Work-in-Progress*, beispielsweise fehlt häufig noch die Javadoc. Alle Gradle-Tasks, +die von Checkstyle-Tasks abhängen (`checkstyleMain`, `check`, `build`, ...) werden +deshalb fehlschlagen. Sie können den DevDungeon aber wie oben beschrieben mit +`./gradlew devDungeon:runDevDungeon` (bzw. über den Task `devDungeon:runDevDungeon` +aus der IDE heraus) starten. + +**WICHTIG**: Achten Sie bitte darauf, dass im Projektpfad **keine Leerzeichen** und +keine Sonderzeichen (Umlaute o.ä.) vorkommen! Dies kann zu seltsamen Fehler führen. +Bitte auch darauf achten, dass Sie als JDK ein **Java SE 21 (LTS)** verwenden. Unter +Windows ist der Einsatz von +[WSL](https://learn.microsoft.com/en-us/windows/wsl/install) empfehlenswert. ### DevDungeon: Refactoring der Klasse IllusionRiddleLevel -Analysieren Sie die Klasse `IllusionRiddleLevel` im Package `level.devlevel`, insbesondere die -beiden Methoden `IllusionRiddleLevel#onTick` und `IllusionRiddleLevel#lightTorch`: +Analysieren Sie die Klasse `IllusionRiddleLevel` im Package `level.devlevel`, +insbesondere die beiden Methoden `IllusionRiddleLevel#onTick` und +`IllusionRiddleLevel#lightTorch`: 1. Welche *Bad Smells* können Sie hier identifizieren? -2. Beheben Sie die Smells durch die **schrittweise Anwendung** von den aus der Vorlesung - bekannten Refactoring-Methoden. Ergänzend zu der Übersicht aus der Vorlesung finden sie - unter [Refactoring Guru] eine erweiterte Darstellung gängiger Refactoring-Techniken. +2. Beheben Sie die Smells durch die **schrittweise Anwendung** von den aus der + Vorlesung bekannten Refactoring-Methoden. Ergänzend zu der Übersicht aus der + Vorlesung finden sie unter [Refactoring + Guru](https://refactoring.guru/refactoring/techniques) eine erweiterte + Darstellung gängiger Refactoring-Techniken. - Machen Sie pro Refactoring-Schritt einen Commit, und halten Sie alle Commits in einem - Pull-Request fest. An diesem können Sie im Praktikum Ihr Vorgehen vorstellen. + Machen Sie pro Refactoring-Schritt einen Commit, und halten Sie alle Commits in + einem Pull-Request fest. An diesem können Sie im Praktikum Ihr Vorgehen + vorstellen. -*Tipp*: Schauen Sie schlechter Namensgebung, nach redundantem Code, nach übermäßig komplexer -Logik, nach Code/Logik in der falschen Klasse (am falschen Ort), nach übermäßig vielen -Parametern, nach fehlendem Javadoc, .... +*Tipp*: Schauen Sie schlechter Namensgebung, nach redundantem Code, nach übermäßig +komplexer Logik, nach Code/Logik in der falschen Klasse (am falschen Ort), nach +übermäßig vielen Parametern, nach fehlendem Javadoc, .... -**Hinweis**: Normalerweise erstellen Sie eine Testsuite, bevor Sie mit dem Refactoring -beginnen. Leider ist durch die Abhängigkeit zu libGDX und der Game-Loop das Testen im (Dev-) -Dungeon nicht trivial, so dass Sie hier ausnahmsweise direkt mit dem Refactoring loslegen -dürfen und auf das Erstellen einer Testsuite verzichten können. +**Hinweis**: Normalerweise erstellen Sie eine Testsuite, bevor Sie mit dem +Refactoring beginnen. Leider ist durch die Abhängigkeit zu libGDX und der Game-Loop +das Testen im (Dev-) Dungeon nicht trivial, so dass Sie hier ausnahmsweise direkt +mit dem Refactoring loslegen dürfen und auf das Erstellen einer Testsuite verzichten +können. ### Protector-Skill für Ihren Hero -Erstellen Sie einen neuen Protector-Skill für den Hero und weisen Sie diesen einer Taste zu. -Bei Nutzung des Skills soll ein neues Protector-Monster an einer zufälligen Position in einem -bestimmten Radius um den Helden erzeugt werden. Das Protector-Monster sucht das räumlich -nächste Monster, nähert sich diesem automatisch bis auf Angriffsdistanz und greift dieses dann -so lange mit Feuerbällen an, bis eines der beiden Monster keine Lebenspunkte mehr hat. Der -Skill soll einen Cool-Down haben, d.h. er soll erst nach einer gewissen Zeit erneut benutzbar -sein. +Erstellen Sie einen neuen Protector-Skill für den Hero und weisen Sie diesen einer +Taste zu. Bei Nutzung des Skills soll ein neues Protector-Monster an einer +zufälligen Position in einem bestimmten Radius um den Helden erzeugt werden. Das +Protector-Monster sucht das räumlich nächste Monster, nähert sich diesem automatisch +bis auf Angriffsdistanz und greift dieses dann so lange mit Feuerbällen an, bis +eines der beiden Monster keine Lebenspunkte mehr hat. Der Skill soll einen Cool-Down +haben, d.h. er soll erst nach einer gewissen Zeit erneut benutzbar sein. -Nutzen Sie diesen neuen Skill im **dritten Level** ("Illusion Riddle") und schauen Sie, ob Sie -dadurch leichter zum Ausgang des Levels kommen. +Nutzen Sie diesen neuen Skill im **dritten Level** ("Illusion Riddle") und schauen +Sie, ob Sie dadurch leichter zum Ausgang des Levels kommen. -**Hinweis**: Erinnern Sie sich an die [ECS-Architektur]. Schauen Sie sich u.a. die Factory -`HeroFactory` und den `FireballSkill` an. +**Hinweis**: Erinnern Sie sich an die [ECS-Architektur](../lecture/misc/dungeon.md). +Schauen Sie sich u.a. die Factory `HeroFactory` und den `FireballSkill` an. ## Refactoring im Bike-Shop -Forken Sie das [Refactoring]-Repo und erzeugen Sie sich eine lokale Arbeitskopie von Ihrem -Fork. Analysieren Sie die Klassen im Package `refactoring`. Sie finden unübersichtlichen und -schlecht strukturierten und schlecht benannten und schlecht dokumentierten Code. +Forken Sie das +[Refactoring](https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_refactoring)-Repo +und erzeugen Sie sich eine lokale Arbeitskopie von Ihrem Fork. Analysieren Sie die +Klassen im Package `refactoring`. Sie finden unübersichtlichen und schlecht +strukturierten und schlecht benannten und schlecht dokumentierten Code. 1. Welche *Bad Smells* können Sie hier identifizieren? -2. Erstellen Sie eine Testsuite, um potentielle Verhaltensänderungen beim Refactoring - identifizieren zu können. +2. Erstellen Sie eine Testsuite, um potentielle Verhaltensänderungen beim + Refactoring identifizieren zu können. -3. Beheben Sie die Smells durch die **schrittweise Anwendung** von den aus der Vorlesung - bekannten Refactoring-Methoden. Wenden Sie dabei *mindestens* die unten genannten Methoden - an: +3. Beheben Sie die Smells durch die **schrittweise Anwendung** von den aus der + Vorlesung bekannten Refactoring-Methoden. Wenden Sie dabei *mindestens* die + unten genannten Methoden an: - - Extract Method/Class - - Move Method/Field - - Encapsulate Method/Field - - Pull Up oder Push Down + - Extract Method/Class + - Move Method/Field + - Encapsulate Method/Field + - Pull Up oder Push Down - Ergänzend zu der Übersicht aus der Vorlesung finden sie unter [Refactoring Guru] eine - erweiterte Darstellung gängiger Refactoring-Techniken. + Ergänzend zu der Übersicht aus der Vorlesung finden sie unter [Refactoring + Guru](https://refactoring.guru/refactoring/techniques) eine erweiterte + Darstellung gängiger Refactoring-Techniken. - Machen Sie pro Refactoring-Schritt einen Commit, und halten Sie alle Commits in einem - gemeinsamen Pull-Request fest. An diesem können Sie im Praktikum Ihr Vorgehen vorstellen. + Machen Sie pro Refactoring-Schritt einen Commit, und halten Sie alle Commits in + einem gemeinsamen Pull-Request fest. An diesem können Sie im Praktikum Ihr + Vorgehen vorstellen. -Nach dem Refactoring sollte ein `./gradlew check` keine Probleme bzgl. Formatierung und -Dokumentation[^2] mehr finden. +Nach dem Refactoring sollte ein `./gradlew check` keine Probleme bzgl. Formatierung +und Dokumentation[^2] mehr finden. -*Tipp*: Schauen Sie schlechter Namensgebung, nach redundantem Code, nach übermäßig komplexer -Logik, nach Code/Logik in der falschen Klasse (am falschen Ort), nach übermäßig vielen -Parametern, nach fehlendem Javadoc, .... +*Tipp*: Schauen Sie schlechter Namensgebung, nach redundantem Code, nach übermäßig +komplexer Logik, nach Code/Logik in der falschen Klasse (am falschen Ort), nach +übermäßig vielen Parametern, nach fehlendem Javadoc, .... # Bearbeitung und Abgabe -- Bearbeitung: Einzelbearbeitung oder bis zu 3er Teams -- Abgabe: - - Post Mortem [im ILIAS] eintragen: +- Bearbeitung: Einzelbearbeitung oder bis zu 3er Teams +- Abgabe: + - Post Mortem [im + ILIAS](https://www.hsbi.de/elearning/goto.php?target=exc_1514856&client_id=FH-Bielefeld) + eintragen: - Verfassen Sie im ILIAS pro Blatt und pro Team-Mitglied ein aussagekräftiges und - nachvollziehbares "*Post Mortem*". Gehen Sie dabei auf folgende Punkte ein: + Verfassen Sie im ILIAS pro Blatt und pro Team-Mitglied ein aussagekräftiges + und nachvollziehbares "*Post Mortem*". Gehen Sie dabei auf folgende Punkte + ein: - 1. Zusammenfassung: Was wurde gemacht? - 2. Implementierungsdetails: Kurze Beschreibung besonders interessanter Aspekte der - Umsetzung. - 3. Was war der schwierigste Teil bei der Bearbeitung? Wie haben Sie dieses Problem - gelöst? - 4. Was haben Sie gelernt oder (besser) verstanden? - 5. Team: Mit wem haben Sie zusammengearbeitet? - 6. Links zu Ihren Pull-Requests mit der Lösung. + 1. Zusammenfassung: Was wurde gemacht? + 2. Implementierungsdetails: Kurze Beschreibung besonders interessanter + Aspekte der Umsetzung. + 3. Was war der schwierigste Teil bei der Bearbeitung? Wie haben Sie dieses + Problem gelöst? + 4. Was haben Sie gelernt oder (besser) verstanden? + 5. Team: Mit wem haben Sie zusammengearbeitet? + 6. Links zu Ihren Pull-Requests mit der Lösung. - Das Post Mortem muss von **jeder Person** im Team **individuell** verfasst und abgegeben - werden. Der Umfang des Textes soll zwischen 200 und 400 Wörtern liegen. + Das Post Mortem muss von **jeder Person** im Team **individuell** verfasst + und abgegeben werden. Der Umfang des Textes soll zwischen 200 und 400 + Wörtern liegen. - Laden Sie hier bitte **nicht** Ihre Lösungen hoch! + Laden Sie hier bitte **nicht** Ihre Lösungen hoch! - - Deadline: 20. Juni, 08:00 Uhr -- Vorstellung im Praktikum: 20. Juni + - Deadline: 20. Juni, 08:00 Uhr +- Vorstellung im Praktikum: 20. Juni -[^1]: Das dritte richtige Level, also das dritte Level *nach* dem Demo-Level. Oder eben das - vierte Level, wenn man das Demo-Level mitzählt :-) +[^1]: Das dritte richtige Level, also das dritte Level *nach* dem Demo-Level. Oder + eben das vierte Level, wenn man das Demo-Level mitzählt :-) [^2]: d.h. Sie müssen gegebenenfalls auch Javadoc ergänzen ... - - [Dungeon-CampusMinden/Dungeon/commit/46530b6]: https://github.com/Dungeon-CampusMinden/Dungeon/commit/46530b6dc970a8cedb0610b92268b9c78345e067 - [code/core/src/controller/ControllerLayer.java]: https://github.com/Dungeon-CampusMinden/Dungeon/blob/46530b6dc970a8cedb0610b92268b9c78345e067/code/core/src/controller/ControllerLayer.java - [DevDungeon]: https://github.com/Dungeon-CampusMinden/dev-dungeon - [\@Flamtky]: https://github.com/Flamtky - [WSL]: https://learn.microsoft.com/en-us/windows/wsl/install - [Refactoring Guru]: https://refactoring.guru/refactoring/techniques - [ECS-Architektur]: ../lecture/misc/dungeon.md - [Refactoring]: https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_refactoring - [im ILIAS]: https://www.hsbi.de/elearning/goto.php?target=exc_1514856&client_id=FH-Bielefeld diff --git a/homework/b08.md b/homework/b08.md index dede226c3..3e13dadd4 100644 --- a/homework/b08.md +++ b/homework/b08.md @@ -1,186 +1,194 @@ --- author: Carsten Gips (HSBI) +no_beamer: true title: "Blatt 08: Bridge Guard Riddle & Syntax Highlighting (Reguläre Ausdrücke, Template-Method, Command)" -no_beamer: true --- - - # Zusammenfassung -Auf diesem Blatt üben Sie den Umgang mit regulären Ausdrücken in Java. Wir nutzen diese -zusammen mit dem Template-Method-Pattern für die Implementierung eines einfachen -Syntax-Highlightings, und im DevDungeon müssen Sie mit einem Brücken-Troll kämpfen und das -Command-Pattern implementieren. +Auf diesem Blatt üben Sie den Umgang mit regulären Ausdrücken in Java. Wir nutzen +diese zusammen mit dem Template-Method-Pattern für die Implementierung eines +einfachen Syntax-Highlightings, und im DevDungeon müssen Sie mit einem Brücken-Troll +kämpfen und das Command-Pattern implementieren. ::: important -**Hinweis**: Bitte denken Sie daran, dass Sie spätestens seit Blatt 04 die Bearbeitung der -Aufgaben in Ihren öffentlich sichtbaren Git-Repos durchführen sollen. +**Hinweis**: Bitte denken Sie daran, dass Sie spätestens seit Blatt 04 die +Bearbeitung der Aufgaben in Ihren öffentlich sichtbaren Git-Repos durchführen +sollen. -Erstellen Sie für **alle** Ihre Lösungen passende Pull-Requests gegen **Ihre eigenen Repos** -(auch wenn dies nicht explizit in den Aufgaben gefordert wird). +Erstellen Sie für **alle** Ihre Lösungen passende Pull-Requests gegen **Ihre eigenen +Repos** (auch wenn dies nicht explizit in den Aufgaben gefordert wird). -Die Links zu Ihren Pull-Requests mit den Lösungen geben Sie bitte immer in Ihrem *Post Mortem* -mit an. +Die Links zu Ihren Pull-Requests mit den Lösungen geben Sie bitte immer in Ihrem +*Post Mortem* mit an. ::: # Aufgaben ## DevDungeon: Brücken-Troll -Klonen Sie das Projekt [DevDungeon] und laden Sie es in Ihrer IDE als Gradle-Projekt. -Betrachten Sie das Sub-Projekt "devDungeon". Dies ist ein von einem Studierenden ([\@Flamtky]) -erstelltes Spiel mit mehreren Leveln, in denen Sie spielerisch verschiedene Aufgaben *in-game* +Klonen Sie das Projekt +[DevDungeon](https://github.com/Dungeon-CampusMinden/dev-dungeon) und laden Sie es +in Ihrer IDE als Gradle-Projekt. Betrachten Sie das Sub-Projekt "devDungeon". Dies +ist ein von einem Studierenden ([\@Flamtky](https://github.com/Flamtky)) erstelltes +Spiel mit mehreren Leveln, in denen Sie spielerisch verschiedene Aufgaben *in-game* und *ex-game* lösen müssen. -Starten Sie den DevDungeon mit `./gradlew devDungeon:runDevDungeon`. Spielen Sie sich für -diese Aufgabe durch das **vierte Level** ("Bridge Guard Riddle")[^1]. +Starten Sie den DevDungeon mit `./gradlew devDungeon:runDevDungeon`. Spielen Sie +sich für diese Aufgabe durch das **vierte Level** ("Bridge Guard Riddle")[^1]. -In diesem Level gibt es im oberen Teil eine Art Brücke, die von einem Brücken-Troll bewacht -wird. +In diesem Level gibt es im oberen Teil eine Art Brücke, die von einem Brücken-Troll +bewacht wird. ![](images/bridgetroll-annot.png){width="80%"} -Diesen müssen Sie besiegen, um die Brücke passieren und die Belohnung (einen magischen Schild) -zu bekommen. Den Schild können Sie im nächsten Level gut gebrauchen: Der Schild schützt Ihren -Hero vor Schäden, auch wenn er nur einen Treffer verträgt und sich dann über eine gewisse Zeit -regenerieren muss, bevor er wieder funktionstüchtig ist. - -*Hinweis*: Sie können natürlich wie immer auch "außen herum" gehen und die Brücke vermeiden, -um ins nächste Level zu kommen. Das ist aber ziemlich gefährlich, und Sie bekommen den -magischen Schild nicht, den Sie für das letzte Level ziemlich dringend brauchen. - -**Hinweis**: Aktuell ist das Projekt DevDungeon an einigen Stellen noch *Work-in-Progress*, -beispielsweise fehlt häufig noch die Javadoc. Alle Gradle-Tasks, die von Checkstyle-Tasks -abhängen (`checkstyleMain`, `check`, `build`, ...) werden deshalb fehlschlagen. Sie können den -DevDungeon aber wie oben beschrieben mit `./gradlew devDungeon:runDevDungeon` (bzw. über den -Task `devDungeon:runDevDungeon` aus der IDE heraus) starten. - -**WICHTIG**: Achten Sie bitte darauf, dass im Projektpfad **keine Leerzeichen** und keine -Sonderzeichen (Umlaute o.ä.) vorkommen! Dies kann zu seltsamen Fehler führen. Bitte auch -darauf achten, dass Sie als JDK ein **Java SE 21 (LTS)** verwenden. Unter Windows ist der -Einsatz von [WSL] empfehlenswert. +Diesen müssen Sie besiegen, um die Brücke passieren und die Belohnung (einen +magischen Schild) zu bekommen. Den Schild können Sie im nächsten Level gut +gebrauchen: Der Schild schützt Ihren Hero vor Schäden, auch wenn er nur einen +Treffer verträgt und sich dann über eine gewisse Zeit regenerieren muss, bevor er +wieder funktionstüchtig ist. + +*Hinweis*: Sie können natürlich wie immer auch "außen herum" gehen und die Brücke +vermeiden, um ins nächste Level zu kommen. Das ist aber ziemlich gefährlich, und Sie +bekommen den magischen Schild nicht, den Sie für das letzte Level ziemlich dringend +brauchen. + +**Hinweis**: Aktuell ist das Projekt DevDungeon an einigen Stellen noch +*Work-in-Progress*, beispielsweise fehlt häufig noch die Javadoc. Alle Gradle-Tasks, +die von Checkstyle-Tasks abhängen (`checkstyleMain`, `check`, `build`, ...) werden +deshalb fehlschlagen. Sie können den DevDungeon aber wie oben beschrieben mit +`./gradlew devDungeon:runDevDungeon` (bzw. über den Task `devDungeon:runDevDungeon` +aus der IDE heraus) starten. + +**WICHTIG**: Achten Sie bitte darauf, dass im Projektpfad **keine Leerzeichen** und +keine Sonderzeichen (Umlaute o.ä.) vorkommen! Dies kann zu seltsamen Fehler führen. +Bitte auch darauf achten, dass Sie als JDK ein **Java SE 21 (LTS)** verwenden. Unter +Windows ist der Einsatz von +[WSL](https://learn.microsoft.com/en-us/windows/wsl/install) empfehlenswert. ### RegExp mit dem Brücken-Troll -Suchen Sie den Brücken-Troll auf und sprechen Sie ihn an. Er wird Ihnen eine Reihe von Fragen -zum Thema reguläre Ausdrücke stellen, die Sie korrekt beantworten müssen. +Suchen Sie den Brücken-Troll auf und sprechen Sie ihn an. Er wird Ihnen eine Reihe +von Fragen zum Thema reguläre Ausdrücke stellen, die Sie korrekt beantworten müssen. -Machen Sie Screenshots von den Fragen und Ihren Antworten, die Sie im Praktikum vorstellen und -diskutieren. +Machen Sie Screenshots von den Fragen und Ihren Antworten, die Sie im Praktikum +vorstellen und diskutieren. ### Command-Pattern mit der Klasse *BridgeControlCommand* -Leider lässt sich der Brücken-Troll offenbar weder durch Diskussion noch durch Kampf besiegen. -Aber vielleicht können Sie die Brücke "aufmachen", so dass er in die Tiefe stürzt? Die *Tiles* -der Brücke bestehen aus sogenannten `PitTile`s: Wenn diese offen sind, fällt man hindurch; -wenn sie geschlossen sind, kann man gefahrlos darauf treten (außer, es ist eine Verzögerung -aktiviert :-) ... Allerdings müssten Sie danach die Brücke auch wieder schließen, um selbst -darüber hinweg laufen zu können ... +Leider lässt sich der Brücken-Troll offenbar weder durch Diskussion noch durch Kampf +besiegen. Aber vielleicht können Sie die Brücke "aufmachen", so dass er in die Tiefe +stürzt? Die *Tiles* der Brücke bestehen aus sogenannten `PitTile`s: Wenn diese offen +sind, fällt man hindurch; wenn sie geschlossen sind, kann man gefahrlos darauf +treten (außer, es ist eine Verzögerung aktiviert :-) ... Allerdings müssten Sie +danach die Brücke auch wieder schließen, um selbst darüber hinweg laufen zu können +... -Schauen Sie sich die Info-Box am Eingang zur Brücke an. Der Hebel bedient mit Hilfe des -Command-Patterns die Brücke. Für die *Commands* gibt es die Klasse `BridgeControlCommand` im -Package `entities.levercommands`, wobei die Methode `BridgeControlCommand#execute` das -*Command* ausführt und die Methode `BridgeControlCommand#undo` das *Command* wieder rückgängig -macht. +Schauen Sie sich die Info-Box am Eingang zur Brücke an. Der Hebel bedient mit Hilfe +des Command-Patterns die Brücke. Für die *Commands* gibt es die Klasse +`BridgeControlCommand` im Package `entities.levercommands`, wobei die Methode +`BridgeControlCommand#execute` das *Command* ausführt und die Methode +`BridgeControlCommand#undo` das *Command* wieder rückgängig macht. Implementieren Sie die beiden Methoden und starten Sie das Spiel erneut. -**Hinweis**: Mit der Methode `Game.currentLevel().tileAt()` können Sie auf ein `Tile` an einer -bestimmte Koordinate zugreifen. +**Hinweis**: Mit der Methode `Game.currentLevel().tileAt()` können Sie auf ein +`Tile` an einer bestimmte Koordinate zugreifen. ## Syntaxhighlighting mit RegExp -Klonen Sie die [Vorgaben "Syntax Highlighting"] und laden Sie das Projekt als Gradle-Projekt -in Ihre IDE. - -Im Package `highlighting` finden Sie einige Klassen, mit denen man ein einfaches Syntax -Highlighting durchführen kann. Dazu arbeitet der Lexer mit sogenannten "Token" (Instanzen der -Klasse `Token`). Diese haben einen regulären Ausdruck, um bestimmte Teile im Code zu erkennen, -beispielsweise Keywords oder Kommentare und anderes. Der Lexer wendet alle Token auf den -aktuellen Eingabezeichenstrom an (Methode `Token#test()`), und die Token prüfen mit "ihrem" -regulären Ausdruck, ob die jeweils passende Eingabesequenz vorliegt. Die regulären Ausdrücke -übergeben Sie dem `Token`-Konstruktor als entsprechendes `Pattern`-Objekt. - -- Die Klasse `Token` speichert dazu ein `Pattern` (einen vorkompilierten regulären Ausdruck) - sowie eine Farbe, die später beim Syntax Highlighting für dieses Token genutzt werden soll. - Bei der Anwendung eines Tokens auf einen String (Methode `Token#test`) wird das gespeicherte - Pattern auf den String angewendet und eine Liste aller passenden Stellen im String - zurückgegeben (`List`). - - Neben dem jeweiligen Pattern kennt jedes Token noch eine `matchingGroup`: Dies ist ein - Integer, der die relevante Matching-Group im regulären Ausdruck bezeichnet. Wenn Sie keine - eigenen Gruppen in einem regulären Ausdruck eingebaut haben, nutzen Sie hier einfach den - Wert 0. - - Zusätzlich kennt jedes Token noch die Farbe für das Syntax-Highlighting in der von uns als - Vorgabe realisierten Swing-GUI (Instanz von `Color`). - -- Der `Lexer` sammelt eine Liste von `Token` und wendet sie in der übergebenen Reihenfolge auf - den Eingabestring an (Methode `Lexer#tokenize`). - -- Die Klasse `LexerUI` dient zum Anzeigen des ursprünglichen Textes und des Ergebnisses. Hier - sieht man recht schnell, ob die Pattern bereits passen ... Man kann auf der linken Seite - auch den Text editieren, und auf der rechten Seite des Fensters wird dann automatisch das - Syntax Highlighting erneut durchgeführt. - -- Die Klasse `Main` dient zum Definieren der konkreten Token (=\> Aufgabe) und auch zum - Starten der Demo. - -**Aufgabe**: Definieren Sie alle in `Main#setupTokens` genannten Token, indem Sie jeweils -einen passenden regulären Ausdruck formulieren und als Pattern in den Konstruktor geben -zusammen mit einer Farbe, mit der dieses Token hervorgehoben werden soll: - -- Strings: alles zwischen `"` und dem nächsten `"` -- Character: genau ein Zeichen zwischen `'` und `'` -- Keywords: `package`, `import`, `class`, `public`, `private`, `final`, `return`, `null`, - `new` (jeweils freistehend, also nicht "newx" o.ä.) -- Annotation: beginnt mit `@`, enthält Buchstaben oder Minuszeichen -- Einzeiliger Kommentar: beginnend mit `//` bis zum Zeilenende -- Mehrzeiliger Kommentar: alles zwischen `/*` und dem nächsten `*/` -- Javadoc-Kommentar: alles zwischen `/**` und dem nächsten `*/` - -Sie können auch mit *Matching Groups* arbeiten und im Token eine bestimmte Gruppe hervorheben -lassen. Dazu geben Sie einfach die Nummer der Matching-Group mit in den Token-Konstruktor. -(Wenn Sie nichts übergeben, wird der gesamte Match genommen - das entspricht dem Wert 0). - -Sollten Token ineinander geschachtelt sein, erkennt der Lexer dies automatisch. Sie brauchen -sich keine Gedanken dazu machen, in welcher Reihenfolge die Token eingefügt und abgearbeitet -werden. Beispiel: Im regulären Ausdruck für den einzeiligen Kommentar brauchen Sie keine -Keywords, Annotationen, Strings usw. erkennen. +Klonen Sie die [Vorgaben "Syntax +Highlighting"](https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_highlighting) +und laden Sie das Projekt als Gradle-Projekt in Ihre IDE. + +Im Package `highlighting` finden Sie einige Klassen, mit denen man ein einfaches +Syntax Highlighting durchführen kann. Dazu arbeitet der Lexer mit sogenannten +"Token" (Instanzen der Klasse `Token`). Diese haben einen regulären Ausdruck, um +bestimmte Teile im Code zu erkennen, beispielsweise Keywords oder Kommentare und +anderes. Der Lexer wendet alle Token auf den aktuellen Eingabezeichenstrom an +(Methode `Token#test()`), und die Token prüfen mit "ihrem" regulären Ausdruck, ob +die jeweils passende Eingabesequenz vorliegt. Die regulären Ausdrücke übergeben Sie +dem `Token`-Konstruktor als entsprechendes `Pattern`-Objekt. + +- Die Klasse `Token` speichert dazu ein `Pattern` (einen vorkompilierten regulären + Ausdruck) sowie eine Farbe, die später beim Syntax Highlighting für dieses Token + genutzt werden soll. Bei der Anwendung eines Tokens auf einen String (Methode + `Token#test`) wird das gespeicherte Pattern auf den String angewendet und eine + Liste aller passenden Stellen im String zurückgegeben (`List`). + + Neben dem jeweiligen Pattern kennt jedes Token noch eine `matchingGroup`: Dies + ist ein Integer, der die relevante Matching-Group im regulären Ausdruck + bezeichnet. Wenn Sie keine eigenen Gruppen in einem regulären Ausdruck eingebaut + haben, nutzen Sie hier einfach den Wert 0. + + Zusätzlich kennt jedes Token noch die Farbe für das Syntax-Highlighting in der + von uns als Vorgabe realisierten Swing-GUI (Instanz von `Color`). + +- Der `Lexer` sammelt eine Liste von `Token` und wendet sie in der übergebenen + Reihenfolge auf den Eingabestring an (Methode `Lexer#tokenize`). + +- Die Klasse `LexerUI` dient zum Anzeigen des ursprünglichen Textes und des + Ergebnisses. Hier sieht man recht schnell, ob die Pattern bereits passen ... Man + kann auf der linken Seite auch den Text editieren, und auf der rechten Seite des + Fensters wird dann automatisch das Syntax Highlighting erneut durchgeführt. + +- Die Klasse `Main` dient zum Definieren der konkreten Token (=\> Aufgabe) und + auch zum Starten der Demo. + +**Aufgabe**: Definieren Sie alle in `Main#setupTokens` genannten Token, indem Sie +jeweils einen passenden regulären Ausdruck formulieren und als Pattern in den +Konstruktor geben zusammen mit einer Farbe, mit der dieses Token hervorgehoben +werden soll: + +- Strings: alles zwischen `"` und dem nächsten `"` +- Character: genau ein Zeichen zwischen `'` und `'` +- Keywords: `package`, `import`, `class`, `public`, `private`, `final`, `return`, + `null`, `new` (jeweils freistehend, also nicht "newx" o.ä.) +- Annotation: beginnt mit `@`, enthält Buchstaben oder Minuszeichen +- Einzeiliger Kommentar: beginnend mit `//` bis zum Zeilenende +- Mehrzeiliger Kommentar: alles zwischen `/*` und dem nächsten `*/` +- Javadoc-Kommentar: alles zwischen `/**` und dem nächsten `*/` + +Sie können auch mit *Matching Groups* arbeiten und im Token eine bestimmte Gruppe +hervorheben lassen. Dazu geben Sie einfach die Nummer der Matching-Group mit in den +Token-Konstruktor. (Wenn Sie nichts übergeben, wird der gesamte Match genommen - das +entspricht dem Wert 0). + +Sollten Token ineinander geschachtelt sein, erkennt der Lexer dies automatisch. Sie +brauchen sich keine Gedanken dazu machen, in welcher Reihenfolge die Token eingefügt +und abgearbeitet werden. Beispiel: Im regulären Ausdruck für den einzeiligen +Kommentar brauchen Sie keine Keywords, Annotationen, Strings usw. erkennen. # Bearbeitung und Abgabe -- Bearbeitung: Einzelbearbeitung oder bis zu 3er Teams -- Abgabe: - - Post Mortem [im ILIAS] eintragen: - - Verfassen Sie im ILIAS pro Blatt und pro Team-Mitglied ein aussagekräftiges und - nachvollziehbares "*Post Mortem*". Gehen Sie dabei auf folgende Punkte ein: +- Bearbeitung: Einzelbearbeitung oder bis zu 3er Teams +- Abgabe: + - Post Mortem [im + ILIAS](https://www.hsbi.de/elearning/goto.php?target=exc_1514856&client_id=FH-Bielefeld) + eintragen: - 1. Zusammenfassung: Was wurde gemacht? - 2. Implementierungsdetails: Kurze Beschreibung besonders interessanter Aspekte der - Umsetzung. - 3. Was war der schwierigste Teil bei der Bearbeitung? Wie haben Sie dieses Problem - gelöst? - 4. Was haben Sie gelernt oder (besser) verstanden? - 5. Team: Mit wem haben Sie zusammengearbeitet? - 6. Links zu Ihren Pull-Requests mit der Lösung. + Verfassen Sie im ILIAS pro Blatt und pro Team-Mitglied ein aussagekräftiges + und nachvollziehbares "*Post Mortem*". Gehen Sie dabei auf folgende Punkte + ein: - Das Post Mortem muss von **jeder Person** im Team **individuell** verfasst und abgegeben - werden. Der Umfang des Textes soll zwischen 200 und 400 Wörtern liegen. + 1. Zusammenfassung: Was wurde gemacht? + 2. Implementierungsdetails: Kurze Beschreibung besonders interessanter + Aspekte der Umsetzung. + 3. Was war der schwierigste Teil bei der Bearbeitung? Wie haben Sie dieses + Problem gelöst? + 4. Was haben Sie gelernt oder (besser) verstanden? + 5. Team: Mit wem haben Sie zusammengearbeitet? + 6. Links zu Ihren Pull-Requests mit der Lösung. - Laden Sie hier bitte **nicht** Ihre Lösungen hoch! + Das Post Mortem muss von **jeder Person** im Team **individuell** verfasst + und abgegeben werden. Der Umfang des Textes soll zwischen 200 und 400 + Wörtern liegen. - - Deadline: 27. Juni, 08:00 Uhr -- Vorstellung im Praktikum: 27. Juni + Laden Sie hier bitte **nicht** Ihre Lösungen hoch! -[^1]: Das vierte richtige Level, also das vierte Level *nach* dem Demo-Level. Oder eben das - fünfte Level, wenn man das Demo-Level mitzählt :-) + - Deadline: 27. Juni, 08:00 Uhr +- Vorstellung im Praktikum: 27. Juni - [DevDungeon]: https://github.com/Dungeon-CampusMinden/dev-dungeon - [\@Flamtky]: https://github.com/Flamtky - [WSL]: https://learn.microsoft.com/en-us/windows/wsl/install - [Vorgaben "Syntax Highlighting"]: https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_highlighting - [im ILIAS]: https://www.hsbi.de/elearning/goto.php?target=exc_1514856&client_id=FH-Bielefeld +[^1]: Das vierte richtige Level, also das vierte Level *nach* dem Demo-Level. Oder + eben das fünfte Level, wenn man das Demo-Level mitzählt :-) diff --git a/homework/b09.md b/homework/b09.md index 4236baa31..5f16485f2 100644 --- a/homework/b09.md +++ b/homework/b09.md @@ -1,131 +1,140 @@ --- author: Carsten Gips (HSBI) -title: "Blatt 09: Zoo (Generics)" no_beamer: true +title: "Blatt 09: Zoo (Generics)" --- - - # Zusammenfassung -Auf diesem Blatt üben Sie das Erstellen und den Einsatz von generischen Klassen und Methoden -in Java. +Auf diesem Blatt üben Sie das Erstellen und den Einsatz von generischen Klassen und +Methoden in Java. ::: important -**Hinweis**: Bitte denken Sie daran, dass Sie spätestens seit Blatt 04 die Bearbeitung der -Aufgaben in Ihren öffentlich sichtbaren Git-Repos durchführen sollen. +**Hinweis**: Bitte denken Sie daran, dass Sie spätestens seit Blatt 04 die +Bearbeitung der Aufgaben in Ihren öffentlich sichtbaren Git-Repos durchführen +sollen. -Erstellen Sie für **alle** Ihre Lösungen passende Pull-Requests gegen **Ihre eigenen Repos** -(auch wenn dies nicht explizit in den Aufgaben gefordert wird). +Erstellen Sie für **alle** Ihre Lösungen passende Pull-Requests gegen **Ihre eigenen +Repos** (auch wenn dies nicht explizit in den Aufgaben gefordert wird). -Die Links zu Ihren Pull-Requests mit den Lösungen geben Sie bitte immer in Ihrem *Post Mortem* -mit an. +Die Links zu Ihren Pull-Requests mit den Lösungen geben Sie bitte immer in Ihrem +*Post Mortem* mit an. ::: # Aufgaben -Sie finden in den [Vorgaben] im Package `zoo` einige Interfaces und Klassen, mit denen man -einen Zoo modellieren kann. Klonen Sie das Vorgabe-Repo und laden Sie das Projekt als -Gradle-Projekt in Ihre IDE. +Sie finden in den +[Vorgaben](https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_zoo) im +Package `zoo` einige Interfaces und Klassen, mit denen man einen Zoo modellieren +kann. Klonen Sie das Vorgabe-Repo und laden Sie das Projekt als Gradle-Projekt in +Ihre IDE. ## Nicht-generische Klassen -Sie finden einige Interfaces, die das Interface `Animal` erweitern, beispielsweise `Fish`, -`Reptile`, `Cat`, ... +Sie finden einige Interfaces, die das Interface `Animal` erweitern, beispielsweise +`Fish`, `Reptile`, `Cat`, ... -Erstellen Sie für jedes dieser Interfaces jeweils zwei Java-Klassen, die konkrete Mitglieder -der jeweiligen Tier-Klasse bzw. -Familie[^1] repräsentieren und die jeweils das Interface -implementieren. Beispielsweise könnte eine Forelle eine Art in der Klasse der Fische sein, -d.h. die Java-Klasse `Trout` könnte das Interface `Fish` implementieren. +Erstellen Sie für jedes dieser Interfaces jeweils zwei Java-Klassen, die konkrete +Mitglieder der jeweiligen Tier-Klasse bzw. -Familie[^1] repräsentieren und die +jeweils das Interface implementieren. Beispielsweise könnte eine Forelle eine Art in +der Klasse der Fische sein, d.h. die Java-Klasse `Trout` könnte das Interface `Fish` +implementieren. -Diese Java-Klassen sollen nicht-generisch sein. Die `move()`-Methode soll einfach einen String -auf der Konsole ausgeben, wie sich dieses Tier bewegen würde. Mit der `getName()`-Methode soll -ein kurzer Name, beispielsweise der Tierklasse, zurückgegeben werden. +Diese Java-Klassen sollen nicht-generisch sein. Die `move()`-Methode soll einfach +einen String auf der Konsole ausgeben, wie sich dieses Tier bewegen würde. Mit der +`getName()`-Methode soll ein kurzer Name, beispielsweise der Tierklasse, +zurückgegeben werden. -*Hinweis*: Die mit `default` markierten Methoden in `Fish`, `Mammal` und `Reptile` brauchen -Sie in Ihren Klassen nicht implementieren. Wir werden später im Semester noch über die -sogenannten ["Default-Methoden"] sprechen. +*Hinweis*: Die mit `default` markierten Methoden in `Fish`, `Mammal` und `Reptile` +brauchen Sie in Ihren Klassen nicht implementieren. Wir werden später im Semester +noch über die sogenannten +["Default-Methoden"](../lecture/java-modern/defaultmethods.md) sprechen. ## Generische Klassen -Um Gehege zu modellieren, erstellen Sie in eine generische Klasse `Habitat` mit einer -Typ-Variablen. Stellen Sie durch geeignete Beschränkung der Typ-Variablen sicher, dass nur -Gehege mit von `Animal` abgeleiteten Typen gebildet werden können. +Um Gehege zu modellieren, erstellen Sie in eine generische Klasse `Habitat` mit +einer Typ-Variablen. Stellen Sie durch geeignete Beschränkung der Typ-Variablen +sicher, dass nur Gehege mit von `Animal` abgeleiteten Typen gebildet werden können. Es sollen (mindestens) folgende Methoden existieren: -- `void add(Animal animal)` zur Aufnahme eines Tieres in das Gehege -- `void remove(Animal animal)` um ein Tier aus einem Gehege zu entfernen -- `String getName()` um den Namen des Geheges zu bestimmen +- `void add(Animal animal)` zur Aufnahme eines Tieres in das Gehege +- `void remove(Animal animal)` um ein Tier aus einem Gehege zu entfernen +- `String getName()` um den Namen des Geheges zu bestimmen -*Hinweis*: Der Parametertyp `Animal` in den Methoden muss entsprechend angepasst werden. +*Hinweis*: Der Parametertyp `Animal` in den Methoden muss entsprechend angepasst +werden. -Jedes einzelne Tier im Gehege kann maximal einmal vorkommen. Begründen Sie die Wahl der -Datenstruktur. +Jedes einzelne Tier im Gehege kann maximal einmal vorkommen. Begründen Sie die Wahl +der Datenstruktur. -Legen Sie in Ihrer `main()`-Methode mindestens zwei konkrete Gehege als Instanzen der neuen -generischen Klasse `Habitat` an. Dabei sollen in diesen konkreten Gehegen nur jeweils -verschiedene Tiere einer "Art" (beispielweise Löwen, Hamster, ...) vorkommen. Fügen Sie einige -passende Tiere in die beiden Gehege ein. +Legen Sie in Ihrer `main()`-Methode mindestens zwei konkrete Gehege als Instanzen +der neuen generischen Klasse `Habitat` an. Dabei sollen in diesen konkreten Gehegen +nur jeweils verschiedene Tiere einer "Art" (beispielweise Löwen, Hamster, ...) +vorkommen. Fügen Sie einige passende Tiere in die beiden Gehege ein. ## Generische Klassen reloaded -Für die Repräsentation eines Zoologischen Gartens mit mehreren verschiedenen Gehegen erstellen -Sie nun eine generische Klasse `Zoo` mit einer Typ-Variablen. Stellen Sie durch geeignete -Beschränkung der Typ-Variablen sicher, dass nur Zoos mit von `Habitat` abgeleiteten Typen -gebildet werden können. +Für die Repräsentation eines Zoologischen Gartens mit mehreren verschiedenen Gehegen +erstellen Sie nun eine generische Klasse `Zoo` mit einer Typ-Variablen. Stellen Sie +durch geeignete Beschränkung der Typ-Variablen sicher, dass nur Zoos mit von +`Habitat` abgeleiteten Typen gebildet werden können. Ein Zoo soll (mindestens) folgende Methoden besitzen: -- `void build(Habitat habitat)` zur Aufnahme eines neuen Geheges in den Zoo -- `void abandon(Habitat habitat)` um ein Gehege aus dem Zoo zu entfernen -- `void visitAllHabitats()` um die Gehege in der Reihenfolge des Hinzufügens zu besuchen (d.h. - ihren Namen auf der Konsole auszugeben) +- `void build(Habitat habitat)` zur Aufnahme eines neuen Geheges in den Zoo +- `void abandon(Habitat habitat)` um ein Gehege aus dem Zoo zu entfernen +- `void visitAllHabitats()` um die Gehege in der Reihenfolge des Hinzufügens zu + besuchen (d.h. ihren Namen auf der Konsole auszugeben) -*Hinweis*: Der Parametertyp `Habitat` in den Methoden muss entsprechend angepasst werden. +*Hinweis*: Der Parametertyp `Habitat` in den Methoden muss entsprechend angepasst +werden. -Jedes Gehege gibt es pro Zoo nur maximal einmal, und die Reihenfolge des Errichtens definiert -eine Ordnung auf den Gehegen. Begründen Sie die Wahl der Datenstruktur. +Jedes Gehege gibt es pro Zoo nur maximal einmal, und die Reihenfolge des Errichtens +definiert eine Ordnung auf den Gehegen. Begründen Sie die Wahl der Datenstruktur. -Legen Sie in Ihrer `main()`-Methode einen konkreten Zoo als Instanz der neuen generischen -Klasse `Zoo` an. Dieser Zoo soll einige Gehege für verschiedene Tiere beinhalten. +Legen Sie in Ihrer `main()`-Methode einen konkreten Zoo als Instanz der neuen +generischen Klasse `Zoo` an. Dieser Zoo soll einige Gehege für verschiedene Tiere +beinhalten. ## Ableiten nicht-generischer Klassen -Leiten Sie von `Zoo` eine nicht-generische Klasse `Aquarium` ab. Aquarien können nur mit -Gehegen angelegt werden, deren Tiere vom Typ `Fish` (oder abgeleitet) sind. +Leiten Sie von `Zoo` eine nicht-generische Klasse `Aquarium` ab. Aquarien können nur +mit Gehegen angelegt werden, deren Tiere vom Typ `Fish` (oder abgeleitet) sind. Legen Sie in Ihrer `main()`-Methode ein konkretes Aquarium als Instanz der neuen -nicht-generischen Klasse `Aquarium` an und gruppieren Sie darin verschiedene Fisch-"Gehege". +nicht-generischen Klasse `Aquarium` an und gruppieren Sie darin verschiedene +Fisch-"Gehege". # Bearbeitung und Abgabe -- Bearbeitung: Einzelbearbeitung oder bis zu 3er Teams -- Abgabe: - - Post Mortem [im ILIAS] eintragen: - - Verfassen Sie im ILIAS pro Blatt und pro Team-Mitglied ein aussagekräftiges und - nachvollziehbares "*Post Mortem*". Gehen Sie dabei auf folgende Punkte ein: +- Bearbeitung: Einzelbearbeitung oder bis zu 3er Teams +- Abgabe: + - Post Mortem [im + ILIAS](https://www.hsbi.de/elearning/goto.php?target=exc_1514856&client_id=FH-Bielefeld) + eintragen: - 1. Zusammenfassung: Was wurde gemacht? - 2. Implementierungsdetails: Kurze Beschreibung besonders interessanter Aspekte der - Umsetzung. - 3. Was war der schwierigste Teil bei der Bearbeitung? Wie haben Sie dieses Problem - gelöst? - 4. Was haben Sie gelernt oder (besser) verstanden? - 5. Team: Mit wem haben Sie zusammengearbeitet? - 6. Links zu Ihren Pull-Requests mit der Lösung. + Verfassen Sie im ILIAS pro Blatt und pro Team-Mitglied ein aussagekräftiges + und nachvollziehbares "*Post Mortem*". Gehen Sie dabei auf folgende Punkte + ein: - Das Post Mortem muss von **jeder Person** im Team **individuell** verfasst und abgegeben - werden. Der Umfang des Textes soll zwischen 200 und 400 Wörtern liegen. + 1. Zusammenfassung: Was wurde gemacht? + 2. Implementierungsdetails: Kurze Beschreibung besonders interessanter + Aspekte der Umsetzung. + 3. Was war der schwierigste Teil bei der Bearbeitung? Wie haben Sie dieses + Problem gelöst? + 4. Was haben Sie gelernt oder (besser) verstanden? + 5. Team: Mit wem haben Sie zusammengearbeitet? + 6. Links zu Ihren Pull-Requests mit der Lösung. - Laden Sie hier bitte **nicht** Ihre Lösungen hoch! + Das Post Mortem muss von **jeder Person** im Team **individuell** verfasst + und abgegeben werden. Der Umfang des Textes soll zwischen 200 und 400 + Wörtern liegen. - - Deadline: 04. Juli, 08:00 Uhr -- Vorstellung im Praktikum: 04. Juli + Laden Sie hier bitte **nicht** Ihre Lösungen hoch! -[^1]: Ok, wir machen hier Informatik. Vermutlich ist die Biologie nicht ganz korrekt ;-) + - Deadline: 04. Juli, 08:00 Uhr +- Vorstellung im Praktikum: 04. Juli - [Vorgaben]: https://github.com/Programmiermethoden-CampusMinden/prog2_ybel_zoo - ["Default-Methoden"]: ../lecture/java-modern/defaultmethods.md - [im ILIAS]: https://www.hsbi.de/elearning/goto.php?target=exc_1514856&client_id=FH-Bielefeld +[^1]: Ok, wir machen hier Informatik. Vermutlich ist die Biologie nicht ganz korrekt + ;-) diff --git a/homework/readme.md b/homework/readme.md index 88b565e91..c4bdc25c5 100644 --- a/homework/readme.md +++ b/homework/readme.md @@ -1,8 +1,7 @@ --- -title: "Praktikum" -no_pdf: true no_beamer: true +no_pdf: true +title: Praktikum --- - Hier finden Sie die Übungsblätter. diff --git a/lecture/building/ant.md b/lecture/building/ant.md index 165186002..b5a35e369 100644 --- a/lecture/building/ant.md +++ b/lecture/building/ant.md @@ -1,54 +1,42 @@ --- +author: Carsten Gips (HSBI) title: "Build-Systeme: Apache Ant" -author: "Carsten Gips (HSBI)" -readings: - - "@Inden2013 [Abschnitt 2.5.2]" -tldr: | - Zum Automatisieren von Arbeitsabläufen (Kompilieren, Testen, ...) stehen in der Java-Welt - verschiedene Tools zur Verfügung: Apache Ant, Apache Maven und Gradle sind sicher die am - bekanntesten darunter. - - In Apache Ant werden die Build-Skripte in XML definiert. Die äußere Klammer ist dabei das - ``. In einem Projekt kann es ein oder mehrere Teilziele (_Targets_) geben, die - untereinander abhängig sein können. Die Targets können quasi "aufgerufen" werden bzw. in - der IDE selektiert und gestartet werden. - - In einem Target kann man schließlich mit _Tasks_ Aufgaben wie Kompilieren, Testen, Aufräumen, - ... erledigen lassen. Dazu gibt es eine breite Palette an vordefinierten Tasks. Zusätzlich - sind umfangreiche Operationen auf dem Filesystem möglich (Ordner erstellen, löschen, Dinge - kopieren, ...). - - Über _Properties_ können Werte und Namen definiert werden, etwa für bestimmte Ordner. Die - Properties sind unveränderliche Variablen (auch wenn man sie im Skript scheinbar neu setzen - kann). - - Über Apache Ivy können analog zu Maven und Gradle definierte Abhängigkeiten aus Maven-Central - aufgelöst werden. - - Im Unterschied zu Maven und Gradle ist in Ant _kein_ Java-Entwicklungsmodell eingebaut. Man - muss sämtliche Targets selbst definieren. -outcomes: - - k3: "Schreiben einfacher Ant-Skripte mit Abhängigkeiten zwischen den Targets" - - k3: "Nutzung von Ant-Filesets (Dateisystemoperationen, Classpath)" - - k3: "Nutzung von Ant-Properties" - - k3: "Ausführen von Ant-Targets aus der IDE heraus" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106214&client_id=FH-Bielefeld" -# name: "Quiz Apache Ant (ILIAS)" -youtube: - - link: "https://youtu.be/LRA1PeQ2pR0" - name: "VL Apache Ant" - - link: "https://youtu.be/EnAQOU_zL1M" - name: "Demo Aufruf von Ant (Konsole, IDE: hello.xml)" - - link: "https://youtu.be/ip8xFcSZC1c" - name: "Demo Properties, Targets, Dependencies (build.xml)" - - link: "https://youtu.be/jizh0bi2TnU" - name: "Demo Abhängigkeiten mit Ivy auflösen (ivydemo.xml)" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/31f6228dac399ec8151484367499e975959f23e6ebaf61e1869b959f903aeea256af0c2cdca66bf249862deebba48e660107feb880267017b798e0b1ee10870f" - name: "VL Apache Ant" --- +::: tldr +Zum Automatisieren von Arbeitsabläufen (Kompilieren, Testen, ...) stehen in der +Java-Welt verschiedene Tools zur Verfügung: Apache Ant, Apache Maven und Gradle sind +sicher die am bekanntesten darunter. + +In Apache Ant werden die Build-Skripte in XML definiert. Die äußere Klammer ist +dabei das ``. In einem Projekt kann es ein oder mehrere Teilziele +(*Targets*) geben, die untereinander abhängig sein können. Die Targets können quasi +"aufgerufen" werden bzw. in der IDE selektiert und gestartet werden. + +In einem Target kann man schließlich mit *Tasks* Aufgaben wie Kompilieren, Testen, +Aufräumen, ... erledigen lassen. Dazu gibt es eine breite Palette an vordefinierten +Tasks. Zusätzlich sind umfangreiche Operationen auf dem Filesystem möglich (Ordner +erstellen, löschen, Dinge kopieren, ...). + +Über *Properties* können Werte und Namen definiert werden, etwa für bestimmte +Ordner. Die Properties sind unveränderliche Variablen (auch wenn man sie im Skript +scheinbar neu setzen kann). + +Über Apache Ivy können analog zu Maven und Gradle definierte Abhängigkeiten aus +Maven-Central aufgelöst werden. + +Im Unterschied zu Maven und Gradle ist in Ant *kein* Java-Entwicklungsmodell +eingebaut. Man muss sämtliche Targets selbst definieren. +::: + +::: youtube +- [VL Apache Ant](https://youtu.be/LRA1PeQ2pR0) +- [Demo Aufruf von Ant (Konsole, IDE: hello.xml)](https://youtu.be/EnAQOU_zL1M) +- [Demo Properties, Targets, Dependencies + (build.xml)](https://youtu.be/ip8xFcSZC1c) +- [Demo Abhängigkeiten mit Ivy auflösen + (ivydemo.xml)](https://youtu.be/jizh0bi2TnU) +::: # Automatisieren von Arbeitsabläufen @@ -60,29 +48,28 @@ Works on my machine ... \bigskip \pause -* Build-Tools: - * **Apache Ant** - * Apache Maven - * Gradle +- Build-Tools: + - **Apache Ant** + - Apache Maven + - Gradle \bigskip ::: notes -* Aufgaben: - * Übersetzen des Quellcodes - * Ausführen der Unit-Tests - * Generieren der Dokumentation - * Packen der Distribution - * Aufräumen temporärer Dateien - * ... - -=> Automatisieren mit Apache Ant: [ant.apache.org](https://ant.apache.org/) +- Aufgaben: + - Übersetzen des Quellcodes + - Ausführen der Unit-Tests + - Generieren der Dokumentation + - Packen der Distribution + - Aufräumen temporärer Dateien + - ... + +=\> Automatisieren mit Apache Ant: [ant.apache.org](https://ant.apache.org/) ::: - # Aufbau von Ant-Skripten: Projekte und Targets -```xml +``` xml @@ -93,21 +80,20 @@ Works on my machine ... ``` ::: notes -* Ein Hauptziel: `project` -* Ein oder mehrere Teilziele: `target` - * Abhängigkeiten der Teilziele untereinander möglich: `depends` - * Vorbedingung für Targets mit `if` oder `unless` +- Ein Hauptziel: `project` +- Ein oder mehrere Teilziele: `target` + - Abhängigkeiten der Teilziele untereinander möglich: `depends` + - Vorbedingung für Targets mit `if` oder `unless` ::: - # Aufgaben erledigen: Tasks ::: notes -* **Tasks**: Aufgaben bzw. Befehle ("Methodenaufruf"), in Targets auszuführen -* Struktur: `` +- **Tasks**: Aufgaben bzw. Befehle ("Methodenaufruf"), in Targets auszuführen +- Struktur: `` ::: -```xml +``` xml @@ -115,21 +101,22 @@ Works on my machine ... \bigskip -* Beispiele: `echo`, `javac`, `jar`, `javadoc`, `junit`, ... -* Je Target mehrere Tasks möglich -* Quellen: - * Eingebaute Tasks - * Optionale Task-Bibliotheken - * Selbst definierte Tasks - -=> Überblick: [ant.apache.org/manual/tasksoverview.html](https://ant.apache.org/manual/tasksoverview.html) +- Beispiele: `echo`, `javac`, `jar`, `javadoc`, `junit`, ... +- Je Target mehrere Tasks möglich +- Quellen: + - Eingebaute Tasks + - Optionale Task-Bibliotheken + - Selbst definierte Tasks -[Konsole/IDE: ant -f hello.xml]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/building/src/ant/hello.xml"} +=\> Überblick: +[ant.apache.org/manual/tasksoverview.html](https://ant.apache.org/manual/tasksoverview.html) +[Konsole/IDE: ant -f hello.xml]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/building/src/ant/hello.xml"} # Properties: Name-Wert-Paare -```xml +``` xml @@ -139,27 +126,27 @@ Works on my machine ... ``` ::: notes -* Setzen von Eigenschaften: `` - * Properties lassen sich nur einmal setzen ("immutable") - * Erneute Definition ist wirkungslos, erzeugt aber leider keinen Fehler -* Nutzung von Properties: `` -* Pfade: "`location`" statt "`value`" nutzen: +- Setzen von Eigenschaften: `` + - Properties lassen sich nur einmal setzen ("immutable") + - Erneute Definition ist wirkungslos, erzeugt aber leider keinen Fehler +- Nutzung von Properties: `` +- Pfade: "`location`" statt "`value`" nutzen: `` ::: \bigskip -* Properties beim Aufruf setzen mit Option "`-D`": \newline +- Properties beim Aufruf setzen mit Option "`-D`": `\newline`{=tex} `ant -Dwuppie=fluppie` ::: notes -[Beispiel build.xml, Properties]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/building/src/ant/build.xml"} +[Beispiel build.xml, Properties]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/building/src/ant/build.xml"} ::: - # Tasks zum Umgang mit Dateien und Ordnern -```xml +``` xml @@ -172,21 +159,21 @@ Works on my machine ... ``` ::: notes -* `` - * Legt auch direkte Unterverzeichnisse an - * Keine Aktion, falls Verzeichnis existiert -* `` - * Löscht eine Datei ("`file`") oder ein Verzeichnis ("`dir`") (rekursiv!) -* `` -* `` - -[Beispiel build.xml, init und clean]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/building/src/ant/build.xml"} +- `` + - Legt auch direkte Unterverzeichnisse an + - Keine Aktion, falls Verzeichnis existiert +- `` + - Löscht eine Datei ("`file`") oder ein Verzeichnis ("`dir`") (rekursiv!) +- `` +- `` + +[Beispiel build.xml, init und clean]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/building/src/ant/build.xml"} ::: - # Nutzung von Filesets in Tasks -```xml +``` xml @@ -201,21 +188,19 @@ Works on my machine ... \bigskip -* "`*`" für beliebig viele Zeichen -* "`?`" für genau ein Zeichen -* "`**`" alle Unterverzeichnisse +- "`*`" für beliebig viele Zeichen +- "`?`" für genau ein Zeichen +- "`**`" alle Unterverzeichnisse ::: notes -Es gibt auch die Variante ``, um Verzeichnisse zu -gruppieren. +Es gibt auch die Variante ``, um Verzeichnisse zu gruppieren. ::: - # Pfade und externe Bibliotheken -* Als Element direkt im Task: +- Als Element direkt im Task: - ```xml + ``` xml @@ -225,7 +210,7 @@ gruppieren. ::: notes D.h. die Einbettung in den `javac`-Task würde etwa so erfolgen: - ```xml + ``` xml @@ -236,15 +221,15 @@ gruppieren. ``` - _Anmerkung_: Neben dem `pathelement` können Sie hier auch (wie im nächsten + *Anmerkung*: Neben dem `pathelement` können Sie hier auch (wie im nächsten Beispiel gezeigt) ein oder mehrere `fileset` nutzen. ::: \bigskip -* Wiederverwendbar durch ID und "`refid`": +- Wiederverwendbar durch ID und "`refid`": - ```xml + ``` xml @@ -257,7 +242,7 @@ gruppieren. ::: notes Die Einbettung in den `javac`-Task würde hier etwa so erfolgen: - ```xml + ``` xml @@ -271,28 +256,28 @@ gruppieren. ``` - _Anmerkung_: Neben dem `fileset` können Sie hier auch (wie oben gezeigt) - ein oder mehrere `pathelement` nutzen. + *Anmerkung*: Neben dem `fileset` können Sie hier auch (wie oben gezeigt) ein + oder mehrere `pathelement` nutzen. ::: \bigskip ::: notes -_Anmerkung_: Laut [ant.apache.org/manual/Tasks/junit.html](https://ant.apache.org/manual/Tasks/junit.html) -benötigt man neben der aktuellen `junit.jar` noch die `ant-junit.jar` im Classpath, um mit dem `junit`-Ant-Task -entsprechende JUnit4-Testfälle ausführen zu können. +*Anmerkung*: Laut +[ant.apache.org/manual/Tasks/junit.html](https://ant.apache.org/manual/Tasks/junit.html) +benötigt man neben der aktuellen `junit.jar` noch die `ant-junit.jar` im Classpath, +um mit dem `junit`-Ant-Task entsprechende JUnit4-Testfälle ausführen zu können. Für JUnit5 gibt es einen neuen Task `JUnitLauncher` (vgl. [ant.apache.org/manual/Tasks/junitlauncher.html](https://ant.apache.org/manual/Tasks/junitlauncher.html)). ::: - -::::::::: notes +::: notes # Beispiele ## Beispiel-Task: Kompilieren -```xml +``` xml @@ -306,7 +291,7 @@ Für JUnit5 gibt es einen neuen Task `JUnitLauncher` (vgl. ## Beispiel-Task: Packen -```xml +``` xml @@ -319,26 +304,26 @@ Für JUnit5 gibt es einen neuen Task `JUnitLauncher` (vgl. ## Beispiel-Task: Testen -* Tests einer Testklasse ausführen: +- Tests einer Testklasse ausführen: - ```xml + ``` xml ``` -* Test ausführen und XML mit Ergebnissen erzeugen: +- Test ausführen und XML mit Ergebnissen erzeugen: - ```xml + ``` xml ``` -* Verschiedene Tests als Batch ausführen: +- Verschiedene Tests als Batch ausführen: - ```xml + ``` xml @@ -360,9 +345,9 @@ Für JUnit5 gibt es einen neuen Task `JUnitLauncher` (vgl. ``` -* Aus Testergebnis (XML) einen Report generieren: +- Aus Testergebnis (XML) einen Report generieren: - ```xml + ``` xml @@ -371,19 +356,19 @@ Für JUnit5 gibt es einen neuen Task `JUnitLauncher` (vgl. ``` -* Abbruch bei Fehler: +- Abbruch bei Fehler: - ```xml + ``` xml ``` -=> `junit.jar` und `ant-junit.jar` (JUnit4.x) im Pfad! +=\> `junit.jar` und `ant-junit.jar` (JUnit4.x) im Pfad! ## Programme ausführen -```xml +``` xml @@ -393,8 +378,7 @@ Für JUnit5 gibt es einen neuen Task `JUnitLauncher` (vgl. ``` -::::::::: - +::: # Ausblick: Laden von Abhängigkeiten mit Apache Ivy @@ -404,7 +388,7 @@ Für JUnit5 gibt es einen neuen Task `JUnitLauncher` (vgl. \bigskip -```xml +``` xml @@ -414,14 +398,15 @@ Für JUnit5 gibt es einen neuen Task `JUnitLauncher` (vgl. ``` ::: notes -Wenn Ivy installiert ist, kann man durch den Eintrag `xmlns:ivy="antlib:org.apache.ivy.ant"` -in der Projekt-Deklaration im Ant-Skript die Ivy-Tasks laden. Der wichtigste Task ist dabei -`ivy:retrieve`, mit dem externe Projektabhängigkeiten heruntergeladen werden können. +Wenn Ivy installiert ist, kann man durch den Eintrag +`xmlns:ivy="antlib:org.apache.ivy.ant"` in der Projekt-Deklaration im Ant-Skript die +Ivy-Tasks laden. Der wichtigste Task ist dabei `ivy:retrieve`, mit dem externe +Projektabhängigkeiten heruntergeladen werden können. ::: \bigskip -```xml +``` xml @@ -432,14 +417,15 @@ in der Projekt-Deklaration im Ant-Skript die Ivy-Tasks laden. Der wichtigste Tas ``` ::: notes -Zur Steuerung von Ivy legt man eine weitere Datei `ivy.xml` an. Das Wurzelelement ist `ivy-module`, -wobei die `version` die niedrigste kompatible Ivy-Version angibt. +Zur Steuerung von Ivy legt man eine weitere Datei `ivy.xml` an. Das Wurzelelement +ist `ivy-module`, wobei die `version` die niedrigste kompatible Ivy-Version angibt. -Der `dependencies`-Abschnitt definiert dann die Abhängigkeiten, die Ivy auflösen muss. Die Schreibweise -ist dabei wie im Maven2 Repository ([mvnrepository.com](https://mvnrepository.com/)) angelegt. Dort -findet man beispielsweise für Apache Commons CLI den Eintrag für Maven ("POM"-Datei): +Der `dependencies`-Abschnitt definiert dann die Abhängigkeiten, die Ivy auflösen +muss. Die Schreibweise ist dabei wie im Maven2 Repository +([mvnrepository.com](https://mvnrepository.com/)) angelegt. Dort findet man +beispielsweise für Apache Commons CLI den Eintrag für Maven ("POM"-Datei): -```xml +``` xml commons-cli @@ -448,16 +434,18 @@ findet man beispielsweise für Apache Commons CLI den Eintrag für Maven ("POM"- ``` -Für die Ivy-Konfiguration übernimmt man die `groupId` als `org`, die `artifactId` als `name` und die -`version` als `rev` im Eintrag `dependency`. +Für die Ivy-Konfiguration übernimmt man die `groupId` als `org`, die `artifactId` +als `name` und die `version` als `rev` im Eintrag `dependency`. -Damit kann Ivy diese Bibliothek über den Ant-Task `ivy:retrieve` vor dem Bauen herunterladen, sofern -die Bibliothek noch nicht lokal vorhanden ist. Eventuelle Abhängigkeiten werden dabei ebenfalls aufgelöst. +Damit kann Ivy diese Bibliothek über den Ant-Task `ivy:retrieve` vor dem Bauen +herunterladen, sofern die Bibliothek noch nicht lokal vorhanden ist. Eventuelle +Abhängigkeiten werden dabei ebenfalls aufgelöst. -Im Detail: Der Ant-Task `ivy:retrieve` löst zunächst die Abhängigkeiten auf und lädt die Dateien (sofern -sie noch nicht vorhanden oder veraltet sind) in den Ivy-Cache (per Default: `~/.ivy2/cache/`). Danach -werden die Dateien in den Default-Library-Order im Projekt kopiert (per Defaul: `./lib/`). Die Ordner kann -man über Optionen im `ivy:retrieve`-Task einstellen. +Im Detail: Der Ant-Task `ivy:retrieve` löst zunächst die Abhängigkeiten auf und lädt +die Dateien (sofern sie noch nicht vorhanden oder veraltet sind) in den Ivy-Cache +(per Default: `~/.ivy2/cache/`). Danach werden die Dateien in den +Default-Library-Order im Projekt kopiert (per Defaul: `./lib/`). Die Ordner kann man +über Optionen im `ivy:retrieve`-Task einstellen. ::: -[Demo: ivydemo.xml]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/building/src/ant/ivydemo.xml"} - +[Demo: ivydemo.xml]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/building/src/ant/ivydemo.xml"} # Ausblick: Weitere Build-Systeme -* [Maven](https://maven.apache.org/) - * War als Nachfolger von Ant gedacht - * Statt wie bei Ant explizit Targets zu formulieren, geht Maven - von einem Standardprojekt aus - nur noch Abweichungen müssen - formuliert werden - * Zieht Abhängigkeiten in zentralen `.maven`-Ordner +- [Maven](https://maven.apache.org/) + - War als Nachfolger von Ant gedacht + - Statt wie bei Ant explizit Targets zu formulieren, geht Maven von einem + Standardprojekt aus - nur noch Abweichungen müssen formuliert werden + - Zieht Abhängigkeiten in zentralen `.maven`-Ordner \bigskip -* [Gradle](https://gradle.org/) - * Eine Art Mischung aus Ant und Maven unter Nutzung der Sprache Groovy +- [Gradle](https://gradle.org/) + - Eine Art Mischung aus Ant und Maven unter Nutzung der Sprache Groovy \bigskip -* [Make](https://www.gnu.org/software/make/) - * DER Klassiker, stammt aus der C-Welt. Kann aber natürlich auch Java. - * Analog zu Ant: Aktionen und Ziele müssen explizit definiert werden - +- [Make](https://www.gnu.org/software/make/) + - DER Klassiker, stammt aus der C-Welt. Kann aber natürlich auch Java. + - Analog zu Ant: Aktionen und Ziele müssen explizit definiert werden # Wrap-Up @@ -495,10 +481,21 @@ Apache Ant: [ant.apache.org](https://ant.apache.org/) \bigskip -* Automatisieren von Arbeitsabläufen -* Apache Ant: Targets, Tasks, Properties - * Targets sind auswählbare Teilziele - * Abhängigkeiten zwischen Targets möglich - * Tasks erledigen Aufgaben (innerhalb Targets) - * Properties sind nicht änderbare Variablen - * Umfangreiche Operationen auf Filesystem möglich +- Automatisieren von Arbeitsabläufen +- Apache Ant: Targets, Tasks, Properties + - Targets sind auswählbare Teilziele + - Abhängigkeiten zwischen Targets möglich + - Tasks erledigen Aufgaben (innerhalb Targets) + - Properties sind nicht änderbare Variablen + - Umfangreiche Operationen auf Filesystem möglich + +::: readings +- @Inden2013 [Abschnitt 2.5.2] +::: + +::: outcomes +- k3: Schreiben einfacher Ant-Skripte mit Abhängigkeiten zwischen den Targets +- k3: Nutzung von Ant-Filesets (Dateisystemoperationen, Classpath) +- k3: Nutzung von Ant-Properties +- k3: Ausführen von Ant-Targets aus der IDE heraus +::: diff --git a/lecture/building/ci.md b/lecture/building/ci.md index 47f13f8ec..5e2109ef1 100644 --- a/lecture/building/ci.md +++ b/lecture/building/ci.md @@ -1,180 +1,121 @@ --- -title: "Continuous Integration (CI)" -author: "Carsten Gips (HSBI)" -readings: - - "@GitlabCI" - - "@GitHubCI" -tldr: | - In größeren Projekten mit mehreren Teams werden die Beteiligten i.d.R. nur noch "ihre" - Codestellen compilieren und testen. Dennoch ist es wichtig, das gesamte Projekt regelmäßig - zu "bauen" und auch umfangreichere Testsuiten regelmäßig laufen zu lassen. Außerdem ist - es wichtig, das in einer definierten Umgebung zu tun und nicht auf einem oder mehreren - Entwicklerrechnern, die i.d.R. (leicht) unterschiedlich konfiguriert sind, um zuverlässige - und nachvollziehbare Ergebnisse zu bekommen. Weiterhin möchte man auf bestimmte Ereignisse - reagieren, wie etwa neue Commits im Git-Server, oder bei Pull-Requests möchte man vor dem - Merge automatisiert sicherstellen, dass damit die vorhandenen Tests alle "grün" sind und - auch die Formatierung etc. stimmt. - - Dafür hat sich "Continuous Integration" etabliert. Hier werden die angesprochenen Prozesse - regelmäßig auf einem dafür eingerichteten System durchgeführt. Aktivitäten wie Übersetzen, - Testen, Style-Checks etc. werden in sogenannten "Pipelines" oder "Workflows" zusammengefasst - und automatisiert durch Commits, Pull-Requests oder Merges auf dem Git-Server ausgelöst. Die - Aktionen können dabei je nach Trigger und Branch unterschiedlich sein, d.h. man könnte etwa - bei PR gegen den Master umfangreichere Tests laufen lassen als bei einem PR gegen einen - Develop-Branch. In einem Workflow oder einer Pipeline können einzelne Aktionen wiederum von - anderen Aktionen abhängen. Das Ergebnis kann man dann auf dem Server einsehen oder bekommt - man komfortabel als Report per Mail zugeschickt. - - Wir schauen uns hier exemplarisch GitHub Actions und GitLab CI/CD an. Um CI sinnvoll einsetzen - zu können, benötigt man Kenntnisse über Build-Tools. "CI" tritt üblicherweise zusammen mit "CD" - (Continuous Delivery) auf, also als "CI/CD". Der "CD"-Teil ist nicht Gegenstand der Betrachtung - in dieser Lehrveranstaltung. -outcomes: - - k2: "Ich kann Arbeitsweise von/mit CI (GitHub, GitLab) erklären" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106219&client_id=FH-Bielefeld" -# name: "Quiz Continuous Integration (ILIAS)" -youtube: - - link: "https://youtu.be/NCWxo-PN4gs" - name: "VL Continuous Integration" - - link: "https://youtu.be/rpkZvuiyvTU" - name: "Demo GitHub Actions" - - link: "https://youtu.be/2ydDA4WY1wA" - name: "Demo Demo GitLab CI/CD" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/74a8f8c2e1a07d9fb70c8984e522d884141b2260c27dadfd7a23884bee8c0573136475ce66f28562097ca34a63fcf9c6d1c45ff695485d79465a4131878180ca" - name: "VL Continuous Integration" -challenges: | - Betrachten Sie erneut das Projekt [Theatrical Players Refactoring Kata](https://github.com/emilybache/Theatrical-Players-Refactoring-Kata). - Erstellen Sie für dieses Projekt einen GitHub-Workflow, der das Projekt kompiliert und die Testsuite ausführt - (nur für den Java-Teil, den restlichen Code können Sie ignorieren). - - Dabei soll das Ausführen der JUnit-Tests nur dann erfolgen, wenn das Kompilieren erfolgreich durchgeführt wurde. - - Der Workflow soll automatisch für Commits in den Hauptbranch sowie für Pull-Requests loslaufen. Es soll zusätzlich - auch manuell aktivierbar sein. - - +author: Carsten Gips (HSBI) +title: Continuous Integration (CI) --- - -::::::::: notes +::: tldr +In größeren Projekten mit mehreren Teams werden die Beteiligten i.d.R. nur noch +"ihre" Codestellen compilieren und testen. Dennoch ist es wichtig, das gesamte +Projekt regelmäßig zu "bauen" und auch umfangreichere Testsuiten regelmäßig laufen +zu lassen. Außerdem ist es wichtig, das in einer definierten Umgebung zu tun und +nicht auf einem oder mehreren Entwicklerrechnern, die i.d.R. (leicht) +unterschiedlich konfiguriert sind, um zuverlässige und nachvollziehbare Ergebnisse +zu bekommen. Weiterhin möchte man auf bestimmte Ereignisse reagieren, wie etwa neue +Commits im Git-Server, oder bei Pull-Requests möchte man vor dem Merge automatisiert +sicherstellen, dass damit die vorhandenen Tests alle "grün" sind und auch die +Formatierung etc. stimmt. + +Dafür hat sich "Continuous Integration" etabliert. Hier werden die angesprochenen +Prozesse regelmäßig auf einem dafür eingerichteten System durchgeführt. Aktivitäten +wie Übersetzen, Testen, Style-Checks etc. werden in sogenannten "Pipelines" oder +"Workflows" zusammengefasst und automatisiert durch Commits, Pull-Requests oder +Merges auf dem Git-Server ausgelöst. Die Aktionen können dabei je nach Trigger und +Branch unterschiedlich sein, d.h. man könnte etwa bei PR gegen den Master +umfangreichere Tests laufen lassen als bei einem PR gegen einen Develop-Branch. In +einem Workflow oder einer Pipeline können einzelne Aktionen wiederum von anderen +Aktionen abhängen. Das Ergebnis kann man dann auf dem Server einsehen oder bekommt +man komfortabel als Report per Mail zugeschickt. + +Wir schauen uns hier exemplarisch GitHub Actions und GitLab CI/CD an. Um CI sinnvoll +einsetzen zu können, benötigt man Kenntnisse über Build-Tools. "CI" tritt +üblicherweise zusammen mit "CD" (Continuous Delivery) auf, also als "CI/CD". Der +"CD"-Teil ist nicht Gegenstand der Betrachtung in dieser Lehrveranstaltung. +::: + +::: youtube +- [VL Continuous Integration](https://youtu.be/NCWxo-PN4gs) +- [Demo GitHub Actions](https://youtu.be/rpkZvuiyvTU) +- [Demo Demo GitLab CI/CD](https://youtu.be/2ydDA4WY1wA) +::: + +::: notes # Motivation: Zusammenarbeit in Teams ## Szenario -* Projekt besteht aus diversen Teilprojekten -* Verschiedene Entwicklungs-Teams arbeiten (getrennt) an verschiedenen Projekten -* Tester entwickeln Testsuiten für die Teilprojekte -* Tester entwickeln Testsuiten für das Gesamtprojekt +- Projekt besteht aus diversen Teilprojekten +- Verschiedene Entwicklungs-Teams arbeiten (getrennt) an verschiedenen Projekten +- Tester entwickeln Testsuiten für die Teilprojekte +- Tester entwickeln Testsuiten für das Gesamtprojekt ## Manuelle Ausführung der Testsuiten reicht nicht -* Belastet den Entwicklungsprozess -* Keine (einheitliche) Veröffentlichung der Ergebnisse -* Keine (einheitliche) Eskalation bei Fehlern -* Keine regelmäßige Integration in Gesamtprojekt +- Belastet den Entwicklungsprozess +- Keine (einheitliche) Veröffentlichung der Ergebnisse +- Keine (einheitliche) Eskalation bei Fehlern +- Keine regelmäßige Integration in Gesamtprojekt ## Continuous Integration -* Regelmäßige, automatische Ausführung: Build und Tests -* Reporting -* Weiterführung der Idee: Regelmäßiges Deployment (*Continuous Deployment*) -::::::::: - +- Regelmäßige, automatische Ausführung: Build und Tests +- Reporting +- Weiterführung der Idee: Regelmäßiges Deployment (*Continuous Deployment*) +::: # Continuous Integration (CI) ![](images/ci.png){width="80%" web_width="60%"} -::::::::: notes +::: notes ## Vorgehen -* Entwickler und Tester committen ihre Änderungen regelmäßig (Git, SVN, ...) -* CI-Server arbeitet Build-Skripte ab, getriggert durch Events: Push-Events, Zeit/Datum, ... - * Typischerweise wird dabei: - * Das Gesamtprojekt übersetzt ("gebaut") - * Die Unit- und die Integrationstests abgearbeitet - * Zu festen Zeiten werden zusätzlich Systemtests gefahren - * Typische weitere Builds: "Nightly Build", Release-Build, ... - * Ergebnisse jeweils auf der Weboberfläche einsehbar (und per E-Mail) +- Entwickler und Tester committen ihre Änderungen regelmäßig (Git, SVN, ...) +- CI-Server arbeitet Build-Skripte ab, getriggert durch Events: Push-Events, + Zeit/Datum, ... + - Typischerweise wird dabei: + - Das Gesamtprojekt übersetzt ("gebaut") + - Die Unit- und die Integrationstests abgearbeitet + - Zu festen Zeiten werden zusätzlich Systemtests gefahren + - Typische weitere Builds: "Nightly Build", Release-Build, ... + - Ergebnisse jeweils auf der Weboberfläche einsehbar (und per E-Mail) ## Einige Vorteile -* Tests werden regelmäßig durchgeführt (auch wenn sie lange dauern oder die +- Tests werden regelmäßig durchgeführt (auch wenn sie lange dauern oder die Maschine stark belasten) -* Es wird regelmäßig ein Gesamt-Build durchgeführt -* Alle Teilnehmer sind über aktuellen Projekt(-zu-)stand informiert +- Es wird regelmäßig ein Gesamt-Build durchgeführt +- Alle Teilnehmer sind über aktuellen Projekt(-zu-)stand informiert ## Beispiele für verbreitete CI-Umgebungen -* [Jenkins](https://www.jenkins.io/) -* [GitLab CI/CD](https://docs.gitlab.com/ee/ci/) -* [GitHub Actions](https://github.com/features/actions) und [GitHub CI/CD](https://resources.github.com/ci-cd/) -* [Bamboo](https://www.atlassian.com/software/bamboo) -* [Travis CI](https://www.travis-ci.com/) -::::::::: +- [Jenkins](https://www.jenkins.io/) +- [GitLab CI/CD](https://docs.gitlab.com/ee/ci/) +- [GitHub Actions](https://github.com/features/actions) und [GitHub + CI/CD](https://resources.github.com/ci-cd/) +- [Bamboo](https://www.atlassian.com/software/bamboo) +- [Travis CI](https://www.travis-ci.com/) +::: [[Live-Demo Gitlab/GitHub]{.ex}]{.slides} - -::::::::: notes +::: notes # GitLab CI/CD -Siehe auch ["Get started with Gitlab CI/CD"](http://git03-ifm-min.ad.hsbi.de/help/ci/quick_start/index.md). -(Für den Zugriff wird VPN benötigt!) - +Siehe auch ["Get started with Gitlab +CI/CD"](http://git03-ifm-min.ad.hsbi.de/help/ci/quick_start/index.md). (Für den +Zugriff wird VPN benötigt!) ## Übersicht über Pipelines ![](images/screenshot-gitlabci-pipelines.png){width="70%"} -* In Spalte "Status" sieht man das Ergebnis der einzelnen Pipelines: - "pending" (die Pipeline läuft gerade), "cancelled" (Pipeline wurde manuell - abgebrochen), "passed" (alle Jobs der Pipeline sind sauber durchgelaufen), - "failed" (ein Job ist fehlgeschlagen, Pipeline wurde deshalb abgebrochen) -* In Spalte "Pipeline" sind die Pipelines eindeutig benannt aufgeführt, - inkl. Trigger (Commit und Branch) -* In Spalte "Stages" sieht man den Zustand der einzelnen Stages +- In Spalte "Status" sieht man das Ergebnis der einzelnen Pipelines: "pending" + (die Pipeline läuft gerade), "cancelled" (Pipeline wurde manuell abgebrochen), + "passed" (alle Jobs der Pipeline sind sauber durchgelaufen), "failed" (ein Job + ist fehlgeschlagen, Pipeline wurde deshalb abgebrochen) +- In Spalte "Pipeline" sind die Pipelines eindeutig benannt aufgeführt, inkl. + Trigger (Commit und Branch) +- In Spalte "Stages" sieht man den Zustand der einzelnen Stages Wenn man mit der Maus auf den Status oder die Stages geht, erfährt man mehr bzw. kann auf eine Seite mit mehr Informationen kommen. @@ -183,22 +124,22 @@ kann auf eine Seite mit mehr Informationen kommen. ![](images/screenshot-gitlabci-triggeredpipeline.png){width="70%"} -Wenn man in eine Pipeline in der Übersicht klickt, werden die einzelnen -Stages dieser Pipeline genauer dargestellt. +Wenn man in eine Pipeline in der Übersicht klickt, werden die einzelnen Stages +dieser Pipeline genauer dargestellt. ## Detailansicht eines Jobs ![](images/screenshot-gitlabci-job.png){width="70%"} Wenn man in einen Job einer Stage klickt, bekommt man quasi die Konsolenausgabe -dieses Jobs. Hier kann man ggf. Fehler beim Ausführen der einzelnen Skripte -oder die Ergebnisse beispielsweise der JUnit-Läufe anschauen. +dieses Jobs. Hier kann man ggf. Fehler beim Ausführen der einzelnen Skripte oder die +Ergebnisse beispielsweise der JUnit-Läufe anschauen. ## GitLab CI/CD: Konfiguration mit YAML-Datei Datei `.gitlab-ci.yml` im Projekt-Ordner: -```yaml +``` yaml stages: - my.compile - my.test @@ -224,8 +165,8 @@ job3: ### Stages -Unter `stages` werden die einzelnen Stages einer Pipeline definiert. Diese werden -in der hier spezifizierten Reihenfolge durchgeführt, d.h. zuerst würde `my.compile` +Unter `stages` werden die einzelnen Stages einer Pipeline definiert. Diese werden in +der hier spezifizierten Reihenfolge durchgeführt, d.h. zuerst würde `my.compile` ausgeführt, und erst wenn alle Jobs in `my.compile` erfolgreich ausgeführt wurden, würde anschließend `my.test` ausgeführt. @@ -233,110 +174,101 @@ Dabei gilt: Die Jobs einer Stage werden (potentiell) parallel zueinander ausgef und die Jobs der nächsten Stage werden erst dann gestartet, wenn alle Jobs der aktuellen Stage erfolgreich beendet wurden. -Wenn keine eigenen `stages` definiert werden, kann man -([lt. Doku](http://git03-ifm-min.ad.hsbi.de/help/ci/yaml/index.md#stages)) -auf die Default-Stages `build`, `test` und `deploy` zurückgreifen. **Achtung**: Sobald -man eigene Stages definiert, stehen diese Default-Stages *nicht* mehr zur Verfügung! +Wenn keine eigenen `stages` definiert werden, kann man ([lt. +Doku](http://git03-ifm-min.ad.hsbi.de/help/ci/yaml/index.md#stages)) auf die +Default-Stages `build`, `test` und `deploy` zurückgreifen. **Achtung**: Sobald man +eigene Stages definiert, stehen diese Default-Stages *nicht* mehr zur Verfügung! ### Jobs `job1`, `job2` und `job3` definieren jeweils einen Job. -* `job1` besteht aus mehreren Befehlen (unter `script`). Alternativ - kann man die bei `job2` gezeigte Syntax nutzen, wenn nur ein - Befehl zu bearbeiten ist. +- `job1` besteht aus mehreren Befehlen (unter `script`). Alternativ kann man die + bei `job2` gezeigte Syntax nutzen, wenn nur ein Befehl zu bearbeiten ist. Die Befehle werden von GitLab CI/CD in einer Shell ausgeführt. -* Die Jobs `job1` und `job2` sind der Stage `my.compile` zugeordnet (Abschnitt +- Die Jobs `job1` und `job2` sind der Stage `my.compile` zugeordnet (Abschnitt `stage`). Einer Stage können mehrere Jobs zugeordnet sein, die dann parallel ausgeführt werden. - Wenn ein Job nicht explizit einer Stage zugeordnet ist, wird er - ([lt. Doku](http://git03-ifm-min.ad.hsbi.de/help/ci/yaml/index.md#stages)) - zur Default-Stage `test` zugewiesen. (Das geht nur, wenn es diese - Stage auch gibt!) - -* Mit `only` und `except` kann man u.a. Branches oder Tags angeben, - für die dieser Job ausgeführt (bzw. nicht ausgeführt) werden soll. + Wenn ein Job nicht explizit einer Stage zugeordnet ist, wird er ([lt. + Doku](http://git03-ifm-min.ad.hsbi.de/help/ci/yaml/index.md#stages)) zur + Default-Stage `test` zugewiesen. (Das geht nur, wenn es diese Stage auch gibt!) -Durch die Kombination von Jobs mit der Zuordnung zu Stages und Events lassen -sich unterschiedliche Pipelines für verschiedene Zwecke definieren. +- Mit `only` und `except` kann man u.a. Branches oder Tags angeben, für die dieser + Job ausgeführt (bzw. nicht ausgeführt) werden soll. +Durch die Kombination von Jobs mit der Zuordnung zu Stages und Events lassen sich +unterschiedliche Pipelines für verschiedene Zwecke definieren. ## Hinweise zur Konfiguration von GitLab CI/CD Im Browser in den Repo-Einstellungen arbeiten: -1. Unter `Settings > General > Visibility, project features, permissions` - das `CI/CD` aktivieren -2. Prüfen unter `Settings > CI/CD > Runners`, dass unter - `Available shared Runners` mind. ein shared Runner verfügbar ist - (mit grün markiert ist) +1. Unter `Settings > General > Visibility, project features, permissions` das + `CI/CD` aktivieren +2. Prüfen unter `Settings > CI/CD > Runners`, dass unter `Available shared Runners` + mind. ein shared Runner verfügbar ist (mit grün markiert ist) 3. Unter `Settings > CI/CD > General pipelines` einstellen: - * `Git strategy`: `git clone` - * `Timeout`: `10m` - * `Public pipelines`: `false` (nicht angehakt) -4. YAML-File (`.gitlab-ci.yml`) in Projektwurzel anlegen, - Aufbau siehe oben + - `Git strategy`: `git clone` + - `Timeout`: `10m` + - `Public pipelines`: `false` (nicht angehakt) +4. YAML-File (`.gitlab-ci.yml`) in Projektwurzel anlegen, Aufbau siehe oben 5. Build-Skript erstellen, **lokal** lauffähig bekommen, dann in Jobs nutzen 6. Im `.gitlab-ci.yml` die relevanten Branches einstellen (s.o.) 7. Pushen, und unter `CI/CD > Pipelines` das Builden beobachten - * in Status reinklicken und schauen, ob und wo es hakt + - in Status reinklicken und schauen, ob und wo es hakt 8. `README.md` anlegen in Projektwurzel (neben `.gitlab-ci.yml`), Markdown-Schnipsel aus `Settings > CI/CD > General pipelines > Pipeline status` auswählen und einfügen .... -_Optional_: +*Optional*: 9. Ggf. Schedules unter `CI/CD > Schedules` anlegen 10. Ggf. extra Mails einrichten: `Settings > Integrations > Pipeline status emails` -::::::::: +::: - - - -::::::::: notes +::: notes # GitHub Actions -Siehe ["GitHub Actions: Automate your workflow from idea to production"](https://github.com/features/actions) -und auch ["GitHub: CI/CD explained"](https://resources.github.com/ci-cd/). - +Siehe ["GitHub Actions: Automate your workflow from idea to +production"](https://github.com/features/actions) und auch ["GitHub: CI/CD +explained"](https://resources.github.com/ci-cd/). ## Übersicht über Workflows ![](images/screenshot-githubci-workflows.png){width="70%"} -Hier sieht man das Ergebnis der letzten Workflows. Dazu sieht man den -Commit und den Branch, auf dem der Workflow gelaufen ist sowie wann er -gelaufen ist. Über die Spalten kann man beispielsweise nach Status oder -Event filtern. +Hier sieht man das Ergebnis der letzten Workflows. Dazu sieht man den Commit und den +Branch, auf dem der Workflow gelaufen ist sowie wann er gelaufen ist. Über die +Spalten kann man beispielsweise nach Status oder Event filtern. -In der Abbildung ist ein Workflow mit dem Namen "GitHub CI" zu sehen, der -aktuell noch läuft. +In der Abbildung ist ein Workflow mit dem Namen "GitHub CI" zu sehen, der aktuell +noch läuft. ## Detailansicht eines Workflows ![](images/screenshot-githubci-triggeredworkflow.png){width="70%"} -Wenn man in einen Workflow in der Übersicht anklickt, werden die einzelnen -Jobs dieses Workflows genauer dargestellt. "job3" ist erfolgreich gelaufen, "job1" -läuft gerade, und "job2" hängt von "job1" ab, d.h. kann erst nach dem erfolgreichen -Lauf von "job2" starten. +Wenn man in einen Workflow in der Übersicht anklickt, werden die einzelnen Jobs +dieses Workflows genauer dargestellt. "job3" ist erfolgreich gelaufen, "job1" läuft +gerade, und "job2" hängt von "job1" ab, d.h. kann erst nach dem erfolgreichen Lauf +von "job2" starten. ## Detailansicht eines Jobs ![](images/screenshot-githubci-job.png){width="70%"} -Wenn man in einen Job anklickt, bekommt man quasi die Konsolenausgabe -dieses Jobs. Hier kann man ggf. Fehler beim Ausführen der einzelnen Skripte -oder die Ergebnisse beispielsweise der JUnit-Läufe anschauen. +Wenn man in einen Job anklickt, bekommt man quasi die Konsolenausgabe dieses Jobs. +Hier kann man ggf. Fehler beim Ausführen der einzelnen Skripte oder die Ergebnisse +beispielsweise der JUnit-Läufe anschauen. ## GitHub Actions: Konfiguration mit YAML-Datei Workflows werden als YAML-Dateien im Ordner `.github/workflows/` angelegt. -```yaml +``` yaml name: GitHub CI on: @@ -385,74 +317,141 @@ Der Name des Workflows wird mit dem Eintrag `name` spezifiziert und sollte sich Dateinamen widerspiegeln, also im Beispiel `.github/workflows/github_ci.yml`. Im Eintrag `on` können die Events definiert werden, die den Workflow triggern. Im -Beispiel ist ein Push-Event auf dem `master`-Branch definiert sowie mit `workflow_dispatch:` -das manuelle Triggern (auf einem beliebigen Branch) freigeschaltet. +Beispiel ist ein Push-Event auf dem `master`-Branch definiert sowie mit +`workflow_dispatch:` das manuelle Triggern (auf einem beliebigen Branch) +freigeschaltet. ### Jobs -Die Jobs werden unter dem Eintrag `jobs` definiert: `job1`, `job2` und `job3` definieren -jeweils einen Job. +Die Jobs werden unter dem Eintrag `jobs` definiert: `job1`, `job2` und `job3` +definieren jeweils einen Job. -* `job1` besteht aus mehreren Befehlen (unter `steps`), die auf einem aktuellen +- `job1` besteht aus mehreren Befehlen (unter `steps`), die auf einem aktuellen virtualisierten Ubuntu-Runner ausgeführt werden. Es wird zunächst das Repo mit Hilfe der Checkout-Action ausgecheckt (`uses: actions/checkout@v4`), das JDK eingerichtet/installiert - (`uses: actions/setup-java@v3`) und der im Repo enthaltene Gradle-Wrapper - auf Unversehrtheit geprüft (`uses: gradle/wrapper-validation-action@v1`). - - Die Actions sind vordefinierte Actions und im Github unter `github.com/` + Action - zu finden, d.h. [`actions/checkout`](https://github.com/actions/checkout) oder - [`actions/setup-java`](https://github.com/actions/setup-java). Actions können - von jedermann definiert und bereitgestellt werden, in diesem Fall handelt es sich - um von GitHub selbst im Namespace "actions" bereit gestellte direkt nutzbare Actions. - Man kann Actions auch selbst im Ordner `.github/actions/` für das Repo definieren - (Beispiel: + (`uses: actions/setup-java@v3`) und der im Repo enthaltene Gradle-Wrapper auf + Unversehrtheit geprüft (`uses: gradle/wrapper-validation-action@v1`). + + Die Actions sind vordefinierte Actions und im Github unter `github.com/` + + Action zu finden, d.h. [`actions/checkout`](https://github.com/actions/checkout) + oder [`actions/setup-java`](https://github.com/actions/setup-java). Actions + können von jedermann definiert und bereitgestellt werden, in diesem Fall handelt + es sich um von GitHub selbst im Namespace "actions" bereit gestellte direkt + nutzbare Actions. Man kann Actions auch selbst im Ordner `.github/actions/` für + das Repo definieren (Beispiel: [plfa.github.io](https://github.com/plfa/plfa.github.io/blob/dev/.github/actions/setup-haskell/action.yml)). - Mit `run` werden Befehle in der Shell auf dem genutzten Runner (hier Ubuntu) ausgeführt. + Mit `run` werden Befehle in der Shell auf dem genutzten Runner (hier Ubuntu) + ausgeführt. -* Die Jobs `job2` ist von `job1` abhängig und wird erst gestartet, wenn `job1` erfolgreich - abgearbeitet ist. +- Die Jobs `job2` ist von `job1` abhängig und wird erst gestartet, wenn `job1` + erfolgreich abgearbeitet ist. Ansonsten können die Jobs prinzipiell parallel ausgeführt werden. -Durch die Kombination von Workflows mit verschiedenen Jobs und Abhängigkeiten zwischen Jobs -lassen sich unterschiedliche Pipelines ("Workflows") für verschiedene Zwecke definieren. +Durch die Kombination von Workflows mit verschiedenen Jobs und Abhängigkeiten +zwischen Jobs lassen sich unterschiedliche Pipelines ("Workflows") für verschiedene +Zwecke definieren. -Es lassen sich auch andere Runner benutzen, etwa ein virtualisiertes Windows oder macOS. -Man kann auch über einen "Matrix-Build" den Workflow auf mehreren Betriebssystemen gleichzeitig -laufen lassen. +Es lassen sich auch andere Runner benutzen, etwa ein virtualisiertes Windows oder +macOS. Man kann auch über einen "Matrix-Build" den Workflow auf mehreren +Betriebssystemen gleichzeitig laufen lassen. -Man kann auch einen Docker-Container benutzen. Dabei muss man beachten, dass dieser am besten -aus einer Registry (etwa von Docker-Hub oder aus der GitHub-Registry) "gezogen" wird, weil das -Bauen des Docker-Containers aus einem Docker-File in der Action u.U. relativ lange dauert. +Man kann auch einen Docker-Container benutzen. Dabei muss man beachten, dass dieser +am besten aus einer Registry (etwa von Docker-Hub oder aus der GitHub-Registry) +"gezogen" wird, weil das Bauen des Docker-Containers aus einem Docker-File in der +Action u.U. relativ lange dauert. ## Hinweise zur Konfiguration von GitHub Actions Im Browser in den Repo-Einstellungen arbeiten: -1. Unter `Settings > Actions > General > Actions permissions` die Actions aktivieren - (Auswahl, welche Actions erlaubt sind) +1. Unter `Settings > Actions > General > Actions permissions` die Actions + aktivieren (Auswahl, welche Actions erlaubt sind) ![](images/screenshot_github_settings_actions.png){width="70%"} -2. Unter `Settings > Actions > General > Workflow permissions` ggf. bestimmen, ob die - Actions das Repo nur lesen dürfen oder auch zusätzlich schreiben dürfen +2. Unter `Settings > Actions > General > Workflow permissions` ggf. bestimmen, ob + die Actions das Repo nur lesen dürfen oder auch zusätzlich schreiben dürfen ![](images/screenshot_github_settings_permissions.png){width="70%"} 3. Unter `Actions > ` den Workflow ggf. deaktivieren: ![](images/screenshot_github_actions.png){width="70%"} -::::::::: - +::: # Wrap-Up Überblick über Continuous Integration: -* Konfigurierbare Aktionen, die auf dem Gitlab-/GitHub-Server ausgeführt werden -* Unterschiedliche Trigger: Commit, Merge, ... -* Aktionen können Branch-spezifisch sein -* Aktionen können von anderen Aktionen abhängen +- Konfigurierbare Aktionen, die auf dem Gitlab-/GitHub-Server ausgeführt werden +- Unterschiedliche Trigger: Commit, Merge, ... +- Aktionen können Branch-spezifisch sein +- Aktionen können von anderen Aktionen abhängen + +::: readings +- @GitlabCI +- @GitHubCI +::: + +::: outcomes +- k2: Ich kann Arbeitsweise von/mit CI (GitHub, GitLab) erklären +::: + +::: challenges +Betrachten Sie erneut das Projekt [Theatrical Players Refactoring +Kata](https://github.com/emilybache/Theatrical-Players-Refactoring-Kata). Erstellen +Sie für dieses Projekt einen GitHub-Workflow, der das Projekt kompiliert und die +Testsuite ausführt (nur für den Java-Teil, den restlichen Code können Sie +ignorieren). + +Dabei soll das Ausführen der JUnit-Tests nur dann erfolgen, wenn das Kompilieren +erfolgreich durchgeführt wurde. + +Der Workflow soll automatisch für Commits in den Hauptbranch sowie für Pull-Requests +loslaufen. Es soll zusätzlich auch manuell aktivierbar sein. + + +::: diff --git a/lecture/building/docker.md b/lecture/building/docker.md index 71c79d6d2..419ed1353 100644 --- a/lecture/building/docker.md +++ b/lecture/building/docker.md @@ -1,72 +1,54 @@ --- -title: "Einführung in Docker" -author: "Carsten Gips (HSBI)" -readings: - - "@Ullenboom2021" - - "@Inden2013" -tldr: | - Container sind im Gegensatz zu herkömmlichen VMs eine schlanke Virtualisierungslösung. - Dabei laufen die Prozesse direkt im Kernel des Host-Betriebssystems, aber abgeschottet - von den anderen Prozessen durch Linux-Techniken wie `cgroups` und `namespaces` (unter - Windows kommt dafür der WSL2 zum Einsatz, unter macOS wird eine kleine Virtualisierung - genutzt). - - Container sind sehr nützlich, wenn man an mehreren Stellen eine identische Arbeitsumgebung - benötigt. Man kann dabei entweder die Images (fertige Dateien) oder die Dockerfiles - (Anweisungen zum Erzeugen eines Images) im Projekt verteilen. Tatsächlich ist es nicht - unüblich, ein Dockerfile in das Projekt-Repo mit einzuchecken. - - Durch Container hat man allerdings im Gegensatz zu herkömmlichen VMs keinen Sicherheitsgewinn, - da die im Container laufende Software ja direkt auf dem Host-Betriebssystem ausgeführt wird. - - Es gibt auf DockerHub fertige Images, die man sich ziehen und starten kann. Ein solches - gestartetes Image nennt sich dann Container und enthält beispielsweise Dateien, die - in den Container gemountet oder kopiert werden. Man kann auch eigene Images bauen, indem - man eine entsprechende Konfiguration (Dockerfile) schreibt. Jeder Befehl bei der Erstellung - eines Images erzeugt einen neuen Layer, die sich dadurch mehrere Images teilen können. - - In der Konfiguration einer Gitlab-CI-Pipeline kann man mit `image` ein Docker-Image - angeben, welches dann in der Pipeline genutzt wird. - - VSCode kann über das Remote-Plugin sich (u.a.) mit Containern verbinden und dann im - Container arbeiten (editieren, compilieren, debuggen, testen, ...). - - In dieser kurzen Einheit kann ich Ihnen nur einen ersten Einstieg in das Thema geben. - Wir haben uns beispielsweise nicht Docker Compose oder Kubernetes angeschaut, und auch - die Themen Netzwerk (zwischen Containern oder zwischen Containern und anderen Rechnern) - und Volumes habe ich außen vor gelassen. Dennoch kommt man in der Praxis bereits mit - den hier vermittelten Basiskenntnissen erstaunlich weit ... -outcomes: - - k2: "Ich kann zwischen Containern und VMs unterscheiden" - - k1: "Ich kenne typische Einsatzgebiete für Container" - - k2: "Ich verstehe, dass Container als abgeschottete Prozesse auf dem Host laufen - kein Sandbox-Effekt" - - k3: "Ich kann Container von DockerHub ziehen" - - k3: "Ich kann Container starten" - - k3: "Ich kann eigene Container definieren und bauen" - - k3: "Ich kann Container in GitLab CI/CD und/oder GitHub Actions einsetzen" - - k3: "Ich kann VSCode mit Containern einsetzen" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106227&client_id=FH-Bielefeld" -# name: "Quiz Docker (ILIAS)" -youtube: - - link: "https://youtu.be/yERVMfUAano" - name: "VL Einführung in Docker" - - link: "https://youtu.be/LE_QcHqUg9Y" - name: "Demo Container in der Konsole" - - link: "https://youtu.be/3Tj3lhcoKro" - name: "Demo GitLab CI/CD und Docker" - - link: "https://youtu.be/jrxoax2fPRI" - name: "Demo GitHub Actions und Docker" - - link: "https://youtu.be/Rs1W_rXkoNM" - name: "Demo VSCode und Docker" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/54268841fd04fa032e9ef425acd88a576a94f572e8179fba131126a0f854adaf8118b42ebcf90451649fd07913a4d873ee5459a1f5a58250ded9749352edcb78" - name: "VL Einführung in Docker" +author: Carsten Gips (HSBI) +title: Einführung in Docker --- +::: tldr +Container sind im Gegensatz zu herkömmlichen VMs eine schlanke +Virtualisierungslösung. Dabei laufen die Prozesse direkt im Kernel des +Host-Betriebssystems, aber abgeschottet von den anderen Prozessen durch +Linux-Techniken wie `cgroups` und `namespaces` (unter Windows kommt dafür der WSL2 +zum Einsatz, unter macOS wird eine kleine Virtualisierung genutzt). + +Container sind sehr nützlich, wenn man an mehreren Stellen eine identische +Arbeitsumgebung benötigt. Man kann dabei entweder die Images (fertige Dateien) oder +die Dockerfiles (Anweisungen zum Erzeugen eines Images) im Projekt verteilen. +Tatsächlich ist es nicht unüblich, ein Dockerfile in das Projekt-Repo mit +einzuchecken. + +Durch Container hat man allerdings im Gegensatz zu herkömmlichen VMs keinen +Sicherheitsgewinn, da die im Container laufende Software ja direkt auf dem +Host-Betriebssystem ausgeführt wird. + +Es gibt auf DockerHub fertige Images, die man sich ziehen und starten kann. Ein +solches gestartetes Image nennt sich dann Container und enthält beispielsweise +Dateien, die in den Container gemountet oder kopiert werden. Man kann auch eigene +Images bauen, indem man eine entsprechende Konfiguration (Dockerfile) schreibt. +Jeder Befehl bei der Erstellung eines Images erzeugt einen neuen Layer, die sich +dadurch mehrere Images teilen können. + +In der Konfiguration einer Gitlab-CI-Pipeline kann man mit `image` ein Docker-Image +angeben, welches dann in der Pipeline genutzt wird. + +VSCode kann über das Remote-Plugin sich (u.a.) mit Containern verbinden und dann im +Container arbeiten (editieren, compilieren, debuggen, testen, ...). + +In dieser kurzen Einheit kann ich Ihnen nur einen ersten Einstieg in das Thema +geben. Wir haben uns beispielsweise nicht Docker Compose oder Kubernetes angeschaut, +und auch die Themen Netzwerk (zwischen Containern oder zwischen Containern und +anderen Rechnern) und Volumes habe ich außen vor gelassen. Dennoch kommt man in der +Praxis bereits mit den hier vermittelten Basiskenntnissen erstaunlich weit ... +::: +::: youtube +- [VL Einführung in Docker](https://youtu.be/yERVMfUAano) +- [Demo Container in der Konsole](https://youtu.be/LE_QcHqUg9Y) +- [Demo GitLab CI/CD und Docker](https://youtu.be/3Tj3lhcoKro) +- [Demo GitHub Actions und Docker](https://youtu.be/jrxoax2fPRI) +- [Demo VSCode und Docker](https://youtu.be/Rs1W_rXkoNM) +::: -# Motivation CI/CD: WFM (_Works For Me_) +# Motivation CI/CD: WFM (*Works For Me*) ![](images/ci.png){width="80%" web_width="60%"} @@ -79,84 +61,91 @@ CI-Server und werden üblicherweise nicht direkt installiert, sondern über eine Virtualisierung bereitgestellt. Selbst wenn man keine CI-Pipelines einsetzt, hat man in Projekten mit mehreren -beteiligten Personen häufig das Problem "_WFM_" ("works for me"). Jeder Entwickler -hat sich auf ihrem Rechner eine Entwicklungsumgebung aufgesetzt und nutzt in der Regel -seine bevorzugte IDE oder sogar unterschiedliche JDK-Versionen ... Dadurch kann es -schnell passieren, dass Probleme oder Fehler auftreten, die sich nicht von allen -Beteiligten immer nachvollziehen lassen. Hier wäre eine einheitliche Entwicklungsumgebung -sinnvoll, die in einer "schlanken" Virtualisierung bereitgestellt wird. +beteiligten Personen häufig das Problem "*WFM*" ("works for me"). Jeder Entwickler +hat sich auf ihrem Rechner eine Entwicklungsumgebung aufgesetzt und nutzt in der +Regel seine bevorzugte IDE oder sogar unterschiedliche JDK-Versionen ... Dadurch +kann es schnell passieren, dass Probleme oder Fehler auftreten, die sich nicht von +allen Beteiligten immer nachvollziehen lassen. Hier wäre eine einheitliche +Entwicklungsumgebung sinnvoll, die in einer "schlanken" Virtualisierung +bereitgestellt wird. Als Entwickler kann man zeitgleich in verschiedenen Projekten beteiligt sein, die unterschiedliche Anforderungen an die Entwicklungstools mit sich bringen. Es könnte -beispielsweise passieren, dass man zeitgleich drei bestimmte Python-Versionen benötigt. -In den meisten Fällen schafft man es (mit ein wenig Aufwand), diese Tools nebeneinander -zu installieren. Oft ist das in der Praxis aber schwierig und fehleranfällig. +beispielsweise passieren, dass man zeitgleich drei bestimmte Python-Versionen +benötigt. In den meisten Fällen schafft man es (mit ein wenig Aufwand), diese Tools +nebeneinander zu installieren. Oft ist das in der Praxis aber schwierig und +fehleranfällig. In diesen Fällen kann eine Virtualisierung helfen. ::: - -# Virtualisierung: Container vs. VM +# Virtualisierung: Container vs. VM ![](images/virtualisierung.png){width="80%" web_width="50%"} ::: notes Wenn man über Virtualisierung auf dem Desktop spricht, kann man grob zwei Varianten unterscheiden. In beiden Fällen ist die Basis die Hardware (Laptop, Desktop-Rechner) -und das darauf laufende (Host-) Betriebssystem (Linux, FreeBSD, macOS, Windows, ...). -Darauf läuft dann wiederum die Virtualisierung. - -Im rechten Bild wird eine herkömmliche Virtualisierung mit virtuellen Maschinen (_VM_) -dargestellt. Dabei wird in der VM ein komplettes Betriebssystem (das "Gast-Betriebssystem") -installiert und darin läuft dann die gewünschte Anwendung. Die Virtualisierung (VirtualBox, -VMware, ...) läuft dabei als Anwendung auf dem Host-Betriebssystem und stellt dem -Gast-Betriebssystem in der VM einen Rechner mit CPU, RAM, ... zur Verfügung und übersetzt -die Systemaufrufe in der VM in die entsprechenden Aufrufe im Host-Betriebssystem. Dies benötigt -in der Regel entsprechende Ressourcen: Durch das komplette Betriebssystem in der VM ist eine -VM (die als Datei im Filesystem des Host-Betriebssystems liegt) oft mehrere 10GB groß. Für die -Übersetzung werden zusätzlich Hardwareressourcen benötigt, d.h. hier gehen CPU-Zyklen und RAM -"verloren" ... Das Starten einer VM dauert entsprechend lange, da hier ein komplettes -Betriebssystem hochgefahren werden muss. Dafür sind die Prozesse in einer VM relativ stark -vom Host-Betriebssystem abgekapselt, so dass man hier von einer "Sandbox" sprechen kann: Viren -o.ä. können nicht so leicht aus einer VM "ausbrechen" und auf das Host-Betriebssystem zugreifen -(quasi nur über Lücken im Gast-Betriebssystem kombiniert mit Lücken in der Virtualisierungssoftware). - -Im linken Bild ist eine schlanke Virtualisierung auf Containerbasis dargestellt. Die Anwendungen -laufen direkt als Prozesse im Host-Betriebssystem, ein Gast-Betriebssystem ist nicht notwendig. -Durch den geschickten Einsatz von `namespaces` und `cgroups` und anderen in Linux und FreeBSD -verfügbaren Techniken werden die Prozesse abgeschottet, d.h. der im Container laufende Prozess -"sieht" die anderen Prozesse des Hosts nicht. Die Erstellung und Steuerung der Container übernimmt -hier beispielsweise Docker. Die Container sind dabei auch wieder Dateien im Host-Filesystem. -Dadurch benötigen Container wesentlich weniger Platz als herkömmliche VMs, der Start einer Anwendung -geht deutlich schneller und die Hardwareressourcen (CPU, RAM, ...) werden effizient genutzt. -Nachteilig ist, dass hier in der Regel ein Linux-Host benötigt wird (für Windows wird mittlerweile -der Linux-Layer (_WSL_) genutzt; für macOS wurde bisher eine Linux-VM im Hintergrund hochgefahren, -mittlerweile wird aber eine eigene schlanke Virtualisierung eingesetzt). Außerdem steht im Container -üblicherweise kein graphisches Benutzerinterface zur Verfügung. Da die Prozesse direkt im -Host-Betriebssystem laufen, stellen Container keine Sicherheitsschicht ("Sandboxen") dar! - -In allen Fällen muss die Hardwarearchitektur beachtet werden: Auf einer Intel-Maschine können -normalerweise keine VMs/Container basierend auf ARM-Architektur ausgeführt werden und umgekehrt. +und das darauf laufende (Host-) Betriebssystem (Linux, FreeBSD, macOS, Windows, +...). Darauf läuft dann wiederum die Virtualisierung. + +Im rechten Bild wird eine herkömmliche Virtualisierung mit virtuellen Maschinen +(*VM*) dargestellt. Dabei wird in der VM ein komplettes Betriebssystem (das +"Gast-Betriebssystem") installiert und darin läuft dann die gewünschte Anwendung. +Die Virtualisierung (VirtualBox, VMware, ...) läuft dabei als Anwendung auf dem +Host-Betriebssystem und stellt dem Gast-Betriebssystem in der VM einen Rechner mit +CPU, RAM, ... zur Verfügung und übersetzt die Systemaufrufe in der VM in die +entsprechenden Aufrufe im Host-Betriebssystem. Dies benötigt in der Regel +entsprechende Ressourcen: Durch das komplette Betriebssystem in der VM ist eine VM +(die als Datei im Filesystem des Host-Betriebssystems liegt) oft mehrere 10GB groß. +Für die Übersetzung werden zusätzlich Hardwareressourcen benötigt, d.h. hier gehen +CPU-Zyklen und RAM "verloren" ... Das Starten einer VM dauert entsprechend lange, da +hier ein komplettes Betriebssystem hochgefahren werden muss. Dafür sind die Prozesse +in einer VM relativ stark vom Host-Betriebssystem abgekapselt, so dass man hier von +einer "Sandbox" sprechen kann: Viren o.ä. können nicht so leicht aus einer VM +"ausbrechen" und auf das Host-Betriebssystem zugreifen (quasi nur über Lücken im +Gast-Betriebssystem kombiniert mit Lücken in der Virtualisierungssoftware). + +Im linken Bild ist eine schlanke Virtualisierung auf Containerbasis dargestellt. Die +Anwendungen laufen direkt als Prozesse im Host-Betriebssystem, ein +Gast-Betriebssystem ist nicht notwendig. Durch den geschickten Einsatz von +`namespaces` und `cgroups` und anderen in Linux und FreeBSD verfügbaren Techniken +werden die Prozesse abgeschottet, d.h. der im Container laufende Prozess "sieht" die +anderen Prozesse des Hosts nicht. Die Erstellung und Steuerung der Container +übernimmt hier beispielsweise Docker. Die Container sind dabei auch wieder Dateien +im Host-Filesystem. Dadurch benötigen Container wesentlich weniger Platz als +herkömmliche VMs, der Start einer Anwendung geht deutlich schneller und die +Hardwareressourcen (CPU, RAM, ...) werden effizient genutzt. Nachteilig ist, dass +hier in der Regel ein Linux-Host benötigt wird (für Windows wird mittlerweile der +Linux-Layer (*WSL*) genutzt; für macOS wurde bisher eine Linux-VM im Hintergrund +hochgefahren, mittlerweile wird aber eine eigene schlanke Virtualisierung +eingesetzt). Außerdem steht im Container üblicherweise kein graphisches +Benutzerinterface zur Verfügung. Da die Prozesse direkt im Host-Betriebssystem +laufen, stellen Container keine Sicherheitsschicht ("Sandboxen") dar! + +In allen Fällen muss die Hardwarearchitektur beachtet werden: Auf einer +Intel-Maschine können normalerweise keine VMs/Container basierend auf +ARM-Architektur ausgeführt werden und umgekehrt. ::: - # Getting started -* DockerHub: fertige Images => [hub.docker.com/search](https://hub.docker.com/search?q=&type=image) +- DockerHub: fertige Images =\> + [hub.docker.com/search](https://hub.docker.com/search?q=&type=image) \smallskip -* Image downloaden: `docker pull ` -* Image starten: `docker run ` +- Image downloaden: `docker pull ` +- Image starten: `docker run ` ::: notes ## Begriffe -* **Docker-File**: Beschreibungsdatei, wie Docker ein Image erzeugen soll. -* **Image**: Enthält die Dinge, die lt. dem Docker-File in das Image gepackt werden sollen. - Kann gestartet werden und erzeugt damit einen Container. -* **Container**: Ein laufendes Images (genauer: eine laufende Instanz eines Images). Kann - dann auch zusätzliche Daten enthalten. +- **Docker-File**: Beschreibungsdatei, wie Docker ein Image erzeugen soll. +- **Image**: Enthält die Dinge, die lt. dem Docker-File in das Image gepackt + werden sollen. Kann gestartet werden und erzeugt damit einen Container. +- **Container**: Ein laufendes Images (genauer: eine laufende Instanz eines + Images). Kann dann auch zusätzliche Daten enthalten. ## Beispiele ::: @@ -168,67 +157,64 @@ normalerweise keine VMs/Container basierend auf ARM-Architektur ausgeführt werd **Beispiele** ::: -``` -docker pull debian:stable-slim -docker run --rm -it debian:stable-slim /bin/sh -``` + docker pull debian:stable-slim + docker run --rm -it debian:stable-slim /bin/sh ::: notes -`debian` ist ein fertiges Images, welches über DockerHub bereit gestellt wird. Mit dem -Postfix `stable-slim` wird eine bestimmte Version angesprochen. +`debian` ist ein fertiges Images, welches über DockerHub bereit gestellt wird. Mit +dem Postfix `stable-slim` wird eine bestimmte Version angesprochen. Mit `docker run debian:stable-slim` startet man das Image, es wird ein Container erzeugt. Dieser enthält den aktuellen Datenstand, d.h. wenn man im Image eine Datei anlegt, wäre diese dann im Container enthalten. -Mit der Option `--rm` wird der Container nach Beendigung automatisch wieder gelöscht. Da -jeder Aufruf von `docker run ` einen neuen Container erzeugt, würden sich sonst -recht schnell viele Container auf dem Dateisystem des Hosts ansammeln, die man dann manuell -aufräumen müsste. Man kann aber einen beendeten Container auch erneut laufen lassen ... -(vgl. Dokumentation von `docker`). Mit der Option `--rm` sind aber auch im Container angelegte -Daten wieder weg! Mit der Option `-it` wird der Container interaktiv gestartet und man landet -in einer Shell. - -Bei der Definition eines Images kann ein "_Entry Point_" definiert werden, d.h. ein Programm, -welches automatisch beim Start des Container ausgeführt wird. Häufig erlauben Images aber auch, -beim Start ein bestimmtes auszuführendes Programm anzugeben. Im obigen Beispiel ist das `/bin/sh`, -also eine Shell ... +Mit der Option `--rm` wird der Container nach Beendigung automatisch wieder +gelöscht. Da jeder Aufruf von `docker run ` einen neuen Container erzeugt, +würden sich sonst recht schnell viele Container auf dem Dateisystem des Hosts +ansammeln, die man dann manuell aufräumen müsste. Man kann aber einen beendeten +Container auch erneut laufen lassen ... (vgl. Dokumentation von `docker`). Mit der +Option `--rm` sind aber auch im Container angelegte Daten wieder weg! Mit der Option +`-it` wird der Container interaktiv gestartet und man landet in einer Shell. + +Bei der Definition eines Images kann ein "*Entry Point*" definiert werden, d.h. ein +Programm, welches automatisch beim Start des Container ausgeführt wird. Häufig +erlauben Images aber auch, beim Start ein bestimmtes auszuführendes Programm +anzugeben. Im obigen Beispiel ist das `/bin/sh`, also eine Shell ... ::: \smallskip -``` -docker pull openjdk:latest -docker run --rm -v "$PWD":/data -w /data openjdk:latest javac Hello.java -docker run --rm -v "$PWD":/data -w /data openjdk:latest java Hello -``` + docker pull openjdk:latest + docker run --rm -v "$PWD":/data -w /data openjdk:latest javac Hello.java + docker run --rm -v "$PWD":/data -w /data openjdk:latest java Hello ::: notes -Auch für Java gibt es vordefinierte Images mit einem JDK. Das Tag "`latest`" zeigt dabei auf die -letzte stabile Version des `openjdk`-Images. Üblicherweise wird "`latest`" von den Entwicklern immer -wieder weiter geschoben, d.h. auch bei anderen Images gibt es ein "`latest`"-Tag. Gleichzeitig ist -es die Default-Einstellung für die Docker-Befehle, d.h. es kann auch weggelassen werden: -`docker run openjdk:latest` und `docker run openjdk` sind gleichwertig. Alternativ kann man hier auch -hier wieder eine konkrete Version angeben. - -Über die Option `-v` wird ein Ordner auf dem Host (hier durch `"$PWD"` dynamisch ermittelt) in den -Container eingebunden ("gemountet"), hier auf den Ordner `/data`. Dort sind dann die Dateien sichtbar, -die im Ordner `"$PWD"` enthalten sind. Über die Option `-w` kann ein Arbeitsverzeichnis definiert -werden. - -Mit `javac Hello.java` wird `javac` im Container aufgerufen auf der Datei `/data/Hello.java` -im Container, d.h. die Datei `Hello.java`, die im aktuellen Ordner des Hosts liegt (und in den -Container gemountet wurde). Das Ergebnis (`Hello.class`) wird ebenfalls in den Ordner `/data/` -im Container geschrieben und erscheint dann im Arbeitsverzeichnis auf dem Host ... Analog kann -dann mit `java Hello` die Klasse ausgeführt werden. +Auch für Java gibt es vordefinierte Images mit einem JDK. Das Tag "`latest`" zeigt +dabei auf die letzte stabile Version des `openjdk`-Images. Üblicherweise wird +"`latest`" von den Entwicklern immer wieder weiter geschoben, d.h. auch bei anderen +Images gibt es ein "`latest`"-Tag. Gleichzeitig ist es die Default-Einstellung für +die Docker-Befehle, d.h. es kann auch weggelassen werden: +`docker run openjdk:latest` und `docker run openjdk` sind gleichwertig. Alternativ +kann man hier auch hier wieder eine konkrete Version angeben. + +Über die Option `-v` wird ein Ordner auf dem Host (hier durch `"$PWD"` dynamisch +ermittelt) in den Container eingebunden ("gemountet"), hier auf den Ordner `/data`. +Dort sind dann die Dateien sichtbar, die im Ordner `"$PWD"` enthalten sind. Über die +Option `-w` kann ein Arbeitsverzeichnis definiert werden. + +Mit `javac Hello.java` wird `javac` im Container aufgerufen auf der Datei +`/data/Hello.java` im Container, d.h. die Datei `Hello.java`, die im aktuellen +Ordner des Hosts liegt (und in den Container gemountet wurde). Das Ergebnis +(`Hello.class`) wird ebenfalls in den Ordner `/data/` im Container geschrieben und +erscheint dann im Arbeitsverzeichnis auf dem Host ... Analog kann dann mit +`java Hello` die Klasse ausgeführt werden. ::: [Demo: Container in der Konsole]{.ex href="https://youtu.be/LE_QcHqUg9Y"} - # Images selbst definieren -```docker +``` docker FROM debian:stable-slim ARG USERNAME=pandoc @@ -253,48 +239,50 @@ USER $USERNAME `docker build -t -f .` ::: notes -`FROM` gibt die Basis an, d.h. hier ein Image von Debian in der Variante `stable-slim`, -d.h. das ist der Basis-Layer für das zu bauende Docker-Image. +`FROM` gibt die Basis an, d.h. hier ein Image von Debian in der Variante +`stable-slim`, d.h. das ist der Basis-Layer für das zu bauende Docker-Image. Über `ARG` werden hier Variablen gesetzt. -`RUN` ist der Befehl, der im Image (hier Debian) ausgeführt wird und einen neuen Layer -hinzufügt. In diesen Layer werden alle Dateien eingefügt, die bei der Ausführung des -Befehls erzeugt oder angelegt werden. Hier im Beispiel wird das Debian-Tool `apt-get` -gestartet und weitere Debian-Pakete installiert. - -Da jeder `RUN`-Befehl einen neuen Layer anlegt, werden die restlichen Konfigurationen -ebenfalls in diesem Lauf durchgeführt. Insbesondere wird ein nicht-Root-User angelegt, -der von der UID und GID dem Default-User in Linux entspricht. Die gemounteten Dateien -haben die selben Rechte wie auf dem Host, und durch die Übereinstimmung von UID/GID -sind die Dateien problemlos zugreifbar und man muss nicht mit dem Root-User arbeiten -(dies wird aus offensichtlichen Gründen als Anti-Pattern angesehen). Bevor der `RUN`-Lauf -abgeschlossen wird, werden alle temporären und später nicht benötigten Dateien von -`apt-get` entfernt, damit diese nicht Bestandteil des Layers werden. - -Mit `WORKDIR` und `USER` wird das Arbeitsverzeichnis gesetzt und auf den angegebenen User -umgeschaltet. Damit muss der User nicht mehr beim Aufruf von außen gesetzt werden. - -Über `docker build -t -f .` wird aus dem angegebenen Dockerfile und -dem Inhalt des aktuellen Ordners ("`.`") ein neues Image erzeugt und mit dem angegebenen -Namen benannt. - - -**Hinweis zum Umgang mit Containern und Updates**: -Bei der Erstellung eines Images sind bestimmte Softwareversionen Teil des Images geworden. -Man kann prinzipiell in einem Container die Software aktualisieren, aber dies geht in dem -Moment wieder verloren, wo der Container beendet und gelöscht wird. Außerdem widerspricht -dies dem Gedanken, dass mehrere Personen mit dem selben Image/Container arbeiten und damit -auch die selben Versionsstände haben. In der Praxis löscht man deshalb das alte Image einfach -und erstellt ein neues, welches dann die aktualisierte Software enthält. -::: +`RUN` ist der Befehl, der im Image (hier Debian) ausgeführt wird und einen neuen +Layer hinzufügt. In diesen Layer werden alle Dateien eingefügt, die bei der +Ausführung des Befehls erzeugt oder angelegt werden. Hier im Beispiel wird das +Debian-Tool `apt-get` gestartet und weitere Debian-Pakete installiert. + +Da jeder `RUN`-Befehl einen neuen Layer anlegt, werden die restlichen +Konfigurationen ebenfalls in diesem Lauf durchgeführt. Insbesondere wird ein +nicht-Root-User angelegt, der von der UID und GID dem Default-User in Linux +entspricht. Die gemounteten Dateien haben die selben Rechte wie auf dem Host, und +durch die Übereinstimmung von UID/GID sind die Dateien problemlos zugreifbar und man +muss nicht mit dem Root-User arbeiten (dies wird aus offensichtlichen Gründen als +Anti-Pattern angesehen). Bevor der `RUN`-Lauf abgeschlossen wird, werden alle +temporären und später nicht benötigten Dateien von `apt-get` entfernt, damit diese +nicht Bestandteil des Layers werden. + +Mit `WORKDIR` und `USER` wird das Arbeitsverzeichnis gesetzt und auf den angegebenen +User umgeschaltet. Damit muss der User nicht mehr beim Aufruf von außen gesetzt +werden. -[Beispiel: debian-latex.df]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/building/src/docker/debian-latex.df"} +Über `docker build -t -f .` wird aus dem angegebenen Dockerfile +und dem Inhalt des aktuellen Ordners ("`.`") ein neues Image erzeugt und mit dem +angegebenen Namen benannt. + +**Hinweis zum Umgang mit Containern und Updates**: Bei der Erstellung eines Images +sind bestimmte Softwareversionen Teil des Images geworden. Man kann prinzipiell in +einem Container die Software aktualisieren, aber dies geht in dem Moment wieder +verloren, wo der Container beendet und gelöscht wird. Außerdem widerspricht dies dem +Gedanken, dass mehrere Personen mit dem selben Image/Container arbeiten und damit +auch die selben Versionsstände haben. In der Praxis löscht man deshalb das alte +Image einfach und erstellt ein neues, welches dann die aktualisierte Software +enthält. +::: +[Beispiel: debian-latex.df]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/building/src/docker/debian-latex.df"} # CI-Pipeline (GitLab) -```yaml +``` yaml default: image: openjdk:17 @@ -308,22 +296,21 @@ job1: ``` ::: notes -In den Gitlab-CI-Pipelines (analog wie in den GitHub-Actions) kann man Docker-Container -für die Ausführung der Pipeline nutzen. - -Mit `image: openjdk:17` wird das Docker-Image `openjdk:17` vom DockerHub geladen und durch -den Runner für die Stages als Container ausgeführt. Die Aktionen im `script`-Teil, wie -beispielsweise `javac Hello.java` werden vom Runner an die Standard-Eingabe der Shell des -Containers gesendet. Im Prinzip entspricht das dem Aufruf auf dem lokalen Rechner: -`docker run openjdk:17 javac Hello.java`. +In den Gitlab-CI-Pipelines (analog wie in den GitHub-Actions) kann man +Docker-Container für die Ausführung der Pipeline nutzen. + +Mit `image: openjdk:17` wird das Docker-Image `openjdk:17` vom DockerHub geladen und +durch den Runner für die Stages als Container ausgeführt. Die Aktionen im +`script`-Teil, wie beispielsweise `javac Hello.java` werden vom Runner an die +Standard-Eingabe der Shell des Containers gesendet. Im Prinzip entspricht das dem +Aufruf auf dem lokalen Rechner: `docker run openjdk:17 javac Hello.java`. ::: [Demo: GitLab CI/CD und Docker]{.ex href="https://youtu.be/3Tj3lhcoKro"} - # CI-Pipeline (GitHub) -```yaml +``` yaml name: demo on: push: @@ -346,17 +333,18 @@ jobs: https://stackoverflow.com/questions/71283311/run-github-workflow-on-docker-image-with-a-dockerfile https://docs.github.com/en/actions/using-jobs/running-jobs-in-a-container -In den GitHub-Actions kann man Docker-Container für die Ausführung der Pipeline nutzen. +In den GitHub-Actions kann man Docker-Container für die Ausführung der Pipeline +nutzen. -Mit `docker://openjdk:17` wird das Docker-Image `openjdk:17` vom DockerHub geladen und auf dem -Ubuntu-Runner als Container ausgeführt. Die Aktionen im `steps`-Teil, wie beispielsweise -`javac Hello.java` werden vom Runner an die Standard-Eingabe der Shell des Containers gesendet. -Im Prinzip entspricht das dem Aufruf auf dem lokalen Rechner: `docker run openjdk:17 javac Hello.java`. +Mit `docker://openjdk:17` wird das Docker-Image `openjdk:17` vom DockerHub geladen +und auf dem Ubuntu-Runner als Container ausgeführt. Die Aktionen im `steps`-Teil, +wie beispielsweise `javac Hello.java` werden vom Runner an die Standard-Eingabe der +Shell des Containers gesendet. Im Prinzip entspricht das dem Aufruf auf dem lokalen +Rechner: `docker run openjdk:17 javac Hello.java`. ::: [Demo: GitHub Actions und Docker]{.ex href="https://youtu.be/jrxoax2fPRI"} - # VSCode und das Plugin "Remote - Containers" ![](images/vscode-remote.png){width="80%"} @@ -364,69 +352,94 @@ Im Prinzip entspricht das dem Aufruf auf dem lokalen Rechner: `docker run openjd ::: notes 1. VSCode (Host): Plugin "Remote - Containers" installieren 2. Docker (Host): Container starten mit Workspace gemountet -3. VSCode (Host): Attach to Container => neues Fenster (Container) +3. VSCode (Host): Attach to Container =\> neues Fenster (Container) 4. VSCode (Container): Plugin "Java Extension Pack" installieren 5. VSCode (Container): Dateien editieren, kompilieren, debuggen, ... -Mit Visual Studio Code (VSC) kann man über SSH oder in einem Container arbeiten. Dazu installiert man sich -VSC lokal auf dem Host und installiert dort das Plugin "Remote - Containers". VSC kann darüber vordefinierte -Docker-Images herunterladen und darin arbeiten oder man kann alternativ einen Container selbst starten und +Mit Visual Studio Code (VSC) kann man über SSH oder in einem Container arbeiten. +Dazu installiert man sich VSC lokal auf dem Host und installiert dort das Plugin +"Remote - Containers". VSC kann darüber vordefinierte Docker-Images herunterladen +und darin arbeiten oder man kann alternativ einen Container selbst starten und diesen mit VSC verbinden ("attachen"). -Beim Verbinden öffnet VSC ein neues Fenster, welches mit dem Container verbunden ist. Nun kann man in diesem -neuen Fenster ganz normal arbeiten, allerdings werden alle Dinge in dem Container erledigt. Man öffnet also -Dateien in diesem Container, editiert sie im Container, übersetzt und testet im Container und nutzt dabei die -im Container installierten Tools. Sogar die entsprechenden VSC-Plugins kann man im Container installieren. - -Damit benötigt man auf einem Host eigentlich nur noch VSC und Docker, aber keine Java-Tools o.ä. und kann -diese über einen im Projekt definierten Container (über ein mit versioniertes Dockerfile) nutzen. - -_Anmerkung_: IntelliJ kann remote nur debuggen, d.h. das Editieren, Übersetzen, Testen läuft lokal auf dem -Host (und benötigt dort den entsprechenden Tool-Stack). Für das Debuggen kann Idea das übersetzte Projekt -auf ein Remote (SSH, Docker) schieben und dort debuggen. - - -Noch einen Schritt weiter geht das Projekt [code-server](https://github.com/coder/code-server): Dieses stellt -u.a. ein Docker-Image [codercom/code-server](https://hub.docker.com/r/codercom/code-server) bereit, welches -einen Webserver startet und über diesen kann man ein im Container laufendes (angepasstes) VSC erreichen. Man -braucht also nur noch Docker und das Image und kann dann über den Webbrowser programmieren. Der Projektordner -wird dabei in den Container gemountet, so dass die Dateien entsprechend zur Verfügung stehen: - -```sh +Beim Verbinden öffnet VSC ein neues Fenster, welches mit dem Container verbunden +ist. Nun kann man in diesem neuen Fenster ganz normal arbeiten, allerdings werden +alle Dinge in dem Container erledigt. Man öffnet also Dateien in diesem Container, +editiert sie im Container, übersetzt und testet im Container und nutzt dabei die im +Container installierten Tools. Sogar die entsprechenden VSC-Plugins kann man im +Container installieren. + +Damit benötigt man auf einem Host eigentlich nur noch VSC und Docker, aber keine +Java-Tools o.ä. und kann diese über einen im Projekt definierten Container (über ein +mit versioniertes Dockerfile) nutzen. + +*Anmerkung*: IntelliJ kann remote nur debuggen, d.h. das Editieren, Übersetzen, +Testen läuft lokal auf dem Host (und benötigt dort den entsprechenden Tool-Stack). +Für das Debuggen kann Idea das übersetzte Projekt auf ein Remote (SSH, Docker) +schieben und dort debuggen. + +Noch einen Schritt weiter geht das Projekt +[code-server](https://github.com/coder/code-server): Dieses stellt u.a. ein +Docker-Image [codercom/code-server](https://hub.docker.com/r/codercom/code-server) +bereit, welches einen Webserver startet und über diesen kann man ein im Container +laufendes (angepasstes) VSC erreichen. Man braucht also nur noch Docker und das +Image und kann dann über den Webbrowser programmieren. Der Projektordner wird dabei +in den Container gemountet, so dass die Dateien entsprechend zur Verfügung stehen: + +``` sh docker run -it --name code-server -p 127.0.0.1:8080:8080 -v "$HOME/.config:/home/coder/.config" -v "$PWD:/home/coder/project" codercom/code-server:latest ``` -Auf diesem Konzept setzt auch der kommerzielle Service [GitHub Codespaces](https://github.com/features/codespaces) -von GitHub auf. +Auf diesem Konzept setzt auch der kommerzielle Service [GitHub +Codespaces](https://github.com/features/codespaces) von GitHub auf. ::: [Demo: VSCode und Docker]{.ex href="https://youtu.be/Rs1W_rXkoNM"} - -::::::::: notes +::: notes # Link-Sammlung -* [Wikipedia: Docker](https://en.wikipedia.org/wiki/Docker_(software)) -* [Wikipedia: Virtuelle Maschinen](https://en.wikipedia.org/wiki/Virtual_machine) -* [Docker: Überblick, Container](https://www.docker.com/resources/what-container) -* [Docker: HowTo](https://docs.docker.com/get-started/) -* [DockerHub: Suche nach fertigen Images](https://hub.docker.com/search?q=&type=image) -* [Docker und Java](https://docs.docker.com/language/java/) -* [Dockerfiles: Best Practices](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/) -* [Gitlab, Docker](http://git03-ifm-min.ad.hsbi.de/help/ci/docker/using_docker_images.md#overriding-the-entrypoint-of-an-image) -* [VSCode: Entwickeln in Docker-Containern](https://code.visualstudio.com/docs/remote/containers) -* @DockerInAction und @DockerInPractice -::::::::: - +- [Wikipedia: Docker](https://en.wikipedia.org/wiki/Docker_(software)) +- [Wikipedia: Virtuelle Maschinen](https://en.wikipedia.org/wiki/Virtual_machine) +- [Docker: Überblick, Container](https://www.docker.com/resources/what-container) +- [Docker: HowTo](https://docs.docker.com/get-started/) +- [DockerHub: Suche nach fertigen + Images](https://hub.docker.com/search?q=&type=image) +- [Docker und Java](https://docs.docker.com/language/java/) +- [Dockerfiles: Best + Practices](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/) +- [Gitlab, + Docker](http://git03-ifm-min.ad.hsbi.de/help/ci/docker/using_docker_images.md#overriding-the-entrypoint-of-an-image) +- [VSCode: Entwickeln in + Docker-Containern](https://code.visualstudio.com/docs/remote/containers) +- @DockerInAction und @DockerInPractice +::: # Wrap-Up -* Schlanke Virtualisierung mit Containern (kein eigenes OS) -* _Kein_ Sandbox-Effekt +- Schlanke Virtualisierung mit Containern (kein eigenes OS) +- *Kein* Sandbox-Effekt \smallskip -* Begriffe: Docker-File vs. Image vs. Container -* Ziehen von vordefinierten Images -* Definition eines eigenen Images -* Arbeiten mit Containern: lokal, CI/CD, VSCode ... +- Begriffe: Docker-File vs. Image vs. Container +- Ziehen von vordefinierten Images +- Definition eines eigenen Images +- Arbeiten mit Containern: lokal, CI/CD, VSCode ... + +::: readings +- @Ullenboom2021 +- @Inden2013 +::: + +::: outcomes +- k2: Ich kann zwischen Containern und VMs unterscheiden +- k1: Ich kenne typische Einsatzgebiete für Container +- k2: Ich verstehe, dass Container als abgeschottete Prozesse auf dem Host + laufen - kein Sandbox-Effekt +- k3: Ich kann Container von DockerHub ziehen +- k3: Ich kann Container starten +- k3: Ich kann eigene Container definieren und bauen +- k3: Ich kann Container in GitLab CI/CD und/oder GitHub Actions einsetzen +- k3: Ich kann VSCode mit Containern einsetzen +::: diff --git a/lecture/building/gradle.md b/lecture/building/gradle.md index 271a63269..56eb390f3 100644 --- a/lecture/building/gradle.md +++ b/lecture/building/gradle.md @@ -1,48 +1,33 @@ --- +author: Carsten Gips (HSBI) title: "Build-Systeme: Gradle" -author: "Carsten Gips (HSBI)" -readings: - - "@Gradle" - - "@Ullenboom2021" - - "@Inden2013" -tldr: | - Um beim Übersetzen und Testen von Software von den spezifischen Gegebenheiten auf einem - Entwicklerrechner unabhängig zu werden, werden häufig sogenannte Build-Tools eingesetzt. - Mit diesen konfiguriert man sein Projekt abseits einer IDE und übersetzt, testet und - baut seine Applikation damit entsprechend unabhängig. In der Java-Welt sind aktuell die - Build-Tools Ant, Maven und Gradle weit verbreitet. - - In Gradle ist ein Java-Entwicklungsmodell quasi eingebaut. Über die Konfigurationsskripte - müssen nur noch bestimmte Details wie benötigte externe Bibliotheken oder die Hauptklasse - und sonstige Projektbesonderheiten konfiguriert werden. Über "Tasks" wie `build`, `test` - oder `run` können Java-Projekte übersetzt, getestet und ausgeführt werden. Dabei werden die - externen Abhängigkeiten (Bibliotheken) aufgelöst (soweit konfiguriert) und auch abhängige - Tasks mit erledigt, etwa muss zum Testen vorher der Source-Code übersetzt werden. - - Gradle bietet eine Fülle an Plugins für bestimmte Aufgaben an, die jeweils mit neuen Tasks - einher kommen. Beispiele sind das Plugin `java`, welches weitere Java-spezifische Tasks - wie `classes` mitbringt, oder das Plugin `checkstyle` zum Überprüfen von Coding-Style-Richtlinien. -outcomes: - - k3: "Ich kann einfache Gradle-Skripte schreiben und verstehen" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106220&client_id=FH-Bielefeld" -# name: "Quiz Gradle (ILIAS)" -youtube: - - link: "https://youtu.be/aVtDkFpwd_E" - name: "VL Build-Systeme: Gradle" - - link: "https://youtu.be/OhQRGaNO4iA" - name: "Demo Gradle" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/3af48428e1b62bd42410e2a802cf355309212ea621509aa20833c5e1c486ffe9214b027c29e55d775f135f45ab7d3fcd59d735812d64bc04a71de8d59df8a3f5" - name: "VL Build-Systeme: Gradle" -challenges: | - Betrachten Sie das Buildskript `gradle.build` aus [Dungeon-CampusMinden/Dungeon](https://github.com/Dungeon-CampusMinden/Dungeon/blob/master/build.gradle). - - Erklären Sie, in welche Abschnitte das Buildskript unterteilt ist und welche Aufgaben diese - Abschnitte jeweils erfüllen. Gehen Sie dabei im _Detail_ auf das Plugin `java` und die dort - bereitgestellten Tasks und deren Abhängigkeiten untereinander ein. --- +::: tldr +Um beim Übersetzen und Testen von Software von den spezifischen Gegebenheiten auf +einem Entwicklerrechner unabhängig zu werden, werden häufig sogenannte Build-Tools +eingesetzt. Mit diesen konfiguriert man sein Projekt abseits einer IDE und +übersetzt, testet und baut seine Applikation damit entsprechend unabhängig. In der +Java-Welt sind aktuell die Build-Tools Ant, Maven und Gradle weit verbreitet. + +In Gradle ist ein Java-Entwicklungsmodell quasi eingebaut. Über die +Konfigurationsskripte müssen nur noch bestimmte Details wie benötigte externe +Bibliotheken oder die Hauptklasse und sonstige Projektbesonderheiten konfiguriert +werden. Über "Tasks" wie `build`, `test` oder `run` können Java-Projekte übersetzt, +getestet und ausgeführt werden. Dabei werden die externen Abhängigkeiten +(Bibliotheken) aufgelöst (soweit konfiguriert) und auch abhängige Tasks mit +erledigt, etwa muss zum Testen vorher der Source-Code übersetzt werden. + +Gradle bietet eine Fülle an Plugins für bestimmte Aufgaben an, die jeweils mit neuen +Tasks einher kommen. Beispiele sind das Plugin `java`, welches weitere +Java-spezifische Tasks wie `classes` mitbringt, oder das Plugin `checkstyle` zum +Überprüfen von Coding-Style-Richtlinien. +::: + +::: youtube +- [VL Build-Systeme: Gradle](https://youtu.be/aVtDkFpwd_E) +- [Demo Gradle](https://youtu.be/OhQRGaNO4iA) +::: # Automatisieren von Arbeitsabläufen @@ -51,30 +36,30 @@ Works on my machine ... ::: ::: notes -Einen häufigen Ausspruch, den man bei der Zusammenarbeit in Teams zu hören -bekommt, ist "Also, bei mir läuft der Code." ... +Einen häufigen Ausspruch, den man bei der Zusammenarbeit in Teams zu hören bekommt, +ist "Also, bei mir läuft der Code." ... Das Problem dabei ist, dass jeder Entwickler eine andere Maschine hat, oft ein -anderes Betriebssystem oder eine andere OS-Version. Dazu kommen noch eine andere -IDE und/oder andere Einstellungen und so weiter. - -Wie bekommt man es hin, dass Code zuverlässig auch auf anderen Rechnern baut? -Ein wichtiger Baustein dafür sind sogenannte "Build-Systeme", also Tools, die -unabhängig von der IDE (und den IDE-Einstellungen) für das Übersetzen der -Software eingesetzt werden und deren Konfiguration dann mit im Repo eingecheckt -wird. Damit kann die Software dann auf allen Rechnern und insbesondere dann auch -auf dem Server (Stichwort "Continuous Integration") unabhängig von der IDE o.ä. -automatisiert gebaut und getestet werden. +anderes Betriebssystem oder eine andere OS-Version. Dazu kommen noch eine andere IDE +und/oder andere Einstellungen und so weiter. + +Wie bekommt man es hin, dass Code zuverlässig auch auf anderen Rechnern baut? Ein +wichtiger Baustein dafür sind sogenannte "Build-Systeme", also Tools, die unabhängig +von der IDE (und den IDE-Einstellungen) für das Übersetzen der Software eingesetzt +werden und deren Konfiguration dann mit im Repo eingecheckt wird. Damit kann die +Software dann auf allen Rechnern und insbesondere dann auch auf dem Server +(Stichwort "Continuous Integration") unabhängig von der IDE o.ä. automatisiert +gebaut und getestet werden. ::: \bigskip \bigskip \pause -* Build-Tools: - * Apache Ant - * Apache Maven - * **Gradle** +- Build-Tools: + - Apache Ant + - Apache Maven + - **Gradle** ::: notes Das sind die drei am häufigsten anzutreffenden Build-Tools in der Java-Welt. @@ -86,30 +71,28 @@ werden, die man benutzen möchte. In Maven wird dagegen von einem bestimmten Entwicklungsmodell ausgegangen, hier müssen nur noch die Abweichungen zu diesem Modell konfiguriert werden. -In Gradle wird eine DSL basierend auf der Skriptsprache Groovy (läuft auf der -JVM) eingesetzt, und es gibt hier wie in Maven ein bestimmtes eingebautes -Entwicklungsmodell. Gradle bringt zusätzlich noch einen Wrapper mit, d.h. es -wird eine Art Gradle-Starter im Repo konfiguriert, der sich quasi genauso -verhält wie ein fest installiertes Gradle (s.u.). - +In Gradle wird eine DSL basierend auf der Skriptsprache Groovy (läuft auf der JVM) +eingesetzt, und es gibt hier wie in Maven ein bestimmtes eingebautes +Entwicklungsmodell. Gradle bringt zusätzlich noch einen Wrapper mit, d.h. es wird +eine Art Gradle-Starter im Repo konfiguriert, der sich quasi genauso verhält wie ein +fest installiertes Gradle (s.u.). -**Achtung**: Während Ant und Maven relativ stabil in der API sind, verändert -sich Gradle teilweise deutlich zwischen den Versionen. Zusätzlich sind bestimmte +**Achtung**: Während Ant und Maven relativ stabil in der API sind, verändert sich +Gradle teilweise deutlich zwischen den Versionen. Zusätzlich sind bestimmte Gradle-Versionen oft noch von bestimmten JDK-Versionen abhängig. In der Praxis bedeutet dies, dass man Gradle-Skripte im Laufe der Zeit relativ oft überarbeiten -muss (einfach nur, damit das Skript wieder läuft - ohne dass man dabei -irgendwelche neuen Features oder sonstige Vorteile erzielen würde). Ein großer -Vorteil ist aber der Gradle-Wrapper (s.u.). +muss (einfach nur, damit das Skript wieder läuft - ohne dass man dabei irgendwelche +neuen Features oder sonstige Vorteile erzielen würde). Ein großer Vorteil ist aber +der Gradle-Wrapper (s.u.). ::: - # Gradle: Eine DSL in Groovy -[DSL: _Domain Specific Language_]{.notes} +[DSL: *Domain Specific Language*]{.notes} \bigskip -```groovy +``` groovy // build.gradle plugins { id 'java' @@ -132,22 +115,23 @@ application { ::: notes Dies ist mit die einfachste Build-Datei für Gradle. -Über Plugins wird die Unterstützung für Java und das Bauen von Applikationen aktiviert, -d.h. es stehen darüber entsprechende spezifische Tasks zur Verfügung. +Über Plugins wird die Unterstützung für Java und das Bauen von Applikationen +aktiviert, d.h. es stehen darüber entsprechende spezifische Tasks zur Verfügung. -Abhängigkeiten sollen hier aus dem Maven-Repository [MavenCentral](https://mvnrepository.com/repos/central) -geladen werden. Zusätzlich wird hier als Abhängigkeit für den Test (`testImplementation`) -die JUnit-Bibliothek in einer Maven-artigen Notation angegeben (vgl. -[mvnrepository.com](https://mvnrepository.com/)). (Für nur zur Übersetzung der Applikation -benötigte Bibliotheken verwendet man stattdessen das Schlüsselwort `implementation`.) +Abhängigkeiten sollen hier aus dem Maven-Repository +[MavenCentral](https://mvnrepository.com/repos/central) geladen werden. Zusätzlich +wird hier als Abhängigkeit für den Test (`testImplementation`) die JUnit-Bibliothek +in einer Maven-artigen Notation angegeben (vgl. +[mvnrepository.com](https://mvnrepository.com/)). (Für nur zur Übersetzung der +Applikation benötigte Bibliotheken verwendet man stattdessen das Schlüsselwort +`implementation`.) -Bei der Initialisierung wurde als Package `fluppie` angegeben. Gradle legt darunter per -Default die Klasse `App` mit einer `main()`-Methode an. Entsprechend kann man über den -Eintrag `application` den Einsprungpunkt in die Applikation konfigurieren. +Bei der Initialisierung wurde als Package `fluppie` angegeben. Gradle legt darunter +per Default die Klasse `App` mit einer `main()`-Methode an. Entsprechend kann man +über den Eintrag `application` den Einsprungpunkt in die Applikation konfigurieren. ::: - -::::::::: notes +::: notes # Gradle-DSL Ein Gradle-Skript ist letztlich ein in Groovy geschriebenes Skript. -[Groovy](https://groovy-lang.org/) ist eine auf Java basierende und auf -der JVM ausgeführte Skriptsprache. Seit einigen Versionen kann man die -Gradle-Build-Skripte auch in der Sprache Kotlin schreiben. - +[Groovy](https://groovy-lang.org/) ist eine auf Java basierende und auf der JVM +ausgeführte Skriptsprache. Seit einigen Versionen kann man die Gradle-Build-Skripte +auch in der Sprache Kotlin schreiben. # Dateien Für das Bauen mit Gradle benötigt man drei Dateien im Projektordner: -* `build.gradle`: Die auf der Gradle-DSL beruhende Definition des Builds - mit den Tasks (und ggf. Abhängigkeiten) eines Projekts. +- `build.gradle`: Die auf der Gradle-DSL beruhende Definition des Builds mit den + Tasks (und ggf. Abhängigkeiten) eines Projekts. - Ein Multiprojekt hat pro Projekt eine solche Build-Datei. Dabei können - die Unterprojekte Eigenschaften der Eltern-Buildskripte "erben" und so - relativ kurz ausfallen. + Ein Multiprojekt hat pro Projekt eine solche Build-Datei. Dabei können die + Unterprojekte Eigenschaften der Eltern-Buildskripte "erben" und so relativ kurz + ausfallen. -* `settings.gradle`: Eine optionale Datei, in der man beispielsweise den - Projektnamen oder bei einem Multiprojekt die relevanten Unterprojekte - festlegt. +- `settings.gradle`: Eine optionale Datei, in der man beispielsweise den + Projektnamen oder bei einem Multiprojekt die relevanten Unterprojekte festlegt. -* `gradle.properties`: Eine weitere optionale Datei, in der projektspezifische +- `gradle.properties`: Eine weitere optionale Datei, in der projektspezifische Properties für den Gradle-Build spezifizieren kann. - # Gradle Init -Um eine neue Gradle-Konfiguration anlegen zu lassen, geht man in einen Ordner -und führt darin `gradle init` aus. Gradle fragt der Reihe nach einige Einstellungen -ab: - -``` -$ gradle init - -Select type of project to generate: - 1: basic - 2: application - 3: library - 4: Gradle plugin -Enter selection (default: basic) [1..4] 2 - -Select implementation language: - 1: C++ - 2: Groovy - 3: Java - 4: Kotlin - 5: Scala - 6: Swift -Enter selection (default: Java) [1..6] 3 - -Split functionality across multiple subprojects?: - 1: no - only one application project - 2: yes - application and library projects -Enter selection (default: no - only one application project) [1..2] 1 - -Select build script DSL: - 1: Groovy - 2: Kotlin -Enter selection (default: Groovy) [1..2] 1 - -Select test framework: - 1: JUnit 4 - 2: TestNG - 3: Spock - 4: JUnit Jupiter -Enter selection (default: JUnit Jupiter) [1..4] 1 - -Project name (default: tmp): wuppie -Source package (default: tmp): fluppie -``` +Um eine neue Gradle-Konfiguration anlegen zu lassen, geht man in einen Ordner und +führt darin `gradle init` aus. Gradle fragt der Reihe nach einige Einstellungen ab: + + $ gradle init + + Select type of project to generate: + 1: basic + 2: application + 3: library + 4: Gradle plugin + Enter selection (default: basic) [1..4] 2 + + Select implementation language: + 1: C++ + 2: Groovy + 3: Java + 4: Kotlin + 5: Scala + 6: Swift + Enter selection (default: Java) [1..6] 3 + + Split functionality across multiple subprojects?: + 1: no - only one application project + 2: yes - application and library projects + Enter selection (default: no - only one application project) [1..2] 1 + + Select build script DSL: + 1: Groovy + 2: Kotlin + Enter selection (default: Groovy) [1..2] 1 + + Select test framework: + 1: JUnit 4 + 2: TestNG + 3: Spock + 4: JUnit Jupiter + Enter selection (default: JUnit Jupiter) [1..4] 1 + + Project name (default: tmp): wuppie + Source package (default: tmp): fluppie Typischerweise möchte man eine Applikation bauen (Auswahl 2 bei der ersten Frage). Als nächstes wird nach der Sprache des Projekts gefragt sowie nach der Sprache für @@ -234,86 +212,77 @@ verwendet werden soll. Damit wird die eingangs gezeigte Konfiguration angelegt. - # Ordner Durch `gradle init` wird ein neuer Ordner `wuppie/` mit folgender Ordnerstruktur angelegt: -``` -drwxr-xr-x 4 cagix cagix 4096 Apr 8 11:43 ./ -drwxrwxrwt 1 cagix cagix 4096 Apr 8 11:43 ../ --rw-r--r-- 1 cagix cagix 154 Apr 8 11:43 .gitattributes --rw-r--r-- 1 cagix cagix 103 Apr 8 11:43 .gitignore -drwxr-xr-x 3 cagix cagix 4096 Apr 8 11:43 app/ -drwxr-xr-x 3 cagix cagix 4096 Apr 8 11:42 gradle/ --rwxr-xr-x 1 cagix cagix 8070 Apr 8 11:42 gradlew* --rw-r--r-- 1 cagix cagix 2763 Apr 8 11:42 gradlew.bat --rw-r--r-- 1 cagix cagix 370 Apr 8 11:43 settings.gradle -``` - -Es werden Einstellungen für Git erzeugt (`.gitattributes` und -`.gitignore`). - -Im Ordner `gradle/` wird der Gradle-Wrapper abgelegt (s.u.). Dieser -Ordner wird normalerweise mit ins Repo eingecheckt. Die Skripte -`gradlew` und `gradlew.bat` sind die Startskripte für den Gradle-Wrapper -(s.u.) und werden normalerweise ebenfalls ins Repo mit eingecheckt. - -Der Ordner `.gradle/` (erscheint ggf. nach dem ersten Lauf von Gradle -auf dem neuen Projekt) ist nur ein Hilfsordner ("Cache") von Gradle. -Hier werden heruntergeladene Dateien etc. abgelegt. Dieser Order -sollte **nicht** ins Repo eingecheckt werden und ist deshalb auch -per Default im generierten `.gitignore` enthalten. (Zusätzlich gibt es -im User-Verzeichnis auch noch einen Ordner `.gradle/` mit einem globalen -Cache.) + drwxr-xr-x 4 cagix cagix 4096 Apr 8 11:43 ./ + drwxrwxrwt 1 cagix cagix 4096 Apr 8 11:43 ../ + -rw-r--r-- 1 cagix cagix 154 Apr 8 11:43 .gitattributes + -rw-r--r-- 1 cagix cagix 103 Apr 8 11:43 .gitignore + drwxr-xr-x 3 cagix cagix 4096 Apr 8 11:43 app/ + drwxr-xr-x 3 cagix cagix 4096 Apr 8 11:42 gradle/ + -rwxr-xr-x 1 cagix cagix 8070 Apr 8 11:42 gradlew* + -rw-r--r-- 1 cagix cagix 2763 Apr 8 11:42 gradlew.bat + -rw-r--r-- 1 cagix cagix 370 Apr 8 11:43 settings.gradle + +Es werden Einstellungen für Git erzeugt (`.gitattributes` und `.gitignore`). + +Im Ordner `gradle/` wird der Gradle-Wrapper abgelegt (s.u.). Dieser Ordner wird +normalerweise mit ins Repo eingecheckt. Die Skripte `gradlew` und `gradlew.bat` sind +die Startskripte für den Gradle-Wrapper (s.u.) und werden normalerweise ebenfalls +ins Repo mit eingecheckt. + +Der Ordner `.gradle/` (erscheint ggf. nach dem ersten Lauf von Gradle auf dem neuen +Projekt) ist nur ein Hilfsordner ("Cache") von Gradle. Hier werden heruntergeladene +Dateien etc. abgelegt. Dieser Order sollte **nicht** ins Repo eingecheckt werden und +ist deshalb auch per Default im generierten `.gitignore` enthalten. (Zusätzlich gibt +es im User-Verzeichnis auch noch einen Ordner `.gradle/` mit einem globalen Cache.) In `settings.gradle` finden sich weitere Einstellungen. Die eigentliche -Gradle-Konfiguration befindet sich zusammen mit dem eigentlichen Projekt -im Unterordner `app/`: - -``` -drwxr-xr-x 4 root root 4096 Apr 8 11:50 ./ -drwxr-xr-x 5 root root 4096 Apr 8 11:49 ../ -drwxr-xr-x 5 root root 4096 Apr 8 11:50 build/ --rw-r--r-- 1 root root 852 Apr 8 11:43 build.gradle -drwxr-xr-x 4 root root 4096 Apr 8 11:43 src/ -``` - -Die Datei `build.gradle` ist die durch `gradle init` erzeugte (und -eingangs gezeigte) Konfigurationsdatei, vergleichbar mit `build.xml` -für Ant oder `pom.xml` für Maven. Im Unterordner `build/` werden die -generierten `.class`-Dateien etc. beim Build-Prozess abgelegt. - -Unter `src/` findet sich dann eine Maven-typische Ordnerstruktur -für die Sourcen: - -``` -$ tree src/ -src/ -|-- main -| |-- java -| | `-- fluppie -| | `-- App.java -| `-- resources -`-- test - |-- java - | `-- fluppie - | `-- AppTest.java - `-- resources -``` +Gradle-Konfiguration befindet sich zusammen mit dem eigentlichen Projekt im +Unterordner `app/`: + + drwxr-xr-x 4 root root 4096 Apr 8 11:50 ./ + drwxr-xr-x 5 root root 4096 Apr 8 11:49 ../ + drwxr-xr-x 5 root root 4096 Apr 8 11:50 build/ + -rw-r--r-- 1 root root 852 Apr 8 11:43 build.gradle + drwxr-xr-x 4 root root 4096 Apr 8 11:43 src/ + +Die Datei `build.gradle` ist die durch `gradle init` erzeugte (und eingangs +gezeigte) Konfigurationsdatei, vergleichbar mit `build.xml` für Ant oder `pom.xml` +für Maven. Im Unterordner `build/` werden die generierten `.class`-Dateien etc. beim +Build-Prozess abgelegt. + +Unter `src/` findet sich dann eine Maven-typische Ordnerstruktur für die Sourcen: + + $ tree src/ + src/ + |-- main + | |-- java + | | `-- fluppie + | | `-- App.java + | `-- resources + `-- test + |-- java + | `-- fluppie + | `-- AppTest.java + `-- resources Unterhalb von `src/` ist ein Ordner `main/` für die Quellen der Applikation (Sourcen -und Ressourcen). Für jede Sprache gibt es einen eigenen Unterordner, hier entsprechend -`java/`. Unterhalb diesem folgt dann die bei der Initialisierung angelegte Package-Struktur -(hier `fluppie` mit der Default-Main-Klasse `App` mit einer `main()`-Methode). Diese -Strukturen wiederholen sich für die Tests unterhalb von `src/test/`. - -Wer die herkömmlichen, deutlich flacheren Strukturen bevorzugt, also unterhalb von `src/` -direkt die Java-Package-Strukturen für die Sourcen der Applikation und unterhalb von `test/` -entsprechend die Strukturen für die JUnit-Test, der kann dies im Build-Skript einstellen: - -```groovy +und Ressourcen). Für jede Sprache gibt es einen eigenen Unterordner, hier +entsprechend `java/`. Unterhalb diesem folgt dann die bei der Initialisierung +angelegte Package-Struktur (hier `fluppie` mit der Default-Main-Klasse `App` mit +einer `main()`-Methode). Diese Strukturen wiederholen sich für die Tests unterhalb +von `src/test/`. + +Wer die herkömmlichen, deutlich flacheren Strukturen bevorzugt, also unterhalb von +`src/` direkt die Java-Package-Strukturen für die Sourcen der Applikation und +unterhalb von `test/` entsprechend die Strukturen für die JUnit-Test, der kann dies +im Build-Skript einstellen: + +``` groovy sourceSets { main { java { @@ -329,94 +298,88 @@ sourceSets { } } ``` -::::::::: - +::: -::::::::: slides +::: slides # Wichtige Gradle-Tasks -* Initialisieren des Projekts: `gradle init` +- Initialisieren des Projekts: `gradle init` \smallskip -* Überblick über die Tasks: `gradle tasks` +- Überblick über die Tasks: `gradle tasks` \bigskip -* Übersetzen: `gradle compileJava` oder `gradle classes` -* Testen: `gradle test` -* Ausführen: `gradle run` -* Aufräumen: `gradle clean` -::::::::: - +- Übersetzen: `gradle compileJava` oder `gradle classes` +- Testen: `gradle test` +- Ausführen: `gradle run` +- Aufräumen: `gradle clean` +::: -::::::::: notes +::: notes # Ablauf eines Gradle-Builds Ein Gradle-Build hat zwei Hauptphasen: Konfiguration und Ausführung. -Während der Konfiguration wird das gesamte Skript durchlaufen (vgl. Ausführung -der direkten Anweisungen eines Tasks). Dabei wird ein Graph erzeugt: welche -Tasks hängen von welchen anderen ab etc. +Während der Konfiguration wird das gesamte Skript durchlaufen (vgl. Ausführung der +direkten Anweisungen eines Tasks). Dabei wird ein Graph erzeugt: welche Tasks hängen +von welchen anderen ab etc. -Anschließend wird der gewünschte Task ausgeführt. Dabei werden zuerst alle -Tasks ausgeführt, die im Graphen auf dem Weg zu dem gewünschten Task liegen. +Anschließend wird der gewünschte Task ausgeführt. Dabei werden zuerst alle Tasks +ausgeführt, die im Graphen auf dem Weg zu dem gewünschten Task liegen. -Mit `gradle tasks` kann man sich die zur Verfügung stehenden Tasks ansehen. -Diese sind der Übersicht halber noch nach "Themen" sortiert. +Mit `gradle tasks` kann man sich die zur Verfügung stehenden Tasks ansehen. Diese +sind der Übersicht halber noch nach "Themen" sortiert. Für eine Java-Applikation sind die typischen Tasks `gradle build` zum Bauen der -Applikation (inkl. Ausführen der Tests) sowie `gradle run` zum Starten der Anwendung. -Wer nur die Java-Sourcen compilieren will, würde den Task `gradle compileJava` nutzen. -Mit `gradle check` würde man compilieren und die Tests ausführen sowie weitere Checks -durchführen (`gradle test` würde nur compilieren und die Tests ausführen), mit `gradle jar` -die Anwendung in ein `.jar`-File packen und mit `gradle javadoc` die Javadoc-Dokumentation -erzeugen und mit `gradle clean` die generierten Hilfsdateien aufräumen (löschen). - +Applikation (inkl. Ausführen der Tests) sowie `gradle run` zum Starten der +Anwendung. Wer nur die Java-Sourcen compilieren will, würde den Task +`gradle compileJava` nutzen. Mit `gradle check` würde man compilieren und die Tests +ausführen sowie weitere Checks durchführen (`gradle test` würde nur compilieren und +die Tests ausführen), mit `gradle jar` die Anwendung in ein `.jar`-File packen und +mit `gradle javadoc` die Javadoc-Dokumentation erzeugen und mit `gradle clean` die +generierten Hilfsdateien aufräumen (löschen). # Plugin-Architektur Für bestimmte Projekttypen gibt es immer wieder die gleichen Aufgaben. Um hier Schreibaufwand zu sparen, existieren verschiedene Plugins für verschiedene Projekttypen. In diesen Plugins sind die entsprechenden Tasks bereits mit den -jeweiligen Abhängigkeiten formuliert. Diese Idee stammt aus Maven, wo dies -für Java-basierte Projekte umgesetzt ist. +jeweiligen Abhängigkeiten formuliert. Diese Idee stammt aus Maven, wo dies für +Java-basierte Projekte umgesetzt ist. -Beispielsweise erhält man über das Plugin `java` den Task `clean` zum Löschen -aller generierten Build-Artefakte, den Task `classes`, der die Sourcen zu -`.class`-Dateien kompiliert oder den Task `test`, der die JUnit-Tests -ausführt ... +Beispielsweise erhält man über das Plugin `java` den Task `clean` zum Löschen aller +generierten Build-Artefakte, den Task `classes`, der die Sourcen zu `.class`-Dateien +kompiliert oder den Task `test`, der die JUnit-Tests ausführt ... Sie können sich Plugins und weitere Tasks relativ leicht auch selbst definieren. - # Auflösen von Abhängigkeiten -Analog zu Maven kann man Abhängigkeiten (etwa in einer bestimmten Version -benötigte Bibliotheken) im Gradle-Skript angeben. Diese werden (transparent für -den User) von einer ebenfalls angegeben Quelle, etwa einem Maven-Repository, -heruntergeladen und für den Build genutzt. Man muss also nicht mehr die -benötigten `.jar`-Dateien der Bibliotheken mit ins Projekt einchecken. -Analog zu Maven können erzeugte Artefakte automatisch publiziert werden, etwa -in einem Maven-Repository. +Analog zu Maven kann man Abhängigkeiten (etwa in einer bestimmten Version benötigte +Bibliotheken) im Gradle-Skript angeben. Diese werden (transparent für den User) von +einer ebenfalls angegeben Quelle, etwa einem Maven-Repository, heruntergeladen und +für den Build genutzt. Man muss also nicht mehr die benötigten `.jar`-Dateien der +Bibliotheken mit ins Projekt einchecken. Analog zu Maven können erzeugte Artefakte +automatisch publiziert werden, etwa in einem Maven-Repository. Für das Projekt benötigte Abhängigkeiten kann man über den Eintrag `dependencies` spezifizieren. Dabei unterscheidet man u.a. zwischen Applikation und Tests: `implementation` und `testImplementation` für das Compilieren und Ausführen von Applikation bzw. Tests. Diese Abhängigkeiten werden durch Gradle über die im -Abschnitt `repositories` konfigurierten Repositories aufgelöst und die entsprechenden -`.jar`-Files geladen (in den `.gradle/`-Ordner). - -Typische Repos sind das Maven-Repo selbst (`mavenCentral()`) oder das Google-Maven-Repo -(`google()`). +Abschnitt `repositories` konfigurierten Repositories aufgelöst und die +entsprechenden `.jar`-Files geladen (in den `.gradle/`-Ordner). -Die Einträge in `dependencies` erfolgen dabei in einer Maven-Notation, die Sie -auch im Maven-Repo [mvnrepository.com](https://mvnrepository.com/) finden. +Typische Repos sind das Maven-Repo selbst (`mavenCentral()`) oder das +Google-Maven-Repo (`google()`). +Die Einträge in `dependencies` erfolgen dabei in einer Maven-Notation, die Sie auch +im Maven-Repo [mvnrepository.com](https://mvnrepository.com/) finden. # Beispiel mit weiteren Konfigurationen (u.a. Checkstyle und Javadoc) -```groovy +``` groovy plugins { id 'java' id 'application' @@ -466,80 +429,103 @@ javadoc { } ``` -Hier sehen Sie übrigens noch eine weitere mögliche Schreibweise für das Notieren -von Abhängigkeiten: `implementation group: 'org.apache.poi', name: 'poi', version: '4.1.2'` -und `implementation 'org.apache.poi:poi:4.1.2'` sind gleichwertig, wobei die letztere -Schreibweise sowohl in den generierten Builds-Skripten und in der offiziellen Dokumentation -bevorzugt wird. - +Hier sehen Sie übrigens noch eine weitere mögliche Schreibweise für das Notieren von +Abhängigkeiten: +`implementation group: 'org.apache.poi', name: 'poi', version: '4.1.2'` und +`implementation 'org.apache.poi:poi:4.1.2'` sind gleichwertig, wobei die letztere +Schreibweise sowohl in den generierten Builds-Skripten und in der offiziellen +Dokumentation bevorzugt wird. # Gradle und Ant (und Maven) -Vorhandene Ant-Buildskripte kann man nach Gradle importieren und ausführen -lassen. Über die DSL kann man auch direkt Ant-Tasks aufrufen. Siehe auch -["Using Ant from Gradle"](https://docs.gradle.org/current/userguide/ant.html). -::::::::: - +Vorhandene Ant-Buildskripte kann man nach Gradle importieren und ausführen lassen. +Über die DSL kann man auch direkt Ant-Tasks aufrufen. Siehe auch ["Using Ant from +Gradle"](https://docs.gradle.org/current/userguide/ant.html). +::: # Gradle-Wrapper -``` -project -|-- app/ -|-- build.gradle -|-- gradlew -|-- gradlew.bat -`-- gradle/ - `-- wrapper/ - |-- gradle-wrapper.jar - `-- gradle-wrapper.properties -``` + project + |-- app/ + |-- build.gradle + |-- gradlew + |-- gradlew.bat + `-- gradle/ + `-- wrapper/ + |-- gradle-wrapper.jar + `-- gradle-wrapper.properties -::::::::: notes +::: notes Zur Ausführung von Gradle-Skripten benötigt man eine lokale Gradle-Installation. Diese sollte für i.d.R. alle User, die das Projekt bauen wollen, identisch sein. Leider ist dies oft nicht gegeben bzw. nicht einfach lösbar. -Zur Vereinfachung gibt es den Gradle-Wrapper `gradlew` (bzw. `gradlew.bat` für Windows). -Dies ist ein kleines Shellskript, welches zusammen mit einigen kleinen `.jar`-Dateien im -Unterordner `gradle/` mit ins Repo eingecheckt wird und welches direkt die Rolle des -`gradle`-Befehls einer Gradle-Installation übernehmen kann. Man kann also in Konfigurationskripten, -beispielsweise für Gitlab CI, alle Aufrufe von `gradle` durch Aufrufe von `gradlew` ersetzen. +Zur Vereinfachung gibt es den Gradle-Wrapper `gradlew` (bzw. `gradlew.bat` für +Windows). Dies ist ein kleines Shellskript, welches zusammen mit einigen kleinen +`.jar`-Dateien im Unterordner `gradle/` mit ins Repo eingecheckt wird und welches +direkt die Rolle des `gradle`-Befehls einer Gradle-Installation übernehmen kann. Man +kann also in Konfigurationskripten, beispielsweise für Gitlab CI, alle Aufrufe von +`gradle` durch Aufrufe von `gradlew` ersetzen. -Beim ersten Aufruf lädt `gradlew` dann die spezifizierte Gradle-Version herunter und speichert -diese in einem lokalen Ordner `.gradle/`. Ab dann greift `gradlew` auf diese lokale (nicht -"installierte") `gradle`-Version zurück. +Beim ersten Aufruf lädt `gradlew` dann die spezifizierte Gradle-Version herunter und +speichert diese in einem lokalen Ordner `.gradle/`. Ab dann greift `gradlew` auf +diese lokale (nicht "installierte") `gradle`-Version zurück. `gradle init` erzeugt den Wrapper automatisch in der verwendeten Gradle-Version mit. -Alternativ kann man den Wrapper nachträglich über `gradle wrapper --gradle-version 6.5` -in einer bestimmten (gewünschten) Version anlegen lassen. +Alternativ kann man den Wrapper nachträglich über +`gradle wrapper --gradle-version 6.5` in einer bestimmten (gewünschten) Version +anlegen lassen. Da der Gradle-Wrapper im Repository eingecheckt ist, benutzen alle Entwickler damit -automatisch die selbe Version, ohne diese auf ihrem System zuvor installieren zu müssen. -Deshalb ist der Einsatz des Wrappers einem fest installierten Gradle vorzuziehen! -::::::::: +automatisch die selbe Version, ohne diese auf ihrem System zuvor installieren zu +müssen. Deshalb ist der Einsatz des Wrappers einem fest installierten Gradle +vorzuziehen! +::: [[Live-Demo Gradle/Gradlew]{.ex}]{.slides} - # Wrap-Up -* Automatisieren von Arbeitsabläufen mit Build-Tools/-Skripten +- Automatisieren von Arbeitsabläufen mit Build-Tools/-Skripten \smallskip -* Einstieg in **Gradle** (DSL zur Konfiguration) - * Typisches Java-Entwicklungsmodell eingebaut - * Konfiguration der Abweichungen (Abhängigkeiten, Namen, ...) - * Gradle-Wrapper: Ersetzt eine feste Installation - +- Einstieg in **Gradle** (DSL zur Konfiguration) + - Typisches Java-Entwicklungsmodell eingebaut + - Konfiguration der Abweichungen (Abhängigkeiten, Namen, ...) + - Gradle-Wrapper: Ersetzt eine feste Installation -::::::::: notes +::: notes # Link-Sammlung Gradle -* ["Getting Started"](https://docs.gradle.org/current/userguide/getting_started.html) -* ["Building Java Applications Sample"](https://docs.gradle.org/current/samples/sample_building_java_applications.html) -* ["Building Java Applications with libraries Sample"](https://docs.gradle.org/current/samples/sample_building_java_applications_multi_project.html) -* ["Building Java Libraries Sample"](https://docs.gradle.org/current/samples/sample_building_java_libraries.html) -* ["Building Java & JVM projects"](https://docs.gradle.org/current/userguide/building_java_projects.html) -::::::::: +- ["Getting + Started"](https://docs.gradle.org/current/userguide/getting_started.html) +- ["Building Java Applications + Sample"](https://docs.gradle.org/current/samples/sample_building_java_applications.html) +- ["Building Java Applications with libraries + Sample"](https://docs.gradle.org/current/samples/sample_building_java_applications_multi_project.html) +- ["Building Java Libraries + Sample"](https://docs.gradle.org/current/samples/sample_building_java_libraries.html) +- ["Building Java & JVM + projects"](https://docs.gradle.org/current/userguide/building_java_projects.html) +::: + +::: readings +- @Gradle +- @Ullenboom2021 +- @Inden2013 +::: + +::: outcomes +- k3: Ich kann einfache Gradle-Skripte schreiben und verstehen +::: + +::: challenges +Betrachten Sie das Buildskript `gradle.build` aus +[Dungeon-CampusMinden/Dungeon](https://github.com/Dungeon-CampusMinden/Dungeon/blob/master/build.gradle). + +Erklären Sie, in welche Abschnitte das Buildskript unterteilt ist und welche +Aufgaben diese Abschnitte jeweils erfüllen. Gehen Sie dabei im *Detail* auf das +Plugin `java` und die dort bereitgestellten Tasks und deren Abhängigkeiten +untereinander ein. +::: diff --git a/lecture/building/maven.md b/lecture/building/maven.md index 5cd665640..36b982884 100644 --- a/lecture/building/maven.md +++ b/lecture/building/maven.md @@ -1,46 +1,33 @@ --- +author: Carsten Gips (HSBI) title: "Build-Systeme: Apache Maven" -author: "Carsten Gips (HSBI)" -readings: - - "@Ullenboom2016" - - "@Inden2013" -tldr: | - Zum Automatisieren von Arbeitsabläufen (Kompilieren, Testen, ...) stehen in der Java-Welt - verschiedene Tools zur Verfügung: Apache Ant, Apache Maven und Gradle sind sicher die am - bekanntesten darunter. - - In Apache Maven ist bereits der typische Java-Standard-Lebenszyklus eingebaut und es müssen - nur noch Abweichungen davon und Festlegung von Versionen und Dependencies in XML formuliert - werden. Dies nennt man auch "_Convention over Configuration_". - - Die Maven-Goals sind auswählbare Ziele und werden durch Plugins bereitgestellt. Zwischen den - Goals sind Abhängigkeiten möglich (und bereits eingebaut). Über Properties kann man noch - Namen und Versionsnummern o.ä. definieren. - - Abhängigkeiten zu externen Bibliotheken werden als Dependencies formuliert: Am besten den - Abschnitt von Maven-Central kopieren. -outcomes: - - k3: "Schreiben einfacher Maven-Skripte zu Übersetzen des Projekts, zum Testen und zum Erzeugen von Jar-Files" - - k3: "Nutzung von Maven-Properties" - - k3: "Einbinden externer Bibliotheken als Dependencies" - - k3: "Ausführen von Maven-Goals aus IDE heraus und Einbindung als Builder" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106215&client_id=FH-Bielefeld" -# name: "Quiz Apache Maven (ILIAS)" -youtube: - - link: "https://youtu.be/YAW-_Wi4LDY" - name: "VL Apache Maven" - - link: "https://youtu.be/8z0UqtzISCA" - name: "Demo Maven-Projekt" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/fb5c1d7d3dc18c911557eeec3d91322aec7005d9b1615445f8c75378cf221bb100cffb83d30a4073179d85dfd831c9ee2466c6dec910852b90ade14924bdfcb0" - name: "VL Apache Maven" --- +::: tldr +Zum Automatisieren von Arbeitsabläufen (Kompilieren, Testen, ...) stehen in der +Java-Welt verschiedene Tools zur Verfügung: Apache Ant, Apache Maven und Gradle sind +sicher die am bekanntesten darunter. + +In Apache Maven ist bereits der typische Java-Standard-Lebenszyklus eingebaut und es +müssen nur noch Abweichungen davon und Festlegung von Versionen und Dependencies in +XML formuliert werden. Dies nennt man auch "*Convention over Configuration*". + +Die Maven-Goals sind auswählbare Ziele und werden durch Plugins bereitgestellt. +Zwischen den Goals sind Abhängigkeiten möglich (und bereits eingebaut). Über +Properties kann man noch Namen und Versionsnummern o.ä. definieren. + +Abhängigkeiten zu externen Bibliotheken werden als Dependencies formuliert: Am +besten den Abschnitt von Maven-Central kopieren. +::: + +::: youtube +- [VL Apache Maven](https://youtu.be/YAW-_Wi4LDY) +- [Demo Maven-Projekt](https://youtu.be/8z0UqtzISCA) +::: # Build-Tool Maven: Alternative zu Ant oder Gradle -```maven +``` maven mvn archetype:generate -DgroupId=de.hsbi.pm -DartifactId=my-project -DarchetypeArtifactId=maven-archetype-quickstart ``` @@ -51,34 +38,39 @@ mvn archetype:generate -DgroupId=de.hsbi.pm -DartifactId=my-project ![](images/screenshot_maven-project.png){width="40%"} ::: notes -Von der zeitlichen Entstehung her kommt Maven nach Ant, aber vor Gradle. Wie in Ant sind -auch die Maven-Buildskripte XML-basierte Textdateien (Gradle nutzt eine Groovy-basierte DSL). - -Allerdings hat Maven im Gegensatz zu Ant bereits ein Modell des Java-Entwicklungsprozess -"eingebaut": Im Ant-Skript muss alles, was man tun möchte, explizit als Target formuliert -werden, d.h. auch ein Kompilieren der Sourcen oder Ausführen der Tests muss extra als Target -ins Ant-Skript geschrieben werden, um benutzbar zu sein. In Maven ist dieses Modell bereits -implementiert, d.h. hier muss man lediglich zusätzliche oder abweichende Dinge im XML-File -konfigurieren. Das nennt man auch ["*convention over configuration*"](https://en.wikipedia.org/wiki/Convention_over_configuration). +Von der zeitlichen Entstehung her kommt Maven nach Ant, aber vor Gradle. Wie in Ant +sind auch die Maven-Buildskripte XML-basierte Textdateien (Gradle nutzt eine +Groovy-basierte DSL). + +Allerdings hat Maven im Gegensatz zu Ant bereits ein Modell des +Java-Entwicklungsprozess "eingebaut": Im Ant-Skript muss alles, was man tun möchte, +explizit als Target formuliert werden, d.h. auch ein Kompilieren der Sourcen oder +Ausführen der Tests muss extra als Target ins Ant-Skript geschrieben werden, um +benutzbar zu sein. In Maven ist dieses Modell bereits implementiert, d.h. hier muss +man lediglich zusätzliche oder abweichende Dinge im XML-File konfigurieren. Das +nennt man auch ["*convention over +configuration*"](https://en.wikipedia.org/wiki/Convention_over_configuration). Der Maven-Aufruf `mvn archetype:generate -DgroupId=de.hsbi.pm -DartifactId=my-project -DarchetypeArtifactId=maven-archetype-quickstart` -erzeugt mit Hilfe des Plugins `archetype`, welches das Ziel (engl.: "*Maven goal*") `generate` -bereitstellt, ein neues Projekt mit dem Namen `my-project` und der initialen Package-Struktur -`de.hsbi.pm`. Das von Maven für die Projekterstellung genutzte Plugin ist unter der ID -`maven-archetype-quickstart` in den Maven-Repositories (etwa [Maven-Central](https://mvnrepository.com/repos/central)) -verfügbar, hier kann man mit der zusätzlichen Option `-DarchetypeVersion=1.4` auf die letzte -Version schalten. - -Die erzeugte Ordnerstruktur entspricht der Standardstruktur von Gradle (Gradle hat diese -quasi von Maven übernommen). Die Konfigurationsdatei für Maven hat den Namen `pom.xml`. - -*Hinweis*: Die `groupId` und `artifactId` werden auch für eine Veröffentlichung des Jar-Files -des Projekts auf dem zentralen Maven-Repository [Maven-Central](https://mvnrepository.com/repos/central) -genutzt. Von hier würde Maven auch als Abhängigkeit konfigurierte Bibliotheken herunterladen. +erzeugt mit Hilfe des Plugins `archetype`, welches das Ziel (engl.: "*Maven goal*") +`generate` bereitstellt, ein neues Projekt mit dem Namen `my-project` und der +initialen Package-Struktur `de.hsbi.pm`. Das von Maven für die Projekterstellung +genutzte Plugin ist unter der ID `maven-archetype-quickstart` in den +Maven-Repositories (etwa [Maven-Central](https://mvnrepository.com/repos/central)) +verfügbar, hier kann man mit der zusätzlichen Option `-DarchetypeVersion=1.4` auf +die letzte Version schalten. + +Die erzeugte Ordnerstruktur entspricht der Standardstruktur von Gradle (Gradle hat +diese quasi von Maven übernommen). Die Konfigurationsdatei für Maven hat den Namen +`pom.xml`. + +*Hinweis*: Die `groupId` und `artifactId` werden auch für eine Veröffentlichung des +Jar-Files des Projekts auf dem zentralen Maven-Repository +[Maven-Central](https://mvnrepository.com/repos/central) genutzt. Von hier würde +Maven auch als Abhängigkeit konfigurierte Bibliotheken herunterladen. ::: - # Lebenszyklus (eingebaut in Maven) ![](images/screenshot_maven-lifecycle.png){width="80%"} @@ -87,13 +79,13 @@ genutzt. Von hier würde Maven auch als Abhängigkeit konfigurierte Bibliotheken In Maven ist das typische Java-Entwicklungsmodell als "Lebenszyklus" implementiert. Entsprechende Plugins stellen die jeweiligen "*Goals*" (Ziele) bereit. Dabei sind -auch die Abhängigkeiten berücksichtigt, d.h. das Ziel `test` erfordert ein `compile` ... +auch die Abhängigkeiten berücksichtigt, d.h. das Ziel `test` erfordert ein `compile` +... ::: - # Project Object Model: *pom.xml* -```xml +``` xml 4.0.0 @@ -122,29 +114,31 @@ auch die Abhängigkeiten berücksichtigt, d.h. das Ziel `test` erfordert ein `co ``` ::: notes -Die Konfigurationsdatei `pom.xml` stellt die Konfiguration für das Maven-Projekt bereit -("Project Object Model"). +Die Konfigurationsdatei `pom.xml` stellt die Konfiguration für das Maven-Projekt +bereit ("Project Object Model"). Es werden mindestens der Name des Projekts sowie die Abhängigkeiten definiert. -Die `groupId` ist ein eindeutiger Bezeichner für die Organisation oder den Autor des Projekts. Oft -wird hier einfach wie im obigen Beispiel eine Package-Struktur genutzt, aber wie im Fall von JUnit -kann dies auch ein einfacher String (dort "`junit`") sein. +Die `groupId` ist ein eindeutiger Bezeichner für die Organisation oder den Autor des +Projekts. Oft wird hier einfach wie im obigen Beispiel eine Package-Struktur +genutzt, aber wie im Fall von JUnit kann dies auch ein einfacher String (dort +"`junit`") sein. -Die `artifactId` ist der eindeutige Name für das Projekt, d.h. unter diesem Namen wird das generierte -Jar-File im Maven-Repository zu finden sein (sofern es denn veröffentlicht wird). +Die `artifactId` ist der eindeutige Name für das Projekt, d.h. unter diesem Namen +wird das generierte Jar-File im Maven-Repository zu finden sein (sofern es denn +veröffentlicht wird). -Über `dependencies` kann man benötigte Abhängigkeiten definieren, hier als Beispiel JUnit in der -4.x Variante ... Diese werden bei Bedarf von Maven vom Maven-Repository heruntergeladen. Die Einträge -für die Dependencies findet man ebenfalls auf [MavenCentral](https://mvnrepository.com/repos/central). +Über `dependencies` kann man benötigte Abhängigkeiten definieren, hier als Beispiel +JUnit in der 4.x Variante ... Diese werden bei Bedarf von Maven vom Maven-Repository +heruntergeladen. Die Einträge für die Dependencies findet man ebenfalls auf +[MavenCentral](https://mvnrepository.com/repos/central). ::: [[Demo für MavenCentral (Suche, Einträge)]{.ex}]{.slides} - # Project Object Model: Plugins -```xml +``` xml ... @@ -171,54 +165,71 @@ für die Dependencies findet man ebenfalls auf [MavenCentral](https://mvnreposit ::: notes Zusätzlich können die Phasen des Build-Prozesses konfiguriert werden, d.h. für die -entsprechenden Plugins finden sich Abschnitte unter `` in der `pom.xml`. +entsprechenden Plugins finden sich Abschnitte unter `` in der +`pom.xml`. -Auf [maven.apache.org/plugins/index.html](https://maven.apache.org/plugins/index.html) +Auf +[maven.apache.org/plugins/index.html](https://maven.apache.org/plugins/index.html) finden Sie eine Übersicht über häufig benutzte Plugins sowie die von den Plugins bereitgestellten Goals sowie Konfigurationsmöglichkeiten. Die entsprechenden POM-Einträge finden Sie analog zu den Dependencies ebenfalls auf -[MavenCentral](https://mvnrepository.com/repos/central) (Tag "plugin" statt "dependency"). +[MavenCentral](https://mvnrepository.com/repos/central) (Tag "plugin" statt +"dependency"). -Plugins können aber auch selbst erstellt werden und in das Projekt eingebunden werden, -ein erster Einstieg ist die [Plugin-API](https://maven.apache.org/ref/3.8.1/maven-plugin-api/). +Plugins können aber auch selbst erstellt werden und in das Projekt eingebunden +werden, ein erster Einstieg ist die +[Plugin-API](https://maven.apache.org/ref/3.8.1/maven-plugin-api/). ::: - ::: notes # Und wie lasse ich jetzt eine Anwendung mal laufen? -* `mvn clean`: Lösche alle generierten Artefakte, beispielsweise `.class`-Dateien. -* `mvn compile` => `mvn compiler:compile`: Übersetze die Sourcen und schiebe die - generierten `.class`-Dateien in den Ordner `target/classes/` (Default). Dazu werden alle - Abhängigkeiten aufgelöst und bei Bedarf (neu) heruntergeladen (Default: Userverzeichnis, - Ordner `.m2/`). -* `mvn test` => `mvn surefire:test`: Lasse die Tests laufen. Hängt von `compile` - ab. Namenskonvention: Alle Klassen mit `*Test.java` und `Test*.java` im Standard-Testordner - `src/test/java/` werden betrachtet (und weitere, vgl. +- `mvn clean`: Lösche alle generierten Artefakte, beispielsweise `.class`-Dateien. +- `mvn compile` =\> `mvn compiler:compile`: Übersetze die Sourcen und schiebe die + generierten `.class`-Dateien in den Ordner `target/classes/` (Default). Dazu + werden alle Abhängigkeiten aufgelöst und bei Bedarf (neu) heruntergeladen + (Default: Userverzeichnis, Ordner `.m2/`). +- `mvn test` =\> `mvn surefire:test`: Lasse die Tests laufen. Hängt von `compile` + ab. Namenskonvention: Alle Klassen mit `*Test.java` und `Test*.java` im + Standard-Testordner `src/test/java/` werden betrachtet (und weitere, vgl. [maven.apache.org/surefire/maven-surefire-plugin/examples/junit-platform.html](https://maven.apache.org/surefire/maven-surefire-plugin/examples/junit-platform.html)). -* `mvn package`: Hängt von `compile` ab und erzeugt ein Jar-File mit dem Namen +- `mvn package`: Hängt von `compile` ab und erzeugt ein Jar-File mit dem Namen "artifactId-version.jar" im Ordner `target/`. Mit `mvn install` kann man dieses Jar-File dann auch dem lokalen Repository im Home-Verzeichnis des Users (`.m2/`) hinzufügen. -* `mvn exec:java -Dexec.mainClass="de.hsbi.pm.Main"`: Hängt von `compile` ab und +- `mvn exec:java -Dexec.mainClass="de.hsbi.pm.Main"`: Hängt von `compile` ab und führt die Klasse `de.hsbi.pm.Main` aus. ::: -[Demo: pom.xml]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/building/src/maven/pom.xml"} - +[Demo: pom.xml]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/building/src/maven/pom.xml"} # Wrap-Up -Apache Maven: [maven.apache.org](https://maven.apache.org), [Maven Getting Started Guide](https://maven.apache.org/guides/getting-started/index.html) +Apache Maven: [maven.apache.org](https://maven.apache.org), [Maven Getting Started +Guide](https://maven.apache.org/guides/getting-started/index.html) \bigskip -* Automatisieren von Arbeitabläufen -* Apache Maven: Goals, Properties, Dependencies => "_Convention over Configuration_", - Java-Standard-Lebenszyklus eingebaut - * Goals sind auswählbare Ziele, bereitgestellt durch Plugins - * Abhängigkeiten zwischen Goals möglich - * Properties agieren wie Variablen, etwa für Versionsnummern - * Abhängigkeiten zu externen Bibliotheken werden als Dependencies - formuliert: [Abschnitt von Maven-Central kopieren]{.notes} +- Automatisieren von Arbeitabläufen +- Apache Maven: Goals, Properties, Dependencies =\> "*Convention over + Configuration*", Java-Standard-Lebenszyklus eingebaut + - Goals sind auswählbare Ziele, bereitgestellt durch Plugins + - Abhängigkeiten zwischen Goals möglich + - Properties agieren wie Variablen, etwa für Versionsnummern + - Abhängigkeiten zu externen Bibliotheken werden als Dependencies formuliert: + [Abschnitt von Maven-Central kopieren]{.notes} + +::: readings +- @Ullenboom2016 +- @Inden2013 +::: + +::: outcomes +- k3: Schreiben einfacher Maven-Skripte zu Übersetzen des Projekts, zum Testen und + zum Erzeugen von Jar-Files +- k3: Nutzung von Maven-Properties +- k3: Einbinden externer Bibliotheken als Dependencies +- k3: Ausführen von Maven-Goals aus IDE heraus und Einbindung als Builder +::: diff --git a/lecture/building/readme.md b/lecture/building/readme.md index fcc46c2a6..8023df262 100644 --- a/lecture/building/readme.md +++ b/lecture/building/readme.md @@ -1,5 +1,6 @@ --- -title: "Bauen von Programmen, Automatisierung, Continuous Integration" -no_pdf: true no_beamer: true +no_pdf: true +title: Bauen von Programmen, Automatisierung, Continuous Integration --- + diff --git a/lecture/git/bisect.md b/lecture/git/bisect.md index 94b59ba93..cf502d3be 100644 --- a/lecture/git/bisect.md +++ b/lecture/git/bisect.md @@ -1,65 +1,53 @@ --- -title: "Fehlersuche mit Git Bisect" -author: "Carsten Gips (HSBI)" -readings: - - "@Chacon2014 [Kap. 7.10]" - - "@AtlassianGit" - - "@GitCheatSheet" -tldr: | - Mit Git Bisect kann man durch Halbierungssuche den Commit finden, der einen - bestimmten Fehler eingeführt hat. - - Dazu startet man den Prozess mit `git bisect start` und teilt Git jeweils den - letzten bekannten "guten" und den ersten bekannten fehlerhaften Commit mit: - `git bisect good ` und `git bisect bad `. Anschließend sucht - Git einen Commit dazwischen und "präsentiert" diesen in der Workingcopy. Wenn - der Commit noch fehlerfrei ist, antwortet man mit `git bisect good`, sonst mit - `git bisect bad`. Auf diese Weise grenzt man den Commit ein, der den Fehler - eingeführt hat. - - Man kann die Workingcopy mit `git bisect reset` anschließend wieder zurücksetzen. - - Der Prozess lässt sich automatisieren mit `git bisect run `, - wobei Git automatisch bei jedem untersuchten Commit das Skript ausführt und - entsprechend reagiert (die Rückgabe 0 bedeutet dabei "OK", ein anderer Rückgabewert - bedeutet "Fehler"). -outcomes: - - k3: "Fehlersuche mit Git Bisect" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106247&client_id=FH-Bielefeld" -# name: "Quiz Git Bisect (ILIAS)" -youtube: - - link: "https://youtu.be/9XWwefIokPg" - name: "VL Git Bisect" - - link: "https://youtu.be/hiiugrQMNR4" - name: "Demo Git Bisect" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/c844865038e41cf59adebab0244758e61af9cf2c76233962be711920ff8f2d614d1a8012ec85d47cf421eaa94a9efedf0917bf369f3adb495c018796d3d89b13" - name: "VL Git Bisect" +author: Carsten Gips (HSBI) +title: Fehlersuche mit Git Bisect --- +::: tldr +Mit Git Bisect kann man durch Halbierungssuche den Commit finden, der einen +bestimmten Fehler eingeführt hat. + +Dazu startet man den Prozess mit `git bisect start` und teilt Git jeweils den +letzten bekannten "guten" und den ersten bekannten fehlerhaften Commit mit: +`git bisect good ` und `git bisect bad `. Anschließend sucht Git +einen Commit dazwischen und "präsentiert" diesen in der Workingcopy. Wenn der Commit +noch fehlerfrei ist, antwortet man mit `git bisect good`, sonst mit +`git bisect bad`. Auf diese Weise grenzt man den Commit ein, der den Fehler +eingeführt hat. + +Man kann die Workingcopy mit `git bisect reset` anschließend wieder zurücksetzen. + +Der Prozess lässt sich automatisieren mit `git bisect run `, +wobei Git automatisch bei jedem untersuchten Commit das Skript ausführt und +entsprechend reagiert (die Rückgabe 0 bedeutet dabei "OK", ein anderer Rückgabewert +bedeutet "Fehler"). +::: + +::: youtube +- [VL Git Bisect](https://youtu.be/9XWwefIokPg) +- [Demo Git Bisect](https://youtu.be/hiiugrQMNR4) +::: # Git Bisect: Wer hats wann kaputt gespielt? -::::::::: notes +::: notes ## Szenario -* Sie haben eine Software ausgeliefert, die offenbar gut funktioniert. -* Sie erweitern die Software und testen intern mit JUnit. -* Nachdem Sie keine Fehler mehr gefunden haben, erstellen Sie ein neues - Release und liefern die neue Version aus. -* Die Kunden beschweren sich über Fehler .... -* Sie erstellen einen neuen Test mit JUnit für das von den Kunden beschriebene - Verhalten und können das Problem verifizieren. Offenbar haben Sie vorher - nicht gründlich genug getestet. -* Sie haben leider keine Idee, woran der Fehler liegt (der Test ist eher - ein Systemtest) und wann er sich in die Code-Basis eingeschlichen hat. +- Sie haben eine Software ausgeliefert, die offenbar gut funktioniert. +- Sie erweitern die Software und testen intern mit JUnit. +- Nachdem Sie keine Fehler mehr gefunden haben, erstellen Sie ein neues Release + und liefern die neue Version aus. +- Die Kunden beschweren sich über Fehler .... +- Sie erstellen einen neuen Test mit JUnit für das von den Kunden beschriebene + Verhalten und können das Problem verifizieren. Offenbar haben Sie vorher nicht + gründlich genug getestet. +- Sie haben leider keine Idee, woran der Fehler liegt (der Test ist eher ein + Systemtest) und wann er sich in die Code-Basis eingeschlichen hat. -=> Hier schlägt die Stunde von Git Bisect :-) +=\> Hier schlägt die Stunde von Git Bisect :-) ## Arbeitsweise -::::::::: - +::: 1. Git Bisect initialisieren @@ -70,10 +58,11 @@ fhmedia: \smallskip 2. Git präsentiert einen Commit zwischen `` und `` - * Alles OK: `git bisect good` - * Fehler vorhanden: `git bisect bad` + - Alles OK: `git bisect good` - => Git sucht nächsten Commit dazwischen ... + - Fehler vorhanden: `git bisect bad` + + =\> Git sucht nächsten Commit dazwischen ... \smallskip @@ -81,28 +70,37 @@ fhmedia: [Live-Demo]{.ex href="https://youtu.be/hiiugrQMNR4"} - -::::::::: notes +::: notes ## Anmerkungen -* Geht auch mit `new`/`old` (statt `good`/`bad`) -* Suche kann auch automatisiert werden: `git bisect run ` - * Skript `my_skript` wird bei jedem Stopp ausgeführt - * Ergebnis (Rückgabewert) wird zur Interpretation `good`/`bad` herangezogen: - * Wert 0 für `good` - * Wert 1 für `bad` -* Wenn Git Bisect einen Commit auswählt, der nicht compiliert o.ä. - (kann eigentlich nicht vorkommen, da wir so etwas ja NIE einchecken!), - dann kann man einen in der Nähe liegenden Commit als Ersatz - auswählen lassen: `git bisect skip` +- Geht auch mit `new`/`old` (statt `good`/`bad`) +- Suche kann auch automatisiert werden: `git bisect run ` + - Skript `my_skript` wird bei jedem Stopp ausgeführt + - Ergebnis (Rückgabewert) wird zur Interpretation `good`/`bad` herangezogen: + - Wert 0 für `good` + - Wert 1 für `bad` +- Wenn Git Bisect einen Commit auswählt, der nicht compiliert o.ä. (kann + eigentlich nicht vorkommen, da wir so etwas ja NIE einchecken!), dann kann man + einen in der Nähe liegenden Commit als Ersatz auswählen lassen: + `git bisect skip` ## Tutorial -Ein schönes Tutorial finden Sie auf -["A beginner's guide to GIT BISECT - The process of elimination"](https://www.metaltoad.com/blog/beginners-guide-git-bisect-process-elimination). -::::::::: - +Ein schönes Tutorial finden Sie auf ["A beginner's guide to GIT BISECT - The process +of +elimination"](https://www.metaltoad.com/blog/beginners-guide-git-bisect-process-elimination). +::: # Wrap-Up -* Git Bisect: Finde Commit, der einen Fehler einführt (Halbierungs-Suche) +- Git Bisect: Finde Commit, der einen Fehler einführt (Halbierungs-Suche) + +::: readings +- @Chacon2014 [Kap. 7.10] +- @AtlassianGit +- @GitCheatSheet +::: + +::: outcomes +- k3: Fehlersuche mit Git Bisect +::: diff --git a/lecture/git/branches.md b/lecture/git/branches.md index b5498b9c3..58dbb332e 100644 --- a/lecture/git/branches.md +++ b/lecture/git/branches.md @@ -1,151 +1,80 @@ --- +author: Carsten Gips (HSBI) title: "Git Branches: Features unabhängig entwickeln und mit Git verwalten" -author: "Carsten Gips (HSBI)" -readings: - - "@Chacon2014 [Kap. 3]" - - "@AtlassianGit" - - "@GitCheatSheet" -tldr: | - Die Commits in Git bauen aufeinander auf und bilden dabei eine verkettete "Liste". Diese "Liste" nennt man - auch *Branch* (Entwicklungszweig). Beim Initialisieren eines Repositories wird automatisch ein Default-Branch - angelegt, auf dem die Commits dann eingefügt werden. - - Weitere Branches kann man mit `git branch` anlegen, und die Workingcopy kann mit `git switch` oder `git checkout` - auf einen anderen Branch umgeschaltet werden. Auf diese Weise kann man an mehreren Features parallel arbeiten, - ohne dass die Arbeiten sich gegenseitig stören. - - Zum Mergen (Vereinigen) von Branches gibt es `git merge`. Dabei werden die Änderungen im angegebenen Branch in - den aktuell in der Workingcopy ausgecheckten Branch integriert und hier ggf. ein neuer Merge-Commit erzeugt. Falls - es in beiden Branches inkompatible Änderungen an der selben Stelle gab, entsteht beim Mergen ein Merge-Konflikt. - Dabei zeigt Git in den betroffenen Dateien jeweils an, welche Änderung aus welchem Branch stammt und man muss - diesen Konflikt durch Editieren der Stellen manuell beheben. - - Mit `git rebase` kann die Wurzel eines Branches an eine andere Stelle verschoben werden. Dies wird später bei - Workflows eine Rolle spielen. -outcomes: - - k3: "Ich kann neue Branches anlegen" - - k3: "Ich kann Branches mergen und mögliche Konflikte auflösen" - - k3: "Ich kann Branches rebasen" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106242&client_id=FH-Bielefeld" -# name: "Quiz Git Branches (ILIAS)" -# - link: "https://learngitbranching.js.org/" -# name: "Tutorial: Welcome to Learn Git Branching" -# - link: "https://marklodato.github.io/visual-git-guide/index-en.html" -# name: "Tutorial: A Visual Git Reference" -youtube: - - link: "https://youtu.be/WXPJOsgeR10" - name: "VL Git Branches" - - link: "https://youtu.be/B8sesK1GyiE" - name: "Demo Anlegen und Mergen von Branches" - - link: "https://youtu.be/iEr9i8auF7c" - name: "Demo Auflösen von Merge-Konflikten" - - link: "https://youtu.be/U4gd0FBBqZQ" - name: "Demo HEAD" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/858546c533e356ef9cdbf1341719281a76d4f9b4405b654dfa6e96c0043ebb87a098da0d8bdca88088d1deb52433f117ab880c24495ce6dafa64ba02cfaabcf2" - name: "VL Git Branches" -challenges: | - **Branches und Merges** - - 1. Legen Sie in Ihrem Projekt einen Branch an. Ändern Sie einige Dateien - und committen Sie die Änderungen. Checken Sie den Master-Branch aus und - mergen Sie die Änderungen. Was beobachten Sie? - - 2. Legen Sie einen weiteren Branch an. Ändern Sie einige Dateien und - committen Sie die Änderungen. Checken Sie den Master-Branch aus und - ändern Sie dort ebenfalls: - - * Ändern Sie eine Datei an einer Stelle, die nicht bereits im Branch - modifiziert wurde. - * Ändern Sie eine Datei an einer Stelle, die bereits im Branch - manipuliert wurde. - - Committen Sie die Änderungen. - - Mergen Sie den Branch jetzt in den Master-Branch. Was beobachten Sie? Wie - lösen Sie Konflikte auf? - - - **Mergen am Beispiel** - - Sie verwalten Ihr Projekt mit Git. Es existieren zwei Branches: `master` (zeigt - auf Commit $C$) und `feature` (zeigt auf Version $F$). In Ihrer Workingcopy - haben Sie den Branch `feature` ausgecheckt: - - ![](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/git/images/before.png?raw=true){width="35%"} - - (1) Mit welcher Befehlsfolge können Sie den Branch `feature` in den Branch `master` - mergen, so dass nach dem Merge die im folgenden Bild dargestellte Situation - entsteht? - - ![](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/git/images/after.png?raw=true){width="35%"} - - (Der Merge läuft ohne Konflikte ab. Es ist irrelevant, welcher Branch am Ende - in der Workingcopy ausgecheckt ist.) - - - - (2) Wie können Sie erreichen, dass es keinen Merge-Commit gibt, sondern dass die Änderungen - in $D$ und $F$ im `master` als eine lineare Folge von Commits erscheinen? - - - - - **Interaktive Git-Tutorials**: Schaffen Sie die Rätsel? - - * [Learn Git Branching](https://learngitbranching.js.org/) - * [Oh My Git!](https://ohmygit.org/) - * [Git Time](https://git.bradwoods.io/) - * [Tutorial: A Visual Git Reference](https://marklodato.github.io/visual-git-guide/index-en.html) --- +::: tldr +Die Commits in Git bauen aufeinander auf und bilden dabei eine verkettete "Liste". +Diese "Liste" nennt man auch *Branch* (Entwicklungszweig). Beim Initialisieren eines +Repositories wird automatisch ein Default-Branch angelegt, auf dem die Commits dann +eingefügt werden. + +Weitere Branches kann man mit `git branch` anlegen, und die Workingcopy kann mit +`git switch` oder `git checkout` auf einen anderen Branch umgeschaltet werden. Auf +diese Weise kann man an mehreren Features parallel arbeiten, ohne dass die Arbeiten +sich gegenseitig stören. + +Zum Mergen (Vereinigen) von Branches gibt es `git merge`. Dabei werden die +Änderungen im angegebenen Branch in den aktuell in der Workingcopy ausgecheckten +Branch integriert und hier ggf. ein neuer Merge-Commit erzeugt. Falls es in beiden +Branches inkompatible Änderungen an der selben Stelle gab, entsteht beim Mergen ein +Merge-Konflikt. Dabei zeigt Git in den betroffenen Dateien jeweils an, welche +Änderung aus welchem Branch stammt und man muss diesen Konflikt durch Editieren der +Stellen manuell beheben. + +Mit `git rebase` kann die Wurzel eines Branches an eine andere Stelle verschoben +werden. Dies wird später bei Workflows eine Rolle spielen. +::: + +::: youtube +- [VL Git Branches](https://youtu.be/WXPJOsgeR10) +- [Demo Anlegen und Mergen von Branches](https://youtu.be/B8sesK1GyiE) +- [Demo Auflösen von Merge-Konflikten](https://youtu.be/iEr9i8auF7c) +- [Demo HEAD](https://youtu.be/U4gd0FBBqZQ) +::: # Neues Feature entwickeln/ausprobieren A---B---C master ::: notes -* Bisher nur lineare Entwicklung: Commits bauen aufeinander auf (lineare Folge von Commits) -* `master` ist der (Default-) Hauptentwicklungszweig - * Pointer auf letzten Commit - * Default-Name: "`master`" (muss aber nicht so sein bzw. kann geändert werden) +- Bisher nur lineare Entwicklung: Commits bauen aufeinander auf (lineare Folge von + Commits) +- `master` ist der (Default-) Hauptentwicklungszweig + - Pointer auf letzten Commit + - Default-Name: "`master`" (muss aber nicht so sein bzw. kann geändert werden) *Anmerkung*: Git und auch Github haben den Namen für den Default-Branch von `master` auf `main`geändert. Der Name an sich ist aber für Git bedeutungslos und kann mittels `git config --global init.defaultBranch ` geändert werden. In Github hat der Default-Branch eine gewisse Bedeutung, beispielsweise ist der Default-Branch das -automatische Ziel beim Anlegen von Pull-Requests. In Github kann man den Default-Namen -global in den User-Einstellungen (Abschnitt "Repositories") und für jedes einzelne -Repository in den Repo-Einstellungen (Abschnitt "Branches") ändern. +automatische Ziel beim Anlegen von Pull-Requests. In Github kann man den +Default-Namen global in den User-Einstellungen (Abschnitt "Repositories") und für +jedes einzelne Repository in den Repo-Einstellungen (Abschnitt "Branches") ändern. ::: \bigskip \pause ::: notes -Entwicklung des neuen Features soll stabilen `master`-Branch nicht beeinflussen -=> Eigenen Entwicklungszweig für die Entwicklung des Features anlegen: +Entwicklung des neuen Features soll stabilen `master`-Branch nicht beeinflussen =\> +Eigenen Entwicklungszweig für die Entwicklung des Features anlegen: ::: 1. [Neuen Branch erstellen:]{.notes} `git branch wuppie` -2. [Neuen Branch auschecken:]{.notes} `git checkout wuppie` oder `git switch wuppie` +2. [Neuen Branch auschecken:]{.notes} `git checkout wuppie` oder + `git switch wuppie` \bigskip -Alternativ: `git checkout -b wuppie` oder `git switch -c wuppie` [(neuer Branch und auschecken in einem Schritt)]{.notes} +Alternativ: `git checkout -b wuppie` oder `git switch -c wuppie` [(neuer Branch und +auschecken in einem Schritt)]{.notes} \bigskip \bigskip - A---B---C master, wuppie - ::: notes Startpunkt: prinzipiell beliebig (jeder Commit in der Historie möglich). @@ -157,14 +86,13 @@ Startpunkt; hier kann jeder beliebige Branch, Tag oder Commit genutzt werden). Nach Anlegen des neuen Branches zeigen beide Pointer auf den selben Commit. -*Anmerkung*: In neueren Git-Versionen wurde der Befehl "`switch`" eingeführt, -mit dem Sie in der Workingcopy auf einen anderen Branch wechseln können. Der -bisherige Befehl "`checkout`" funktioniert aber weiterhin. +*Anmerkung*: In neueren Git-Versionen wurde der Befehl "`switch`" eingeführt, mit +dem Sie in der Workingcopy auf einen anderen Branch wechseln können. Der bisherige +Befehl "`checkout`" funktioniert aber weiterhin. ::: [[Konsole]{.ex}]{.slides} - # Arbeiten im Entwicklungszweig ... D wuppie @@ -172,16 +100,14 @@ bisherige Befehl "`checkout`" funktioniert aber weiterhin. A---B---C master ::: notes -* Entwicklung des neuen Features erfolgt im eigenen Branch: beeinflusst den +- Entwicklung des neuen Features erfolgt im eigenen Branch: beeinflusst den stabilen `master`-Branch nicht -* Wenn in der Workingcopy der Feature-Branch ausgecheckt ist, gehen die - Commits in den Feature-Branch; der `master` bleibt auf dem alten Stand -* Wenn der `master` ausgecheckt wäre, würden die Änderungen in den `master` - gehen, d.h. der `master` würde sich ab Commit `C` parallel zu `wuppie` - entwickeln +- Wenn in der Workingcopy der Feature-Branch ausgecheckt ist, gehen die Commits in + den Feature-Branch; der `master` bleibt auf dem alten Stand +- Wenn der `master` ausgecheckt wäre, würden die Änderungen in den `master` gehen, + d.h. der `master` würde sich ab Commit `C` parallel zu `wuppie` entwickeln ::: - # Problem: Fehler im ausgelieferten Produkt D wuppie @@ -212,17 +138,15 @@ Das führt zu dieser Situation: E fix ::: notes -`git checkout ` holt den aktuellen Stand des jeweiligen -Branches in die Workingcopy. (Das geht in neueren Git-Versionen auch -mit `git switch `.) +`git checkout ` holt den aktuellen Stand des jeweiligen Branches in die +Workingcopy. (Das geht in neueren Git-Versionen auch mit `git switch `.) -Man kann weitere Branches anlegen, d.h. hier im Beispiel ein neuer -Feature-Branch `fix`, der auf dem `master` basiert. Analog könnte man -auch Branches auf der Basis von `wuppie` anlegen ... +Man kann weitere Branches anlegen, d.h. hier im Beispiel ein neuer Feature-Branch +`fix`, der auf dem `master` basiert. Analog könnte man auch Branches auf der Basis +von `wuppie` anlegen ... ::: - -# Fix ist stabil: Integration in _master_ +# Fix ist stabil: Integration in *master* D wuppie / @@ -233,7 +157,7 @@ auch Branches auf der Basis von `wuppie` anlegen ... \bigskip 1. `git checkout master` -2. `git merge fix` => **fast forward** von `master` +2. `git merge fix` =\> **fast forward** von `master` 3. `git branch -d fix` ::: notes @@ -247,32 +171,31 @@ Der letzte Schritt entfernt den Branch `fix`. / A---B---C---E master - ::: notes -* Allgemein: `git merge ` führt die Änderungen im angegebenen Branch - `` in den aktuell in der Workingcopy ausgecheckten Branch ein. Daraus - resultiert für den aktuell ausgecheckten Branch ein neuer Commit, der Branch - `` bleibt dagegen auf seinem bisherigen Stand. +- Allgemein: `git merge ` führt die Änderungen im angegebenen Branch + `` in den aktuell in der Workingcopy ausgecheckten Branch ein. + Daraus resultiert für den aktuell ausgecheckten Branch ein neuer Commit, der + Branch `` bleibt dagegen auf seinem bisherigen Stand. Beispiel: - * Die Workingcopy ist auf `A` - * `git merge B` führt `A` und `B` zusammen: `B` wird **in** `A` gemergt - * Wichtig: Der Merge-Commit (sofern nötig) findet hierbei in `A` statt! + + - Die Workingcopy ist auf `A` + - `git merge B` führt `A` und `B` zusammen: `B` wird **in** `A` gemergt + - Wichtig: Der Merge-Commit (sofern nötig) findet hierbei in `A` statt! In der Abbildung ist `A` der `master` und `B` der `fix`. -* Nach dem Merge existieren beide Branches weiter (sofern sie nicht explizit +- Nach dem Merge existieren beide Branches weiter (sofern sie nicht explizit gelöscht werden) -* Hier im Beispiel findet ein sogenannter "Fast forward" statt. +- Hier im Beispiel findet ein sogenannter "Fast forward" statt. - "Fast forward" ist ein günstiger Spezialfall beim Merge: Beide Branches - liegen in einer direkten Kette, d.h. der Zielbranch kann einfach - "weitergeschaltet" werden. Ein Merge-Commit ist in diesem Fall nicht - notwendig und wird auch nicht angelegt. + "Fast forward" ist ein günstiger Spezialfall beim Merge: Beide Branches liegen + in einer direkten Kette, d.h. der Zielbranch kann einfach "weitergeschaltet" + werden. Ein Merge-Commit ist in diesem Fall nicht notwendig und wird auch nicht + angelegt. ::: - # Feature weiter entwickeln ... D---F wuppie @@ -285,19 +208,18 @@ Der letzte Schritt entfernt den Branch `fix`. 2. Weitere Änderungen im Branch `wuppie` ... ::: notes -`git switch ` holt den aktuellen Stand des jeweiligen Branches in -die Workingcopy. Man kann also jederzeit in der Workingcopy die Branches wechseln -und entsprechend weiterarbeiten. +`git switch ` holt den aktuellen Stand des jeweiligen Branches in die +Workingcopy. Man kann also jederzeit in der Workingcopy die Branches wechseln und +entsprechend weiterarbeiten. *Hinweis*: Während der neue `git switch`-Befehl nur Branches umschalten kann, -funktioniert `git checkout` sowohl mit Branchnamen und Dateinamen - damit kann -man also auch eine andere Version einer Datei in der Workingcopy "auschecken". -Falls gleiche Branch- und Dateinamen existieren, muss man für das Auschecken -einer Datei noch "`--`" nutzen: `git checkout -- `. +funktioniert `git checkout` sowohl mit Branchnamen und Dateinamen - damit kann man +also auch eine andere Version einer Datei in der Workingcopy "auschecken". Falls +gleiche Branch- und Dateinamen existieren, muss man für das Auschecken einer Datei +noch "`--`" nutzen: `git checkout -- `. ::: - -# Feature ist stabil: Integration in _master_ +# Feature ist stabil: Integration in *master* D---F wuppie D---F wuppie / => / \ @@ -307,40 +229,41 @@ einer Datei noch "`--`" nutzen: `git checkout -- `. \bigskip 1. `git checkout master` -2. `git merge wuppie` \newline => Kein *fast forward* möglich: Git sucht nach gemeinsamen Vorgänger +2. `git merge wuppie` `\newline`{=tex}=\> Kein *fast forward* möglich: Git sucht + nach gemeinsamen Vorgänger ::: notes -Hier im Beispiel ist der Standardfall beim Mergen dargestellt: Die beiden -Branches liegen nicht in einer direkten Kette von Commits, d.h. hier wurde -parallel weitergearbeitet. +Hier im Beispiel ist der Standardfall beim Mergen dargestellt: Die beiden Branches +liegen nicht in einer direkten Kette von Commits, d.h. hier wurde parallel +weitergearbeitet. -Git sucht in diesem Fall nach dem gemeinsamen Vorgänger beider Branches und -führt die jeweiligen Änderungen (Differenzen) seit diesem Vorgänger in einem -Merge-Commit zusammen. +Git sucht in diesem Fall nach dem gemeinsamen Vorgänger beider Branches und führt +die jeweiligen Änderungen (Differenzen) seit diesem Vorgänger in einem Merge-Commit +zusammen. -Im `master` entsteht ein neuer Commit, da kein *fast forward* beim -Zusammenführen der Branches möglich! +Im `master` entsteht ein neuer Commit, da kein *fast forward* beim Zusammenführen +der Branches möglich! *Anmerkung*: `git checkout wuppie; git merge master` würde den `master` in den `wuppie` mergen, d.h. der Merge-Commit wäre dann in `wuppie`. - Beachten Sie dabei die "Merge-Richtung": -* Die Workingcopy ist auf `A` -* `git merge B` führt `A` und `B` zusammen: `B` wird **in** `A` gemergt -* Wichtig: Der Merge-Commit (sofern nötig) findet hierbei in `A` statt! + +- Die Workingcopy ist auf `A` +- `git merge B` führt `A` und `B` zusammen: `B` wird **in** `A` gemergt +- Wichtig: Der Merge-Commit (sofern nötig) findet hierbei in `A` statt! In der Abbildung ist `A` der `master` und `B` der `wuppie`. -**Achtung**: Richtung beachten! `git checkout A; git merge B` führt beide Branches zusammen, -genauer: führt die Änderungen von `B` in `A` ein, d.h. der entsprechende Merge-Commit ist in `A`! +**Achtung**: Richtung beachten! `git checkout A; git merge B` führt beide Branches +zusammen, genauer: führt die Änderungen von `B` in `A` ein, d.h. der entsprechende +Merge-Commit ist in `A`! ::: - # Konflikte beim Mergen ::: notes -(Parallele) Änderungen an selber Stelle => Merge-Konflikte +(Parallele) Änderungen an selber Stelle =\> Merge-Konflikte ::: $ git merge wuppie @@ -365,12 +288,11 @@ Git fügt Konflikt-Marker in die Datei ein: >>>>>>> wuppie:hero.java ::: notes -* Der Teil mit `HEAD` ist aus dem aktuellen Branch in der Workingcopy -* Der Teil aus dem zu mergenden Branch ist unter `wuppie` notiert -* Das `=======` trennt beide Bereiche +- Der Teil mit `HEAD` ist aus dem aktuellen Branch in der Workingcopy +- Der Teil aus dem zu mergenden Branch ist unter `wuppie` notiert +- Das `=======` trennt beide Bereiche ::: - ::: notes # Merge-Konflikte auflösen @@ -388,7 +310,6 @@ Alternativ: Nutzung graphischer Oberflächen mittels `git mergetool` [Konsole: Branchen und Mergen]{.ex href="https://youtu.be/B8sesK1GyiE"} - # Rebasen: Verschieben von Branches D---F wuppie D---F wuppie @@ -396,24 +317,26 @@ Alternativ: Nutzung graphischer Oberflächen mittels `git mergetool` A---B---C---E master A---B---C---E---G master ::: notes -Bisher haben wir Branches durch Mergen zusammengeführt. Dabei entsteht in der Regel ein extra -Merge-Commit (im Beispiel `G`), außer es handelt sich um ein *fast forward*. Außerdem erkennt -man in der Historie sehr gut, dass hier in einem separaten Branch gearbeitet wurde, der irgendwann -in den `master` gemergt wurde. - -Leider wird dieses Vorgehen in großen Projekten recht schnell sehr unübersichtlich. Außerdem -werden Merges in der Regeln nur von besonders berechtigten Personen (Manager) durchgeführt, die im -Falle von Merge-Konflikten diese dann selbst auflösen müssten (ohne aber die fachliche Befähigung -zu haben). Hier greift man dann häufig zur Alternative *Rebase*. Dabei wird der Ursprung eines -Branches auf einen bestimmten Commit verschoben. Im Anschluss ist dann ein Merge mit *fast forward*, -also ohne die typischen rautenförmigen Ketten in der Historie und ohne extra Merge-Commit möglich. -Dies kann aber auch als Nachteil gesehen werden, da man in der Historie den früheren Branch nicht -mehr erkennt! Ein weiterer schwerwiegender Nachteil ist, dass alle Commits im verschobenen Branch -umgeschrieben werden und damit neue Commit-IDs bekommen. Das verursacht bei der Zusammenarbeit in -Projekten massive Probleme! Als Vorteil gilt, dass man mögliche Merge-Konflikte bereits beim Rebasen -auflösen muss, d.h. hier muss derjenige, der den Merge "beantragt", durch einen vorherigen Rebase den -konfliktfreien Merge sicherstellen. Mehr dazu in ["Branching-Strategien"](branching-strategies.md) -und ["Workflows"](workflows.md). +Bisher haben wir Branches durch Mergen zusammengeführt. Dabei entsteht in der Regel +ein extra Merge-Commit (im Beispiel `G`), außer es handelt sich um ein *fast +forward*. Außerdem erkennt man in der Historie sehr gut, dass hier in einem +separaten Branch gearbeitet wurde, der irgendwann in den `master` gemergt wurde. + +Leider wird dieses Vorgehen in großen Projekten recht schnell sehr unübersichtlich. +Außerdem werden Merges in der Regeln nur von besonders berechtigten Personen +(Manager) durchgeführt, die im Falle von Merge-Konflikten diese dann selbst auflösen +müssten (ohne aber die fachliche Befähigung zu haben). Hier greift man dann häufig +zur Alternative *Rebase*. Dabei wird der Ursprung eines Branches auf einen +bestimmten Commit verschoben. Im Anschluss ist dann ein Merge mit *fast forward*, +also ohne die typischen rautenförmigen Ketten in der Historie und ohne extra +Merge-Commit möglich. Dies kann aber auch als Nachteil gesehen werden, da man in der +Historie den früheren Branch nicht mehr erkennt! Ein weiterer schwerwiegender +Nachteil ist, dass alle Commits im verschobenen Branch umgeschrieben werden und +damit neue Commit-IDs bekommen. Das verursacht bei der Zusammenarbeit in Projekten +massive Probleme! Als Vorteil gilt, dass man mögliche Merge-Konflikte bereits beim +Rebasen auflösen muss, d.h. hier muss derjenige, der den Merge "beantragt", durch +einen vorherigen Rebase den konfliktfreien Merge sicherstellen. Mehr dazu in +["Branching-Strategien"](branching-strategies.md) und ["Workflows"](workflows.md). ::: \pause @@ -422,7 +345,7 @@ und ["Workflows"](workflows.md). \bigskip :::::: columns -::: {.column width="40%"} +:::: {.column width="40%"} \vspace{4mm} git rebase master wuppie @@ -430,57 +353,125 @@ und ["Workflows"](workflows.md). ::: notes führt zu ::: -::: -::: {.column width="40%"} +:::: +::: {.column width="40%"} D'---F' wuppie / A---B---C---E master - ::: :::::: ::: notes -Nach dem Rebase von `wuppie` auf `master` sieht es so aus, als ob der Branch `wuppie` -eben erst vom `master` abgezweigt wurde. Damit ist dann ein *fast forward* Merge von `wuppie` -in den `master` möglich, d.h. es gibt keine Raute und auch keinen extra Merge-Commit (hier nicht -gezeigt). +Nach dem Rebase von `wuppie` auf `master` sieht es so aus, als ob der Branch +`wuppie` eben erst vom `master` abgezweigt wurde. Damit ist dann ein *fast forward* +Merge von `wuppie` in den `master` möglich, d.h. es gibt keine Raute und auch keinen +extra Merge-Commit (hier nicht gezeigt). -Man beachte aber die Änderung der Commit-IDs von `wuppie`: Aus `D` wird `D'`! (Datum, Ersteller -und Message bleiben aber erhalten.) +Man beachte aber die Änderung der Commit-IDs von `wuppie`: Aus `D` wird `D'`! +(Datum, Ersteller und Message bleiben aber erhalten.) ::: - # Don't lose your HEAD -* Branches sind wie Zeiger auf letzten Stand (Commit) eines Zweiges +- Branches sind wie Zeiger auf letzten Stand (Commit) eines Zweiges + +- `HEAD`: Spezieller Pointer -* `HEAD`: Spezieller Pointer - * Zeigt auf den aktuellen Branch der Workingcopy + - Zeigt auf den aktuellen Branch der Workingcopy \bigskip -* Früheren Commit auschecken (ohne Branch): "headless state" - * Workingcopy ist auf früherem Commit - * Kein Branch => Änderungen gehen verloren! +- Früheren Commit auschecken (ohne Branch): "headless state" + - Workingcopy ist auf früherem Commit + + - Kein Branch =\> Änderungen gehen verloren! ::: notes - Eventuelle Änderungen würden ganz normal als Commits auf - dem `HEAD`-Branch aufgezeichnet. Sobald man aber einen - anderen Branch auscheckt, wird der `HEAD` auf diesen anderen - Branch gesetzt, so dass die eben gemachten Commits "in der - Luft hängen". Sofern man die SHA's kennt, kommt man noch auf - die Commits zurück. Allerdings laufen von Zeit zu Zeit interne - Aufräum-Aktionen, so dass die Chance gut steht, dass die - "kopflosen" Commits irgendwann tatsächlich verschwinden. + Eventuelle Änderungen würden ganz normal als Commits auf dem `HEAD`-Branch + aufgezeichnet. Sobald man aber einen anderen Branch auscheckt, wird der + `HEAD` auf diesen anderen Branch gesetzt, so dass die eben gemachten Commits + "in der Luft hängen". Sofern man die SHA's kennt, kommt man noch auf die + Commits zurück. Allerdings laufen von Zeit zu Zeit interne Aufräum-Aktionen, + so dass die Chance gut steht, dass die "kopflosen" Commits irgendwann + tatsächlich verschwinden. ::: [[Konsole: Commit auschecken]{.ex}]{.slides} - # Wrap-Up -* Anlegen von Branches mit `git branch` -* Umschalten der Workingcopy auf anderen Branch: `git checkout` oder `git switch` -* Mergen von Branches und Auflösen von Konflikten: `git merge` -* Verschieben von Branches mit `git rebase` +- Anlegen von Branches mit `git branch` +- Umschalten der Workingcopy auf anderen Branch: `git checkout` oder `git switch` +- Mergen von Branches und Auflösen von Konflikten: `git merge` +- Verschieben von Branches mit `git rebase` + +::: readings +- @Chacon2014 [Kap. 3] +- @AtlassianGit +- @GitCheatSheet +::: + +::: outcomes +- k3: Ich kann neue Branches anlegen +- k3: Ich kann Branches mergen und mögliche Konflikte auflösen +- k3: Ich kann Branches rebasen +::: + +::: challenges +**Branches und Merges** + +1. Legen Sie in Ihrem Projekt einen Branch an. Ändern Sie einige Dateien und + committen Sie die Änderungen. Checken Sie den Master-Branch aus und mergen Sie + die Änderungen. Was beobachten Sie? + +2. Legen Sie einen weiteren Branch an. Ändern Sie einige Dateien und committen Sie + die Änderungen. Checken Sie den Master-Branch aus und ändern Sie dort ebenfalls: + + - Ändern Sie eine Datei an einer Stelle, die nicht bereits im Branch + modifiziert wurde. + - Ändern Sie eine Datei an einer Stelle, die bereits im Branch manipuliert + wurde. + + Committen Sie die Änderungen. + + Mergen Sie den Branch jetzt in den Master-Branch. Was beobachten Sie? Wie lösen + Sie Konflikte auf? + +**Mergen am Beispiel** + +Sie verwalten Ihr Projekt mit Git. Es existieren zwei Branches: `master` (zeigt auf +Commit $C$) und `feature` (zeigt auf Version $F$). In Ihrer Workingcopy haben Sie +den Branch `feature` ausgecheckt: + +![](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/git/images/before.png?raw=true){width="35%"} + +(1) Mit welcher Befehlsfolge können Sie den Branch `feature` in den Branch `master` + mergen, so dass nach dem Merge die im folgenden Bild dargestellte Situation + entsteht? + +![](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/git/images/after.png?raw=true){width="35%"} + +(Der Merge läuft ohne Konflikte ab. Es ist irrelevant, welcher Branch am Ende in der +Workingcopy ausgecheckt ist.) + + + +(2) Wie können Sie erreichen, dass es keinen Merge-Commit gibt, sondern dass die + Änderungen in $D$ und $F$ im `master` als eine lineare Folge von Commits + erscheinen? + + + +**Interaktive Git-Tutorials**: Schaffen Sie die Rätsel? + +- [Learn Git Branching](https://learngitbranching.js.org/) +- [Oh My Git!](https://ohmygit.org/) +- [Git Time](https://git.bradwoods.io/) +- [Tutorial: A Visual Git + Reference](https://marklodato.github.io/visual-git-guide/index-en.html) +::: diff --git a/lecture/git/branching-strategies.md b/lecture/git/branching-strategies.md index 8dbe1e216..41a882b76 100644 --- a/lecture/git/branching-strategies.md +++ b/lecture/git/branching-strategies.md @@ -1,59 +1,50 @@ --- -title: "Branching-Strategien mit Git" -author: "Carsten Gips (HSBI)" -readings: - - "@Chacon2014 [Kap. 3]" - - "@GitFlow" - - "@GitHubFlow" - - "@GitHubFlowGH" - - "@AtlassianGit" - - "@GitCheatSheet" -tldr: | - Das Erstellen und Mergen von Branches ist in Git besonders einfach. Dies kann man sich in der Entwicklung zunutze machen - und die einzelnen Features unabhängig voneinander in eigenen Hilfs-Branches ausarbeiten. - - Es haben sich zwei grundlegende Modelle etabliert: "Git-Flow" und "GitHub Flow". - - In **Git-Flow** gibt es ein umfangreiches Konzept mit verschiedenen Branches für feste Aufgaben, welches sich besonders - gut für Entwicklungmodelle mit festen Releases eignet. Es gibt zwei langlaufende Branches: `master` enthält den stabilen - veröffentlichten Stand, in `develop` werden die Ergebnisse der Entwicklung gesammelt. Features werden in kleinen Feature-Branches - entwickelt, die von `develop` abzweigen und dort wieder hineinmünden. Für Releases wird von `develop` ein eigener Release-Branch - angelegt und nach Finalisierung in den `master` und in `develop` gemergt. Fixes werden vom `master` abgezweigt, und wieder - in den `master` und auch nach `develop` integriert. Dadurch stehen auf dem `master` immer die stabilen Release-Stände zur - Verfügung, und im `develop` sammeln sich die Entwicklungsergebnisse. - - Der **GitHub Flow** basiert auf einem deutlich schlankeren Konzept und passt gut für die kontinuierliche Entwicklung ohne - echte Releases. Hier hat man auch wieder einen `master` als langlaufenden Branch, der die stabilen Release-Stände enthält. - Vom `master` zweigen direkt die kleinen Feature-Branches ab und werden auch wieder direkt in den `master` integriert. -outcomes: - - k3: "Ich kann mit Themenbranches in der Entwicklung arbeiten" - - k3: "Ich kann das 'Git-Flow'-Modell anwenden" - - k3: "Ich kann das 'GitHub Flow'-Modell anwenden" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106243&client_id=FH-Bielefeld" -# name: "Quiz Git Branching-Strategien (ILIAS)" -youtube: - - link: "https://youtu.be/v1WHIPdoA0k" - name: "VL Git Branching-Strategien" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/aad8053a927c859084bf51c4145ed70f49a0e87a1cf246b3d232d266d6d77bd57c140bda4159b09ab8218345523463036bcb92ebef2591cd573ebdf3745f4ede" - name: "VL Git Branching-Strategien" +author: Carsten Gips (HSBI) +title: Branching-Strategien mit Git --- +::: tldr +Das Erstellen und Mergen von Branches ist in Git besonders einfach. Dies kann man +sich in der Entwicklung zunutze machen und die einzelnen Features unabhängig +voneinander in eigenen Hilfs-Branches ausarbeiten. + +Es haben sich zwei grundlegende Modelle etabliert: "Git-Flow" und "GitHub Flow". + +In **Git-Flow** gibt es ein umfangreiches Konzept mit verschiedenen Branches für +feste Aufgaben, welches sich besonders gut für Entwicklungmodelle mit festen +Releases eignet. Es gibt zwei langlaufende Branches: `master` enthält den stabilen +veröffentlichten Stand, in `develop` werden die Ergebnisse der Entwicklung +gesammelt. Features werden in kleinen Feature-Branches entwickelt, die von `develop` +abzweigen und dort wieder hineinmünden. Für Releases wird von `develop` ein eigener +Release-Branch angelegt und nach Finalisierung in den `master` und in `develop` +gemergt. Fixes werden vom `master` abgezweigt, und wieder in den `master` und auch +nach `develop` integriert. Dadurch stehen auf dem `master` immer die stabilen +Release-Stände zur Verfügung, und im `develop` sammeln sich die +Entwicklungsergebnisse. + +Der **GitHub Flow** basiert auf einem deutlich schlankeren Konzept und passt gut für +die kontinuierliche Entwicklung ohne echte Releases. Hier hat man auch wieder einen +`master` als langlaufenden Branch, der die stabilen Release-Stände enthält. Vom +`master` zweigen direkt die kleinen Feature-Branches ab und werden auch wieder +direkt in den `master` integriert. +::: + +::: youtube +- [VL Git Branching-Strategien](https://youtu.be/v1WHIPdoA0k) +::: # Nutzung von Git in Projekten: Verteiltes Git (und Workflows) ![](images/distributed.png){width="80%" web_width="60%"} - ::: notes Git ermöglicht ein einfaches und schnelles Branchen. Dies kann man mit entsprechenden Branching-Strategien sinnvoll für die SW-Entwicklung einsetzen. -Im Folgenden sollen also die Frage betrachtet werden: **Wie setze ich Branches sinnvoll ein?** +Im Folgenden sollen also die Frage betrachtet werden: **Wie setze ich Branches +sinnvoll ein?** ::: - # Umgang mit Branches: Themen-Branches I---J---K wuppieV1 @@ -64,32 +55,29 @@ Im Folgenden sollen also die Frage betrachtet werden: **Wie setze ich Branches s \ G---H test - ::: notes -Branchen ist in Git sehr einfach und schnell. Deshalb wird (gerade auch im Vergleich mit -SVN) gern und viel gebrancht. +Branchen ist in Git sehr einfach und schnell. Deshalb wird (gerade auch im Vergleich +mit SVN) gern und viel gebrancht. -Ein häufiges anzutreffendes Modell ist dabei die Nutzung von -**Themen-Branches**: Man hat einen Hauptzweig (`master`). Wann immer eine neue -Idee oder ein Baustein unabhängig entwickelt werden soll/kann, wird ein -entsprechender Themen-Branch aufgemacht. Dabei handelt es sich normalerweise -um **kleine Einheiten**! +Ein häufiges anzutreffendes Modell ist dabei die Nutzung von **Themen-Branches**: +Man hat einen Hauptzweig (`master`). Wann immer eine neue Idee oder ein Baustein +unabhängig entwickelt werden soll/kann, wird ein entsprechender Themen-Branch +aufgemacht. Dabei handelt es sich normalerweise um **kleine Einheiten**! Themenbranches haben in der Regel eine **kurze Lebensdauer**: Wenn die Entwicklung -abgeschlossen ist, wird die Idee bzw. der Baustein in den Hauptzweig integriert -und der Themenbranch gelöscht. - -* Vorteil: Die Entwicklung im Themenbranch ist in sich gekapselt und stört - nicht die Entwicklung in anderen Branches (und diese stören umgekehrt nicht - die Entwicklung im Themenbranch). - -* Nachteil: - * Mangelnder Überblick durch viele Branches - * Ursprung der Themenbranches muss überlegt gewählt werden, d.h. alle - dort benötigten Features müssen zu dem Zeitpunkt im Hauptzweig - vorhanden sein -::: +abgeschlossen ist, wird die Idee bzw. der Baustein in den Hauptzweig integriert und +der Themenbranch gelöscht. + +- Vorteil: Die Entwicklung im Themenbranch ist in sich gekapselt und stört nicht + die Entwicklung in anderen Branches (und diese stören umgekehrt nicht die + Entwicklung im Themenbranch). +- Nachteil: + + - Mangelnder Überblick durch viele Branches + - Ursprung der Themenbranches muss überlegt gewählt werden, d.h. alle dort + benötigten Features müssen zu dem Zeitpunkt im Hauptzweig vorhanden sein +::: # Umgang mit Branches: Langlaufende Branches @@ -99,37 +87,31 @@ und der Themenbranch gelöscht. \ F---G---H topic - ::: notes -Häufig findet man in (größeren) Projekten Branches, die über die gesamte -Lebensdauer des Projekts existieren, sogenannte "langlaufende Branches". +Häufig findet man in (größeren) Projekten Branches, die über die gesamte Lebensdauer +des Projekts existieren, sogenannte "langlaufende Branches". Normalerweise gibt es einen Branch, in dem stets der stabile Stand des Projekts -enthalten ist. Dies ist häufig der `master`. In diesem Branch gibt es nur -sehr wenige Commits: normalerweise nur Merges aus dem `develop`-Branch (etwa -bei Fertigstellung einer Release-Version) und ggf. Fehlerbehebungen. - -Die aktive Entwicklung findet in einem separaten Branch statt: `develop`. Hier -nutzt man zusätzlich Themen-Branches für die Entwicklung einzelner Features, -die nach Fertigstellung in den `develop` gemergt werden. - -Kleinere Projekte kommen meist mit den zwei langlaufenden Branches in der -obigen Darstellung aus. Bei größeren Projekten finden sich häufig noch etliche -weitere langlaufende Branches, beispielsweise "Proposed Updates" etc. beim -Linux-Kernel. - -* Vorteile: - * Mehr Struktur im Projekt durch in ihrer Semantik wohldefinierte - Branches - * Durch weniger Commits pro Branch lässt sich die Historie leichter - verfolgen (u.a. auch aus bestimmter Rollen-Perspektive: Entwickler, - Manager, ...) - -* Nachteile: Bestimmte "ausgezeichnete" Branches; zusätzliche Regeln zum - Umgang mit diesen beachten +enthalten ist. Dies ist häufig der `master`. In diesem Branch gibt es nur sehr +wenige Commits: normalerweise nur Merges aus dem `develop`-Branch (etwa bei +Fertigstellung einer Release-Version) und ggf. Fehlerbehebungen. + +Die aktive Entwicklung findet in einem separaten Branch statt: `develop`. Hier nutzt +man zusätzlich Themen-Branches für die Entwicklung einzelner Features, die nach +Fertigstellung in den `develop` gemergt werden. + +Kleinere Projekte kommen meist mit den zwei langlaufenden Branches in der obigen +Darstellung aus. Bei größeren Projekten finden sich häufig noch etliche weitere +langlaufende Branches, beispielsweise "Proposed Updates" etc. beim Linux-Kernel. + +- Vorteile: + - Mehr Struktur im Projekt durch in ihrer Semantik wohldefinierte Branches + - Durch weniger Commits pro Branch lässt sich die Historie leichter verfolgen + (u.a. auch aus bestimmter Rollen-Perspektive: Entwickler, Manager, ...) +- Nachteile: Bestimmte "ausgezeichnete" Branches; zusätzliche Regeln zum Umgang + mit diesen beachten ::: - # Komplexe Branching-Strategie: Git-Flow A---B---------------------G---J1 master @@ -144,27 +126,25 @@ Linux-Kernel. \ E1---E2---E3---E4---E5 featureC - ::: notes Das Git-Flow-Modell von Vincent Driessen ([nvie.com/posts/a-successful-git-branching-model](http://nvie.com/posts/a-successful-git-branching-model/)) -zeigt einen in der Praxis überaus bewährten Umgang mit Branches. Lesen Sie an -der angegebenen Stelle nach, besser kann man die Nutzung dieses eleganten -Modells eigentlich nicht erklären :-) +zeigt einen in der Praxis überaus bewährten Umgang mit Branches. Lesen Sie an der +angegebenen Stelle nach, besser kann man die Nutzung dieses eleganten Modells +eigentlich nicht erklären :-) ::: - -# Git-Flow: Hauptzweige _master_ und _develop_ +# Git-Flow: Hauptzweige *master* und *develop* A---B-------E---------------J master \ / / C---D---F---G---H---I---K develop - ::: notes -Bei Git-Flow gibt es zwei langlaufende Branches: Den `master`, der immer den stabilen -Stand enthält und in den *nie* ein direkter Commit gemacht wird, sowie den `develop`, -wo letztlich (ggf. über Themenbranches) die eigentliche Entwicklung stattfindet. +Bei Git-Flow gibt es zwei langlaufende Branches: Den `master`, der immer den +stabilen Stand enthält und in den *nie* ein direkter Commit gemacht wird, sowie den +`develop`, wo letztlich (ggf. über Themenbranches) die eigentliche Entwicklung +stattfindet. Änderungen werden zunächst im `develop` erstellt und getestet. Wenn die Features stabil sind, erfolgt ein Merge von `develop` in den `master`. Hier kann noch der @@ -175,7 +155,6 @@ Entwicklungsarbeit mehr. Nach Fertigstellung wird der `release` dann sowohl in d `master` als auch `develop` gemergt. ::: - # Git-Flow: Weitere Branches als Themen-Branches A---B---------------------I-------------K master @@ -188,17 +167,14 @@ Entwicklungsarbeit mehr. Nach Fertigstellung wird der `release` dann sowohl in d \ / E1---E2---E3---E4---E5 featureC - ::: notes -Für die Entwicklung eigenständiger Features bietet es sich auch im -Git-Flow an, vom `develop` entsprechende Themenbranches abzuzweigen -und darin jeweils isoliert die Features zu entwickeln. Wenn diese -Arbeiten eine gewisse Reife haben, werden die Featurebranches in den -`develop` integriert. +Für die Entwicklung eigenständiger Features bietet es sich auch im Git-Flow an, vom +`develop` entsprechende Themenbranches abzuzweigen und darin jeweils isoliert die +Features zu entwickeln. Wenn diese Arbeiten eine gewisse Reife haben, werden die +Featurebranches in den `develop` integriert. ::: - -::::::::: notes +::: notes # Git-Flow: Merging-Detail ---C--------E develop @@ -209,25 +185,22 @@ vs. ---C---D1---D2 develop git merge +Wenn beim Mergen ein "*fast forward*" möglich ist, würde Git beim Mergen eines +(Feature-) Branches in den `develop` (oder allgemein in einen anderen Branch) +*keinen* separaten Commit erzeugen (Situation rechts in der Abbildung). -Wenn beim Mergen ein "*fast forward*" möglich ist, würde Git beim Mergen -eines (Feature-) Branches in den `develop` (oder allgemein in einen anderen -Branch) *keinen* separaten Commit erzeugen (Situation rechts in der Abbildung). - -Damit erscheint der `develop`-Branch wie eine lineare Folge von Commits. In -manchen Projekten wird dies bevorzugt, weil die Historie sehr übersichtlich -aussieht. +Damit erscheint der `develop`-Branch wie eine lineare Folge von Commits. In manchen +Projekten wird dies bevorzugt, weil die Historie sehr übersichtlich aussieht. -Allerdings verliert man die Information, dass hier ein Feature entwickelt wurde -und wann es in den `develop` integriert wurde (linke Seite in obiger Abbildung). -Häufig wird deshalb ein extra Merge-Commit mit `git merge --no-ff ` -(extra Schalter "`--no-ff`") erzwungen, obwohl ein "*fast forward*" möglich wäre. - -Anmerkung: Man kann natürlich auch über Konventionen in den Commit-Kommentaren -eine gewisse Übersichtlichkeit erzwingen. Beispielsweise könnte man vereinbaren, -dass alle Commit-Kommentare zu einem Feature "A" mit "`feature a: `" starten müssen. -::::::::: +Allerdings verliert man die Information, dass hier ein Feature entwickelt wurde und +wann es in den `develop` integriert wurde (linke Seite in obiger Abbildung). Häufig +wird deshalb ein extra Merge-Commit mit `git merge --no-ff ` (extra Schalter +"`--no-ff`") erzwungen, obwohl ein "*fast forward*" möglich wäre. +Anmerkung: Man kann natürlich auch über Konventionen in den Commit-Kommentaren eine +gewisse Übersichtlichkeit erzwingen. Beispielsweise könnte man vereinbaren, dass +alle Commit-Kommentare zu einem Feature "A" mit "`feature a:`" starten müssen. +::: # Git-Flow: Umgang mit Fehlerbehebung @@ -237,18 +210,16 @@ dass alle Commit-Kommentare zu einem Feature "A" mit "`feature a: `" starten mü \ \ C1-------F2 develop - ::: notes -Wenn im stabilen Branch (also dem `master`) ein Problem bekannt wird, -darf man es nicht einfach im `master` fixen. Stattdessen wird ein extra -Branch vom `master` abgezweigt, in dem der Fix entwickelt wird. Nach -Fertigstellung wird dieser Branch sowohl in den `master` als auch den -`develop` gemergt, damit auch im Entwicklungszweig der Fehler behoben ist. +Wenn im stabilen Branch (also dem `master`) ein Problem bekannt wird, darf man es +nicht einfach im `master` fixen. Stattdessen wird ein extra Branch vom `master` +abgezweigt, in dem der Fix entwickelt wird. Nach Fertigstellung wird dieser Branch +sowohl in den `master` als auch den `develop` gemergt, damit auch im +Entwicklungszweig der Fehler behoben ist. Dadurch entspricht jeder Commit im `master` einem Release. ::: - # Vereinfachte Braching-Strategie: GitHub Flow A---B---C----D-----------E master @@ -257,68 +228,82 @@ Dadurch entspricht jeder Commit im `master` einem Release. \ / tb1---tb2---tb3 topicB - ::: notes -Github verfolgt eine deutlich vereinfachte Strategie: "GitHub Flow" -(vgl. ["GitHub Flow" (S. Chacon)](https://githubflow.github.io/) -bzw. ["GitHub flow" (GitHub, Inc.)](https://docs.github.com/en/get-started/quickstart/github-flow)). +Github verfolgt eine deutlich vereinfachte Strategie: "GitHub Flow" (vgl. ["GitHub +Flow" (S. Chacon)](https://githubflow.github.io/) bzw. ["GitHub flow" (GitHub, +Inc.)](https://docs.github.com/en/get-started/quickstart/github-flow)). -Hier ist der stabile Stand ebenfalls immer im `master`. Features werden ebenso -wie im Git-Flow-Modell in eigenen Feature-Branches entwickelt. +Hier ist der stabile Stand ebenfalls immer im `master`. Features werden ebenso wie +im Git-Flow-Modell in eigenen Feature-Branches entwickelt. Allerdings zweigen Feature-Branches *immer direkt* vom `master` ab und werden nach dem Test auch immer dort wieder direkt integriert (es gibt also keine weiteren langlaufenden Branches wie `develop` oder `release`). - In der obigen Abbildung ist zu sehen, dass für die Entwicklung eines Features ein entsprechender Themenbranch vom `master` abgezweigt wird. Darin erfolgt dann die Entwicklung des Features, d.h. mehrere Commits. Das Mergen des Features in den `master` erfolgt dann aber nicht lokal, sondern mit einem "Pull-Request" auf dem Server: Sobald man im Feature-Branch einen "diskussionswürdigen" Stand hat, wird ein **Pull-Request** (*PR*) über die Weboberfläche aufgemacht (streng genommen gehört -dies in die Kategorie ["Zusammenarbeit" bzw. "Workflows"](workflows.md); -außerdem gehört ein PR nicht zu Git selbst, sondern zum Tooling von Github). In -einem PR können andere Entwickler den Code kommentieren und ergänzen. Jeder weitere -Commit auf dem Themenbranch wird ebenfalls Bestandteil des Pull-Requests. Parallel -laufen ggf. automatisierte Tests etc. und durch das Akzeptieren des PR in der -Weboberfläche erfolgt schließlich der Merge des Feature-Branches in den `master`. +dies in die Kategorie ["Zusammenarbeit" bzw. "Workflows"](workflows.md); außerdem +gehört ein PR nicht zu Git selbst, sondern zum Tooling von Github). In einem PR +können andere Entwickler den Code kommentieren und ergänzen. Jeder weitere Commit +auf dem Themenbranch wird ebenfalls Bestandteil des Pull-Requests. Parallel laufen +ggf. automatisierte Tests etc. und durch das Akzeptieren des PR in der Weboberfläche +erfolgt schließlich der Merge des Feature-Branches in den `master`. ::: - ::: notes -# Diskussion: Git-Flow vs. GitHub Flow - -In der Praxis zeigt sich, dass das Git-Flow-Modell besonders gut geeignet ist, -wenn man tatsächlich so etwas wie "Releases" hat, die zudem nicht zu häufig -auftreten. +# Diskussion: Git-Flow vs. GitHub Flow -Das GitHub-Flow-Vorgehen bietet sich an, wenn man entweder keine Releases hat -oder diese sehr häufig erfolgen (typisch bei agiler Vorgehensweise). Zudem -vermeidet man so, dass die Feature-Branches zu lange laufen, womit normalerweise -die Wahrscheinlichkeit von Merge-Konflikten stark steigt. -**Achtung**: Da die Feature-Branches direkt in den `master`, also den stabilen -Produktionscode gemergt werden, ist es hier besonders wichtig, *vor* dem Merge -entsprechende Tests durchzuführen und den Merge erst zu machen, wenn alle Tests -"grün" sind. +In der Praxis zeigt sich, dass das Git-Flow-Modell besonders gut geeignet ist, wenn +man tatsächlich so etwas wie "Releases" hat, die zudem nicht zu häufig auftreten. +Das GitHub-Flow-Vorgehen bietet sich an, wenn man entweder keine Releases hat oder +diese sehr häufig erfolgen (typisch bei agiler Vorgehensweise). Zudem vermeidet man +so, dass die Feature-Branches zu lange laufen, womit normalerweise die +Wahrscheinlichkeit von Merge-Konflikten stark steigt. **Achtung**: Da die +Feature-Branches direkt in den `master`, also den stabilen Produktionscode gemergt +werden, ist es hier besonders wichtig, *vor* dem Merge entsprechende Tests +durchzuführen und den Merge erst zu machen, wenn alle Tests "grün" sind. -Hier ein paar Einstiegsseiten für die Diskussion, die teilweise sehr erbittert -(und mit ideologischen Zügen) geführt wird (erinnert an die Diskussionen, welche +Hier ein paar Einstiegsseiten für die Diskussion, die teilweise sehr erbittert (und +mit ideologischen Zügen) geführt wird (erinnert an die Diskussionen, welche Linux-Distribution die bessere sei): -* [Git-Flow-Modell von Vincent Driessen](https://nvie.com/posts/a-successful-git-branching-model/) -* [Kurzer Überblick über das GitHub-Flow-Modell](https://guides.github.com/introduction/flow/) -* [Diskussion des GitHub-Flow-Modells (Github)](https://githubflow.github.io/) -* [Luca Mezzalira: "Git-Flow vs Github Flow"](https://lucamezzalira.com/2014/03/10/git-flow-vs-github-flow/) -* [Scott Schacon, Autor des Pro-Git-Buchs](https://scottchacon.com/2011/08/31/github-flow.html) -* [Noch eine (längere) Betrachtung (Robin Daugherty)](https://hackernoon.com/a-branching-and-releasing-strategy-that-fits-github-flow-be1b6c48eca2) +- [Git-Flow-Modell von Vincent + Driessen](https://nvie.com/posts/a-successful-git-branching-model/) +- [Kurzer Überblick über das + GitHub-Flow-Modell](https://guides.github.com/introduction/flow/) +- [Diskussion des GitHub-Flow-Modells (Github)](https://githubflow.github.io/) +- [Luca Mezzalira: "Git-Flow vs Github + Flow"](https://lucamezzalira.com/2014/03/10/git-flow-vs-github-flow/) +- [Scott Schacon, Autor des + Pro-Git-Buchs](https://scottchacon.com/2011/08/31/github-flow.html) +- [Noch eine (längere) Betrachtung (Robin + Daugherty)](https://hackernoon.com/a-branching-and-releasing-strategy-that-fits-github-flow-be1b6c48eca2) ::: - # Wrap-Up -* Einsatz von Themenbranches für die Entwicklung -* Unterschiedliche Modelle: +- Einsatz von Themenbranches für die Entwicklung +- Unterschiedliche Modelle: - Git-Flow: umfangreiches Konzept, gut für Entwicklung mit festen Releases - - GitHub Flow: deutlich schlankeres Konzept, passend für kontinuierliche Entwicklung ohne echte Releases + - GitHub Flow: deutlich schlankeres Konzept, passend für kontinuierliche + Entwicklung ohne echte Releases + +::: readings +- @Chacon2014 [Kap. 3] +- @GitFlow +- @GitHubFlow +- @GitHubFlowGH +- @AtlassianGit +- @GitCheatSheet +::: + +::: outcomes +- k3: Ich kann mit Themenbranches in der Entwicklung arbeiten +- k3: Ich kann das 'Git-Flow'-Modell anwenden +- k3: Ich kann das 'GitHub Flow'-Modell anwenden +::: diff --git a/lecture/git/git-basics.md b/lecture/git/git-basics.md index c4a415c13..e5dca2405 100644 --- a/lecture/git/git-basics.md +++ b/lecture/git/git-basics.md @@ -1,77 +1,40 @@ --- -title: "Basics der Versionsverwaltung mit Git (lokale Repos)" -author: "Carsten Gips (HSBI)" -readings: - - "@Chacon2014 [Kap. 2]" - - "@AtlassianGit" - - "@GitCheatSheet" -tldr: | - Änderungen an Dateien (in der Workingcopy) werden mit `git add` zum "Staging" (Index) hinzugefügt. - Dies ist eine Art Sammelbereich für Änderungen, die mit dem nächsten Commit in das Repository - überführt werden. Neue (bisher nicht versionierte Dateien) müssen ebenfalls zunächst mit - `git add` zum Staging hinzugefügt werden. - - Änderungen kann man mit `git log` betrachten, dabei erhält man u.a. eine Liste der Commits und der - jeweiligen Commmit-Messages. - - Mit `git diff` kann man gezielt Änderungen zwischen Commits oder Branches betrachten. - - Mit `git tag` kann man bestimmte Commits mit einem "Stempel" (zusätzlicher Name) versehen, um diese - leichter finden zu können. - - Wichtig sind die Commit-Messages: Diese sollten eine kurze Zusammenfassung haben, die **aktiv** - formuliert wird (was ändert dieser Commit: "Formatiere den Java-Code entsprechend Style"; nicht aber - "Java-Code nach Style formatiert"). Falls der Kommentar länger sein soll, folgt eine Leerzeile auf - die erste Zeile (Zusammenfassung) und danach ein Block mit der längeren Erklärung. -outcomes: - - k3: "Ich kann Dateien zur Versionskontrolle hinzufügen bzw. aus der Versionierung löschen" - - k3: "Ich kann Änderungen (geänderte Dateien) zum Staging hinzufügen und committen" - - k3: "Ich kann Unterschiede zwischen Commits herausfinden und mir die Historie des Repos anschauen" - - k3: "Ich kann gezielt Dateien und Ordner von der Versionierung ausnehmen (ignorieren)" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106241&client_id=FH-Bielefeld" -# name: "Quiz Git Basics (ILIAS)" -youtube: - - link: "https://youtu.be/GxJI8nmZVE8" - name: "VL Git Basics" - - link: "https://youtu.be/ITF8wj8GluM" - name: "Demo New Files" - - link: "https://youtu.be/SFIVudlVUhg" - name: "Demo Arbeitsablauf: Datei ändern - stagen - committen" - - link: "https://youtu.be/0uczjI7wsrQ" - name: "Demo Amend" - - link: "https://youtu.be/vmb-PZ1Efkg" - name: "Demo Log" - - link: "https://youtu.be/XB8lfGuU6ZI" - name: "Demo Diff" - - link: "https://youtu.be/F1W0RqrxCho" - name: "Demo Tag" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/3a44c8a32e7699db77ae922c6b8944acf0d8c65b78d02859e707ffdf783ea45a78200312cdb8102c1052f382101b69a5092bcaf0a11ded36b98f4552a4aca345" - name: "VL Git Basics" -challenges: | - **Versionierung 101** - - 1. Legen Sie ein Repository an. - 2. Fügen Sie Dateien dem Verzeichnis hinzu und stellen Sie *einige* davon - unter Versionskontrolle. - 3. Ändern Sie eine Datei und versionieren Sie die Änderung. - 4. Was ist der Unterschied zwischen "`git add .; git commit`" und - "`git commit -a`"? - 5. Wie finden Sie heraus, welche Dateien geändert wurden? - 6. Entfernen Sie eine Datei aus der Versionskontrolle, aber nicht aus dem - Verzeichnis! - 7. Entfernen Sie eine Datei komplett (Versionskontrolle und Verzeichnis). - 8. Ändern Sie eine Datei und betrachten die Unterschiede zum letzten Commit. - 9. Fügen Sie eine geänderte Datei zum Index hinzu. Was erhalten Sie bei - `git diff `? - 10. Wie können Sie einen früheren Stand einer Datei wiederherstellen? Wie - finden Sie überhaupt den Stand? - 11. Legen Sie sich ein Java-Projekt in Ihrer IDE an an. Stellen Sie dieses - Projekt unter Git-Versionskontrolle. Führen Sie die vorigen Schritte mit - Ihrer IDE durch. +author: Carsten Gips (HSBI) +title: Basics der Versionsverwaltung mit Git (lokale Repos) --- +::: tldr +Änderungen an Dateien (in der Workingcopy) werden mit `git add` zum "Staging" +(Index) hinzugefügt. Dies ist eine Art Sammelbereich für Änderungen, die mit dem +nächsten Commit in das Repository überführt werden. Neue (bisher nicht versionierte +Dateien) müssen ebenfalls zunächst mit `git add` zum Staging hinzugefügt werden. + +Änderungen kann man mit `git log` betrachten, dabei erhält man u.a. eine Liste der +Commits und der jeweiligen Commmit-Messages. + +Mit `git diff` kann man gezielt Änderungen zwischen Commits oder Branches +betrachten. + +Mit `git tag` kann man bestimmte Commits mit einem "Stempel" (zusätzlicher Name) +versehen, um diese leichter finden zu können. + +Wichtig sind die Commit-Messages: Diese sollten eine kurze Zusammenfassung haben, +die **aktiv** formuliert wird (was ändert dieser Commit: "Formatiere den Java-Code +entsprechend Style"; nicht aber "Java-Code nach Style formatiert"). Falls der +Kommentar länger sein soll, folgt eine Leerzeile auf die erste Zeile +(Zusammenfassung) und danach ein Block mit der längeren Erklärung. +::: + +::: youtube +- [VL Git Basics](https://youtu.be/GxJI8nmZVE8) +- [Demo New Files](https://youtu.be/ITF8wj8GluM) +- [Demo Arbeitsablauf: Datei ändern - stagen - + committen](https://youtu.be/SFIVudlVUhg) +- [Demo Amend](https://youtu.be/0uczjI7wsrQ) +- [Demo Log](https://youtu.be/vmb-PZ1Efkg) +- [Demo Diff](https://youtu.be/XB8lfGuU6ZI) +- [Demo Tag](https://youtu.be/F1W0RqrxCho) +::: # Versionsverwaltung mit Git: Typische Arbeitsschritte @@ -94,7 +57,6 @@ challenges: | 9. Änderungen verteilen (verteiltes Arbeiten, Workflows) - # Dateien unter Versionskontrolle stellen \bigskip @@ -104,12 +66,12 @@ challenges: | ::: notes 1. `git add .` (oder `git add `) - => Stellt alle Dateien (bzw. die Datei ``) - im aktuellen Verzeichnis unter Versionskontrolle + =\> Stellt alle Dateien (bzw. die Datei ``) im aktuellen Verzeichnis unter + Versionskontrolle 2. `git commit` - => Fügt die Dateien dem Repository hinzu + =\> Fügt die Dateien dem Repository hinzu ::: \bigskip @@ -119,7 +81,6 @@ challenges: | [[Konsole]{.ex}]{.slides} - # Änderungen einpflegen \bigskip @@ -128,37 +89,35 @@ challenges: | \bigskip -* Abfrage mit: `git status` -* "Staging" von modifizierten Dateien: `git add ` -* Committen der Änderungen im Stage: `git commit` +- Abfrage mit: `git status` +- "Staging" von modifizierten Dateien: `git add ` +- Committen der Änderungen im Stage: `git commit` ::: notes -*Anmerkung*: Alternativ auch mit `git commit -m "Kommentar"`, um das Öffnen -des Editors zu vermeiden ... geht einfach schneller ;) +*Anmerkung*: Alternativ auch mit `git commit -m "Kommentar"`, um das Öffnen des +Editors zu vermeiden ... geht einfach schneller ;) ::: ::: notes Das "staging area" stellt eine Art Zwischenebene zwischen Working Copy und -Repository dar: Die Änderungen sind temporär "gesichert", aber noch nicht -endgültig im Repository eingepflegt ("committed"). +Repository dar: Die Änderungen sind temporär "gesichert", aber noch nicht endgültig +im Repository eingepflegt ("committed"). -Man kann den Stage dazu nutzen, um Änderungen an einzelnen Dateien zu sammeln -und diese dann (in einem Commit) gemeinsam einzuchecken. +Man kann den Stage dazu nutzen, um Änderungen an einzelnen Dateien zu sammeln und +diese dann (in einem Commit) gemeinsam einzuchecken. Man kann den Stage in der Wirkung umgehen, indem man alle in der Working Copy -vorliegenden Änderungen per `git commit -a -m "Kommentar"` eincheckt. Der -Schalter "`-a`" nimmt alle vorliegenden Änderungen an **bereits versionierten** -Dateien, fügt diese dem Stage hinzu und führt dann den Commit durch. Das ist -das von SVN bekannte Verhalten. Achtung: Nicht versionierte Dateien bleiben -dabei außen vor! +vorliegenden Änderungen per `git commit -a -m "Kommentar"` eincheckt. Der Schalter +"`-a`" nimmt alle vorliegenden Änderungen an **bereits versionierten** Dateien, fügt +diese dem Stage hinzu und führt dann den Commit durch. Das ist das von SVN bekannte +Verhalten. Achtung: Nicht versionierte Dateien bleiben dabei außen vor! ::: [[Konsole]{.ex}]{.slides} - # Letzten Commit ergänzen -* `git commit --amend -m "Eigentlich wollte ich das so sagen"` +- `git commit --amend -m "Eigentlich wollte ich das so sagen"` ::: notes Wenn keine Änderungen im Stage sind, wird so die letzte Commit-Message geändert. @@ -166,111 +125,105 @@ dabei außen vor! \bigskip -* `git add ; git commit --amend` +- `git add ; git commit --amend` ::: notes - Damit können vergessene Änderungen an der Datei `` - zusätzlich im letzten Commit aufgezeichnet werden. + Damit können vergessene Änderungen an der Datei `` zusätzlich im letzten + Commit aufgezeichnet werden. **In beiden Fällen ändert sich die Commit-ID!** ::: [[Konsole]{.ex}]{.slides} - ::: notes # Weitere Datei-Operationen: hinzufügen, umbenennen, löschen -* Neue (unversionierte) Dateien und Änderungen an versionierten Dateien zum Staging hinzufügen: `git add ` -* Löschen von Dateien (Repo+Workingcopy): `git rm ` -* Löschen von Dateien (nur Repo): `git rm --cached ` -* Verschieben/Umbenennen: `git mv ` - +- Neue (unversionierte) Dateien und Änderungen an versionierten Dateien zum + Staging hinzufügen: `git add ` +- Löschen von Dateien (Repo+Workingcopy): `git rm ` +- Löschen von Dateien (nur Repo): `git rm --cached ` +- Verschieben/Umbenennen: `git mv ` -Aus Sicht von Git sind zunächst alle Dateien "untracked", d.h. stehen nicht -unter Versionskontrolle. +Aus Sicht von Git sind zunächst alle Dateien "untracked", d.h. stehen nicht unter +Versionskontrolle. Mit `git add ` (und `git commit`) werden Dateien in den Index (den Staging-Bereich, d.h. nach dem Commit letztlich in das Repository) aufgenommen. -Danach stehen sie unter "Beobachtung" (Versionskontrolle). So lange, wie eine -Datei identisch zur Version im Repository ist, gilt sie als unverändert -("unmodified"). Eine Änderung führt entsprechend zum Zustand "modified", und -ein `git add ` speichert die Änderungen im Stage. Ein Commit überführt -die im Stage vorgemerkte Änderung in das Repo, d.h. die Datei gilt wieder -als "unmodified". - -Wenn eine Datei nicht weiter versioniert werden soll, kann sie aus dem Repo -entfernt werden. Dies kann mit `git rm ` geschehen, wobei die Datei auch -aus der Workingcopy gelöscht wird. Wenn die Datei erhalten bleiben soll, aber -nicht versioniert werden soll (also als "untracked" markiert werden soll), dann -muss sie mit `git rm --cached ` aus der Versionskontrolle gelöscht werden. -Achtung: Die Datei ist dann nur ab dem aktuellen Commit gelöscht, d.h. frühere -Revisionen enthalten die Datei noch! +Danach stehen sie unter "Beobachtung" (Versionskontrolle). So lange, wie eine Datei +identisch zur Version im Repository ist, gilt sie als unverändert ("unmodified"). +Eine Änderung führt entsprechend zum Zustand "modified", und ein `git add ` +speichert die Änderungen im Stage. Ein Commit überführt die im Stage vorgemerkte +Änderung in das Repo, d.h. die Datei gilt wieder als "unmodified". + +Wenn eine Datei nicht weiter versioniert werden soll, kann sie aus dem Repo entfernt +werden. Dies kann mit `git rm ` geschehen, wobei die Datei auch aus der +Workingcopy gelöscht wird. Wenn die Datei erhalten bleiben soll, aber nicht +versioniert werden soll (also als "untracked" markiert werden soll), dann muss sie +mit `git rm --cached ` aus der Versionskontrolle gelöscht werden. Achtung: Die +Datei ist dann nur ab dem aktuellen Commit gelöscht, d.h. frühere Revisionen +enthalten die Datei noch! Wenn eine Datei umbenannt werden soll, geht das mit `git mv `. Letztlich ist dies nur eine Abkürzung für die Folge `git rm --cached `, manuelles Umbenennen der Datei in der Workingcopy und `git add `. ::: - # Commits betrachten -* Liste aller Commits: `git log` - * `git log -` oder `git log --since="3 days ago"` - [Meldungen eingrenzen ...]{.notes} - * `git log --stat` [Statistik ...]{.notes} - * `git log --author="pattern"` [Commits eines Autors]{.notes} - * `git log ` [Änderungen einer Datei]{.notes} +- Liste aller Commits: `git log` + - `git log -` oder `git log --since="3 days ago"` [Meldungen eingrenzen + ...]{.notes} + - `git log --stat` [Statistik ...]{.notes} + - `git log --author="pattern"` [Commits eines Autors]{.notes} + - `git log ` [Änderungen einer Datei]{.notes} \bigskip -* Inhalt eines Commits: `git show` +- Inhalt eines Commits: `git show` [[Konsole]{.ex}]{.slides} - # Änderungen und Logs betrachten -* `git diff []` +- `git diff []` Änderungen zwischen Workingcopy und letztem Commit (ohne Stage) ::: notes Das "staging area" wird beim Diff von Git behandelt, als wären die dort - hinzugefügten Änderungen bereits eingecheckt (genauer: als letzter Commit - im aktuellen Branch im Repo vorhanden). - D.h. wenn Änderungen in einer Datei mittels `git add ` dem Stage - hinzugefügt wurden, zeigt `git diff ` keine Änderungen an! + hinzugefügten Änderungen bereits eingecheckt (genauer: als letzter Commit im + aktuellen Branch im Repo vorhanden). D.h. wenn Änderungen in einer Datei mittels + `git add ` dem Stage hinzugefügt wurden, zeigt `git diff ` keine + Änderungen an! ::: \bigskip -* `git diff commitA commitB` +- `git diff commitA commitB` Änderungen zwischen Commits \bigskip -* Blame: `git blame ` +- Blame: `git blame ` Wer hat was wann gemacht? [[Konsole]{.ex}]{.slides} - -# Dateien ignorieren: _.gitignore_ +# Dateien ignorieren: *.gitignore* ::: notes -* Nicht alle Dateien gehören ins Repo: - * generierte Dateien: `.class` - * temporäre Dateien -* Datei `.gitignore` anlegen und committen - * Wirkt auch für Unterordner - * Inhalt: Reguläre Ausdrücke für zu ignorierende Dateien und Ordner +- Nicht alle Dateien gehören ins Repo: + - generierte Dateien: `.class` + - temporäre Dateien +- Datei `.gitignore` anlegen und committen + - Wirkt auch für Unterordner + - Inhalt: Reguläre Ausdrücke für zu ignorierende Dateien und Ordner ::: - -```{.gitignore} +``` gitignore # Compiled source # *.class *.o @@ -285,79 +238,81 @@ manuelles Umbenennen der Datei in der Workingcopy und `git add `. [man 5 gitignore]{.ex href="https://linux.die.net/man/5/gitignore"} - # Zeitmaschine -* Änderungen in Workingcopy rückgängig machen - * Änderungen nicht in Stage: `git checkout ` oder `git restore ` - * Änderungen in Stage: `git reset HEAD ` oder `git restore --staged ` +- Änderungen in Workingcopy rückgängig machen + - Änderungen nicht in Stage: `git checkout ` oder `git restore ` + - Änderungen in Stage: `git reset HEAD ` oder + `git restore --staged ` ::: notes - => Hinweise von `git status` beachten! + =\> Hinweise von `git status` beachten! ::: \bigskip -* Datei aus altem Stand holen: - * `git checkout `, oder - * `git restore --source ` -* Commit verwerfen, Geschichte neu: `git revert ` +- Datei aus altem Stand holen: + - `git checkout `, oder + - `git restore --source ` +- Commit verwerfen, Geschichte neu: `git revert ` ::: notes -*Hinweis*: In den neueren Versionen von Git ist der Befehl `git restore` hinzugekommen, mit -dem Änderungen rückgängig gemacht werden können. Der bisherige Befehl `git checkout` steht -immer noch zur Verfügung und bietet über `git restore` hinaus weitere Anwendungsmöglichkeiten. +*Hinweis*: In den neueren Versionen von Git ist der Befehl `git restore` +hinzugekommen, mit dem Änderungen rückgängig gemacht werden können. Der bisherige +Befehl `git checkout` steht immer noch zur Verfügung und bietet über `git restore` +hinaus weitere Anwendungsmöglichkeiten. ::: \bigskip -* Stempel (Tag) vergeben: - `git tag ` -* Tags anzeigen: `git tag` und `git show ` +- Stempel (Tag) vergeben: `git tag ` +- Tags anzeigen: `git tag` und `git show ` [[Konsole]{.ex}]{.slides} - # Wann und wie committen? \Large + ::: center **Jeder Commit stellt einen Rücksetzpunkt dar!** ::: -\normalsize +\normalsize \bigskip \bigskip \bigskip [Typische Regeln:]{.notes} -* Kleinere "Häppchen" einchecken: ein Feature oder Task - [(das nennt man auch _atomic commit_: das kleinste Set an Änderungen, die - gemeinsam Sinn machen und die ggf. gemeinsam zurückgesetzt werden können)]{.notes} -* Logisch zusammenhängende Änderungen gemeinsam einchecken -* Projekt muss nach Commit compilierbar sein -* Projekt sollte nach Commit lauffähig sein +- Kleinere "Häppchen" einchecken: ein Feature oder Task [(das nennt man auch + *atomic commit*: das kleinste Set an Änderungen, die gemeinsam Sinn machen und + die ggf. gemeinsam zurückgesetzt werden können)]{.notes} +- Logisch zusammenhängende Änderungen gemeinsam einchecken +- Projekt muss nach Commit compilierbar sein +- Projekt sollte nach Commit lauffähig sein ::: notes Ein Commit sollte in sich geschlossen sein, d.h. die kleinste Menge an Änderungen enthalten, die gemeinsam einen Sinn ergeben und die (bei Bedarf) gemeinsam zurückgesetzt oder verschoben werden können. Das nennt man auch **atomic commit**. -Wenn Sie versuchen, die Änderungen in Ihrem Commit zu beschreiben (siehe nächste Folie -"Commit-Messages"), dann werden Sie einen _atomic commit_ mit einem kurzen Satz (natürlich -im Imperativ!) beschreiben können. Wenn Sie mehr Text brauchen, haben Sie wahrscheinlich -keinen _atomic commit_ mehr vor sich. +Wenn Sie versuchen, die Änderungen in Ihrem Commit zu beschreiben (siehe nächste +Folie "Commit-Messages"), dann werden Sie einen *atomic commit* mit einem kurzen +Satz (natürlich im Imperativ!) beschreiben können. Wenn Sie mehr Text brauchen, +haben Sie wahrscheinlich keinen *atomic commit* mehr vor sich. -**Lesen Sie dazu auch [How atomic Git commits dramatically increased my productivity - and will increase yours too](https://dev.to/samuelfaure/how-atomic-git-commits-dramatically-increased-my-productivity-and-will-increase-yours-too-4a84).** +**Lesen Sie dazu auch [How atomic Git commits dramatically increased my +productivity - and will increase yours +too](https://dev.to/samuelfaure/how-atomic-git-commits-dramatically-increased-my-productivity-and-will-increase-yours-too-4a84).** ::: - # Schreiben von Commit-Messages: WARUM?! -:::::: notes +::: notes Schauen Sie sich einmal einen Screenshot eines `git log --oneline 61e48f0..e2c8076` -im [Dungeon-CampusMinden/Dungeon](https://github.com/Dungeon-CampusMinden/Dungeon) an: +im [Dungeon-CampusMinden/Dungeon](https://github.com/Dungeon-CampusMinden/Dungeon) +an: ![](images/screenshot_git_log.png){web_width="60%"} @@ -366,27 +321,28 @@ bestimmten Commit oder wollen eine bestimmte Änderung finden ... Wenn man das genauer analysiert, dann stören bestimmte Dinge: -* Mischung aus Deutsch und Englisch -* "Vor-sich-hin-Murmeln": "Layer system 5" -* Teileweise werden Tags genutzt wie `[BUG]`, aber nicht durchgängig -* Mischung zwischen verschiedenen Formen: "Repo umbenennen", "Benenne Repo um", "Repo umbenannt" -* Unterschiedliche Groß- und Kleinschreibung -* Sehr unterschiedlich lange Zeilen/Kommentare +- Mischung aus Deutsch und Englisch +- "Vor-sich-hin-Murmeln": "Layer system 5" +- Teileweise werden Tags genutzt wie `[BUG]`, aber nicht durchgängig +- Mischung zwischen verschiedenen Formen: "Repo umbenennen", "Benenne Repo um", + "Repo umbenannt" +- Unterschiedliche Groß- und Kleinschreibung +- Sehr unterschiedlich lange Zeilen/Kommentare **Das Beachten einheitlicher Regeln ist enorm wichtig!** -Leider sagt sich das so leicht - in der Praxis macht man es dann -doch schnell wieder unsauber. Dennoch, auch im Dungeon-Repo gibt -es einen positiven Trend (`git log --oneline 8039d6c..7f49e89`): +Leider sagt sich das so leicht - in der Praxis macht man es dann doch schnell wieder +unsauber. Dennoch, auch im Dungeon-Repo gibt es einen positiven Trend +(`git log --oneline 8039d6c..7f49e89`): ![](images/screenshot_git_log_recent.png){web_width="80%"} -Typische Regeln und Konventionen tauchen überall auf, beispielsweise -in @Chacon2014 oder bei Tim Pope (siehe nächstes Beispiel) oder bei -["How to Write a Git Commit Message"](https://cbea.ms/git-commit/). -:::::: +Typische Regeln und Konventionen tauchen überall auf, beispielsweise in @Chacon2014 +oder bei Tim Pope (siehe nächstes Beispiel) oder bei ["How to Write a Git Commit +Message"](https://cbea.ms/git-commit/). +::: -```markdown +``` markdown Short (50 chars or less) summary of changes More detailed explanatory text, if necessary. Wrap it to about @@ -404,143 +360,190 @@ Further paragraphs come after blank lines. vary here ``` -[["A Note About Git Commit Messages"](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) by [Tim Pope](https://tpo.pe/) on tbaggery.com]{.origin} +[["A Note About Git Commit +Messages"](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) by +[Tim Pope](https://tpo.pe/) on tbaggery.com]{.origin} -:::::: notes -Denken Sie sich die Commit-Message als E-Mail an einen zukünftigen Entwickler, -der das in fünf Jahren liest! +:::: notes +Denken Sie sich die Commit-Message als E-Mail an einen zukünftigen Entwickler, der +das in fünf Jahren liest! -Vom Aufbau her hat eine E-Mail auch eine Summary und dann den eigentlichen Inhalt ... -Erklären Sie das **"WARUM"** der Änderung! (Das "WER", "WAS", "WANN" wird bereits -automatisch von Git aufgezeichnet ...) +Vom Aufbau her hat eine E-Mail auch eine Summary und dann den eigentlichen Inhalt +... Erklären Sie das **"WARUM"** der Änderung! (Das "WER", "WAS", "WANN" wird +bereits automatisch von Git aufgezeichnet ...) ::: center -**Lesen (und beachten) Sie unbedingt auch ["How to Write a Git Commit Message"](https://cbea.ms/git-commit/)!** +**Lesen (und beachten) Sie unbedingt auch ["How to Write a Git Commit +Message"](https://cbea.ms/git-commit/)!** ::: -:::::: +:::: [[Analogie E-Mail an zukünftigen Entwickler]{.ex}]{.slides} - ::: notes # Ausflug "Conventional Commits" -Die Commit-Messages dienen vor allem der Dokumentation und werden von Entwicklern gelesen. - -Wenn man die Messages ein wenig stärker formalisieren würde, dann könnte man diese aber auch -mit Tools verarbeiten und beispielsweise automatisiert Changelogs oder Release-Texte verfassen! - -Betrachten Sie einmal das Projekt [ConventionalCommits.org](https://github.com/conventional-commits/conventionalcommits.org). -Dies ist ein solcher Versuch, die Commit-Messages (a) einheitlicher und lesbarer zu gestalten -und (b) auch eine Tool-gestützte Auswertung zu erlauben. - -Das Projekt schlägt als Erweitung der üblichen Regeln zum Formatieren von Commit-Messages vor, -dass in der ersten Zeile der _Summary_ noch eine Abkürzung für die in diesem Commit erfolgte -Änderung (Bug-Fix, neues Feature, ...) vorangestellt wird. Dieser Abkürzung kann in Klammern -noch der Scope der Änderung hinzugefügt werden, beispielsweise den Bereich im Projekt, der von -diesem Commit berührt wird. Wenn es eine _breaking change_ ist, also alter Code nach dieser Änderung -sich anders verhält oder vielleicht sogar nicht mehr kompiliert, wird noch ein "!" hinter dem +Die Commit-Messages dienen vor allem der Dokumentation und werden von Entwicklern +gelesen. + +Wenn man die Messages ein wenig stärker formalisieren würde, dann könnte man diese +aber auch mit Tools verarbeiten und beispielsweise automatisiert Changelogs oder +Release-Texte verfassen! + +Betrachten Sie einmal das Projekt +[ConventionalCommits.org](https://github.com/conventional-commits/conventionalcommits.org). +Dies ist ein solcher Versuch, die Commit-Messages (a) einheitlicher und lesbarer zu +gestalten und (b) auch eine Tool-gestützte Auswertung zu erlauben. + +Das Projekt schlägt als Erweitung der üblichen Regeln zum Formatieren von +Commit-Messages vor, dass in der ersten Zeile der *Summary* noch eine Abkürzung für +die in diesem Commit erfolgte Änderung (Bug-Fix, neues Feature, ...) vorangestellt +wird. Dieser Abkürzung kann in Klammern noch der Scope der Änderung hinzugefügt +werden, beispielsweise den Bereich im Projekt, der von diesem Commit berührt wird. +Wenn es eine *breaking change* ist, also alter Code nach dieser Änderung sich anders +verhält oder vielleicht sogar nicht mehr kompiliert, wird noch ein "!" hinter dem Typ der Änderung ergänzt. -**Beispiel**: Stellen Sie sich vor, im Dungeon-Projekt wurde ein neues Verhalten hinzugefügt. +**Beispiel**: Stellen Sie sich vor, im Dungeon-Projekt wurde ein neues Verhalten +hinzugefügt. -1. Normalerweise hätten Sie vielleicht diese Message geschrieben (angepasste Version aus [Dungeon-CampusMinden/Dungeon/pull/469](https://github.com/Dungeon-CampusMinden/Dungeon/pull/469)): +1. Normalerweise hätten Sie vielleicht diese Message geschrieben (angepasste + Version aus + [Dungeon-CampusMinden/Dungeon/pull/469](https://github.com/Dungeon-CampusMinden/Dungeon/pull/469)): - ``` - add fight skill + add fight skill - - `DamageProjectileSkill` creates a new entity which causes `HealthDamage` when hitting another entity - - `FireballSkill` is a more concrete implementation of this - - Melee skills can be created with `DamageProjectileSkill` using a customised range - - Example: the `FireballSkill` has a range of 10, a melee would have a considerably smaller range + - `DamageProjectileSkill` creates a new entity which causes `HealthDamage` when hitting another entity + - `FireballSkill` is a more concrete implementation of this + - Melee skills can be created with `DamageProjectileSkill` using a customised range + - Example: the `FireballSkill` has a range of 10, a melee would have a considerably smaller range - fixes #24 - fixes #126 - fixes #224 - ``` + fixes #24 + fixes #126 + fixes #224 -2. Mit [ConventionalCommits.org](https://www.conventionalcommits.org/en/v1.0.0/#examples) könnte - das dann so aussehen: +2. Mit + [ConventionalCommits.org](https://www.conventionalcommits.org/en/v1.0.0/#examples) + könnte das dann so aussehen: - ``` - feat: add fight skill + feat: add fight skill - - `DamageProjectileSkill` creates a new entity which causes `HealthDamage` when hitting another entity - - `FireballSkill` is a more concrete implementation of this - - Melee skills can be created with `DamageProjectileSkill` using a customised range - - Example: the `FireballSkill` has a range of 10, a melee would have a considerably smaller range + - `DamageProjectileSkill` creates a new entity which causes `HealthDamage` when hitting another entity + - `FireballSkill` is a more concrete implementation of this + - Melee skills can be created with `DamageProjectileSkill` using a customised range + - Example: the `FireballSkill` has a range of 10, a melee would have a considerably smaller range - fixes #24 - fixes #126 - fixes #224 - ``` + fixes #24 + fixes #126 + fixes #224 - Da es sich um ein neues Feature handelt, wurde der Summary in der ersten Zeile ein `feat: ` vorangestellt. + Da es sich um ein neues Feature handelt, wurde der Summary in der ersten Zeile + ein `feat:` vorangestellt. - Die zu verwendenden Typen/Abkürzungen sind im Prinzip frei definierbar. Das Projekt + Die zu verwendenden Typen/Abkürzungen sind im Prinzip frei definierbar. Das + Projekt [ConventionalCommits.org](https://github.com/conventional-commits/conventionalcommits.org) - schlägt eine Reihe von Abkürzungen vor. Auf diese Weise sollen in möglichst allen Projekten, die Conventional - Commits nutzen, die selben Abkürzungen/Typen eingesetzt werden und so eine Tool-gestützte Auswertung möglich - werden. + schlägt eine Reihe von Abkürzungen vor. Auf diese Weise sollen in möglichst + allen Projekten, die Conventional Commits nutzen, die selben Abkürzungen/Typen + eingesetzt werden und so eine Tool-gestützte Auswertung möglich werden. 3. Oder zusätzlich mit dem Scope der Änderung: - ``` - feat(game): add fight skill - - - `DamageProjectileSkill` creates a new entity which causes `HealthDamage` when hitting another entity - - `FireballSkill` is a more concrete implementation of this - - Melee skills can be created with `DamageProjectileSkill` using a customised range - - Example: the `FireballSkill` has a range of 10, a melee would have a considerably smaller range + feat(game): add fight skill - fixes #24 - fixes #126 - fixes #224 - ``` + - `DamageProjectileSkill` creates a new entity which causes `HealthDamage` when hitting another entity + - `FireballSkill` is a more concrete implementation of this + - Melee skills can be created with `DamageProjectileSkill` using a customised range + - Example: the `FireballSkill` has a range of 10, a melee would have a considerably smaller range - Der Typ `feat` wurde hier noch ergänzt um einen frei definierbaren Identifier für den Projektbereich. Dieser - wird in Klammern direkt hinter den Typ notiert (hier `feat(game): `). + fixes #24 + fixes #126 + fixes #224 - Im Beispiel habe ich als Bereich "game" genommen, weil die Änderung sich auf den Game-Aspekt des Projekts - bezieht. Im konkreten Projekt wären andere Bereiche eventuell "dsl" (für die im Projekt entwickelte - Programmiersprache plus Interpreter) und "blockly" (für die Integration von Google Blockly zur Programmierung - des Dungeons mit LowCode-Ansätzen). Das ist aber letztlich vom Projekt abhängig und weitestgehend - Geschmackssache. + Der Typ `feat` wurde hier noch ergänzt um einen frei definierbaren Identifier + für den Projektbereich. Dieser wird in Klammern direkt hinter den Typ notiert + (hier `feat(game):`). -4. Oder zusätzlich noch als Auszeichnung "breaking change" (hier mit _scope_, geht aber auch ohne _scope_): + Im Beispiel habe ich als Bereich "game" genommen, weil die Änderung sich auf den + Game-Aspekt des Projekts bezieht. Im konkreten Projekt wären andere Bereiche + eventuell "dsl" (für die im Projekt entwickelte Programmiersprache plus + Interpreter) und "blockly" (für die Integration von Google Blockly zur + Programmierung des Dungeons mit LowCode-Ansätzen). Das ist aber letztlich vom + Projekt abhängig und weitestgehend Geschmackssache. - ``` - feat(game)!: add fight skill +4. Oder zusätzlich noch als Auszeichnung "breaking change" (hier mit *scope*, geht + aber auch ohne *scope*): - - `DamageProjectileSkill` creates a new entity which causes `HealthDamage` when hitting another entity - - `FireballSkill` is a more concrete implementation of this - - Melee skills can be created with `DamageProjectileSkill` using a customised range - - Example: the `FireballSkill` has a range of 10, a melee would have a considerably smaller range + feat(game)!: add fight skill - fixes #24 - fixes #126 - fixes #224 - ``` + - `DamageProjectileSkill` creates a new entity which causes `HealthDamage` when hitting another entity + - `FireballSkill` is a more concrete implementation of this + - Melee skills can be created with `DamageProjectileSkill` using a customised range + - Example: the `FireballSkill` has a range of 10, a melee would have a considerably smaller range - Angenommen, das neue Feature muss in der API etwas ändern, so dass existierender Code nun nicht mehr - funktionieren würde. Dies wird mit dem extra Ausrufezeichen hinter dem Typ/Scope kenntlich gemacht - (hier `feat(game)!: `). + fixes #24 + fixes #126 + fixes #224 - Zusätzlich kann man einen "Footer" in die Message einbauen, also eine extra Zeile am Ende, die mit dem String - "BREAKING CHANGE:" eingeleitet wird. (vgl. [Conventional Commits > Examples](https://www.conventionalcommits.org/en/v1.0.0/#examples)) + Angenommen, das neue Feature muss in der API etwas ändern, so dass existierender + Code nun nicht mehr funktionieren würde. Dies wird mit dem extra Ausrufezeichen + hinter dem Typ/Scope kenntlich gemacht (hier `feat(game)!:`). + Zusätzlich kann man einen "Footer" in die Message einbauen, also eine extra + Zeile am Ende, die mit dem String "BREAKING CHANGE:" eingeleitet wird. (vgl. + [Conventional Commits \> + Examples](https://www.conventionalcommits.org/en/v1.0.0/#examples)) -Es gibt noch viele weitere Initiativen, Commit-Messages lesbarer zu gestalten und zu vereinheitlichen. -Schauen Sie sich beispielsweise einmal [gitmoji.dev](https://github.com/carloscuesta/gitmoji) an. -(_Mit einem Einsatz in einem professionellen Umfeld wäre ich hier aber sehr ... vorsichtig._) +Es gibt noch viele weitere Initiativen, Commit-Messages lesbarer zu gestalten und zu +vereinheitlichen. Schauen Sie sich beispielsweise einmal +[gitmoji.dev](https://github.com/carloscuesta/gitmoji) an. (*Mit einem Einsatz in +einem professionellen Umfeld wäre ich hier aber sehr ... vorsichtig.*) ::: - # Wrap-Up -* Änderungen einpflegen zweistufig (`add`, `commit`) -* Status der Workingcopy mit `status` ansehen -* Logmeldungen mit `log` ansehen -* Änderungen auf einem File mit `diff` bzw. `blame` ansehen -* Projektstand markieren mit `tag` -* Ignorieren von Dateien/Ordnern: Datei `.gitignore` +- Änderungen einpflegen zweistufig (`add`, `commit`) +- Status der Workingcopy mit `status` ansehen +- Logmeldungen mit `log` ansehen +- Änderungen auf einem File mit `diff` bzw. `blame` ansehen +- Projektstand markieren mit `tag` +- Ignorieren von Dateien/Ordnern: Datei `.gitignore` + +::: readings +- @Chacon2014 [Kap. 2] +- @AtlassianGit +- @GitCheatSheet +::: + +::: outcomes +- k3: Ich kann Dateien zur Versionskontrolle hinzufügen bzw. aus der Versionierung + löschen +- k3: Ich kann Änderungen (geänderte Dateien) zum Staging hinzufügen und committen +- k3: Ich kann Unterschiede zwischen Commits herausfinden und mir die Historie des + Repos anschauen +- k3: Ich kann gezielt Dateien und Ordner von der Versionierung ausnehmen + (ignorieren) +::: + +::: challenges +**Versionierung 101** + +1. Legen Sie ein Repository an. +2. Fügen Sie Dateien dem Verzeichnis hinzu und stellen Sie *einige* davon unter + Versionskontrolle. +3. Ändern Sie eine Datei und versionieren Sie die Änderung. +4. Was ist der Unterschied zwischen "`git add .; git commit`" und + "`git commit -a`"? +5. Wie finden Sie heraus, welche Dateien geändert wurden? +6. Entfernen Sie eine Datei aus der Versionskontrolle, aber nicht aus dem + Verzeichnis! +7. Entfernen Sie eine Datei komplett (Versionskontrolle und Verzeichnis). +8. Ändern Sie eine Datei und betrachten die Unterschiede zum letzten Commit. +9. Fügen Sie eine geänderte Datei zum Index hinzu. Was erhalten Sie bei + `git diff `? +10. Wie können Sie einen früheren Stand einer Datei wiederherstellen? Wie finden Sie + überhaupt den Stand? +11. Legen Sie sich ein Java-Projekt in Ihrer IDE an an. Stellen Sie dieses Projekt + unter Git-Versionskontrolle. Führen Sie die vorigen Schritte mit Ihrer IDE + durch. +::: diff --git a/lecture/git/git-intro.md b/lecture/git/git-intro.md index 3a12a524f..6aa1fce75 100644 --- a/lecture/git/git-intro.md +++ b/lecture/git/git-intro.md @@ -1,137 +1,116 @@ --- +author: Carsten Gips (HSBI) title: "Intro: Versionskontrolle in der Softwareentwicklung" -author: "Carsten Gips (HSBI)" -readings: - - "@Chacon2014 [Kap. 1 und 2]" - - "@AtlassianGit" - - "@GitCheatSheet" -tldr: | - In der Softwareentwicklung wird häufig ein Versionsmanagementsystem (VCS) eingesetzt, welches die Verwaltung - von Versionsständen und Änderungen ermöglicht. Ein Repository sammelt dabei die verschiedenen Änderungen - (quasi wie eine Datenbank der Software-Versionsstände). Die Software *Git* ist verbreiteter Vertreter und - arbeitet mit dezentralen Repositories. - - Ein neues lokales Repository kann man mit `git init` anlegen. Der Befehl legt den Unterordner `.git/` im - aktuellen Ordner an, darin befindet sich das lokale Repository und weitere von Git benötigte Dateien - (FINGER WEG!). Die Dateien und anderen Unterordner im aktuellen Ordner können nun der Versionskontrolle - hinzugefügt werden. - - Den lokal vorliegenden (Versions-) Stand der Dateien im aktuellen Ordner nennt man auch "Workingcopy". - - Ein bereits existierendes Repo kann mit `git clone ` geklont werden. - - [GitHub](https://github.com) ist nicht Git, sondern ein kommerzieller Anbieter, der das Hosten von - Git-Repositories und weitere Features anbietet. -outcomes: - - k1: "Ich kenne verschiedene Varianten der Versionierung" - - k1: "Ich kann die Begriffe 'Workingcopy' und 'Repository' definieren" - - k2: "Ich kann zwischen 'Github' und 'Git' unterscheiden" - - k2: "Ich kann auf meinem Rechner lokale Git-Repositories anlegen" - - k3: "Ich kann mit den Git-Befehlen zum Anlegen von lokalen Repos auf der Konsole umgehen" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106239&client_id=FH-Bielefeld" -# name: "Quiz Git Intro (ILIAS)" -youtube: - - link: "https://youtu.be/Ac3-pZhVf_c" - name: "VL Git Intro" - - link: "https://youtu.be/0noYvZvQhic" - name: "Demo Config" - - link: "https://youtu.be/ZaWEwIpER-U" - name: "Demo Repo" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/12ee47d53c582cf255d80fd186bb79bebeb65e63ca954a8070cb270eb82c4e5d492dc812da74cbdcdb3e697eeccdaf0b585852697306ac82d890229adffbf401" - name: "VL Git Intro" --- +::: tldr +In der Softwareentwicklung wird häufig ein Versionsmanagementsystem (VCS) +eingesetzt, welches die Verwaltung von Versionsständen und Änderungen ermöglicht. +Ein Repository sammelt dabei die verschiedenen Änderungen (quasi wie eine Datenbank +der Software-Versionsstände). Die Software *Git* ist verbreiteter Vertreter und +arbeitet mit dezentralen Repositories. -# Typische Probleme bei SW-Entwicklung +Ein neues lokales Repository kann man mit `git init` anlegen. Der Befehl legt den +Unterordner `.git/` im aktuellen Ordner an, darin befindet sich das lokale +Repository und weitere von Git benötigte Dateien (FINGER WEG!). Die Dateien und +anderen Unterordner im aktuellen Ordner können nun der Versionskontrolle hinzugefügt +werden. + +Den lokal vorliegenden (Versions-) Stand der Dateien im aktuellen Ordner nennt man +auch "Workingcopy". + +Ein bereits existierendes Repo kann mit `git clone ` geklont werden. + +[GitHub](https://github.com) ist nicht Git, sondern ein kommerzieller Anbieter, der +das Hosten von Git-Repositories und weitere Features anbietet. +::: + +::: youtube +- [VL Git Intro](https://youtu.be/Ac3-pZhVf_c) +- [Demo Config](https://youtu.be/0noYvZvQhic) +- [Demo Repo](https://youtu.be/ZaWEwIpER-U) +::: -* Was hat wer wann (und wo) geändert? Und warum? -* Ich brauche den Stand von gestern/letzter Woche/... -* Ich will schnell mal eine neue Idee ausprobieren ... -* Ich arbeite an mehreren Rechnern (Synchronisation) -* Wir müssen gemeinsam an der gleichen Codebasis arbeiten. -* Wir arbeiten am Release v42, aber Kunde braucht schnell einen Fix für v40 +# Typische Probleme bei SW-Entwicklung +- Was hat wer wann (und wo) geändert? Und warum? +- Ich brauche den Stand von gestern/letzter Woche/... +- Ich will schnell mal eine neue Idee ausprobieren ... +- Ich arbeite an mehreren Rechnern (Synchronisation) +- Wir müssen gemeinsam an der gleichen Codebasis arbeiten. +- Wir arbeiten am Release v42, aber Kunde braucht schnell einen Fix für v40 # Folgen SW-Entwicklung ohne Versionsverwaltung -![](images/screenshot_zusammenarbeit_ohne_vcs.png){width=80% web_width="60%"} +![](images/screenshot_zusammenarbeit_ohne_vcs.png){width="80%" web_width="60%"} ::: notes -* Filesystem müllt voll mit manuell versionierten - Dateien/Sicherungen ala `file_20120507_version2_cagi.txt` -* Ordner/Projekte müssen dupliziert werden für neue Ideen -* Code müllt voll mit auskommentierten Zeilen ("Könnte ja noch gebraucht werden") -* Unklar, wann welche Änderung von wem warum eingeführt wurde -* Unbeabsichtigtes Überschreiben mit älteren Versionen beim Upload - in gemeinsamen Filesharing-Bereich +- Filesystem müllt voll mit manuell versionierten Dateien/Sicherungen ala + `file_20120507_version2_cagi.txt` +- Ordner/Projekte müssen dupliziert werden für neue Ideen +- Code müllt voll mit auskommentierten Zeilen ("Könnte ja noch gebraucht werden") +- Unklar, wann welche Änderung von wem warum eingeführt wurde +- Unbeabsichtigtes Überschreiben mit älteren Versionen beim Upload in gemeinsamen + Filesharing-Bereich ::: - # Prinzip Versionsverwaltung -:::::: columns +::::: columns ::: {.column width="48%"} - ![](images/local.png){width="60%" web_width="40%"} - ::: -::: {.column width="50%"} +::: {.column width="50%"} \vspace{10mm} -* **Repository:** - **Datenbank** mit verschiedenen Versionsständen, Kommentaren, Tags etc. +- **Repository:** **Datenbank** mit verschiedenen Versionsständen, Kommentaren, + Tags etc. \bigskip -* **Workingcopy:** - **Arbeitskopie** eines bestimmten Versionsstandes - +- **Workingcopy:** **Arbeitskopie** eines bestimmten Versionsstandes ::: -:::::: - +::::: # Varianten: Zentrale Versionsverwaltung (Beispiel SVN) ![](images/centralised.png){width="80%" web_width="40%"} ::: notes -Es gibt ein zentrales Repository (typischerweise auf einem Server), von dem die Developer einen -bestimmten Versionsstand "auschecken" (sich lokal kopieren) und in welches sie Änderungen wieder -zurück "pushen". +Es gibt ein zentrales Repository (typischerweise auf einem Server), von dem die +Developer einen bestimmten Versionsstand "auschecken" (sich lokal kopieren) und in +welches sie Änderungen wieder zurück "pushen". -Zur Abfrage der Historie und zum Veröffentlichen von Änderungen benötigt man entsprechend immer -eine Verbindung zum Server. +Zur Abfrage der Historie und zum Veröffentlichen von Änderungen benötigt man +entsprechend immer eine Verbindung zum Server. ::: - # Varianten: Verteilte Versionsverwaltung (Beispiel Git) ![](images/distributed.png){width="80%" web_width="60%"} ::: notes -In diesem Szenario hat jeder Developer nicht nur die Workingcopy, sondern auch noch eine Kopie -des Repositories. Zusätzlich kann es einen oder mehrere Server geben, auf denen dann nur das -Repository vorgehalten wird, d.h. dort gibt es normalerweise keine Workingcopy. Damit kann -unabhängig voneinander gearbeitet werden. - -Allerdings besteht nun die Herausforderung, die geänderten Repositories miteinander abzugleichen. -Das kann zwischen dem lokalen Rechner und dem Server passieren, aber auch zwischen zwei "normalen" -Rechnern (also zwischen den Developern). - - -**Hinweis**: _GitHub ain't no Git!_ Git ist eine Technologie zur Versionsverwaltung. Es gibt verschiedene -Implementierungen und Plugins für IDEs und Editoren. [GitHub](https://github.com) ist dagegen _ein_ -Dienstleister, wo man Git-Repositories ablegen kann und auf diese mit Git (von der Konsole oder aus der -IDE) zugreifen kann. Darüber hinaus bietet der Service aber zusätzliche Features an, beispielsweise -ein Issue-Management oder sogenannte _Pull-Requests_. Dies hat aber zunächst mit Git nichts zu tun. -Weitere populäre Anbieter sind beispielsweise [Bitbucket](https://bitbucket.org/) oder [Gitlab](https://gitlab.com) -oder [Gitea](https://gitea.io/en-us/), wobei einige auch selbst gehostet werden können. +In diesem Szenario hat jeder Developer nicht nur die Workingcopy, sondern auch noch +eine Kopie des Repositories. Zusätzlich kann es einen oder mehrere Server geben, auf +denen dann nur das Repository vorgehalten wird, d.h. dort gibt es normalerweise +keine Workingcopy. Damit kann unabhängig voneinander gearbeitet werden. + +Allerdings besteht nun die Herausforderung, die geänderten Repositories miteinander +abzugleichen. Das kann zwischen dem lokalen Rechner und dem Server passieren, aber +auch zwischen zwei "normalen" Rechnern (also zwischen den Developern). + +**Hinweis**: *GitHub ain't no Git!* Git ist eine Technologie zur Versionsverwaltung. +Es gibt verschiedene Implementierungen und Plugins für IDEs und Editoren. +[GitHub](https://github.com) ist dagegen *ein* Dienstleister, wo man +Git-Repositories ablegen kann und auf diese mit Git (von der Konsole oder aus der +IDE) zugreifen kann. Darüber hinaus bietet der Service aber zusätzliche Features an, +beispielsweise ein Issue-Management oder sogenannte *Pull-Requests*. Dies hat aber +zunächst mit Git nichts zu tun. Weitere populäre Anbieter sind beispielsweise +[Bitbucket](https://bitbucket.org/) oder [Gitlab](https://gitlab.com) oder +[Gitea](https://gitea.io/en-us/), wobei einige auch selbst gehostet werden können. ::: - # Versionsverwaltung mit Git: Typische Arbeitsschritte 1. Repository anlegen (oder clonen) @@ -153,22 +132,21 @@ oder [Gitea](https://gitea.io/en-us/), wobei einige auch selbst gehostet werden 9. Änderungen verteilen (verteiltes Arbeiten, Workflows) - # (Globale) Konfiguration **Minimum**: -* `git config --global user.name ` -* `git config --global user.email ` +- `git config --global user.name ` +- `git config --global user.email ` ::: notes Diese Konfiguration muss man nur einmal machen. -Wenn man den Schalter `--global` weglässt, gelten die Einstellungen nur -für das aktuelle Projekt/Repo. +Wenn man den Schalter `--global` weglässt, gelten die Einstellungen nur für das +aktuelle Projekt/Repo. -Zumindest Namen und EMail-Adresse **muss** man setzen, da Git diese -Information beim Anlegen der Commits speichert (== benötigt!). +Zumindest Namen und EMail-Adresse **muss** man setzen, da Git diese Information beim +Anlegen der Commits speichert (== benötigt!). ::: \bigskip @@ -176,45 +154,59 @@ Information beim Anlegen der Commits speichert (== benötigt!). **Aliase**: -* `git config --global alias.ci commit` -* `git config --global alias.co checkout` -* `git config --global alias.br branch` -* `git config --global alias.st status` -* `git config --global alias.ll 'log --all --graph --decorate --oneline'` +- `git config --global alias.ci commit` +- `git config --global alias.co checkout` +- `git config --global alias.br branch` +- `git config --global alias.st status` +- `git config --global alias.ll 'log --all --graph --decorate --oneline'` ::: notes -Zusätzlich kann man weitere Einstellungen vornehmen, etwa auf bunte -Ausgabe umschalten: `git config --global color.ui auto` oder Abkürzungen -(Aliase) für Befehle definieren: `git config --global alias.ll 'log --all --oneline --graph --decorate'` ... +Zusätzlich kann man weitere Einstellungen vornehmen, etwa auf bunte Ausgabe +umschalten: `git config --global color.ui auto` oder Abkürzungen (Aliase) für +Befehle definieren: +`git config --global alias.ll 'log --all --oneline --graph --decorate'` ... -Git (und auch GitHub) hat kürzlich den Namen des Default-Branches von `master` -auf `main` geändert. Dies kann man in Git ebenfalls selbst einstellen: +Git (und auch GitHub) hat kürzlich den Namen des Default-Branches von `master` auf +`main` geändert. Dies kann man in Git ebenfalls selbst einstellen: `git config --global init.defaultBranch `. -Anschauen kann man sich die Einstellungen in der Textdatei `~/.gitconfig` -oder per Befehl `git config --global -l`. +Anschauen kann man sich die Einstellungen in der Textdatei `~/.gitconfig` oder per +Befehl `git config --global -l`. ::: [[Konsole]{.ex}]{.slides} - # Neues Repo anlegen -* `git init` +- `git init` - => Erzeugt neues Repository im akt. Verzeichnis + =\> Erzeugt neues Repository im akt. Verzeichnis \bigskip -* `git clone ` +- `git clone ` - => Erzeugt (verlinkte) Kopie [des Repos unter ``]{.notes} + =\> Erzeugt (verlinkte) Kopie [des Repos unter ``]{.notes} [[Konsole]{.ex}]{.slides} - # Wrap-Up -* Git: Versionsmanagement mit dezentralen Repositories -* Anlegen eines lokalen Repos mit `git init` -* Clonen eines existierenden Repos mit `git clone ` +- Git: Versionsmanagement mit dezentralen Repositories +- Anlegen eines lokalen Repos mit `git init` +- Clonen eines existierenden Repos mit `git clone ` + +::: readings +- @Chacon2014 [Kap. 1 und 2] +- @AtlassianGit +- @GitCheatSheet +::: + +::: outcomes +- k1: Ich kenne verschiedene Varianten der Versionierung +- k1: Ich kann die Begriffe 'Workingcopy' und 'Repository' definieren +- k2: Ich kann zwischen 'Github' und 'Git' unterscheiden +- k2: Ich kann auf meinem Rechner lokale Git-Repositories anlegen +- k3: Ich kann mit den Git-Befehlen zum Anlegen von lokalen Repos auf der Konsole + umgehen +::: diff --git a/lecture/git/readme.md b/lecture/git/readme.md index 22e56fbba..7d4467a47 100644 --- a/lecture/git/readme.md +++ b/lecture/git/readme.md @@ -1,5 +1,6 @@ --- -title: "Versionierung mit Git" -no_pdf: true no_beamer: true +no_pdf: true +title: Versionierung mit Git --- + diff --git a/lecture/git/remotes.md b/lecture/git/remotes.md index 0afd2ff9f..2a7d794ea 100644 --- a/lecture/git/remotes.md +++ b/lecture/git/remotes.md @@ -1,106 +1,57 @@ --- -title: "Arbeiten mit Git Remotes (dezentrale Repos)" -author: "Carsten Gips (HSBI)" -readings: - - "@Chacon2014 [Kap. 3]" - - "@AtlassianGit" - - "@GitCheatSheet" -tldr: | - Eine der Stärken von Git ist das Arbeiten mit verteilten Repositories. Zu jeder Workingcopy gehört - eine Kopie des Repositories, wodurch jederzeit alle Informationen einsehbar sind und auch offline - gearbeitet werden kann. Allerdings muss man für die Zusammenarbeit mit anderen Entwicklern die lokalen - Repos mit den "entfernten" Repos (auf dem Server oder anderen Entwicklungsrechnern) synchronisieren. - - Beim Klonen eines Repositories mit `git clone ` wird das fremde Repo mit dem Namen `origin` - im lokalen Repo festgehalten. Dieser Name wird auch als Präfix für die Branches in diesem Repo genutzt, - d.h. die Branches im Remote-Repo tauchen als `origin/` im lokalen Repo auf. Diese Remote-Branches - kann man nicht direkt bearbeiten, sondern man muss diese Remote-Branches in einem lokalen Branch auschecken - und dann darin weiterarbeiten. Es können beliebig viele weitere Remotes dem eigenen Repository hinzugefügt - werden. - - Änderungen aus einem Remote-Repo können mit `git fetch ` in das lokale Repo geholt werden. - Dies aktualisiert **nur** die Remote-Branches `/`! Die Änderungen können anschließend - mit `git merge /` in den aktuell in der Workingcopy ausgecheckten Branch gemergt werden. - (_Anmerkung_: Wenn mehrere Personen an einem Branch arbeiten, will man die eigenen Arbeiten in dem Branch - vermutlich eher auf den aktuellen Stand des Remote **rebasen** statt mergen!) Eigene Änderungen können - mit `git push ` in das Remote-Repo geschoben werden. - - Um den Umgang mit den Remote-Branches und den davon abgeleiteten lokalen Branches zu vereinfachen, - gibt es das Konzept der "Tracking Branches". Dabei "folgt" ein lokaler Branch einem Remote-Branch. - Ein einfaches `git pull` oder `git push` holt dann Änderungen aus dem Remote-Branch in den ausgecheckten - lokalen Branch bzw. schiebt Änderungen im lokalen Branch in den Remote-Branch. -outcomes: - - k3: "Ich kann Clones von fremden Git-Repositories erzeugen" - - k3: "Ich kann Änderungen vom fremden Repo holen" - - k3: "Ich kann meine lokale Branches aktualisieren" - - k3: "Ich kann lokale Änderungen ins fremde Repo pushen" - - k2: "Ich kann den Unterschied zwischen lokalen Branches und entfernten Branches erklären" - - k3: "Ich kann sowohl lokale Branches als auch entfernte Branches anlegen und damit arbeiten" - - k3: "Ich kann Tracking Branches zum Vereinfachen der Arbeit anlegen" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106245&client_id=FH-Bielefeld" -# name: "Quiz Git Remotes (ILIAS)" -youtube: - - link: "https://youtu.be/_uhEseblDYU" - name: "VL Git Remotes" - - link: "https://youtu.be/moqywsxtEy8" - name: "Demo Fetch, Pull und Push" - - link: "https://youtu.be/0RoqM5Wmxfc" - name: "Demo Tracking-Branches" - - link: "https://youtu.be/jL4AvSsjjKg" - name: "Demo Verknüpfen weiterer Remotes" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/8aa3f0e96448352e436aead353f54a3de6bbd3737bf33b6bd935b098352690544dc527e59424068ec65b6e0d67f6aff6bd6aa0a602127ce2a02a360658e0151d" - name: "VL Git Remotes" -challenges: | - **Synchronisierung mit Remote-Repos** - - Sie haben ein Repo von github.com geklont. Beide Repos, das Original auf dem Server - als auch Ihre lokale Kopie, haben sich danach unabhängig voneinander weiter - entwickelt (siehe Skizze). - - ![](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/git/images/remote-branches-2.png?raw=true){width="60%"} - - Wie können Sie Ihre Änderung im lokalen Repo auf den Server pushen? Analysieren Sie die - Situation und erklären Sie zwei verschiedene Lösungsansätze und geben Sie jeweils die - entsprechenden Git-Befehle an. - - - - - **Interaktive Git-Tutorials**: Schaffen Sie die Rätsel? - - * [Learn Git Branching](https://learngitbranching.js.org/) - * [Oh My Git!](https://ohmygit.org/) - * [Git Time](https://git.bradwoods.io/) +author: Carsten Gips (HSBI) +title: Arbeiten mit Git Remotes (dezentrale Repos) --- +::: tldr +Eine der Stärken von Git ist das Arbeiten mit verteilten Repositories. Zu jeder +Workingcopy gehört eine Kopie des Repositories, wodurch jederzeit alle Informationen +einsehbar sind und auch offline gearbeitet werden kann. Allerdings muss man für die +Zusammenarbeit mit anderen Entwicklern die lokalen Repos mit den "entfernten" Repos +(auf dem Server oder anderen Entwicklungsrechnern) synchronisieren. + +Beim Klonen eines Repositories mit `git clone ` wird das fremde Repo mit dem +Namen `origin` im lokalen Repo festgehalten. Dieser Name wird auch als Präfix für +die Branches in diesem Repo genutzt, d.h. die Branches im Remote-Repo tauchen als +`origin/` im lokalen Repo auf. Diese Remote-Branches kann man nicht direkt +bearbeiten, sondern man muss diese Remote-Branches in einem lokalen Branch +auschecken und dann darin weiterarbeiten. Es können beliebig viele weitere Remotes +dem eigenen Repository hinzugefügt werden. + +Änderungen aus einem Remote-Repo können mit `git fetch ` in das lokale Repo +geholt werden. Dies aktualisiert **nur** die Remote-Branches `/`! +Die Änderungen können anschließend mit `git merge /` in den aktuell +in der Workingcopy ausgecheckten Branch gemergt werden. (*Anmerkung*: Wenn mehrere +Personen an einem Branch arbeiten, will man die eigenen Arbeiten in dem Branch +vermutlich eher auf den aktuellen Stand des Remote **rebasen** statt mergen!) Eigene +Änderungen können mit `git push ` in das Remote-Repo geschoben +werden. + +Um den Umgang mit den Remote-Branches und den davon abgeleiteten lokalen Branches zu +vereinfachen, gibt es das Konzept der "Tracking Branches". Dabei "folgt" ein lokaler +Branch einem Remote-Branch. Ein einfaches `git pull` oder `git push` holt dann +Änderungen aus dem Remote-Branch in den ausgecheckten lokalen Branch bzw. schiebt +Änderungen im lokalen Branch in den Remote-Branch. +::: + +::: youtube +- [VL Git Remotes](https://youtu.be/_uhEseblDYU) +- [Demo Fetch, Pull und Push](https://youtu.be/moqywsxtEy8) +- [Demo Tracking-Branches](https://youtu.be/0RoqM5Wmxfc) +- [Demo Verknüpfen weiterer Remotes](https://youtu.be/jL4AvSsjjKg) +::: # Nutzung von Git in Projekten: Verteiltes Git (und Workflows) ![](images/distributed.png){width="80%" web_width="65%"} - ::: notes -Git ermöglicht eine einfaches Zusammenarbeit in verteilten Teams. Nachdem wir die verschiedenen -Branching-Strategien betrachtet haben, soll im Folgenden die Frage betrachtet werden: **Wie arbeite -ich sinnvoll über Git mit anderen Kollegen und Teams zusammen? Welche Modelle haben sich etabliert?** +Git ermöglicht eine einfaches Zusammenarbeit in verteilten Teams. Nachdem wir die +verschiedenen Branching-Strategien betrachtet haben, soll im Folgenden die Frage +betrachtet werden: **Wie arbeite ich sinnvoll über Git mit anderen Kollegen und +Teams zusammen? Welche Modelle haben sich etabliert?** ::: - # Clonen kann sich lohnen ... https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture @@ -109,7 +60,7 @@ ich sinnvoll über Git mit anderen Kollegen und Teams zusammen? Welche Modelle h \bigskip -=> `git clone https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture` +=\> `git clone https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture` \bigskip @@ -118,32 +69,29 @@ ich sinnvoll über Git mit anderen Kollegen und Teams zusammen? Welche Modelle h ---C---D---E master ^origin/master - ::: notes Git-Repository mit der URL `` in lokalen Ordner `` auschecken: -* `git clone []` -* Workingcopy ist automatisch über den Namen `origin` mit dem remote Repo auf - dem Server verbunden -* Lokaler Branch `master` ist mit dem remote Branch `origin/master` verbunden - ("Tracking Branch", s.u.), der den Stand des `master`-Branches auf dem - Server spiegelt - +- `git clone []` +- Workingcopy ist automatisch über den Namen `origin` mit dem remote Repo auf dem + Server verbunden +- Lokaler Branch `master` ist mit dem remote Branch `origin/master` verbunden + ("Tracking Branch", s.u.), der den Stand des `master`-Branches auf dem Server + spiegelt Für die URL sind verschiedene Protokolle möglich, beispielsweise: -* "`file://`" für über das Dateisystem erreichbare Repositories (ohne Server) -* "`https://`" für Repo auf einem Server: Authentifikation mit Username - und Passwort (!) -* "`git@`" für Repo auf einem Server: Authentifikation mit **SSH-Key** (diese - Variante wird im Praktikum im Zusammenspiel mit dem Gitlab-Server im - SW-Labor verwendet) +- "`file://`" für über das Dateisystem erreichbare Repositories (ohne Server) +- "`https://`" für Repo auf einem Server: Authentifikation mit Username und + Passwort (!) +- "`git@`" für Repo auf einem Server: Authentifikation mit **SSH-Key** (diese + Variante wird im Praktikum im Zusammenspiel mit dem Gitlab-Server im SW-Labor + verwendet) ::: [[Hinweis auf Protokolle, Beispiel]{.ex}]{.slides} - -# Eigener und entfernter _master_ entwickeln sich weiter ... +# Eigener und entfernter *master* entwickeln sich weiter ... https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture @@ -151,7 +99,7 @@ Für die URL sind verschiedene Protokolle möglich, beispielsweise: \bigskip -[`

`{=markdown}]{.notes} +[]{.notes} ./Prog2-Lecture/ (lokaler Rechner) @@ -165,34 +113,28 @@ Nach dem Auschecken liegen (in diesem Beispiel) drei `master`-Branches vor: 2. der lokale `master`, und 3. die lokale Referenz auf den `master`-Branch auf dem Server: `origin/master`. -Der lokale `master` ist ein normaler Branch und kann durch Commits verändert -werden. - -Der `master` auf dem Server kann sich ebenfalls ändern, beispielsweise weil -jemand anderes seine lokalen Änderungen mit dem Server abgeglichen hat -(`git push`, s.u.). +Der lokale `master` ist ein normaler Branch und kann durch Commits verändert werden. -Der Branch `origin/master` lässt sich nicht direkt verändern! Das ist lediglich -eine lokale Referenz auf den `master`-Branch auf dem Server und zeigt an, -welchen Stand man bei der letzten Synchronisierung hatte. D.h. erst mit dem -nächsten Abgleich wird sich dieser Branch ändern (sofern sich der entsprechende -Branch auf dem Server verändert hat). +Der `master` auf dem Server kann sich ebenfalls ändern, beispielsweise weil jemand +anderes seine lokalen Änderungen mit dem Server abgeglichen hat (`git push`, s.u.). -*Anmerkung*: Dies gilt analog für alle anderen Branches. Allerdings wird -nur der `origin/master` beim Clonen automatisch als lokaler Branch ausgecheckt. +Der Branch `origin/master` lässt sich nicht direkt verändern! Das ist lediglich eine +lokale Referenz auf den `master`-Branch auf dem Server und zeigt an, welchen Stand +man bei der letzten Synchronisierung hatte. D.h. erst mit dem nächsten Abgleich wird +sich dieser Branch ändern (sofern sich der entsprechende Branch auf dem Server +verändert hat). +*Anmerkung*: Dies gilt analog für alle anderen Branches. Allerdings wird nur der +`origin/master` beim Clonen automatisch als lokaler Branch ausgecheckt. -Zur Abbildung: -Während man lokal arbeitet (Commit `H` auf dem lokalen `master`), kann es passieren, -dass sich auch das remote Repo ändert. Im Beispiel wurden dort die beiden Commits -`F` und `G` angelegt (durch `git push`, s.u.). +Zur Abbildung: Während man lokal arbeitet (Commit `H` auf dem lokalen `master`), +kann es passieren, dass sich auch das remote Repo ändert. Im Beispiel wurden dort +die beiden Commits `F` und `G` angelegt (durch `git push`, s.u.). Wichtig: Da in der Zwischenzeit das lokale Repo nicht mit dem Server abgeglichen -wurde, zeigt der remote Branch `origin/master` immer noch auf den Commit -`E`! +wurde, zeigt der remote Branch `origin/master` immer noch auf den Commit `E`! ::: - # Änderungen im Remote holen und Branches zusammenführen https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture @@ -201,7 +143,7 @@ wurde, zeigt der remote Branch `origin/master` immer noch auf den Commit \bigskip -=> `git fetch origin` +=\> `git fetch origin` \bigskip @@ -211,31 +153,30 @@ wurde, zeigt der remote Branch `origin/master` immer noch auf den Commit \ F---G origin/master - -::::::::: notes +::: notes ## Änderungen auf dem Server mit dem eigenen Repo abgleichen Mit `git fetch origin` alle Änderungen holen -* Alle remote Branches werden aktualisiert und entsprechen den jeweiligen - Branches auf dem Server: Im Beispiel zeigt jetzt `origin/master` ebenso - wie der `master` auf dem Server auf den Commit `G`. -* Neue Branches auf dem Server werden ebenfalls "geholt", d.h. sie liegen - nach dem Fetch als entsprechende remote Branches vor -* Auf dem Server gelöschte Branches werden nicht automatisch lokal gelöscht; - dies kann man mit `git fetch --prune origin` automatisch erreichen - -*Wichtig*: Es werden nur die remote Branches aktualisiert, nicht die lokalen Branches! +- Alle remote Branches werden aktualisiert und entsprechen den jeweiligen Branches + auf dem Server: Im Beispiel zeigt jetzt `origin/master` ebenso wie der `master` + auf dem Server auf den Commit `G`. +- Neue Branches auf dem Server werden ebenfalls "geholt", d.h. sie liegen nach dem + Fetch als entsprechende remote Branches vor +- Auf dem Server gelöschte Branches werden nicht automatisch lokal gelöscht; dies + kann man mit `git fetch --prune origin` automatisch erreichen +*Wichtig*: Es werden nur die remote Branches aktualisiert, nicht die lokalen +Branches! -## _master_-Branch nach "git fetch origin" zusammenführen +## *master*-Branch nach "git fetch origin" zusammenführen 1. Mit `git checkout master` Workingcopy auf eigenen `master` umstellen -2. Mit `git merge origin/master` Änderungen am `origin/master` in eigenen `master` mergen +2. Mit `git merge origin/master` Änderungen am `origin/master` in eigenen `master` + mergen 3. Mit `git push origin master` eigene Änderungen ins remote Repo pushen - -[`

`{=markdown}]{.notes} +[]{.notes} https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture @@ -243,8 +184,7 @@ Mit `git fetch origin` alle Änderungen holen \ / F---G -[`

`{=markdown}]{.notes} - +[]{.notes} ./Prog2-Lecture/ (lokaler Rechner) @@ -252,78 +192,73 @@ Mit `git fetch origin` alle Änderungen holen \ /^origin/master F---G -*Anmerkung*: Schritt (2) kann man auch per `git pull origin master` erledigen ... Ein `pull` -fasst `fetch` und `merge` zusammen (s.u.). - -*Anmerkung* Statt dem `merge` in Schritt (2) kann man auch den lokalen `master` auf den -aktualisierten `origin/master` rebasen und vermeidet damit die "Raute". Der `pull` kann -mit der Option "`--rebase`" auf "rebase" umgestellt werden (per Default wird bei `pull` -ein "merge" ausgeführt). +*Anmerkung*: Schritt (2) kann man auch per `git pull origin master` erledigen ... +Ein `pull` fasst `fetch` und `merge` zusammen (s.u.). +*Anmerkung* Statt dem `merge` in Schritt (2) kann man auch den lokalen `master` auf +den aktualisierten `origin/master` rebasen und vermeidet damit die "Raute". Der +`pull` kann mit der Option "`--rebase`" auf "rebase" umgestellt werden (per Default +wird bei `pull` ein "merge" ausgeführt). -## Auf dem Server ist nur ein _fast forward merge_ möglich +## Auf dem Server ist nur ein *fast forward merge* möglich Sie können Ihre Änderungen in Ihrem lokalen `master` auch direkt in das remote Repo pushen, solange auf dem Server ein **fast forward merge** möglich ist. Wenn aber (wie in der Abbildung) der lokale und der remote `master` divergieren, müssen Sie den Merge wie beschrieben lokal durchführen (`fetch`/`merge` oder `pull`) -und das Ergebnis wieder in das remote Repo pushen (dann ist ja wieder ein -*fast forward merge* möglich, es sei denn, jemand hat den remote `master` in der +und das Ergebnis wieder in das remote Repo pushen (dann ist ja wieder ein *fast +forward merge* möglich, es sei denn, jemand hat den remote `master` in der Zwischenzeit weiter geschoben - dann muss die Aktualisierung erneut durchgeführt werden). -::::::::: - -[Beispiel für Zusammenführen (merge und push), Anmerkung zu fast forward merge]{.ex href="https://youtu.be/moqywsxtEy8"} +::: +[Beispiel für Zusammenführen (merge und push), Anmerkung zu fast forward merge]{.ex +href="https://youtu.be/moqywsxtEy8"} # Branches und Remotes -* Eigenen (neuen) lokalen Branch ins remote Repo schicken - * `git push ` +- Eigenen (neuen) lokalen Branch ins remote Repo schicken + - `git push ` \bigskip -* Neuer Branch im remote Repo - * `git fetch ` holt (auch) alle neuen Branches - * Lokale Änderungen an remote Branches nicht möglich! \newline - => **Remote Branch in lokalen Branch mergen** (oder auschecken) +- Neuer Branch im remote Repo + - `git fetch ` holt (auch) alle neuen Branches + - Lokale Änderungen an remote Branches nicht möglich! `\newline`{=tex} =\> + **Remote Branch in lokalen Branch mergen** (oder auschecken) [[Beispiel]{.ex}]{.slides} - # Zusammenfassung: Arbeiten mit Remotes -1. Änderungen vom Server holen: `git fetch ` \newline - => Holt alle Änderungen vom Repo `` ins eigene Repo - (Workingcopy bleibt unangetastet!) +1. Änderungen vom Server holen: `git fetch ` `\newline`{=tex} =\> Holt alle + Änderungen vom Repo `` ins eigene Repo (Workingcopy bleibt + unangetastet!) \smallskip -2. Aktuellen lokalen Branch auffrischen: `git merge /` \newline - (oder alternativ `git pull `) +2. Aktuellen lokalen Branch auffrischen: `git merge /` + `\newline`{=tex} (oder alternativ `git pull `) \smallskip 3. Eigene Änderungen hochladen: `git push ` +::: notes +## Anmerkung: *push* geht nur, wenn -::::::::: notes -## Anmerkung: _push_ geht nur, wenn - -1. Ziel ein "bare"-Repository ist, **und** -2. keine Konflikte entstehen - -=> im remote Repo **nur** "fast forward"-Merge möglich +1. Ziel ein "bare"-Repository ist, **und** +2. keine Konflikte entstehen -=> bei Konflikten erst `fetch` und `merge`, danach `push` +=\> im remote Repo **nur** "fast forward"-Merge möglich -**Anmerkung**: Ein "bare"-Repository enthält keine Workingcopy, sondern nur -das Repo an sich. Die ist bei Repos, die Sie auf einem Server wie Gitlab oder -Github anlegen, automatisch der Fall. Sie können aber auch lokal ein solches -"bare"-Repo anlegen, indem Sie beim Initialisieren den Schalter `--bare` -mitgeben: `git init --bare` ... +=\> bei Konflikten erst `fetch` und `merge`, danach `push` +**Anmerkung**: Ein "bare"-Repository enthält keine Workingcopy, sondern nur das Repo +an sich. Die ist bei Repos, die Sie auf einem Server wie Gitlab oder Github anlegen, +automatisch der Fall. Sie können aber auch lokal ein solches "bare"-Repo anlegen, +indem Sie beim Initialisieren den Schalter `--bare` mitgeben: `git init --bare` ... ## Beispiel @@ -334,48 +269,47 @@ mitgeben: `git init --bare` ... ... # Herumspielen am lokalen Master git push origin master # lokalen Master auf Server schicken -::::::::: - +::: # Vereinfachung: Tracking Branches -* **Tracking Branch**: lokaler Branch, der remote Branch "verfolgt" - * Beispiel: lokaler `master`-Branch folgt `origin/master` per Default +- **Tracking Branch**: lokaler Branch, der remote Branch "verfolgt" + - Beispiel: lokaler `master`-Branch folgt `origin/master` per Default \bigskip -* **Vereinfachung im Workflow**: - * ` git pull` entspricht +- **Vereinfachung im Workflow**: + - `git pull` entspricht 1. `git fetch ` **plus** 2. `git merge /` - * `git push` entspricht `git push ` + - `git push` entspricht `git push ` \bigskip Vorsicht: `pull` und `push` beziehen sich nur auf ausgecheckten Tracking Branch - # Einrichten von Tracking Branches -* `git clone`: lokaler `master` trackt automatisch `origin/master` +- `git clone`: lokaler `master` trackt automatisch `origin/master` \smallskip -* Remote Branch als Tracking Branch einrichten: +- Remote Branch als Tracking Branch einrichten: 1. [Änderungen aus remote Repo holen:]{.notes} `git fetch ` - 2. [Tracking Branch anlegen:]{.notes} `git checkout -t /` - [(=> Option `-t` richtet den remote Branch als Tracking Branch ein)]{.notes} + 2. [Tracking Branch anlegen:]{.notes} `git checkout -t /` [(=\> + Option `-t` richtet den remote Branch als Tracking Branch ein)]{.notes} \smallskip -* Lokalen [neuen]{.notes} Branch [ins remote Repo schicken und]{.notes} als Tracking Branch einrichten: +- Lokalen [neuen]{.notes} Branch [ins remote Repo schicken und]{.notes} als + Tracking Branch einrichten: 1. [Lokalen Branch erzeugen:]{.notes} `git checkout -b ` 2. [Lokalen Branch ins Repo schicken:]{.notes} `git push -u ` - [(=> Option `-u` richtet den lokalen Branch als Tracking Branch ein)]{.notes} + [(=\> Option `-u` richtet den lokalen Branch als Tracking Branch + ein)]{.notes} [[Beispiel]{.ex}]{.slides} - # Hinzufügen eines (weiteren) Remote Repository ![](images/screenshot_branches.png){width="75%" web_width="65%"} @@ -391,30 +325,82 @@ Sie können einem Repo beliebig viele Remotes hinzufügen: ::: notes **Beispiel**: `git remote add andi git@github.com:andi/repo.git` - -* Remote `origin` wird bei `clone` automatisch angelegt -* Ansehen der Remotes mit `git remote -v` -* `fetch`, `push` und `pull` jeweils über den vergebenen Namen +- Remote `origin` wird bei `clone` automatisch angelegt +- Ansehen der Remotes mit `git remote -v` +- `fetch`, `push` und `pull` jeweils über den vergebenen Namen Beispiel: `git fetch andi` oder `git push origin master` ::: [[Beispiel]{.ex}]{.slides} - # Wrap-Up -* Synchronisierung des lokalen Repos mit anderen Repos - * Repo kopieren: `git clone ` - * Interner Name fürs fremde Repo: `origin` - * Änderungen vom fremden Repo holen: `git fetch ` - * Änderungen in lokalen Branch einpflegen: `git merge /` - * Eigene Änderungen ins fremde Repo schieben: `git push ` +- Synchronisierung des lokalen Repos mit anderen Repos + - Repo kopieren: `git clone ` + - Interner Name fürs fremde Repo: `origin` + - Änderungen vom fremden Repo holen: `git fetch ` + - Änderungen in lokalen Branch einpflegen: `git merge /` + - Eigene Änderungen ins fremde Repo schieben: `git push ` \bigskip -* Tracking Branches (Konzept, Anwendung) - * Remote Branches können lokal nicht verändert werden: - * In lokale Branches mergen, oder - * Tracking Branches anlegen => einfaches `pull` und `push` nutzen - * Tracking Branches sind lokale Branches, die remote Branches verfolgen ("tracken") +- Tracking Branches (Konzept, Anwendung) + - Remote Branches können lokal nicht verändert werden: + - In lokale Branches mergen, oder + - Tracking Branches anlegen =\> einfaches `pull` und `push` nutzen + - Tracking Branches sind lokale Branches, die remote Branches verfolgen + ("tracken") + +::: readings +- @Chacon2014 [Kap. 3] +- @AtlassianGit +- @GitCheatSheet +::: + +::: outcomes +- k3: Ich kann Clones von fremden Git-Repositories erzeugen +- k3: Ich kann Änderungen vom fremden Repo holen +- k3: Ich kann meine lokale Branches aktualisieren +- k3: Ich kann lokale Änderungen ins fremde Repo pushen +- k2: Ich kann den Unterschied zwischen lokalen Branches und entfernten Branches + erklären +- k3: Ich kann sowohl lokale Branches als auch entfernte Branches anlegen und + damit arbeiten +- k3: Ich kann Tracking Branches zum Vereinfachen der Arbeit anlegen +::: + +::: challenges +**Synchronisierung mit Remote-Repos** + +Sie haben ein Repo von github.com geklont. Beide Repos, das Original auf dem Server +als auch Ihre lokale Kopie, haben sich danach unabhängig voneinander weiter +entwickelt (siehe Skizze). + +![](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/git/images/remote-branches-2.png?raw=true){width="60%"} + +Wie können Sie Ihre Änderung im lokalen Repo auf den Server pushen? Analysieren Sie +die Situation und erklären Sie zwei verschiedene Lösungsansätze und geben Sie +jeweils die entsprechenden Git-Befehle an. + + + +**Interaktive Git-Tutorials**: Schaffen Sie die Rätsel? + +- [Learn Git Branching](https://learngitbranching.js.org/) +- [Oh My Git!](https://ohmygit.org/) +- [Git Time](https://git.bradwoods.io/) +::: diff --git a/lecture/git/workflows.md b/lecture/git/workflows.md index 64a81bce8..8ef989ebc 100644 --- a/lecture/git/workflows.md +++ b/lecture/git/workflows.md @@ -1,102 +1,81 @@ --- +author: Carsten Gips (HSBI) title: "Zusammenarbeit: Git-Workflows und Merge-/Pull-Requests" -author: "Carsten Gips (HSBI)" -readings: - - "@Chacon2014 [Kap. 5, 4.8, 6]" - - "@AtlassianGit" - - "@GitCheatSheet" -tldr: | - Git erlaubt unterschiedliche Formen der Zusammenarbeit. - - Bei kleinen Teams kann man einen einfachen zentralen Ansatz einsetzen. Dabei gibt es ein zentrales - Repo auf dem Server, und alle Team-Mitglieder dürfen direkt in dieses Repo pushen. Hier muss man - sich gut absprechen und ein vernünftiges Branching-Schema ist besonders wichtig. - - In größeren Projekten gibt es oft ein zentrales öffentliches Repo, wo aber nur wenige Personen - Schreibrechte haben. Hier forkt man sich dieses Repo, erstellt also eine öffentliche Kopie auf dem - Server. Diese Kopie klont man lokal und arbeitet hier und pusht die Änderungen in den eigenen öffentlich - sichtbaren Fork. Um die Änderungen ins Projekt-Repo integrieren zu lassen, wird auf dem Server ein - sogenannter Merge-Request (Gitlab) bzw. Pull-Request (GitHub) erstellt. Dies erlaubt zusätzlich ein - Review und eine Diskussion direkt am Code. Damit man die Änderungen im Hauptprojekt in den eigenen - Fork bekommt, trägt man das Hauptprojekt als weiteres Remote in die Workingcopy ein und aktualisiert - regelmäßig die Hauptbranches, von denen dann auch die eigenen Feature-Branches ausgehen sollten. -outcomes: - - k2: "Ich kenne verschiedene Git-Workflows für die Zusammenarbeit und kann den Ablauf erklären" - - k2: "Ich kann den Unterschied zwischen einem Pull/Merge und einem Pull/Rebase erklären" - - k2: "Ich verstehe, welche Commits zu einem Bestandteil eines Merge-/Pull-Requests werden (und warum)" - - k3: "Ich kann den zentralisierten Workflow einsetzen" - - k3: "Ich kann den einfachen verteilten Workflow mit unterschiedlichen Repos einsetzen" - - k3: "Ich kann meinen Clone und meinen Fork bei/mit Änderungen im/aus dem 'blessed Repo' aktualisieren" - - k3: "Ich kann meine Beiträge zu einem Projekt als Merge-/Pull-Requests einreichen" - - k3: "Ich kann meine Merge-/Pull-Requests aktualisieren" - - k3: "Ich kann in Merge-/Pull-Requests Anmerkungen am Code hinterlegen und an den Feedback-Diskussionen teilnehmen" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106246&client_id=FH-Bielefeld" -# name: "Quiz Git Workflows (ILIAS)" -youtube: - - link: "https://youtu.be/3xqmNGN39wE" - name: "VL Git Workflows" - - link: "https://youtu.be/-8NOia7k0WI" - name: "Demo Anlegen eines Forks, Erstellen eines Pull-Requests (PR)" - - link: "https://youtu.be/4LaZc080Ajo" - name: "Demo Arbeiten mit einem PR, Review eines PR" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/b673fe50e18f064b2b5c81108c81dfea940b2eb81790d54b27e8ade26f0afa10d0d05f8a0c0c852e8e9da6c558065f053033d83384810b47a6ca7f5110482153" - name: "VL Git Workflows" --- +::: tldr +Git erlaubt unterschiedliche Formen der Zusammenarbeit. + +Bei kleinen Teams kann man einen einfachen zentralen Ansatz einsetzen. Dabei gibt es +ein zentrales Repo auf dem Server, und alle Team-Mitglieder dürfen direkt in dieses +Repo pushen. Hier muss man sich gut absprechen und ein vernünftiges Branching-Schema +ist besonders wichtig. + +In größeren Projekten gibt es oft ein zentrales öffentliches Repo, wo aber nur +wenige Personen Schreibrechte haben. Hier forkt man sich dieses Repo, erstellt also +eine öffentliche Kopie auf dem Server. Diese Kopie klont man lokal und arbeitet hier +und pusht die Änderungen in den eigenen öffentlich sichtbaren Fork. Um die +Änderungen ins Projekt-Repo integrieren zu lassen, wird auf dem Server ein +sogenannter Merge-Request (Gitlab) bzw. Pull-Request (GitHub) erstellt. Dies erlaubt +zusätzlich ein Review und eine Diskussion direkt am Code. Damit man die Änderungen +im Hauptprojekt in den eigenen Fork bekommt, trägt man das Hauptprojekt als weiteres +Remote in die Workingcopy ein und aktualisiert regelmäßig die Hauptbranches, von +denen dann auch die eigenen Feature-Branches ausgehen sollten. +::: + +::: youtube +- [VL Git Workflows](https://youtu.be/3xqmNGN39wE) +- [Demo Anlegen eines Forks, Erstellen eines Pull-Requests + (PR)](https://youtu.be/-8NOia7k0WI) +- [Demo Arbeiten mit einem PR, Review eines PR](https://youtu.be/4LaZc080Ajo) +::: # Nutzung von Git in Projekten: Verteiltes Git (und Workflows) ![](images/distributed.png){width="80%" web_width="65%"} - ::: notes Git ermöglicht ein einfaches und schnelles Branchen. Dies kann man mit entsprechenden Branching-Strategien sinnvoll für die SW-Entwicklung einsetzen. -Auf der anderen Seite ermöglicht Git ein sehr einfaches verteiltes Arbeiten. -Auch hier ergeben sich verschiedene Workflows, wie man mit anderen Entwicklern -an einem Projekt arbeiten will/kann. +Auf der anderen Seite ermöglicht Git ein sehr einfaches verteiltes Arbeiten. Auch +hier ergeben sich verschiedene Workflows, wie man mit anderen Entwicklern an einem +Projekt arbeiten will/kann. - -Im Folgenden sollen also die Frage betrachtet werden: **Wie gestalte ich die Zusammenarbeit?** -Antwort: Workflows mit Git ... +Im Folgenden sollen also die Frage betrachtet werden: **Wie gestalte ich die +Zusammenarbeit?** Antwort: Workflows mit Git ... ::: - # Zusammenarbeit: Zentraler Workflow mit Git (analog zu SVN) ![](images/centralised.png){width="80%" web_width="55%"} ::: notes -In kleinen Projektgruppen wie beispielsweise Ihrer Arbeitsgruppe wird häufig -ein einfacher zentralisierter Workflow bei der Versionsverwaltung genutzt. Im +In kleinen Projektgruppen wie beispielsweise Ihrer Arbeitsgruppe wird häufig ein +einfacher zentralisierter Workflow bei der Versionsverwaltung genutzt. Im Mittelpunkt steht dabei ein zentrales Repository, auf dem alle Teammitglieder gleichberechtigt und direkt pushen dürfen. -* Vorteile: - * Einfachstes denkbares Modell - * Ein gemeinsames Repo (wie bei SVN) - * Alle haben Schreibzugriff auf ein gemeinsames Repo +- Vorteile: + - Einfachstes denkbares Modell + - Ein gemeinsames Repo (wie bei SVN) + - Alle haben Schreibzugriff auf ein gemeinsames Repo \smallskip -* Nachteile: - * Definition und Umsetzung von Rollen mit bestimmten Rechten ("Manager", - "Entwickler", "Gast-Entwickler", ...) schwierig bis unmöglich - (das ist kein Git-Thema, sondern hängt von der Unterstützung durch den - Anbieter des Servers ab) - * Jeder darf überall pushen: Enge und direkte Abstimmung nötig - * Modell funktioniert meist nur in sehr kleinen Teams (2..3 Personen) +- Nachteile: + - Definition und Umsetzung von Rollen mit bestimmten Rechten ("Manager", + "Entwickler", "Gast-Entwickler", ...) schwierig bis unmöglich (das ist kein + Git-Thema, sondern hängt von der Unterstützung durch den Anbieter des + Servers ab) + - Jeder darf überall pushen: Enge und direkte Abstimmung nötig + - Modell funktioniert meist nur in sehr kleinen Teams (2..3 Personen) ::: - # Zusammenarbeit: Einfacher verteilter Workflow mit Git ![](images/workflow_remote.png){width="80%" web_width="65%"} - ::: notes In großen und/oder öffentlichen Projekten wird üblicherweise ein Workflow eingesetzt, der auf den Möglichkeiten von verteilten Git-Repositories basiert. @@ -107,34 +86,33 @@ unterschieden. Sie finden dieses Vorgehen beispielsweise beim Linux-Kernel und auch häufig bei Projekten auf Github. -* Es existiert ein geschütztes ("blessed") Master-Repo - * Stellt die Referenz für das Projekt dar - * Push-Zugriff nur für ausgewählte Personen ("Integrationsmanager") +- Es existiert ein geschütztes ("blessed") Master-Repo + - Stellt die Referenz für das Projekt dar + - Push-Zugriff nur für ausgewählte Personen ("Integrationsmanager") \smallskip -* Entwickler - * Forken das Master-Repo auf dem Server und klonen ihren Fork lokal - * Arbeiten auf lokalem Klon: Unabhängige Entwicklung eines Features - * Pushen ihren Stand in ihren Fork (ihr eigenes öffentliches Repo): +- Entwickler + - Forken das Master-Repo auf dem Server und klonen ihren Fork lokal + - Arbeiten auf lokalem Klon: Unabhängige Entwicklung eines Features + - Pushen ihren Stand in ihren Fork (ihr eigenes öffentliches Repo): Veröffentlichung des Beitrags zum Projekt (sobald fertig bzw. diskutierbar) - * Lösen Pull- bzw. Merge-Request gegen das Master-Repo aus: Beitrag - soll geprüft und ins Projekt aufgenommen werden (Merge ins Master-Repo - durch den Integrationsmanager) + - Lösen Pull- bzw. Merge-Request gegen das Master-Repo aus: Beitrag soll + geprüft und ins Projekt aufgenommen werden (Merge ins Master-Repo durch den + Integrationsmanager) \smallskip -* Integrationsmanager - * Prüft die Änderungen im Pull- bzw. Merge-Request und fordert ggf. +- Integrationsmanager + - Prüft die Änderungen im Pull- bzw. Merge-Request und fordert ggf. Nacharbeiten an bzw. lehnt Integration ab (technische oder politische Gründe) - * Führt Merge der Entwickler-Zweige mit den Hauptzweigen durch Akzeptieren - der Pull- bzw. Merge-Requests durch: Beitrag der Entwickler ist im - Projekt angekommen und ist beim nächsten Pull in deren lokalen Repos - vorhanden + - Führt Merge der Entwickler-Zweige mit den Hauptzweigen durch Akzeptieren der + Pull- bzw. Merge-Requests durch: Beitrag der Entwickler ist im Projekt + angekommen und ist beim nächsten Pull in deren lokalen Repos vorhanden -Den hier gezeigten Zusammenhang kann man auf weitere Ebenen verteilen, vgl. den -im Linux-Kernel gelebten "Dictator and Lieutenants Workflow" (siehe Literatur). +Den hier gezeigten Zusammenhang kann man auf weitere Ebenen verteilen, vgl. den im +Linux-Kernel gelebten "Dictator and Lieutenants Workflow" (siehe Literatur). *Hinweis*: Hier wird nur die Zusammenarbeit im verteilten Team dargestellt. Dazu kommt noch das Arbeiten mit verschiedenen Branches! @@ -143,45 +121,41 @@ kommt noch das Arbeiten mit verschiedenen Branches! und das geschützte ("blessed") Master-Repo als `upstream` referenziert. ::: - -::::::::: notes +::: notes ## Anmerkungen zum Forken -Sie könnten auch das Original-Repo direkt clonen. Allerdings würden dann die -`push` dort aufschlagen, was in der Regel nicht erwünscht ist (und auch nicht -erlaubt ist). +Sie könnten auch das Original-Repo direkt clonen. Allerdings würden dann die `push` +dort aufschlagen, was in der Regel nicht erwünscht ist (und auch nicht erlaubt ist). -Deshalb forkt man das Original-Repo auf dem Server, d.h. auf dem Server wird -eine Kopie des Original-Repos im eigenen Namespace angelegt. Auf diese Kopie -hat man dann uneingeschränkten Zugriff. - -Zusätzliche kurze Video-Anleitungen von GitHub: [Working with forks](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks) +Deshalb forkt man das Original-Repo auf dem Server, d.h. auf dem Server wird eine +Kopie des Original-Repos im eigenen Namespace angelegt. Auf diese Kopie hat man dann +uneingeschränkten Zugriff. +Zusätzliche kurze Video-Anleitungen von GitHub: [Working with +forks](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks) ## Anmerkungen zu den Namen für die Remotes: `origin` und `upstream` -Üblicherweise checkt man die *Kopie* lokal aus (d.h. erzeugt einen Clone). -In der Workingcopy verweist dann `origin` auf die Kopie. Um Änderungen am -Original-Repo zu erhalten, fügt man dieses unter dem Namen `upstream` als -weiteres Remote-Repo hinzu. Dies ist eine nützliche *Konvention*. - -Um Änderungen aus dem Original-Repo in den eigenen Fork (und die Workingcopy) -zu bringen, führt man dann einfach folgendes aus (im Beispiel für den `master`): +Üblicherweise checkt man die *Kopie* lokal aus (d.h. erzeugt einen Clone). In der +Workingcopy verweist dann `origin` auf die Kopie. Um Änderungen am Original-Repo zu +erhalten, fügt man dieses unter dem Namen `upstream` als weiteres Remote-Repo hinzu. +Dies ist eine nützliche *Konvention*. +Um Änderungen aus dem Original-Repo in den eigenen Fork (und die Workingcopy) zu +bringen, führt man dann einfach folgendes aus (im Beispiel für den `master`): $ git checkout master # Workingcopy auf master $ git pull upstream master # Aktualisiere lokalen master mit master aus Original-Repo $ git push origin master # Pushe lokalen master in den Fork -::::::::: +::: [[Beispiel: Forken mit Gitlab, Namen für Remotes]{.ex}]{.slides} - -# Feature-Branches aktualisieren: Mergen mit _master_ vs. Rebase auf _master_ +# Feature-Branches aktualisieren: Mergen mit *master* vs. Rebase auf *master* ::: notes -Im Netz finden sich häufig Anleitungen, wonach man Änderungen im `master` -mit einem Merge in den Feature-Branch holt, also sinngemäß: +Im Netz finden sich häufig Anleitungen, wonach man Änderungen im `master` mit einem +Merge in den Feature-Branch holt, also sinngemäß: ::: $ git checkout master # Workingcopy auf master @@ -190,38 +164,38 @@ mit einem Merge in den Feature-Branch holt, also sinngemäß: $ git merge master # Aktualisiere feature: Merge master in feature $ git push origin feature # Push aktuellen feature ins Team-Repo -::::::::: notes +::: notes Das funktioniert rein technisch betrachtet. -Allerdings spielt in den meisten Git-Projekten der `master` (bzw. `main`) üblicherweise eine -besondere Rolle (vgl. [Branching-Strategien](branching-strategies.md)) und ist üblicherweise -stets das **Ziel** eines Merge, aber nie die *Quelle*! D.h. per Konvention geht der Fluß von +Allerdings spielt in den meisten Git-Projekten der `master` (bzw. `main`) +üblicherweise eine besondere Rolle (vgl. +[Branching-Strategien](branching-strategies.md)) und ist üblicherweise stets das +**Ziel** eines Merge, aber nie die *Quelle*! D.h. per Konvention geht der Fluß von Änderungen stets **in** den `master` (und nicht heraus). -Wenn man sich nicht an diese Konvention hält, hat man später möglicherweise Probleme, die -Merge-Historie zu verstehen (welche Änderung kam von woher)! +Wenn man sich nicht an diese Konvention hält, hat man später möglicherweise +Probleme, die Merge-Historie zu verstehen (welche Änderung kam von woher)! -Um die Änderungen im `master` in einen Feature-Branch zu bekommen, sollte deshalb ein **Rebase** -des Feature-Branches auf den aktuellen `master` bevorzugt werden. +Um die Änderungen im `master` in einen Feature-Branch zu bekommen, sollte deshalb +ein **Rebase** des Feature-Branches auf den aktuellen `master` bevorzugt werden. **Merk-Regel**: Merge niemals nie den `master` in Feature-Branches! -**Achtung**: Ein Rebase bei veröffentlichten Branches ist problematisch, sobald Dritte auf -diesem Branch arbeiten oder den Branch als Basis für ihre eigenen Arbeiten nutzen und dadurch -entsprechend auf die Commit-IDs angewiesen sind. Nach einem Rebase stimmen diese Commit-IDs -nicht mehr, was normalerweise mindestens zu Verärgerung führt ... -Die Dritten müssten ihre Arbeit dann auf den neuen Feature-Branch (d.h. den Feature-Branch -nach dessen Rebase) rebasen ... vgl. auch "The Perils of Rebasing" in Abschnitt "3.6 Rebasing" -in [@Chacon2014]. - +**Achtung**: Ein Rebase bei veröffentlichten Branches ist problematisch, sobald +Dritte auf diesem Branch arbeiten oder den Branch als Basis für ihre eigenen +Arbeiten nutzen und dadurch entsprechend auf die Commit-IDs angewiesen sind. Nach +einem Rebase stimmen diese Commit-IDs nicht mehr, was normalerweise mindestens zu +Verärgerung führt ... Die Dritten müssten ihre Arbeit dann auf den neuen +Feature-Branch (d.h. den Feature-Branch nach dessen Rebase) rebasen ... vgl. auch +"The Perils of Rebasing" in Abschnitt "3.6 Rebasing" in [@Chacon2014]. ## Mögliches Szenario im Praktikum -Im Praktikum haben Sie das Vorgabe-Repo. Dieses könnten Sie als `upstream` in Ihre lokale -Workingcopy einbinden. +Im Praktikum haben Sie das Vorgabe-Repo. Dieses könnten Sie als `upstream` in Ihre +lokale Workingcopy einbinden. -Mit Ihrem Team leben Sie vermutlich einen zentralen Workflow, d.h. Sie binden Ihr gemeinsames -Repo als `origin` in Ihre lokale Workingcopy ein. +Mit Ihrem Team leben Sie vermutlich einen zentralen Workflow, d.h. Sie binden Ihr +gemeinsames Repo als `origin` in Ihre lokale Workingcopy ein. Dann müssen Sie den lokalen `master` aus *beiden* Remotes aktualisieren. Zusätzlich wollen Sie Ihren aktuellen Themenbranch auf den aktuellen `master` rebasen. @@ -234,48 +208,45 @@ wollen Sie Ihren aktuellen Themenbranch auf den aktuellen `master` rebasen. $ git push -f origin feature # Push aktuellen feature ins Team-Repo ("-f" wg. geänderter IDs durch rebase) **Anmerkung**: Dabei können in Ihrem `master` die unschönen "Rauten" entstehen. Wenn -Sie das vermeiden wollen, tauschen Sie den zweiten und den dritten Schritt und führen -den Pull gegen den Upstream-`master` als `pull --rebase` durch. Dann müssen Sie Ihren -lokalen `master` allerdings auch force-pushen in Ihr Team-Repo und die anderen -Team-Mitglieder sollten darüber informiert werden, dass sich der `master` auf -inkompatible Weise geändert hat ... -::::::::: +Sie das vermeiden wollen, tauschen Sie den zweiten und den dritten Schritt und +führen den Pull gegen den Upstream-`master` als `pull --rebase` durch. Dann müssen +Sie Ihren lokalen `master` allerdings auch force-pushen in Ihr Team-Repo und die +anderen Team-Mitglieder sollten darüber informiert werden, dass sich der `master` +auf inkompatible Weise geändert hat ... +::: [[Besser einen Pull/Rebase für den Feature-Branch machen!]{.ex}]{.slides} - # Kommunikation: Merge- bzw. Pull-Requests ::: notes -Mergen kann man auf der Konsole (oder in der IDE) und anschließend die (neuen) Branches -auf den Server pushen. +Mergen kann man auf der Konsole (oder in der IDE) und anschließend die (neuen) +Branches auf den Server pushen. -Die verschiedenen Git-Server erlauben ebenfalls ein GUI-gestütztes Mergen von Branches: -"Merge-Requests" (*MR*, Gitlab) bzw. "Pull-Requests" (*PR*, Github). Das hat gegenüber -dem lokalen Mergen wichtige Vorteile: Andere Entwickler sehen den beabsichtigten Merge -(frühzeitig) und können direkt den Code kommentieren und die vorgeschlagenen Änderungen -diskutieren, aber auch allgemeine Kommentare abgeben. +Die verschiedenen Git-Server erlauben ebenfalls ein GUI-gestütztes Mergen von +Branches: "Merge-Requests" (*MR*, Gitlab) bzw. "Pull-Requests" (*PR*, Github). Das +hat gegenüber dem lokalen Mergen wichtige Vorteile: Andere Entwickler sehen den +beabsichtigten Merge (frühzeitig) und können direkt den Code kommentieren und die +vorgeschlagenen Änderungen diskutieren, aber auch allgemeine Kommentare abgeben. Falls möglich, sollte man einen MR/PR immer dem Entwickler zuweisen, der sich weiter um diesen MR/PR kümmern wird (also zunächst ist man das erstmal selbst). Zusätzlich kann man einen Reviewer bestimmen, d.h. wer soll sich den Code ansehen. - Hier ein Screenshot der Änderungsansicht unseres Gitlab-Servers (SW-Labor): ::: ![](images/screenshot_merge-request_code.png){width="80%"} -[[Diskussion Merge vs. MR, Live-Demo Gitlab]{.ex}]{.slides} - +[[Diskussion Merge vs. MR, Live-Demo Gitlab]{.ex}]{.slides} ::: notes -Nachfolgend für den selben MR aus der letzten Abbildung noch die reine Diskussionsansicht: +Nachfolgend für den selben MR aus der letzten Abbildung noch die reine +Diskussionsansicht: ![](images/screenshot_merge-request_discussion.png){width="80%"} ::: - ::: notes Zusätzliche kurze Video-Anleitungen von GitHub: @@ -283,58 +254,82 @@ Zusätzliche kurze Video-Anleitungen von GitHub: - [How to merge a pull request](https://www.youtube.com/watch?v=FDXSgyDGmho) ::: - -::::::::: notes +::: notes # Best Practices bei Merge-/Pull-Requests 1. MR/PR so zeitig wie möglich aufmachen - * Am besten sofort, wenn ein neuer Branch auf den Server gepusht wird! - * Ggf. mit dem Präfix "WIP" im Titel gegen unbeabsichtigtes vorzeitiges Mergen + - Am besten sofort, wenn ein neuer Branch auf den Server gepusht wird! + - Ggf. mit dem Präfix "WIP" im Titel gegen unbeabsichtigtes vorzeitiges Mergen sperren ... (bei GitHub als "Draft"-PR öffnen) 2. Auswahl Start- und Ziel-Branch (und ggf. Ziel-Repo) - * Es gibt verschiedene Stellen, um einen MR/PR zu erstellen. Manchmal kann man nur - noch den Ziel-Branch einstellen, manchmal beides. - * Bitte auch darauf achten, welches Ziel-Repo eingestellt ist! Bei Forks wird + - Es gibt verschiedene Stellen, um einen MR/PR zu erstellen. Manchmal kann man + nur noch den Ziel-Branch einstellen, manchmal beides. + - Bitte auch darauf achten, welches Ziel-Repo eingestellt ist! Bei Forks wird hier immer das Original-Repo voreingestellt! - * Den Ziel-Branch kann man ggf. auch nachträglich durch Editieren des MR/PR - anpassen (Start-Branch und Ziel-Repo leider nicht, also beim Erstellen aufpassen!). + - Den Ziel-Branch kann man ggf. auch nachträglich durch Editieren des MR/PR + anpassen (Start-Branch und Ziel-Repo leider nicht, also beim Erstellen + aufpassen!). 3. Titel (*Summary*): Das ist das, was man in der Übersicht sieht! - * Per Default wird die letzte Commit-Message eingesetzt. - * Analog zur Commit-Message: Bitte hier unbedingt einen sinnvollen Titel + - Per Default wird die letzte Commit-Message eingesetzt. + - Analog zur Commit-Message: Bitte hier unbedingt einen sinnvollen Titel einsetzen: Was macht der MR/PR (kurz)? 4. Beschreibung: Was passiert, wenn man diesen MR/PR akzeptiert (ausführlicher)? - * Analog zur Commit-Message sollte hier bei Bedarf die Summary ausformuliert + - Analog zur Commit-Message sollte hier bei Bedarf die Summary ausformuliert werden und beschreiben, was der MR/PR ändert. 5. Assignee: Wer soll sich drum kümmern? - * Ein MR/PR sollte immer jemanden zugewiesen sein, d.h. nicht "unassigned" + - Ein MR/PR sollte immer jemanden zugewiesen sein, d.h. nicht "unassigned" sein. Ansonsten ist nicht klar, wer den Request durchführen/akzeptieren soll. - * Außerdem taucht ein nicht zugewiesener MR/PR nicht in der Übersicht "meiner" + - Außerdem taucht ein nicht zugewiesener MR/PR nicht in der Übersicht "meiner" MR/PR auf, d.h. diese MR/PR haben die Tendenz, vergessen zu werden! 6. Diskussion am (und neben) dem Code - * Nur die vorgeschlagenen Code-Änderungen diskutieren! - * Weitergehende Diskussionen (etwa über Konzepte o.ä.) besser in separaten Issues - erledigen, da sonst die Anzeige des MR/PR langsam wird (ist beispielsweise ein - Problem bei Gitlab). + - Nur die vorgeschlagenen Code-Änderungen diskutieren! + - Weitergehende Diskussionen (etwa über Konzepte o.ä.) besser in separaten + Issues erledigen, da sonst die Anzeige des MR/PR langsam wird (ist + beispielsweise ein Problem bei Gitlab). 7. Weitere Commits auf dem zu mergenden Branch gehen automatisch mit in den Request -8. Weitere Entwickler kann man mit "`@username`" in einem Kommentar auf "CC" - setzen und in die Diskussion einbinden +8. Weitere Entwickler kann man mit "`@username`" in einem Kommentar auf "CC" setzen + und in die Diskussion einbinden *Anmerkung*: Bei Gitlab (d.h. auch bei dem Gitlab-Server im SW-Labor) gibt es "*Merge-Requests*" (MR). Bei Github gibt es "*Pull-Requests*" (PR) ... -::::::::: - +::: # Wrap-Up -* Git-Workflows für die Zusammenarbeit: - * einfacher zentraler Ansatz für kleine Arbeitsgruppen vs. - * einfacher verteilter Ansatz mit einem "blessed" Repo [(häufig in Open-Source-Projekten zu finden)]{.notes} +- Git-Workflows für die Zusammenarbeit: + - einfacher zentraler Ansatz für kleine Arbeitsgruppen vs. + - einfacher verteilter Ansatz mit einem "blessed" Repo [(häufig in + Open-Source-Projekten zu finden)]{.notes} \smallskip -* Aktualisieren Ihres Clones und Ihres Forks mit Änderungen aus dem "blessed" Repo -* Unterschied zwischen einem Pull/Merge und einem Pull/Rebase -* Erstellen von Beiträgen zu einem Projekt über Merge-Requests - * Welche Commits werden Bestandteil eines Merge-Requests (und warum) - * Diskussion über den Code in Merge-Requests +- Aktualisieren Ihres Clones und Ihres Forks mit Änderungen aus dem "blessed" Repo +- Unterschied zwischen einem Pull/Merge und einem Pull/Rebase +- Erstellen von Beiträgen zu einem Projekt über Merge-Requests + - Welche Commits werden Bestandteil eines Merge-Requests (und warum) + - Diskussion über den Code in Merge-Requests + +::: readings +- @Chacon2014 [Kap. 5, 4.8, 6] +- @AtlassianGit +- @GitCheatSheet +::: + +::: outcomes +- k2: Ich kenne verschiedene Git-Workflows für die Zusammenarbeit und kann den + Ablauf erklären +- k2: Ich kann den Unterschied zwischen einem Pull/Merge und einem Pull/Rebase + erklären +- k2: Ich verstehe, welche Commits zu einem Bestandteil eines Merge-/Pull-Requests + werden (und warum) +- k3: Ich kann den zentralisierten Workflow einsetzen +- k3: Ich kann den einfachen verteilten Workflow mit unterschiedlichen Repos + einsetzen +- k3: Ich kann meinen Clone und meinen Fork bei/mit Änderungen im/aus dem 'blessed + Repo' aktualisieren +- k3: Ich kann meine Beiträge zu einem Projekt als Merge-/Pull-Requests einreichen +- k3: Ich kann meine Merge-/Pull-Requests aktualisieren +- k3: Ich kann in Merge-/Pull-Requests Anmerkungen am Code hinterlegen und an den + Feedback-Diskussionen teilnehmen +::: diff --git a/lecture/git/worktree.md b/lecture/git/worktree.md index 986be75ab..634145de3 100644 --- a/lecture/git/worktree.md +++ b/lecture/git/worktree.md @@ -1,45 +1,38 @@ --- -title: "Git Worktree" -author: "Carsten Gips (HSBI)" -tldr: | - Git Worktree erlaubt es, Branches in separaten Ordnern auszuchecken. Diese Ordner sind - mit der Workingcopy verknüpft, d.h. alle Änderungen über Git-Befehle werden automatisch - mit der Workingcopy "synchronisiert". Im Unterschied zum erneuten Clonen hat man in den - verknüpften Ordnern aber nicht die gesamte Historie noch einmal neu als `.git`-Ordner, - sondern nur den Link auf die Workingcopy, wodurch viel Platz gespart wird. Damit bilden - Git Worktrees eine elegante Möglichkeit, parallel an verschiedenen Branches zu arbeiten. -outcomes: - - k2: "Vorteile von Git Worktree" - - k2: "Prinzipielle Arbeitsweise von Git Worktree" - - k3: "Anlegen von Worktrees" - - k3: "Anzeigen von Worktrees" - - k3: "Löschen von Worktrees" -youtube: - - link: "https://youtu.be/nDkg6WvA0bk" - name: "VL Git Worktree" - - link: "https://youtu.be/RtXrv0oK3-w" - name: "Demo Git Worktree" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/b76b9f68f573856669e52ebe4cf4cf8803afb356c6c5d448bb46ab3582457bfb6c111310ce0359bb0a3348f5ab93d7ea58ac73e4fdfdfecd691590640f184af7" - name: "VL Git Worktree" +author: Carsten Gips (HSBI) +title: Git Worktree --- +::: tldr +Git Worktree erlaubt es, Branches in separaten Ordnern auszuchecken. Diese Ordner +sind mit der Workingcopy verknüpft, d.h. alle Änderungen über Git-Befehle werden +automatisch mit der Workingcopy "synchronisiert". Im Unterschied zum erneuten Clonen +hat man in den verknüpften Ordnern aber nicht die gesamte Historie noch einmal neu +als `.git`-Ordner, sondern nur den Link auf die Workingcopy, wodurch viel Platz +gespart wird. Damit bilden Git Worktrees eine elegante Möglichkeit, parallel an +verschiedenen Branches zu arbeiten. +::: + +::: youtube +- [VL Git Worktree](https://youtu.be/nDkg6WvA0bk) +- [Demo Git Worktree](https://youtu.be/RtXrv0oK3-w) +::: # Git Worktree - Mehrere Branches parallel auschecken ## Szenario -* Sie arbeiten an einem Projekt -* Großes Repo mit vielen Versionen und Branches -* Ungesicherte Änderungen im Featurebranch -* Wichtige Bugfixes an alter Version nötig +- Sie arbeiten an einem Projekt +- Großes Repo mit vielen Versionen und Branches +- Ungesicherte Änderungen im Featurebranch +- Wichtige Bugfixes an alter Version nötig \bigskip ## Lösungsansätze -* `git stash` nutzen und Branch wechseln -* Repo erneut in anderem Ordner auschecken +- `git stash` nutzen und Branch wechseln +- Repo erneut in anderem Ordner auschecken \bigskip @@ -56,10 +49,10 @@ fhmedia: unübersichtlich werden - zu welchem Branch gehören welche Einträge in der Stash-Liste? - Außerdem kann es gerade in größeren Projekten passieren, dass sich die Konfiguration - zwischenzeitlich ändert. Wenn Sie jetzt in der IDE einfach auf einen alten Stand - mit einer anderen Konfiguration wechseln, kann es schnell passieren, dass sich die - IDE "verschluckt" und Sie dadurch viel Arbeit haben. + Außerdem kann es gerade in größeren Projekten passieren, dass sich die + Konfiguration zwischenzeitlich ändert. Wenn Sie jetzt in der IDE einfach auf + einen alten Stand mit einer anderen Konfiguration wechseln, kann es schnell + passieren, dass sich die IDE "verschluckt" und Sie dadurch viel Arbeit haben. 2. Nochmal woanders auschecken @@ -70,23 +63,21 @@ fhmedia: ursprüngliche Workingcopy! Das kann bei alten/großen Projekten schnell recht groß werden und Probleme verursachen. - Außerdem ist die Synchronisierung zwischen den beiden Workingcopies (der ursprünglichen - und der neuen) nicht vorhanden bzw. das müssen Sie manuell per `git push` und `git pull` - (in jeder Kopie des Repos!) erledigen! + Außerdem ist die Synchronisierung zwischen den beiden Workingcopies (der + ursprünglichen und der neuen) nicht vorhanden bzw. das müssen Sie manuell per + `git push` und `git pull` (in jeder Kopie des Repos!) erledigen! ::: \bigskip ## Git Worktree kann helfen! -**=> Mehrere Branches gleichzeitig auschecken (als neue Ordner im Dateisystem)** - +**=\> Mehrere Branches gleichzeitig auschecken (als neue Ordner im Dateisystem)** # How to use Git Worktree ![](images/linkedworktrees.png){width="80%"} - # Worktree anlegen ::: center @@ -102,12 +93,13 @@ Mit `git worktree add ../wuppie foo` würden Sie also parallel zum aktuellen Ord (wo Ihre Workingcopy enthalten ist) einen neuen Ordner `wuppie/` anlegen und darin den Branch `foo` auschecken. -Wenn Sie in den Ordner `wuppie` wechseln, finden Sie auch eine _Datei_ `.git`. Darin +Wenn Sie in den Ordner `wuppie` wechseln, finden Sie auch eine *Datei* `.git`. Darin ist lediglich der Pfad zur Workingcopy vermerkt, damit Git Änderungen auch in die eigentliche Workingcopy spiegeln kann. Dies ist der sogenannte "linked worktree". -Im Vergleich dazu finden Sie in der eigentlichen Workingcopy einen _Ordner_ `.git`, -der üblicherweise die gesamte Historie etc. enthält und entsprechend groß werden kann. +Im Vergleich dazu finden Sie in der eigentlichen Workingcopy einen *Ordner* `.git`, +der üblicherweise die gesamte Historie etc. enthält und entsprechend groß werden +kann. Den Befehl `git worktree add` gibt es in verschiedenen Versionen. In der Kurzform `git worktree add ` würde ein neuer Branch angelegt und ausgecheckt, der der @@ -128,31 +120,29 @@ selben Ordner oder in einem Unterordner anlegen. werden (unstabiles Verhalten)! ::: - # Worktree wechseln -* Worktrees anzeigen: `git worktree list` -* Worktree wechseln: Ordner wechseln (IDE: neues Projekt) +- Worktrees anzeigen: `git worktree list` +- Worktree wechseln: Ordner wechseln (IDE: neues Projekt) ::: notes -Die Worktrees sind aus Sicht des Dateisystems einfach Ordner. Die `.git`-Datei verlinkt -für Git den Ordner mit der ursprünglichen Workingcopy. +Die Worktrees sind aus Sicht des Dateisystems einfach Ordner. Die `.git`-Datei +verlinkt für Git den Ordner mit der ursprünglichen Workingcopy. -Um also mit einem Worktree arbeiten zu können, wechseln Sie einfach das Verzeichnis. In -einer IDE würden Sie entsprechend ein neues Projekt anlegen. So können Sie gleichzeitig -in verschiedenen Branches arbeiten. +Um also mit einem Worktree arbeiten zu können, wechseln Sie einfach das Verzeichnis. +In einer IDE würden Sie entsprechend ein neues Projekt anlegen. So können Sie +gleichzeitig in verschiedenen Branches arbeiten. -Änderungen in einem Worktree werden automatisch in die ursprüngliche Workingcopy gespiegelt. -Analog können Sie in einem Worktree auf die aktuelle Historie aus der ursprünglichen Workingcopy -zugreifen. +Änderungen in einem Worktree werden automatisch in die ursprüngliche Workingcopy +gespiegelt. Analog können Sie in einem Worktree auf die aktuelle Historie aus der +ursprünglichen Workingcopy zugreifen. -_Hinweis_: Sie können in den Ordnern zwar Branches wechseln, aber nicht auf einen Branch, -der bereits in einem anderen Ordner (Worktree) ausgecheckt ist. Es ist gute Praxis, dass -die Ordnernamen dem ausgecheckten Branch (linked Worktree) entsprechen, um Verwirrungen -zu vermeiden. +*Hinweis*: Sie können in den Ordnern zwar Branches wechseln, aber nicht auf einen +Branch, der bereits in einem anderen Ordner (Worktree) ausgecheckt ist. Es ist gute +Praxis, dass die Ordnernamen dem ausgecheckten Branch (linked Worktree) entsprechen, +um Verwirrungen zu vermeiden. ::: - # Worktree löschen ::: center @@ -160,21 +150,30 @@ zu vermeiden. ::: ::: notes -Sofern der Worktree "clean" ist, es also keine nicht comitteten Änderungen gibt, können -Sie mit `git worktree remove ` einen Worktree `` wieder löschen. +Sofern der Worktree "clean" ist, es also keine nicht comitteten Änderungen gibt, +können Sie mit `git worktree remove ` einen Worktree `` wieder +löschen. -Dabei bleibt der Ordner erhalten - Sie können ihn selbst löschen oder später wiederverwenden. +Dabei bleibt der Ordner erhalten - Sie können ihn selbst löschen oder später +wiederverwenden. ::: - # Wrap-Up Git Worktree: Auschecken von Branches in separate Ordner -* Anlegen: `git worktree add ` -* Anschauen: `git worktree list` -* Löschen: `git worktree remove ` +- Anlegen: `git worktree add ` +- Anschauen: `git worktree list` +- Löschen: `git worktree remove ` \bigskip -* Dokumentation: https://git-scm.com/docs/git-worktree +- Dokumentation: https://git-scm.com/docs/git-worktree + +::: outcomes +- k2: Vorteile von Git Worktree +- k2: Prinzipielle Arbeitsweise von Git Worktree +- k3: Anlegen von Worktrees +- k3: Anzeigen von Worktrees +- k3: Löschen von Worktrees +::: diff --git a/lecture/gui/events.md b/lecture/gui/events.md index c0eb31dd7..ef475c2b6 100644 --- a/lecture/gui/events.md +++ b/lecture/gui/events.md @@ -1,57 +1,45 @@ --- -title: "Swing Events" -author: "Carsten Gips (HSBI)" -readings: - - "@Java-SE-Tutorial" - - "@Ullenboom2021 [Kap. 18]" -tldr: | - In Swing-Komponenten werden Events ausgelöst, wenn der User mit den Komponenten interagiert. - - Zur Bearbeitung der Events kann man Listener bei den Komponenten registrieren, die bei Auftreten - eines Events benachrichtigt werden (Observer-Pattern: Die Observer werden in Swing "Listener" - genannt). - - Es gibt für alle möglichen Formen von Interaktion mit Komponenten vordefinierte Interfaces für - die Event-Listener. Da man hier wie üblich immer alle Methoden implementieren muss, selbst wenn - man nur auf wenige Events reagieren möchte, gibt es zusätzlich sogenannte "Adapter": Dies sind - Klassen, die das jeweilige Event-Listener-Interface mit leeren Methodenrümpfen implementieren. - Bei Nutzung der Adapter-Klassen müssen dann nur noch die benötigten Methoden überschrieben - werden. -outcomes: - - k2: "Unterschied zwischen den Listenern und den entsprechenden Adaptern" - - k3: "Anwendung des Observer-Pattern, beispielsweise als Listener in Swing, aber auch in eigenen Programmen" - - k3: "Nutzung von ActionListener, MouseListener, KeyListener, FocusListener" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106248&client_id=FH-Bielefeld" -# name: "Quiz Swing (ILIAS)" -youtube: - - link: "https://youtu.be/Un-FS88__VU" - name: "VL Swing Events" - - link: "https://youtu.be/hjchoDaqcWY" - name: "Demo Swing Events und Listener" - - link: "https://youtu.be/GaKMBAXY19w" - name: "Demo MouseListener vs. MouseAdapter" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/75585089cf1cf8f79e8edd8639cdbeef9ebb7f4bc08344ad482be8e76e342588969e4d0e65c0c3196cee6b11a060b6271ec7b22a997f14e967bf075232eab1a7" - name: "VL Swing Events" +author: Carsten Gips (HSBI) +title: Swing Events --- +::: tldr +In Swing-Komponenten werden Events ausgelöst, wenn der User mit den Komponenten +interagiert. + +Zur Bearbeitung der Events kann man Listener bei den Komponenten registrieren, die +bei Auftreten eines Events benachrichtigt werden (Observer-Pattern: Die Observer +werden in Swing "Listener" genannt). + +Es gibt für alle möglichen Formen von Interaktion mit Komponenten vordefinierte +Interfaces für die Event-Listener. Da man hier wie üblich immer alle Methoden +implementieren muss, selbst wenn man nur auf wenige Events reagieren möchte, gibt es +zusätzlich sogenannte "Adapter": Dies sind Klassen, die das jeweilige +Event-Listener-Interface mit leeren Methodenrümpfen implementieren. Bei Nutzung der +Adapter-Klassen müssen dann nur noch die benötigten Methoden überschrieben werden. +::: + +::: youtube +- [VL Swing Events](https://youtu.be/Un-FS88__VU) +- [Demo Swing Events und Listener](https://youtu.be/hjchoDaqcWY) +- [Demo MouseListener vs. MouseAdapter](https://youtu.be/GaKMBAXY19w) +::: # Reaktion auf Events: Anwendung Observer-Pattern ::: notes -* Swing-GUI läuft in Dauerschleife -* Komponenten registrieren Ereignisse (Events): - * Mausklick - * Tastatureingaben - * Mauszeiger über Komponente - * ... -* Reaktion mit passendem Listener: Observer Pattern! +- Swing-GUI läuft in Dauerschleife +- Komponenten registrieren Ereignisse (Events): + - Mausklick + - Tastatureingaben + - Mauszeiger über Komponente + - ... +- Reaktion mit passendem Listener: Observer Pattern! ::: ![](images/ActionListener.png){width="80%"} -=> Observer aus dem Observer-Pattern! +=\> Observer aus dem Observer-Pattern! ::: notes In Swing werden die "Observer" als "Listener" bezeichnet. @@ -59,12 +47,11 @@ In Swing werden die "Observer" als "Listener" bezeichnet. \bigskip -```java +``` java component.addActionListener(ActionListener); component.addMouseListener(MouseListener); ``` - # Arten von Events ![](images/EventListener.png){width="80%"} @@ -75,28 +62,27 @@ beispielsweise für Maus- oder Tastaturereignisse oder wenn ein Element den Foku bekommt und viele weitere. ::: - # Details zu Listenern -* Ein Listener kann bei mehreren Observables registriert sein: +- Ein Listener kann bei mehreren Observables registriert sein: - ```java + ``` java Handler single = new Handler(); singleButton.addActionListener(single); multiButton.addActionListener(single); ``` -* Ein Observable kann mehrere Listener bedienen: +- Ein Observable kann mehrere Listener bedienen: - ```java + ``` java multiButton.addActionListener(new Handler()); multiButton.addActionListener(new Handler()); ``` -* Sequentielles Abarbeiten der Events bzw. Benachrichtigung der Observer - -[Demo: events.ListenerDemo]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/events/ListenerDemo.java"} +- Sequentielles Abarbeiten der Events bzw. Benachrichtigung der Observer +[Demo: events.ListenerDemo]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/events/ListenerDemo.java"} # Wie komme ich an die Daten eines Events? @@ -104,39 +90,51 @@ bekommt und viele weitere. **Event-Objekte**: Quelle des Events plus aufgetretene Daten -[Demo: events.MouseListenerDemo]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/events/MouseListenerDemo.java"} - +[Demo: events.MouseListenerDemo]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/events/MouseListenerDemo.java"} -# Listener vs. Adapter +# Listener vs. Adapter ::: notes -* Vielzahl möglicher Events -* Jeweils passendes Event-Objekt u. Event-Listener-Interface -* Oft nur wenige Methoden, u.U. aber viele Methoden +- Vielzahl möglicher Events +- Jeweils passendes Event-Objekt u. Event-Listener-Interface +- Oft nur wenige Methoden, u.U. aber viele Methoden ::: -=> Bei Nutzung eines Event-Listeners müssen immer **alle** -Methoden implementiert werden [(auch nicht benötigte)!]{.notes} +=\> Bei Nutzung eines Event-Listeners müssen immer **alle** Methoden implementiert +werden [(auch nicht benötigte)!]{.notes} \bigskip Abhilfe: **Adapter**-Klassen: -* Für viele Event-Listener-Interfaces existieren Adapter-Klassen -* Implementieren jeweils ein Interface -* Alle Methoden mit **leerem** Body vorhanden +- Für viele Event-Listener-Interfaces existieren Adapter-Klassen +- Implementieren jeweils ein Interface +- Alle Methoden mit **leerem** Body vorhanden \smallskip -=> Nur benötigte Listener-Methoden überschreiben. - -[Demo: events.MouseAdapterDemo]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/events/MouseAdapterDemo.java"} +=\> Nur benötigte Listener-Methoden überschreiben. +[Demo: events.MouseAdapterDemo]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/events/MouseAdapterDemo.java"} # Wrap-Up Observer-Pattern in Swing-Komponenten: -* Events: Enthalten Source-Objekt und Informationen -* Event-Listener: Interfaces mit Methoden zur Reaktion -* Adapter: Listener mit leeren Methodenrümpfen +- Events: Enthalten Source-Objekt und Informationen +- Event-Listener: Interfaces mit Methoden zur Reaktion +- Adapter: Listener mit leeren Methodenrümpfen + +::: readings +- @Java-SE-Tutorial +- @Ullenboom2021 [Kap. 18] +::: + +::: outcomes +- k2: Unterschied zwischen den Listenern und den entsprechenden Adaptern +- k3: Anwendung des Observer-Pattern, beispielsweise als Listener in Swing, aber + auch in eigenen Programmen +- k3: Nutzung von ActionListener, MouseListener, KeyListener, FocusListener +::: diff --git a/lecture/gui/java2d.md b/lecture/gui/java2d.md index fd3f7ba13..ddfc48048 100644 --- a/lecture/gui/java2d.md +++ b/lecture/gui/java2d.md @@ -1,90 +1,76 @@ --- -title: "Einführung in Graphics und Java 2D" -author: "Carsten Gips (HSBI)" -readings: - - "@Java-SE-Tutorial" - - "@Ullenboom2021 [Kap. 18]" -tldr: | - Swing-Komponenten zeichnen mit `paintComponent()` auf einem `Graphics`-Objekt. Die Methode - wird von Swing selbst aufgerufen; man kann sie durch den Aufruf von `repaint()` auf einer - Swing-Komponente aber manuell triggern. - - Die Klasse `Graphics` bietet verschiedene einfache Methoden zum Zeichnen von Linien, Rechtecken, - Ovalen und Texten ... Die davon ableitende Klasse `Graphics2D` bietet deutlich mehr Möglichkeiten, - und das Argument beim Aufruf von `paintComponent()` ist zwar formal vom Typ `Graphics`, in der - Praxis aber oft vom Typ `Graphics2D` (Typprüfung und anschließender Cast nötig). - - Das Koordinatensystem in Java2D hat den Ursprung in der linken oberen Ecke. - - Geometrische Primitive und Text werden in der aktuell ausgewählten Zeichenfarbe gerendert. Die - Rechtecke, Ovale und Polygone existieren auch als "gefüllte" Variante. - - Da bei einem Aufruf von `paintComponent()` stets das komplette Objekt neu gezeichnet wird, kann - man dies in einer Game-Loop nutzen: Pro Schritt berechnet man für alle Objekte die neue Position, - lässt ggf. weitere Interaktion o.ä. berechnen und zeichnet anschließend die Objekte über den Aufruf - von `repaint()` neu. In der Game-Loop werden also keine Threads benötigt. -outcomes: - - k2: "Unterschied und Zusammenhang zwischen Swing und AWT" - - k2: "Swing-Komponenten erben `paintComponent(Graphics)`" - - k2: "`paintComponent(Graphics)` wird durch Events oder durch `repaint()` aufgerufen" - - k3: "Auf Graphics-Objekt zeichnen mit geometrischen Primitiven: Nutzung von draw(), fill(), drawString()" - - k3: "Einstellung von Farbe und Font" - - k3: "Erzeugen von Bewegung ohne Nutzung von Threads" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106249&client_id=FH-Bielefeld" -# name: "Quiz Java2D (ILIAS)" -youtube: - - link: "https://youtu.be/LDE_Kbc9w7k" - name: "VL Java2D" - - link: "https://youtu.be/vzBH0MjJ0rM" - name: "Demo geometrische Objekte" - - link: "https://youtu.be/F-6fIGeGAcY" - name: "Demo Fonts" - - link: "https://youtu.be/BLTZ3XhbvkY" - name: "Demo Polygone" - - link: "https://youtu.be/wfVwSyTgm-w" - name: "Demo Bewegung und 2D-Spiel" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/b5f92913ea6f152f6e38b71776fb665ebd0e823d907bb8eb5268964cbe5e1cbdd0c810e99638ef9d9bbe6d011b7cd11aa24f2e150a6d705ccad4d829fe177ae4" - name: "VL Java2D" +author: Carsten Gips (HSBI) +title: Einführung in Graphics und Java 2D --- +::: tldr +Swing-Komponenten zeichnen mit `paintComponent()` auf einem `Graphics`-Objekt. Die +Methode wird von Swing selbst aufgerufen; man kann sie durch den Aufruf von +`repaint()` auf einer Swing-Komponente aber manuell triggern. + +Die Klasse `Graphics` bietet verschiedene einfache Methoden zum Zeichnen von Linien, +Rechtecken, Ovalen und Texten ... Die davon ableitende Klasse `Graphics2D` bietet +deutlich mehr Möglichkeiten, und das Argument beim Aufruf von `paintComponent()` ist +zwar formal vom Typ `Graphics`, in der Praxis aber oft vom Typ `Graphics2D` +(Typprüfung und anschließender Cast nötig). + +Das Koordinatensystem in Java2D hat den Ursprung in der linken oberen Ecke. + +Geometrische Primitive und Text werden in der aktuell ausgewählten Zeichenfarbe +gerendert. Die Rechtecke, Ovale und Polygone existieren auch als "gefüllte" +Variante. + +Da bei einem Aufruf von `paintComponent()` stets das komplette Objekt neu gezeichnet +wird, kann man dies in einer Game-Loop nutzen: Pro Schritt berechnet man für alle +Objekte die neue Position, lässt ggf. weitere Interaktion o.ä. berechnen und +zeichnet anschließend die Objekte über den Aufruf von `repaint()` neu. In der +Game-Loop werden also keine Threads benötigt. +::: + +::: youtube +- [VL Java2D](https://youtu.be/LDE_Kbc9w7k) +- [Demo geometrische Objekte](https://youtu.be/vzBH0MjJ0rM) +- [Demo Fonts](https://youtu.be/F-6fIGeGAcY) +- [Demo Polygone](https://youtu.be/BLTZ3XhbvkY) +- [Demo Bewegung und 2D-Spiel](https://youtu.be/wfVwSyTgm-w) +::: # GUIs mit Java ![](images/java2d.png){width="40%"} -[Demo: java2d.simplegame.J2DTeaser]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/java2d/simplegame/J2DTeaser.java"} - +[Demo: java2d.simplegame.J2DTeaser]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/java2d/simplegame/J2DTeaser.java"} # Einführung in die Java 2D API ::: notes -* Bisher: Anordnung von Widgets als GUI -* Jetzt: Wie kann man mit Java zeichnen etc.? +- Bisher: Anordnung von Widgets als GUI +- Jetzt: Wie kann man mit Java zeichnen etc.? ::: Swing-Komponenten erben von `javax.swing.JComponent`: -```java +``` java public void paintComponent(Graphics g) ``` -* Wird durch Events aufgerufen -* Oder "von Hand" mit `void repaint()` +- Wird durch Events aufgerufen + +- Oder "von Hand" mit `void repaint()` ::: notes - Methode `repaint()` der Swing-Komponente aufrufen => dadurch wird - dann intern die Methode `paintComponent()` der Komponente aufgerufen zum - Neuzeichnen auf dem Graphics-Objekt. + Methode `repaint()` der Swing-Komponente aufrufen =\> dadurch wird dann intern + die Methode `paintComponent()` der Komponente aufgerufen zum Neuzeichnen auf dem + Graphics-Objekt. ::: Objekt vom Typ `Graphics` stellt graphischen Kontext dar ::: notes -* Geom. Primitive zeichnen mit `draw*` und `fill*` -* Rendern mit `drawString` und `drawImage` -* ... +- Geom. Primitive zeichnen mit `draw*` und `fill*` +- Rendern mit `drawString` und `drawImage` +- ... `Graphics2D` beherrscht zusätzliche Methoden zum Beeinflussen des Renderings ::: @@ -92,33 +78,32 @@ Objekt vom Typ `Graphics` stellt graphischen Kontext dar \bigskip ::: center -=> **Methode überschreiben und auf der GUI malen** +=\> **Methode überschreiben und auf der GUI malen** ::: ::: notes -* Basis: `java.awt.Graphics`; davon abgeleitet `java.awt.Graphics2D` -* Methode zum Zeichnen: `paintComponent()` -* Umgang mit Farben: `java.awt.Color` -* Umgang mit Zeichen und Fonts: `java.awt.Font` -* Geom. Primitive: `java.awt.Polygon`, `java.awt.geom.{Line2D, Rectangle2D, Ellipse2D}`, ... +- Basis: `java.awt.Graphics`; davon abgeleitet `java.awt.Graphics2D` +- Methode zum Zeichnen: `paintComponent()` +- Umgang mit Farben: `java.awt.Color` +- Umgang mit Zeichen und Fonts: `java.awt.Font` +- Geom. Primitive: `java.awt.Polygon`, + `java.awt.geom.{Line2D, Rectangle2D, Ellipse2D}`, ... ::: - # Java2D Koordinatensystem ![](images/java2d-koordinaten.png){width="60%"} \bigskip -* Koordinatensystem lokal zum Graphics-Objekt -* Einheiten in Pixel(!) - +- Koordinatensystem lokal zum Graphics-Objekt +- Einheiten in Pixel(!) # Einfache Objekte zeichnen Methoden von `java.awt.Graphics` (Auswahl): -```java +``` java public void drawLine(int x1, int y1, int x2, int y2) public void drawRect(int x, int y, int width, int height) public void fillRect(int x, int y, int width, int height) @@ -130,21 +115,22 @@ public void fillOval(int x, int y, int width, int height) Vorher Strichfarbe setzen: `Graphics.setColor(Color color)`: -* Farb-Konstanten in `java.awt.Color`: `RED`, `GREEN`, `WHITE`, ... -* Ansonsten über Konstruktor, beispielsweise als RGB: +- Farb-Konstanten in `java.awt.Color`: `RED`, `GREEN`, `WHITE`, ... - ```java +- Ansonsten über Konstruktor, beispielsweise als RGB: + + ``` java public Color(int r, int g, int b) // Rot/Grün/Blau, Werte zw. 0 und 255 ``` -[Demo: java2d.SimpleDrawings]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/java2d/SimpleDrawings.java"} - +[Demo: java2d.SimpleDrawings]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/java2d/SimpleDrawings.java"} # Fonts und Strings Fonts über Font-Klasse einstellen: `Graphics.setFont(Font font);` -```java +``` java public Font(String name, int style, int size) ``` @@ -152,29 +138,29 @@ public Font(String name, int style, int size) `Graphics` kann Strings "zeichnen": -```java +``` java public void drawString(String str, int x, int y); ``` Vorher Font und Farbe setzen! -[Demo: java2d.SimpleFonts]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/java2d/SimpleFonts.java"} - +[Demo: java2d.SimpleFonts]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/java2d/SimpleFonts.java"} # Einfache Polygone definieren Polygone zeichnen: `Graphics.drawPolygon(Polygon p)`: -```java +``` java public Polygon() public Polygon(int[] xPoints, int[] yPoints, int points) public void addPoint(int x, int y) ``` ::: notes -=> weitere Methoden von `Graphics` (Auswahl): +=\> weitere Methoden von `Graphics` (Auswahl): -```java +``` java public void drawPolyline(int[] xp, int[] yp, int np) public void drawPolygon(int[] xp, int[] yp, int np) @@ -187,7 +173,7 @@ public void drawPolygon(Polygon p) Polygone mit Farbe füllen: `Graphics.fillPolygon(Polygon p)` ::: notes -```java +``` java public void fillPolygon(int[] xp, int[] yp, int np) public void fillPolygon(Polygon p) ``` @@ -197,45 +183,44 @@ Statt `drawPolygon()` .... Vorher Farbe setzen! -[Demo: java2d.SimplePoly]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/java2d/SimplePoly.java"} - +[Demo: java2d.SimplePoly]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/java2d/SimplePoly.java"} # Ausblick I: Umgang mit Bildern -```java +``` java BufferedImage img = ImageIO.read(new File("DukeWave.gif")); boolean Graphics.drawImage(Image img, int x, int y, ImageObserver observer); ``` +# Ausblick II: *Graphics2D* kann noch mehr ... -# Ausblick II: _Graphics2D_ kann noch mehr ... - -```java +``` java Graphics g; Graphics2D g2 = (Graphics2D) g; ``` \smallskip -=> `Line2D`, `Rectangle2D`, ... +=\> `Line2D`, `Rectangle2D`, ... \smallskip -* Strichstärken, Strichmuster -* Clippings -* Transformationen: rotieren, ... -* Zeichnen in Bildern, Rendern von Ausschnitten -* ... - +- Strichstärken, Strichmuster +- Clippings +- Transformationen: rotieren, ... +- Zeichnen in Bildern, Rendern von Ausschnitten +- ... # Spiele mit Bewegung Beobachtung: `paintComponent()` schreibt `Graphics`-Objekt komplett neu! ::: notes -* Kein Löschen von Objekten nötig -* Es müssen alle im nächsten Schritt sichtbaren Objekte stets neu gezeichnet werden +- Kein Löschen von Objekten nötig +- Es müssen alle im nächsten Schritt sichtbaren Objekte stets neu gezeichnet + werden ::: \bigskip @@ -247,56 +232,54 @@ Idee: Je Zeitschritt: 3. Objekte mit `paintComponent()` neu in GUI zeichnen ::: notes -* Möglichkeit 1: Alle Objekte in zentraler Datenstruktur halten und die - Bewegung im Hauptprogramm berechnen - * Unschön: Das Hauptprogramm muss Hintergrundwissen über die Objekte - und deren Bewegung haben +- Möglichkeit 1: Alle Objekte in zentraler Datenstruktur halten und die Bewegung + im Hauptprogramm berechnen + - Unschön: Das Hauptprogramm muss Hintergrundwissen über die Objekte und deren + Bewegung haben \smallskip -* Möglichkeit 2: Die Objekte wissen selbst, wie sie sich bewegen und haben - eine Methode, deren Aufruf die Bewegung durchführt - * Objekte als Listener im Hauptprogramm registrieren - * Hauptprogramm gibt Zeittakt vor und ruft je Schritt für alle Listener - die Bewege-Methode auf => Listener berechnen ihre neue Position - * Hauptprogramm kann weitere Prüfungen (Kollision etc) auslösen - * Hauptprogramm ruft für alle Listener eine Paint-Methode auf => - Listener stellen sich auf GUI dar ... +- Möglichkeit 2: Die Objekte wissen selbst, wie sie sich bewegen und haben eine + Methode, deren Aufruf die Bewegung durchführt + - Objekte als Listener im Hauptprogramm registrieren + - Hauptprogramm gibt Zeittakt vor und ruft je Schritt für alle Listener die + Bewege-Methode auf =\> Listener berechnen ihre neue Position + - Hauptprogramm kann weitere Prüfungen (Kollision etc) auslösen + - Hauptprogramm ruft für alle Listener eine Paint-Methode auf =\> Listener + stellen sich auf GUI dar ... - => Observer-Pattern nutzen + =\> Observer-Pattern nutzen ::: -[[Hinweis: Zentrale Struktur vs. Observer-Pattern]{.ex}]{.slides} - +[[Hinweis: Zentrale Struktur vs. Observer-Pattern]{.ex}]{.slides} # Erinnerung: Observer Pattern ![](images/observer.png){width="80%"} -[[Hinweis auf Push- vs. Pull-Modell]{.ex}]{.slides} +[[Hinweis auf Push- vs. Pull-Modell]{.ex}]{.slides} ::: notes -* Anzahl der Observer muss nicht bekannt sein - zur Laufzeit erweiterbar! -* Verschiedene Update-Methoden für unterschiedliche Observer denkbar -* **Push-Modell**: Benötigte Daten werden der Update-Methode mitgegeben -* **Pull-Modell**: Update-Methode nur als Trigger, Observer holen sich die Daten selbst -* Referenz auf Observable mitgeben - Observer braucht dann keine Referenz - auf das Observable halten und kann sich bei verschiedenen Observables - registrieren -* `Observer` werden (vor allem im Swing-Umfeld) manchmal auch `Listener` genannt +- Anzahl der Observer muss nicht bekannt sein - zur Laufzeit erweiterbar! +- Verschiedene Update-Methoden für unterschiedliche Observer denkbar +- **Push-Modell**: Benötigte Daten werden der Update-Methode mitgegeben +- **Pull-Modell**: Update-Methode nur als Trigger, Observer holen sich die Daten + selbst +- Referenz auf Observable mitgeben - Observer braucht dann keine Referenz auf das + Observable halten und kann sich bei verschiedenen Observables registrieren +- `Observer` werden (vor allem im Swing-Umfeld) manchmal auch `Listener` genannt ::: - # Spielobjekte als Observer (Listener) ::: notes Objekte können sich auf `Graphics` darstellen: -* Ursprung, Breite, Höhe -* Schrittweite pro Bewegungsschritt +- Ursprung, Breite, Höhe +- Schrittweite pro Bewegungsschritt ::: -```java +``` java abstract class GameObject { abstract void move(); abstract void paintTo(Graphics g); // entspricht Observer#update() @@ -313,66 +296,82 @@ class GameRect extends GameObject { Weitere evtl. nützliche Methoden: -* Check auf Kollision -* Methode zum Umdrehen der Bewegungsrichtung - +- Check auf Kollision +- Methode zum Umdrehen der Bewegungsrichtung # Oberfläche zusammenbauen 1. Spielfeld von `JPanel` ableiten: Observable 2. Observer registrieren: Liste mit Spiel-Objekten anlegen 3. `paintComponent()` vom Spielfeld überschreiben - * für alle Observer (Spiel-Objekte) `paintTo()` aufrufen + - für alle Observer (Spiel-Objekte) `paintTo()` aufrufen \smallskip 4. Hauptschleife für Spiel: - * Taktgeber (Zeit, Interaktion) - * Je Schritt `move()` für alle Observer aufrufen - * Weitere Berechnungen (Kollisionen, Interaktionen, ...) - * `Spielfeld.repaint()` aufrufen => Neuzeichnen mit `paintComponent()` + - Taktgeber (Zeit, Interaktion) + - Je Schritt `move()` für alle Observer aufrufen + - Weitere Berechnungen (Kollisionen, Interaktionen, ...) + - `Spielfeld.repaint()` aufrufen =\> Neuzeichnen mit `paintComponent()` ::: notes **Pro Schritt**: -1. `move()` für alle Objekte aufrufen: Objekte setzen ihren Ursprung weiter - (**ohne Aktualisierung des Bildes!**) +1. `move()` für alle Objekte aufrufen: Objekte setzen ihren Ursprung weiter (**ohne + Aktualisierung des Bildes!**) 2. Prüfungen: Kollision/Berührung, aus dem Bild wandern ... - * Beispiele für Verhalten bei Berührung: - * beide kehren ihre Bewegungsrichtung um - * das kleinere Objekt verschwindet - * Beispiele für Umgang mit Objekten, die aus dem Bild wandern: - * auf der anderen Seite einblenden - * Bewegungsrichtung umkehren - * Objekt aus dem Spiel nehmen + + - Beispiele für Verhalten bei Berührung: + - beide kehren ihre Bewegungsrichtung um + - das kleinere Objekt verschwindet + - Beispiele für Umgang mit Objekten, die aus dem Bild wandern: + - auf der anderen Seite einblenden + - Bewegungsrichtung umkehren + - Objekt aus dem Spiel nehmen 3. Interaktionen - * Greifen sich Monster und Held an? - * Öffnet der Held eine Truhe? - * Sammelt der Held etwas auf? - * ... -3. `repaint()` im Spielfeld aufrufen => damit wird `paintComponent()` aufgerufen, + - Greifen sich Monster und Held an? + - Öffnet der Held eine Truhe? + - Sammelt der Held etwas auf? + - ... + +4. `repaint()` im Spielfeld aufrufen =\> damit wird `paintComponent()` aufgerufen, in `paintComponent()` wird für alle Spielobjekte deren `paintTo()` aufgerufen - und damit ein Neuzeichnen aller Objekte ausgelöst + und damit ein Neuzeichnen aller Objekte ausgelöst ::: -[Demo: java2d.simplegame.J2DTeaser]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/java2d/simplegame/J2DTeaser.java"} - +[Demo: java2d.simplegame.J2DTeaser]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/java2d/simplegame/J2DTeaser.java"} # Wrap-Up -* Java2D: Swing-Komponenten zeichnen mit `paintComponent()` auf `Graphics` -* `Graphics`: Methoden zum Zeichnen von Linien, Rechtecken, Ovalen, Text ... - * Koordinatensystem: Ursprung links oben! - * Geom. Primitive und Text werden in ausgewählter Zeichenfarbe gerendert - * Rechtecke, Ovale, Polygone auch als "gefüllte" Variante - * Mehr Möglichkeiten: `Graphics2D` +- Java2D: Swing-Komponenten zeichnen mit `paintComponent()` auf `Graphics` +- `Graphics`: Methoden zum Zeichnen von Linien, Rechtecken, Ovalen, Text ... + - Koordinatensystem: Ursprung links oben! + - Geom. Primitive und Text werden in ausgewählter Zeichenfarbe gerendert + - Rechtecke, Ovale, Polygone auch als "gefüllte" Variante + - Mehr Möglichkeiten: `Graphics2D` \bigskip -* Spiel: Game-Loop - * Bewege Objekte: Rechne neue Position aus - * Interagiere: Angriffe, Sammeln, ... - * Zeichne Objekte neu +- Spiel: Game-Loop + - Bewege Objekte: Rechne neue Position aus + - Interagiere: Angriffe, Sammeln, ... + - Zeichne Objekte neu + +::: readings +- @Java-SE-Tutorial +- @Ullenboom2021 [Kap. 18] +::: + +::: outcomes +- k2: Unterschied und Zusammenhang zwischen Swing und AWT +- k2: Swing-Komponenten erben paintComponent(Graphics) +- k2: paintComponent(Graphics) wird durch Events oder durch repaint() aufgerufen +- k3: Auf Graphics-Objekt zeichnen mit geometrischen Primitiven: Nutzung von + draw(), fill(), drawString() +- k3: Einstellung von Farbe und Font +- k3: Erzeugen von Bewegung ohne Nutzung von Threads +::: diff --git a/lecture/gui/layouts.md b/lecture/gui/layouts.md index 521eb4844..10469cb72 100644 --- a/lecture/gui/layouts.md +++ b/lecture/gui/layouts.md @@ -1,82 +1,32 @@ --- +author: Carsten Gips (HSBI) title: "Swing: Layout-Manager" -author: "Carsten Gips (HSBI)" -readings: - - "@Java-SE-Tutorial" - - "@Ullenboom2021 [Kap. 18]" -tldr: | - Zur Anordnung von Komponenten greift Swing auf sogenannte Layout-Manager zurück. - - Hier gibt es viele verschiedene Ausprägungen und Spielarten. Es gibt beispielsweise: - * das `BorderLayout`, eine gitterartige Struktur mit fünf Elementen, - * das `FlowLayout`, eine einzeilige Anordnung mit automatischem Umbruch bei Platzmangel, - * das `GridLayout`, eine tabellenartige Struktur, in der alle Elemente gleich groß dargestellt werden, und - * das `GridBagLayout`, welches sich prinzipiell wie das `GridLayout` verhält und mehr Möglichkeiten bietet. - Zur Anordnung der Komponenten greift man hier auf `GridBagConstraints` zurück und kann sehr genau und - sehr flexibel definieren, wo und wie die Komponenten angeordnet sein sollen und sich bei Größenänderungen - des Containers verhalten sollen. -outcomes: - - k3: "Anwenden der verschiedenen Layout-Manager: BorderLayout, FlowLayout, GridLayout, GridBagLayout" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106248&client_id=FH-Bielefeld" -# name: "Quiz Swing (ILIAS)" -youtube: - - link: "https://youtu.be/An7tQMW1A3E" - name: "VL Swing: Layout-Manager" - - link: "https://youtu.be/tifXSKXSUYw" - name: "Demo BorderLayout" - - link: "https://youtu.be/tYTDhv6lwT8" - name: "Demo FlowLayout" - - link: "https://youtu.be/JyN_Wozg3ms" - name: "Demo GridLayout" - - link: "https://youtu.be/95PG2alVTSo" - name: "Demo GridBagLayout" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/16d8509024067614df7909666ebb26ac42eb672180fd8b9f846d61f6d9d5c7d75a7430799469971666cbbe531fab23f7b78884c9bd432e901bfe81975e84da2b" - name: "VL Swing: Layout-Manager" -challenges: | - In den [Vorgaben](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/gui/src/challenges) - eine Implementierung für ein TicTacToe-Spiel. Ihre - Aufgabe ist es, eine grafische Benutzeroberfläche für das Spiel zu entwickeln. - - Ihr Fenster soll sich immer in der Mitte des Bildschirms starten und einen Titel - besitzen. - - Beim Start des Spiels sollen beide Spieler ihre Namen eingeben können, nutzen Sie - dafür `JTextField`. Stellen Sie sicher, dass nur gültige Eingaben getätigt werden. - Sind die beiden Namen gültig, erstellen Sie die jeweiligen `Player` und starten Sie - ein Spiel `TicTacToe`. - - Im Zentrum Ihres Spielfensters soll das Spielfeld angezeigt werden. Nutzen Sie dafür - das `GridLayout` und `JButton`. Die Buttons repräsentieren dabei die Felder des Spiels. - Beim Drücken eines Buttons soll die Methode `TicTacToe#makeMove` aufgerufen werden. - - Mit `TicTacToe#getGameField` können Sie sich das aktuelle Spielfeld übergeben lassen. - Sorgen Sie dafür, dass Ihre Oberfläche immer den aktuellen Zustand des Spielfeldes - anzeigt. - - Prüfen Sie nach jedem Spielzug den Status des Spiels mit `TicTacToe#getCurrentGameState`: - - - Wenn ein Spieler gewonnen hat, soll ein `JOptionPane` angezeigt werden und dem Gewinner - gratulieren. - - Wenn ein Unentschieden gespielt wurde, soll ein `JOptionPane` angezeigt werden und das - Unentschieden angezeigt werden. - - In beiden Fällen soll danach eine neue Runde gestartet werden. - - Im unteren Bereich des Fensters soll der Spieler angezeigt werden, der aktuell am Zug ist. - Im unteren Bereich des Fensters soll auch der aktuelle Punktestand angezeigt werden. - - Das Fenster soll eine Menüleiste mit folgenden Punkten haben: - - - Exit: Beendet das Programm. - - New Game: Startet das Spiel neu und erlaubt die neue Eingabe der Spielernamen. - - Clear: Setzt das aktuelle Spielfeld zurück. - - Denken Sie bei der Umsetzung daran, dass der Benutzer nur die Oberfläche sieht und bedienen - kann. Stellen Sie sicher, dass alle Bedienelemente verständlich sind. Nutzen Sie ggf. `JLabel`, - um Texte auf der UI anzuzeigen. --- +::: tldr +Zur Anordnung von Komponenten greift Swing auf sogenannte Layout-Manager zurück. + +Hier gibt es viele verschiedene Ausprägungen und Spielarten. Es gibt beispielsweise: + +- das `BorderLayout`, eine gitterartige Struktur mit fünf Elementen, +- das `FlowLayout`, eine einzeilige Anordnung mit automatischem Umbruch bei + Platzmangel, +- das `GridLayout`, eine tabellenartige Struktur, in der alle Elemente gleich groß + dargestellt werden, und +- das `GridBagLayout`, welches sich prinzipiell wie das `GridLayout` verhält und + mehr Möglichkeiten bietet. Zur Anordnung der Komponenten greift man hier auf + `GridBagConstraints` zurück und kann sehr genau und sehr flexibel definieren, wo + und wie die Komponenten angeordnet sein sollen und sich bei Größenänderungen des + Containers verhalten sollen. +::: + +::: youtube +- [VL Swing: Layout-Manager](https://youtu.be/An7tQMW1A3E) +- [Demo BorderLayout](https://youtu.be/tifXSKXSUYw) +- [Demo FlowLayout](https://youtu.be/tYTDhv6lwT8) +- [Demo GridLayout](https://youtu.be/JyN_Wozg3ms) +- [Demo GridBagLayout](https://youtu.be/95PG2alVTSo) +::: # Überblick @@ -86,18 +36,17 @@ Anordnung der Komponenten [in einem Container ist]{.notes} abhängig vom **Layou Verschiedene beliebte Layout-Manager: -* `BorderLayout` -* `FlowLayout` -* `GridLayout` -* `GridBagLayout` -* ... - +- `BorderLayout` +- `FlowLayout` +- `GridLayout` +- `GridBagLayout` +- ... -# _BorderLayout_ +# *BorderLayout* ![](images/screenshot-borderlayout.png){width="40%"} -```java +``` java JPanel contentPane = new JPanel(); contentPane.setLayout(new BorderLayout()); @@ -110,30 +59,31 @@ contentPane.add(new JButton("South"), BorderLayout.SOUTH); // also: PAGE_END ``` ::: notes -Es gibt fünf verschiedene Bereiche, in denen die Komponenten bei einem Border-Layout angeordnet -werden können. Für die "historischen" Konstanten `NORTH`, `SOUTH`, `WEST` und `EAST` gibt es -mittlerweile neue Namen, die eher am Aufbau einer Seite orientiert werden können. +Es gibt fünf verschiedene Bereiche, in denen die Komponenten bei einem Border-Layout +angeordnet werden können. Für die "historischen" Konstanten `NORTH`, `SOUTH`, `WEST` +und `EAST` gibt es mittlerweile neue Namen, die eher am Aufbau einer Seite +orientiert werden können. -Man kann auch nur einige Teile nutzen, bei der Tabellen-Demo beispielsweise wurde nur der -`NORTH`-Bereich für den Tabellenkopf und der `CENTER`-Bereich für die eigentliche Tabelle -genutzt. +Man kann auch nur einige Teile nutzen, bei der Tabellen-Demo beispielsweise wurde +nur der `NORTH`-Bereich für den Tabellenkopf und der `CENTER`-Bereich für die +eigentliche Tabelle genutzt. -Wenn das Fenster vergrößert wird, bekommt zunächst der Mittelteil den neuen zur Verfügung stehenden -Platz. Die anderen Bereiche werden dabei auf vergrößert, aber nur so weit, dass der neue verfügbare -Platz ggf. ausgefüllt wird. +Wenn das Fenster vergrößert wird, bekommt zunächst der Mittelteil den neuen zur +Verfügung stehenden Platz. Die anderen Bereiche werden dabei auf vergrößert, aber +nur so weit, dass der neue verfügbare Platz ggf. ausgefüllt wird. -Mit den Methoden `setHgap()` und `setVgap()` kann der Abstand zwischen den Komponenten eingestellt -werden (horizontal und vertikal, Abstände in Pixel). +Mit den Methoden `setHgap()` und `setVgap()` kann der Abstand zwischen den +Komponenten eingestellt werden (horizontal und vertikal, Abstände in Pixel). ::: -[Demo: layout.Border]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/layout/Border.java"} +[Demo: layout.Border]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/layout/Border.java"} - -# _FlowLayout_ +# *FlowLayout* ![](images/screenshot-flowlayout.png){width="60%"} -```java +``` java JPanel contentPane = new JPanel(); contentPane.setLayout(new FlowLayout()); @@ -144,24 +94,26 @@ contentPane.add(new JButton("Label 3")); ``` ::: notes -Das `FlowLayout` ist ein sehr einfaches Layout, welches per Default in `JPanel` genutzt wird. +Das `FlowLayout` ist ein sehr einfaches Layout, welches per Default in `JPanel` +genutzt wird. -Die Komponenten werden der Reihe nach in einer Zeile angeordnet. Wenn der Platz nicht ausreicht, -bricht diese Zeile um in mehrere Zeilen. +Die Komponenten werden der Reihe nach in einer Zeile angeordnet. Wenn der Platz +nicht ausreicht, bricht diese Zeile um in mehrere Zeilen. -Per Default werden die Komponenten zentriert angeordnet. Über den Konstruktor oder die Methoden -`setAlignment()` und `setHgap()` bzw. `setVgap()` kann aber eine andere Ausrichtung definiert -werden, ebenso wie ein vertikales und horizontales Padding zwischen den Komponenten. +Per Default werden die Komponenten zentriert angeordnet. Über den Konstruktor oder +die Methoden `setAlignment()` und `setHgap()` bzw. `setVgap()` kann aber eine andere +Ausrichtung definiert werden, ebenso wie ein vertikales und horizontales Padding +zwischen den Komponenten. ::: -[Demo: layout.Flow]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/layout/Flow.java"} - +[Demo: layout.Flow]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/layout/Flow.java"} -# _GridLayout_ +# *GridLayout* ![](images/screenshot-gridlayout.png){width="40%"} -```java +``` java JPanel contentPane = new JPanel(); contentPane.setLayout(new GridLayout(0, 3)); @@ -172,47 +124,49 @@ contentPane.add(new JButton("Label 3")); ``` ::: notes -Das `GridLayout` ist ein sehr einfaches Layout mit einer tabellenartigen Struktur. Dabei werden -die Komponenten nacheinander auf die "Zellen" verteilt, beginnend mit der ersten Zeile. Alle -Komponenten werden dabei gleich groß dargestellt. +Das `GridLayout` ist ein sehr einfaches Layout mit einer tabellenartigen Struktur. +Dabei werden die Komponenten nacheinander auf die "Zellen" verteilt, beginnend mit +der ersten Zeile. Alle Komponenten werden dabei gleich groß dargestellt. -Über den Konstruktor wird die Anzahl der gewünschten Zeilen und Spalten angegeben. Es darf auch -für einen der beiden Parameter der Wert 0 verwendet werden, in diesem Fall werden so viele Zeilen -oder Spalten angelegt, wie für die hinzugefügten Komponenten benötigt. +Über den Konstruktor wird die Anzahl der gewünschten Zeilen und Spalten angegeben. +Es darf auch für einen der beiden Parameter der Wert 0 verwendet werden, in diesem +Fall werden so viele Zeilen oder Spalten angelegt, wie für die hinzugefügten +Komponenten benötigt. -Auch in diesem Layout kann das Padding über die Methoden `setHgap()` bzw. `setVgap()` eingestellt -werden. +Auch in diesem Layout kann das Padding über die Methoden `setHgap()` bzw. +`setVgap()` eingestellt werden. ::: -[Demo: layout.Grid]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/layout/Grid.java"} +[Demo: layout.Grid]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/layout/Grid.java"} +# Komplexer Layout-Manager: *GridBagLayout* -# Komplexer Layout-Manager: _GridBagLayout_ - -* Layout-Manager ähnlich zu `GridLayout` -* Zusätzlich `GridBagConstraints`: Verhalten bei Größenveränderungen +- Layout-Manager ähnlich zu `GridLayout` +- Zusätzlich `GridBagConstraints`: Verhalten bei Größenveränderungen \bigskip -| Constraint | Bedeutung | -|:-------------|:------------------------------------------------------------------------------------------| -| `gridx` | **Spalte** für Komponente (linke obere Ecke) | -| `gridy` | **Zeile** für Komponente (linke obere Ecke) | -| `gridwidth` | **Anzahl der Spalten** für Komponente | -| `gridheight` | **Anzahl der Zeilen** für Komponente | -| `fill` | Vergrößert **Komponente** in Richtung: `NONE`, `HORIZONTAL`, `VERTICAL`, `BOTH` | -| `weightx` | Platz in x-Richtung wird unter den **Grid-Slots** entsprechend ihrem "Gewicht" aufgeteilt | -| `weighty` | Platz in y-Richtung wird unter den **Grid-Slots** entsprechend ihrem "Gewicht" aufgeteilt | +| Constraint | Bedeutung | +|:-----------|:-----------------------------------------------------------------------| +| `gridx` | **Spalte** für Komponente (linke obere Ecke) | +| `gridy` | **Zeile** für Komponente (linke obere Ecke) | +| `gridwidth` | **Anzahl der Spalten** für Komponente | +| `gridheight` | **Anzahl der Zeilen** für Komponente | +| `fill` | Vergrößert **Komponente** in Richtung: `NONE`, `HORIZONTAL`, `VERTICAL`, `BOTH` | +| `weightx` | Platz in x-Richtung wird unter den **Grid-Slots** entsprechend ihrem "Gewicht" aufgeteilt | +| `weighty` | Platz in y-Richtung wird unter den **Grid-Slots** entsprechend ihrem "Gewicht" aufgeteilt | ::: notes -Beim Hinzufügen einer Komponente wird eine Instanz der Klasse `GridBagConstraints` mitgegeben. -Diese definiert, wie die Komponente in der gitterartigen Struktur konkret angeordnet werden soll: -Startposition im Gitter (x, y) bzw (Spalte, Zeile), wie viele Spalten oder Zeilen soll die -Komponente überstreichen und wie soll auf Größenänderungen des Containers reagiert werden. +Beim Hinzufügen einer Komponente wird eine Instanz der Klasse `GridBagConstraints` +mitgegeben. Diese definiert, wie die Komponente in der gitterartigen Struktur +konkret angeordnet werden soll: Startposition im Gitter (x, y) bzw (Spalte, Zeile), +wie viele Spalten oder Zeilen soll die Komponente überstreichen und wie soll auf +Größenänderungen des Containers reagiert werden. Beispiel: -```java +``` java JPanel contentPane = new JPanel(); contentPane.setLayout(new GridBagLayout()); @@ -227,26 +181,85 @@ c2.weighty = 0.5; contentPane.add(new JButton("Label 2"), c2); ``` -Der Button wird dem Panel mit dem GridBagLayout hinzugefügt und soll in Spalte 1 und Zeile 0 -angeordnet werden. Er soll sich dabei über 2 Zeilen erstrecken (und 1 Spalte). Der Button soll -sich in vertikaler Richtung vergrößern, sofern Platz zur Verfügung steht. +Der Button wird dem Panel mit dem GridBagLayout hinzugefügt und soll in Spalte 1 und +Zeile 0 angeordnet werden. Er soll sich dabei über 2 Zeilen erstrecken (und 1 +Spalte). Der Button soll sich in vertikaler Richtung vergrößern, sofern Platz zur +Verfügung steht. -Dem Grid-Slot wird ein Gewicht in x- und in y-Richtung von je 0.5 mitgegeben. Bei einer Änderung -des Containers in der jeweiligen Richtung wird der neue Platz unter den Slots gemäß ihren Gewichten -aufgeteilt. +Dem Grid-Slot wird ein Gewicht in x- und in y-Richtung von je 0.5 mitgegeben. Bei +einer Änderung des Containers in der jeweiligen Richtung wird der neue Platz unter +den Slots gemäß ihren Gewichten aufgeteilt. ::: -[Demo: layout.GridBag]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/layout/GridBag.java"} - +[Demo: layout.GridBag]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/layout/GridBag.java"} # Wrap-Up -* Anordnung von Komponenten lässt sich mit Layout-Manager steuern +- Anordnung von Komponenten lässt sich mit Layout-Manager steuern \bigskip -* Auswahl von beliebten Layout-Managern: - * `BorderLayout`: Gitterartige Struktur mit fünf Elementen - * `FlowLayout`: Zeilenweise Anordnung (Umbruch bei Platzmangel) - * `GridLayout`: Tabellenartige Struktur, Elemente gleich groß - * `GridBagLayout`: Wie `GridLayout`, mit mehr Möglichkeiten: `GridBagConstraints` +- Auswahl von beliebten Layout-Managern: + - `BorderLayout`: Gitterartige Struktur mit fünf Elementen + - `FlowLayout`: Zeilenweise Anordnung (Umbruch bei Platzmangel) + - `GridLayout`: Tabellenartige Struktur, Elemente gleich groß + - `GridBagLayout`: Wie `GridLayout`, mit mehr Möglichkeiten: + `GridBagConstraints` + +::: readings +- @Java-SE-Tutorial +- @Ullenboom2021 [Kap. 18] +::: + +::: outcomes +- k3: Anwenden der verschiedenen Layout-Manager: BorderLayout, FlowLayout, + GridLayout, GridBagLayout +::: + +::: challenges +In den +[Vorgaben](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/gui/src/challenges) +eine Implementierung für ein TicTacToe-Spiel. Ihre Aufgabe ist es, eine grafische +Benutzeroberfläche für das Spiel zu entwickeln. + +Ihr Fenster soll sich immer in der Mitte des Bildschirms starten und einen Titel +besitzen. + +Beim Start des Spiels sollen beide Spieler ihre Namen eingeben können, nutzen Sie +dafür `JTextField`. Stellen Sie sicher, dass nur gültige Eingaben getätigt werden. +Sind die beiden Namen gültig, erstellen Sie die jeweiligen `Player` und starten Sie +ein Spiel `TicTacToe`. + +Im Zentrum Ihres Spielfensters soll das Spielfeld angezeigt werden. Nutzen Sie dafür +das `GridLayout` und `JButton`. Die Buttons repräsentieren dabei die Felder des +Spiels. Beim Drücken eines Buttons soll die Methode `TicTacToe#makeMove` aufgerufen +werden. + +Mit `TicTacToe#getGameField` können Sie sich das aktuelle Spielfeld übergeben +lassen. Sorgen Sie dafür, dass Ihre Oberfläche immer den aktuellen Zustand des +Spielfeldes anzeigt. + +Prüfen Sie nach jedem Spielzug den Status des Spiels mit +`TicTacToe#getCurrentGameState`: + +- Wenn ein Spieler gewonnen hat, soll ein `JOptionPane` angezeigt werden und dem + Gewinner gratulieren. +- Wenn ein Unentschieden gespielt wurde, soll ein `JOptionPane` angezeigt werden + und das Unentschieden angezeigt werden. +- In beiden Fällen soll danach eine neue Runde gestartet werden. + +Im unteren Bereich des Fensters soll der Spieler angezeigt werden, der aktuell am +Zug ist. Im unteren Bereich des Fensters soll auch der aktuelle Punktestand +angezeigt werden. + +Das Fenster soll eine Menüleiste mit folgenden Punkten haben: + +- Exit: Beendet das Programm. +- New Game: Startet das Spiel neu und erlaubt die neue Eingabe der Spielernamen. +- Clear: Setzt das aktuelle Spielfeld zurück. + +Denken Sie bei der Umsetzung daran, dass der Benutzer nur die Oberfläche sieht und +bedienen kann. Stellen Sie sicher, dass alle Bedienelemente verständlich sind. +Nutzen Sie ggf. `JLabel`, um Texte auf der UI anzuzeigen. +::: diff --git a/lecture/gui/readme.md b/lecture/gui/readme.md index fcb58bba4..41928a1d3 100644 --- a/lecture/gui/readme.md +++ b/lecture/gui/readme.md @@ -1,5 +1,6 @@ --- -title: "Graphische Oberflächen mit Swing und Java2D" -no_pdf: true no_beamer: true +no_pdf: true +title: Graphische Oberflächen mit Swing und Java2D --- + diff --git a/lecture/gui/swing-basics.md b/lecture/gui/swing-basics.md index 2a9c2c1b4..16517a17f 100644 --- a/lecture/gui/swing-basics.md +++ b/lecture/gui/swing-basics.md @@ -1,100 +1,86 @@ --- +author: Carsten Gips (HSBI) title: "Swing 101: Basics" -author: "Carsten Gips (HSBI)" -readings: - - "@Java-SE-Tutorial" - - "@Ullenboom2021 [Kap. 18]" -tldr: | - Swing baut auf AWT auf und ersetzt dieses. Der designierte Nachfolger JavaFX wurde nie wirklich - als Ersatz angenommen und ist mittlerweile sogar wieder aus dem JDK bzw. der Java SE herausgenommen - worden. - - Swing-Fenster bestehen zunächst aus Top-Level-Komponenten wie einem Frame oder einem Dialog. - Darin können "atomare Komponenten" wie Buttons, Label, Textfelder, ... eingefügt werden. Zur - Gruppierung können Komponenten wie Panels genutzt werden. - - Fenster werden über die Methode `pack()` berechnet (Größe, Anordnung) und müssen explizit - sichtbar gemacht werden (`setVisible(true)`). Per Default läuft die Anwendung weiter, - nachdem das Hauptfenster geschlossen wurde (konfigurierbar). - - Swing ist nicht Thread-safe, man darf also nicht mit mehreren Threads parallel die Komponenten - bearbeiten. Events werden in Swing durch einen speziellen Thread verarbeitet, dem sogenannten - _Event Dispatch Thread_ (EDT). Dieser wird über den Aufruf von `pack()` automatisch gestartet. - Da das Hauptprogramm `main()` in einem eigenen Thread läuft und ja die ganzen Komponenten - quasi schrittweise erzeugt, kann es hier zu Konflikten kommen. Deshalb sollte das Erzeugen der - Swing-GUI als neuer Thread ("Runnable") dem EDT übergeben werden. -outcomes: - - k2: "Unterschied und Zusammenhang zwischen Swing und AWT" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106248&client_id=FH-Bielefeld" -# name: "Quiz Swing (ILIAS)" -youtube: - - link: "https://youtu.be/ynwu6LuSLgQ" - name: "VL Swing 101" - - link: "https://youtu.be/L3Y2mB7-jRQ" - name: "Demo Einfaches Fenster" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/4b3f17de4155c07ed1b7e568d19eea47a865912d93a68adba04f22601c0c509034d5328f3e66131bbfdb8fe31c2a23bf95d67a3df57e3df4e548612d9f4bfd16" - name: "VL Swing 101" --- +::: tldr +Swing baut auf AWT auf und ersetzt dieses. Der designierte Nachfolger JavaFX wurde +nie wirklich als Ersatz angenommen und ist mittlerweile sogar wieder aus dem JDK +bzw. der Java SE herausgenommen worden. + +Swing-Fenster bestehen zunächst aus Top-Level-Komponenten wie einem Frame oder einem +Dialog. Darin können "atomare Komponenten" wie Buttons, Label, Textfelder, ... +eingefügt werden. Zur Gruppierung können Komponenten wie Panels genutzt werden. + +Fenster werden über die Methode `pack()` berechnet (Größe, Anordnung) und müssen +explizit sichtbar gemacht werden (`setVisible(true)`). Per Default läuft die +Anwendung weiter, nachdem das Hauptfenster geschlossen wurde (konfigurierbar). + +Swing ist nicht Thread-safe, man darf also nicht mit mehreren Threads parallel die +Komponenten bearbeiten. Events werden in Swing durch einen speziellen Thread +verarbeitet, dem sogenannten *Event Dispatch Thread* (EDT). Dieser wird über den +Aufruf von `pack()` automatisch gestartet. Da das Hauptprogramm `main()` in einem +eigenen Thread läuft und ja die ganzen Komponenten quasi schrittweise erzeugt, kann +es hier zu Konflikten kommen. Deshalb sollte das Erzeugen der Swing-GUI als neuer +Thread ("Runnable") dem EDT übergeben werden. +::: + +::: youtube +- [VL Swing 101](https://youtu.be/ynwu6LuSLgQ) +- [Demo Einfaches Fenster](https://youtu.be/L3Y2mB7-jRQ) +::: # Wiederholung GUI in Java -* **AWT**: `abstract window toolkit` - * Älteres Framework ("Legacy") - * "Schwergewichtig": plattformangepasst - * Paket `java.awt` +- **AWT**: `abstract window toolkit` + - Älteres Framework ("Legacy") + - "Schwergewichtig": plattformangepasst + - Paket `java.awt` \bigskip -* **Swing** - * Nutzt AWT - * "Leichtgewichtig": rein in Java implementiert - * Paket `javax.swing` +- **Swing** + - Nutzt AWT + - "Leichtgewichtig": rein in Java implementiert + - Paket `javax.swing` \bigskip -* **JavaFX** - * Soll als Ersatz für Swing dienen - * Community eher verhalten - * Weiterentwicklung immer wieder unklar - * Nicht mehr im JDK/Java SE Plattform enthalten - * Vergleichsweise komplexes Framework, auch ohne Java programmierbar +- **JavaFX** + - Soll als Ersatz für Swing dienen + - Community eher verhalten + - Weiterentwicklung immer wieder unklar + - Nicht mehr im JDK/Java SE Plattform enthalten + - Vergleichsweise komplexes Framework, auch ohne Java programmierbar (Skriptsprache FXML) ::: notes -_Anmerkung_: In Swing reimplementierte Klassen aus AWT: -Präfix "J": `java.awt.Button` (AWT) => `javax.swing.JButton` (Swing) +*Anmerkung*: In Swing reimplementierte Klassen aus AWT: Präfix "J": +`java.awt.Button` (AWT) =\> `javax.swing.JButton` (Swing) ::: - # Graphische Komponenten einer GUI -* Top-Level Komponenten - * Darstellung direkt auf Benutzeroberfläche des Betriebssystems - * Beispiele: Fenster, Dialoge - -* Atomare Komponenten - * Enthalten i.d.R. keine weiteren Komponenten - * Beispiele: Label, Buttons, Bilder - -* Gruppierende Komponenten - * Bündeln und gruppieren andere Komponenten - * Beispiele: JPanel +- Top-Level Komponenten + - Darstellung direkt auf Benutzeroberfläche des Betriebssystems + - Beispiele: Fenster, Dialoge +- Atomare Komponenten + - Enthalten i.d.R. keine weiteren Komponenten + - Beispiele: Label, Buttons, Bilder +- Gruppierende Komponenten + - Bündeln und gruppieren andere Komponenten + - Beispiele: JPanel \bigskip -**Achtung**: -Unterteilung nicht im API ausgedrückt: Alle Swing-Bausteine leiten von +**Achtung**: Unterteilung nicht im API ausgedrückt: Alle Swing-Bausteine leiten von Klasse `javax.swing.JComponent` ab! -=> Nutzung "falscher" Methoden führt zu Laufzeitfehlern. - +=\> Nutzung "falscher" Methoden führt zu Laufzeitfehlern. # Ein einfaches Fenster -```java +``` java public class FirstWindow { public static void main(String[] args) { JFrame frame = new JFrame("Hello World :)"); @@ -110,31 +96,35 @@ public class FirstWindow { ::: notes ## Elemente -Es wird ein neuer Frame angelegt als Top-Level-Komponente. Der Fenstertitel wird auf "Hello World :)" -gesetzt. +Es wird ein neuer Frame angelegt als Top-Level-Komponente. Der Fenstertitel wird auf +"Hello World :)" gesetzt. -Zusätzlich wird spezifiziert, dass sich das Programm durch Schließen des Fensters beenden soll. -Anderenfalls würde man zwar das sichtbare Fenster schließen, aber das Programm würde weiter laufen. +Zusätzlich wird spezifiziert, dass sich das Programm durch Schließen des Fensters +beenden soll. Anderenfalls würde man zwar das sichtbare Fenster schließen, aber das +Programm würde weiter laufen. -Mit der Swing-Methode `pack()` werden alle Komponenten berechnet und die Fenstergröße bestimmt, so dass -alle Komponenten Platz haben. Bis dahin ist das Fenster aber unsichtbar und wird erst über den Aufruf -von `setVisible(true)` auch dargestellt. +Mit der Swing-Methode `pack()` werden alle Komponenten berechnet und die +Fenstergröße bestimmt, so dass alle Komponenten Platz haben. Bis dahin ist das +Fenster aber unsichtbar und wird erst über den Aufruf von `setVisible(true)` auch +dargestellt. ## Swing und Multithreading: Event Dispatch Thread -Leider ist die Welt nicht ganz so einfach. In Swing werden Events wie das Drücken eines Buttons -durch den _Event Dispatch Thread_ (EDT) abgearbeitet. (Zum Thema Events in Swing siehe Einheit -["Swing Events"](events.md).) Der EDT wird mit dem Erzeugen der -visuellen Komponenten für die Swing-Objekte durch den Aufruf der Swing-Methoden `show()`, -`setVisible()` und `pack()` erstellt. Bereits beim Realisieren der Komponenten könnten diese -Events auslösen, die dann durch den EDT verarbeitet werden und an mögliche Listener verteilt -werden. Dummerweise wird das `main()` von der JVM aber in einem eigenen Thread abgearbeitet - es -könnten also zwei Threads parallel durch die hier erzeugte Swing-GUI laufen, und Swing ist -**nicht Thread-safe**! Komponenten dürfen nicht durch verschiedene Threads manipuliert werden. +Leider ist die Welt nicht ganz so einfach. In Swing werden Events wie das Drücken +eines Buttons durch den *Event Dispatch Thread* (EDT) abgearbeitet. (Zum Thema +Events in Swing siehe Einheit ["Swing Events"](events.md).) Der EDT wird mit dem +Erzeugen der visuellen Komponenten für die Swing-Objekte durch den Aufruf der +Swing-Methoden `show()`, `setVisible()` und `pack()` erstellt. Bereits beim +Realisieren der Komponenten könnten diese Events auslösen, die dann durch den EDT +verarbeitet werden und an mögliche Listener verteilt werden. Dummerweise wird das +`main()` von der JVM aber in einem eigenen Thread abgearbeitet - es könnten also +zwei Threads parallel durch die hier erzeugte Swing-GUI laufen, und Swing ist +**nicht Thread-safe**! Komponenten dürfen nicht durch verschiedene Threads +manipuliert werden. Die Lösung ist, die Realisierung der Komponenten als Job für den EDT zu "verpacken": -```java +``` java SwingUtilities.invokeLater( new Runnable() { public void run() { @@ -148,31 +138,43 @@ SwingUtilities.invokeLater( }); ``` -Mit `new Runnable()` wird ein neues Objekt vom Typ `Runnable` anlegt - im Prinzip ein neuer, noch nicht -gestarteter Thread mit der Hauptmethode `run()`. Dieses Runnable wird mit `SwingUtilities.invokeLater()` -dem EDT zu Ausführung übergeben. Wir werden uns das Thema Erzeugen und Starten von Threads in der Einheit -["Einführung in die nebenläufige Programmierung mit Threads"](../java-classic/threads-intro.md) -genauer ansehen. +Mit `new Runnable()` wird ein neues Objekt vom Typ `Runnable` anlegt - im Prinzip +ein neuer, noch nicht gestarteter Thread mit der Hauptmethode `run()`. Dieses +Runnable wird mit `SwingUtilities.invokeLater()` dem EDT zu Ausführung übergeben. +Wir werden uns das Thema Erzeugen und Starten von Threads in der Einheit +["Einführung in die nebenläufige Programmierung mit +Threads"](../java-classic/threads-intro.md) genauer ansehen. -Siehe auch ["Concurrency in Swing"](https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html). +Siehe auch ["Concurrency in +Swing"](https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html). -[Beispiel: basics.FirstWindow]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/basics/FirstWindow.java"} +[Beispiel: basics.FirstWindow]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/basics/FirstWindow.java"} ::: -[Demo: basics.SecondWindow]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/basics/SecondWindow.java"} - +[Demo: basics.SecondWindow]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/basics/SecondWindow.java"} # Wrap-Up -* Swing baut auf AWT auf und nutzt dieses -* JavaFX ist moderner, aber kein Swing-Ersatz geworden +- Swing baut auf AWT auf und nutzt dieses +- JavaFX ist moderner, aber kein Swing-Ersatz geworden \bigskip -* Basics: - * Swing-Fenster haben Top-Level-Komponenten: `JFrame`, ... - * Atomare Komponenten wie Buttons, Label, ... können gruppiert werden - * Fenster müssen explizit sichtbar gemacht werden - * Nach Schließen des Fensters läuft die Applikation weiter (Default) - * Swing-Events werden durch den _Event Dispatch Thread_ (EDT) verarbeitet \newline - => Aufpassen mit Multithreading! +- Basics: + - Swing-Fenster haben Top-Level-Komponenten: `JFrame`, ... + - Atomare Komponenten wie Buttons, Label, ... können gruppiert werden + - Fenster müssen explizit sichtbar gemacht werden + - Nach Schließen des Fensters läuft die Applikation weiter (Default) + - Swing-Events werden durch den *Event Dispatch Thread* (EDT) verarbeitet + `\newline`{=tex} =\> Aufpassen mit Multithreading! + +::: readings +- @Java-SE-Tutorial +- @Ullenboom2021 [Kap. 18] +::: + +::: outcomes +- k2: Unterschied und Zusammenhang zwischen Swing und AWT +::: diff --git a/lecture/gui/tables.md b/lecture/gui/tables.md index bd76b9696..e3d760ba0 100644 --- a/lecture/gui/tables.md +++ b/lecture/gui/tables.md @@ -1,65 +1,54 @@ --- +author: Carsten Gips (HSBI) title: "Swing: Tabellen" -author: "Carsten Gips (HSBI)" -readings: - - "@Java-SE-Tutorial" - - "@Ullenboom2021 [Kap. 18]" -tldr: | - In Swing kann man komplexe Daten mit `JTable` in einer tabellenartigen Struktur darstellen. - - Swing-Komponenten (nicht nur `JTable`!) nutzen das MVC-Pattern und trennen Daten und Anzeige. - Allerdings werden in Swing die Teile "Controller" und "View" miteinander verschmolzen (etwa - im `JTable`) und nur das "Model" wird separat erzeugt zur Verwaltung der Daten. -outcomes: - - k2: "Trennung von Anzeige und Daten: View und Model (MVC-Pattern)" - - k3: "Anzeige von Tabellen mit JTable" - - k3: "Einsatz eines eigenen Datenmodells" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106248&client_id=FH-Bielefeld" -# name: "Quiz Swing (ILIAS)" -youtube: - - link: "https://youtu.be/_iq_Grvhk90" - name: "VL Swing: Tabellen" - - link: "https://youtu.be/27o_A5SDbm0" - name: "Demo Swing: einfache Tabellen" - - link: "https://youtu.be/tEB7ZF6cdvM" - name: "Demo Swing: Tabellen mit Sortierung" - - link: "https://youtu.be/cZhXVcRsTLY" - name: "Demo Swing: Tabellen mit Daten-Modell" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/411be806527dfa1a27b15be19aa1e65abf193c24416432fc6bf04e376d11937cb2335d6b0c78e286c7483772268c37a15bdc06858b6acfd773d48f3f2aabddfa" - name: "VL Swing: Tabellen" --- +::: tldr +In Swing kann man komplexe Daten mit `JTable` in einer tabellenartigen Struktur +darstellen. + +Swing-Komponenten (nicht nur `JTable`!) nutzen das MVC-Pattern und trennen Daten und +Anzeige. Allerdings werden in Swing die Teile "Controller" und "View" miteinander +verschmolzen (etwa im `JTable`) und nur das "Model" wird separat erzeugt zur +Verwaltung der Daten. +::: + +::: youtube +- [VL Swing: Tabellen](https://youtu.be/_iq_Grvhk90) +- [Demo Swing: einfache Tabellen](https://youtu.be/27o_A5SDbm0) +- [Demo Swing: Tabellen mit Sortierung](https://youtu.be/tEB7ZF6cdvM) +- [Demo Swing: Tabellen mit Daten-Modell](https://youtu.be/cZhXVcRsTLY) +::: # Einfache Tabelle mit festen Daten -* Einfache Tabelle erzeugen: +- Einfache Tabelle erzeugen: - ```java + ``` java public JTable(final Object[][] rowData, final Object[] columnNames) ``` -* Daten gleich mit erzeugen/übergeben: +- Daten gleich mit erzeugen/übergeben: - ```java + ``` java Object[][] rowData = { { "Hein", "Bloed", 5 }, { "Susi", "Studi", 2 } }; ``` -* Tabellenkopf als einfaches Array: +- Tabellenkopf als einfaches Array: - ```java + ``` java Object[] columnNames = { "Vorname", "Name", "ect" }; ``` ::: notes -Damit der Tabellenkopf angezeigt wird, muss die Tabelle lt. [Dokumentation](https://docs.oracle.com/javase/tutorial/uiswing/components/table.html) -entweder in eine `JScrollPane` verpackt werden oder der Tabellenkopf muss manuell geeignet -untergebracht werden, beispielsweise über ein `BorderLayout`: +Damit der Tabellenkopf angezeigt wird, muss die Tabelle lt. +[Dokumentation](https://docs.oracle.com/javase/tutorial/uiswing/components/table.html) +entweder in eine `JScrollPane` verpackt werden oder der Tabellenkopf muss manuell +geeignet untergebracht werden, beispielsweise über ein `BorderLayout`: -```java +``` java JTable table = new JTable(data, columns); contentPane.setLayout(new BorderLayout()); @@ -68,30 +57,30 @@ contentPane.add(table, BorderLayout.CENTER); ``` ::: -[Demo: tables.SimpleTable]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/tables/SimpleTable.java"} - +[Demo: tables.SimpleTable]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/tables/SimpleTable.java"} # Selektierbare und sortierbare Tabelle -* Tabelle sortierbar machen: +- Tabelle sortierbar machen: - ```java + ``` java table.setAutoCreateRowSorter(true); ``` ::: notes - Damit kann man im Tabellenkopf auf eine Spalte klicken und die Tabelle wird entsprechend - dieser Spalte sortiert. + Damit kann man im Tabellenkopf auf eine Spalte klicken und die Tabelle wird + entsprechend dieser Spalte sortiert. - _Hinweis_: Dazu muss der Tabellenkopf sichtbar sein. + *Hinweis*: Dazu muss der Tabellenkopf sichtbar sein. - _Hinweis_: Man kann auch eigene Sortierer implementieren. Diese leiten von `TableRowSorter` - ab und werden über `table.setRowSorter()` gesetzt. + *Hinweis*: Man kann auch eigene Sortierer implementieren. Diese leiten von + `TableRowSorter` ab und werden über `table.setRowSorter()` gesetzt. ::: -* Selektion erkennen und reagieren mit MouseListener: +- Selektion erkennen und reagieren mit MouseListener: - ```java + ``` java table.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { TableModel model = table.getModel(); @@ -101,82 +90,83 @@ contentPane.add(table, BorderLayout.CENTER); }}); ``` -[Demo: tables.SelectTable]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/tables/SelectTable.java"} - +[Demo: tables.SelectTable]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/tables/SelectTable.java"} # Einschub: MVC-Pattern ![](images/mvc.png){width="60%"} ::: notes -Das [Model-View-Controller-Pattern (MVC)](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) -ist vor allem im Bereich User-Interfaces anzutreffen. - -Das "Model" kennt und verwaltet die Daten. Es ist unabhängig von der konkreten Darstellung der Daten und kann -neben den Daten auch Teile der Geschäftslogik beinhalten. Änderungen im Model werden per Observer-Pattern an -den View weitergereicht. - -Der "View" ist für die Darstellung der Daten zuständig und für die Benutzerinteraktion. Der View kennt das Model, -ist aber nicht für die Verwaltung der Daten zuständig. Benutzerinteraktion wird über das Observer-Pattern an den +Das [Model-View-Controller-Pattern +(MVC)](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) ist vor +allem im Bereich User-Interfaces anzutreffen. + +Das "Model" kennt und verwaltet die Daten. Es ist unabhängig von der konkreten +Darstellung der Daten und kann neben den Daten auch Teile der Geschäftslogik +beinhalten. Änderungen im Model werden per Observer-Pattern an den View +weitergereicht. + +Der "View" ist für die Darstellung der Daten zuständig und für die +Benutzerinteraktion. Der View kennt das Model, ist aber nicht für die Verwaltung der +Daten zuständig. Benutzerinteraktion wird über das Observer-Pattern an den Controller weitergeleitet. -Der "Controller" verwaltet das Model und den View und kümmert sich um die Verarbeitung der Nutzerinteraktion. -Bei User-Interaktion mit dem View wird der Controller über das Observer-Pattern vom View benachrichtigt, -verarbeitet die Interaktion und kann entsprechende Änderungen an das Model weitergeben. +Der "Controller" verwaltet das Model und den View und kümmert sich um die +Verarbeitung der Nutzerinteraktion. Bei User-Interaktion mit dem View wird der +Controller über das Observer-Pattern vom View benachrichtigt, verarbeitet die +Interaktion und kann entsprechende Änderungen an das Model weitergeben. Dieses Pattern findet sich mittlerweile in diversen (leichten) Variationen. ::: - # Daten in separatem Modell -* Modell muss von `AbstractTableModel` ableiten -* Methoden zur Interaktion mit Tabelle implementieren! -* Tabelle wird über Instanz von diesem Modell erzeugt -* Tabelle ist View und Controller zugleich, trägt sich bei Erzeugung - als Listener beim Modell ein +- Modell muss von `AbstractTableModel` ableiten +- Methoden zur Interaktion mit Tabelle implementieren! +- Tabelle wird über Instanz von diesem Modell erzeugt +- Tabelle ist View und Controller zugleich, trägt sich bei Erzeugung als Listener + beim Modell ein \smallskip -* Modell muss die Tabelle über Änderungen an den Daten informieren: +- Modell muss die Tabelle über Änderungen an den Daten informieren: - ```java + ``` java fireTableCellUpdated(row, col); ``` \bigskip -=> Kapselung der Daten! Müssen nicht als Array o.ä. vorliegen! - +=\> Kapselung der Daten! Müssen nicht als Array o.ä. vorliegen! # Modelleigenschaften -* Korrekte Spalten-Typen und damit bessere Anzeige: +- Korrekte Spalten-Typen und damit bessere Anzeige: - ```java + ``` java public Class getColumnClass(int c) { return getValueAt(0, c).getClass(); } ``` -* Spalten können gegen Editieren gesperrt werden: +- Spalten können gegen Editieren gesperrt werden: - ```java + ``` java public boolean isCellEditable(int row, int col) { return col >= 2; } ``` -* Kontrolle über Änderung der Daten in `setValueAt()` - -* Daten können in beliebigem Format vorliegen! Interface nach - "außen" dennoch tabellenartig. +- Kontrolle über Änderung der Daten in `setValueAt()` +- Daten können in beliebigem Format vorliegen! Interface nach "außen" dennoch + tabellenartig. ::: notes # Eigene Listener beim Modell registrieren -```java +``` java TableModel m = table.getModel(); m.addTableModelListener(new TableModelListener() { public void tableChanged(TableModelEvent e) { @@ -196,13 +186,24 @@ Zusätzlich kann man beim Modell eigene Listener registrieren, die auf Events du Änderungen der Tabelle reagieren können. ::: -[Demo: tables.ModelTable]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/tables/ModelTable.java"} - +[Demo: tables.ModelTable]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/tables/ModelTable.java"} # Wrap-Up -* Fortgeschrittene Swing-Komponenten - * Komplexe Daten mit `JTable` anzeigen - * Swing-Komponenten (nicht nur `JTable`!) haben Datenmodelle \newline +- Fortgeschrittene Swing-Komponenten + - Komplexe Daten mit `JTable` anzeigen + - Swing-Komponenten (nicht nur `JTable`!) haben Datenmodelle `\newline`{=tex} (können separat erzeugt werden, haben eigene Listener, ...) -* Trennung Daten und Anzeige: MVC-Pattern +- Trennung Daten und Anzeige: MVC-Pattern + +::: readings +- @Java-SE-Tutorial +- @Ullenboom2021 [Kap. 18] +::: + +::: outcomes +- k2: Trennung von Anzeige und Daten: View und Model (MVC-Pattern) +- k3: Anzeige von Tabellen mit JTable +- k3: Einsatz eines eigenen Datenmodells +::: diff --git a/lecture/gui/widgets.md b/lecture/gui/widgets.md index ae5807e17..8734fbdf1 100644 --- a/lecture/gui/widgets.md +++ b/lecture/gui/widgets.md @@ -1,52 +1,38 @@ --- +author: Carsten Gips (HSBI) title: "Swing: Nützliche Widgets" -author: "Carsten Gips (HSBI)" -readings: - - "@Java-SE-Tutorial" - - "@Ullenboom2021 [Kap. 18]" -tldr: | - Neben den Standardkomponenenten `JFrame` für ein Fenster, `JPanel` für ein Panel (auch zum - Gruppieren anderer Komponenten), `JButton` (Button) und `JTextArea` (Texteingabe) gibt es - eine Reihe weiterer nützlicher Swing-Komponenten: - - * `JRadioButton` für Radio-Buttons und `JCheckBox` für Checkbox-Buttons sowie `ButtonGroup`` - für die logische Verbindung von diesen Buttons (es kann nur ein Button einer ButtonGroup - aktiv sein - wenn ein anderer Button aktiviert wird, wird der zuletzt aktive Button - automatisch deaktiviert) - * Dateiauswahldialoge mit `JFileChooser` und `FileFilter` zum Vorfiltern der Anzeige - * Einfache (modale) Dialoge mit `JOptionPane` - * `JTabbedPane` als Panel mit Tabs - * `JScrollPane`, um Eingabefelder bei Bedarf scrollbar zu machen - * Anlegen einer Menüleiste mit `JMenuBar`, dabei sind die Menüs `JMenu` und die Einträge `JMenuItem`` - * Kontextmenüs mit `JPopupMenu` -outcomes: - - k3: "Umgang mit komplexeren Swing-Komponenten: JRadioButton, JFileChooser, JOptionPane, JTabbedPane, JScrollPane, JMenuBar, JPopupMenu" - - k3: "Nutzung von ActionListener, MouseListener, KeyListener, FocusListener" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106248&client_id=FH-Bielefeld" -# name: "Quiz Swing (ILIAS)" -youtube: - - link: "https://youtu.be/cUtK-yL5Wpw" - name: "VL Swing Widgets" - - link: "https://youtu.be/auu5wr0lr3w" - name: "Demo JRadioButton" - - link: "https://youtu.be/HEm7ATvdYJo" - name: "Demo JFileChooser" - - link: "https://youtu.be/kfTVZ_W8u6o" - name: "Demo JOptionPane" - - link: "https://youtu.be/EAuT4n5mfAg" - name: "Demo JTabbedPane und JScrollPane" - - link: "https://youtu.be/zMlrKRV8WIY" - name: "Demo JMenuBar" - - link: "https://youtu.be/ftHDFIkaC-E" - name: "Demo JPopupMenu" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/b6208b6b99b3510b6ecc59d4acc2152536c124c45e7b28ac853327dacb237f74ffedecb414549809d806d2f7a0bb7ee9af6361b985cbe339bc2de9f52392fafb" - name: "VL Swing Widgets" --- +::: tldr +Neben den Standardkomponenenten `JFrame` für ein Fenster, `JPanel` für ein Panel +(auch zum Gruppieren anderer Komponenten), `JButton` (Button) und `JTextArea` +(Texteingabe) gibt es eine Reihe weiterer nützlicher Swing-Komponenten: + +- `JRadioButton` für Radio-Buttons und `JCheckBox` für Checkbox-Buttons sowie + `ButtonGroup` für die logische Verbindung von diesen Buttons (es kann nur + ein Button einer ButtonGroup aktiv sein - wenn ein anderer Button aktiviert + wird, wird der zuletzt aktive Button automatisch deaktiviert) +- Dateiauswahldialoge mit `JFileChooser` und `FileFilter` zum Vorfiltern der + Anzeige +- Einfache (modale) Dialoge mit `JOptionPane` +- `JTabbedPane` als Panel mit Tabs +- `JScrollPane`, um Eingabefelder bei Bedarf scrollbar zu machen +- Anlegen einer Menüleiste mit `JMenuBar`, dabei sind die Menüs `JMenu` und die + Einträge `JMenuItem` +- Kontextmenüs mit `JPopupMenu` +::: + +::: youtube +- [VL Swing Widgets](https://youtu.be/cUtK-yL5Wpw) +- [Demo JRadioButton](https://youtu.be/auu5wr0lr3w) +- [Demo JFileChooser](https://youtu.be/HEm7ATvdYJo) +- [Demo JOptionPane](https://youtu.be/kfTVZ_W8u6o) +- [Demo JTabbedPane und JScrollPane](https://youtu.be/EAuT4n5mfAg) +- [Demo JMenuBar](https://youtu.be/zMlrKRV8WIY) +- [Demo JPopupMenu](https://youtu.be/ftHDFIkaC-E) +::: -# Radiobuttons: _JRadioButton_ +# Radiobuttons: *JRadioButton* \bigskip @@ -55,22 +41,22 @@ fhmedia: \bigskip ::: notes -* Erzeugen einen neuen "Knopf" (rund) - - vergleiche `JCheckBox` => eckiger "Knopf" -* Parameter: Beschriftung und Aktivierung -* Reagieren mit `ItemListener` +- Erzeugen einen neuen "Knopf" (rund) + - vergleiche `JCheckBox` =\> eckiger "Knopf" +- Parameter: Beschriftung und Aktivierung +- Reagieren mit `ItemListener` ::: \bigskip -* **Logische Gruppierung der Buttons**: `ButtonGroup` - * `JRadioButton` sind **unabhängige** Objekte - * Normalerweise nur ein Button aktiviert - * Aktivierung eines Buttons => vormals aktivierter Button deaktiviert +- **Logische Gruppierung der Buttons**: `ButtonGroup` + - `JRadioButton` sind **unabhängige** Objekte + - Normalerweise nur ein Button aktiviert + - Aktivierung eines Buttons =\> vormals aktivierter Button deaktiviert \smallskip - ```java + ``` java JRadioButton b1 = new JRadioButton("Button 1", true); JRadioButton b2 = new JRadioButton("Button 2", false); @@ -78,16 +64,16 @@ fhmedia: radioGroup.add(b1); radioGroup.add(b2); ``` -[Demo: widgets.RadioButtonDemo]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/widgets/RadioButtonDemo.java"} - +[Demo: widgets.RadioButtonDemo]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/widgets/RadioButtonDemo.java"} -# Dateien oder Verzeichnisse auswählen: _JFileChooser_ +# Dateien oder Verzeichnisse auswählen: *JFileChooser* ![](images/screenshot-filechooser.png){width="40%"} \bigskip -```java +``` java JFileChooser fc = new JFileChooser("Startverzeichnis"); fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); if (fc.showOpenDialog() == JFileChooser.APPROVE_OPTION) @@ -95,47 +81,50 @@ if (fc.showOpenDialog() == JFileChooser.APPROVE_OPTION) ``` ::: notes -* `fc.setFileSelectionMode()`: Dateien, Ordner oder beides auswählbar -* Anzeigen mit `fc.showOpenDialog()` -* Rückgabewert vergleichen mit `JFileChooser.APPROVE_OPTION`: - Datei/Ordner wurde ausgewählt => Prüfen! -* Selektierte Datei als `File` bekommen: `fc.getSelectedFile()` +- `fc.setFileSelectionMode()`: Dateien, Ordner oder beides auswählbar +- Anzeigen mit `fc.showOpenDialog()` +- Rückgabewert vergleichen mit `JFileChooser.APPROVE_OPTION`: Datei/Ordner wurde + ausgewählt =\> Prüfen! +- Selektierte Datei als `File` bekommen: `fc.getSelectedFile()` **Filtern der Anzeige**: `FileFilter` -* Setzen mit `JFileChooser.setFileFilter()` -* Überschreiben von - * `boolean accept(File f)` - * `String getDescription()` +- Setzen mit `JFileChooser.setFileFilter()` +- Überschreiben von + - `boolean accept(File f)` + - `String getDescription()` ::: -[Demo: widgets.FileChooserDemo]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/widgets/FileChooserDemo.java"} - +[Demo: widgets.FileChooserDemo]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/widgets/FileChooserDemo.java"} # TabbedPane und Scroll-Bars \bigskip -* **TabbedPane**: `JTabbedPane` - * Container für weitere Komponenten - * Methode zum Hinzufügen anderer Swing-Komponenten: +- **TabbedPane**: `JTabbedPane` + - Container für weitere Komponenten + + - Methode zum Hinzufügen anderer Swing-Komponenten: \smallskip - ```java + ``` java public void addTab(String title, Icon icon, Component component, String tip) ``` \bigskip -* **Scroll-Bars**: `JScrollPane` - * Container für weitere Komponenten - * Scroll-Bars werden bei Bedarf sichtbar - * Hinzufügen einer Komponente: +- **Scroll-Bars**: `JScrollPane` + - Container für weitere Komponenten + + - Scroll-Bars werden bei Bedarf sichtbar + + - Hinzufügen einer Komponente: \smallskip - ```java + ``` java JPanel panel = new JPanel(); JTextArea text = new JTextArea(5, 10); @@ -149,16 +138,16 @@ if (fc.showOpenDialog() == JFileChooser.APPROVE_OPTION) * Wirkung der Scrollpane zeigen (letzter Tab) --> -[Demo: widgets.TabbedPaneDemo]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/widgets/TabbedPaneDemo.java"} +[Demo: widgets.TabbedPaneDemo]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/widgets/TabbedPaneDemo.java"} - -# Dialoge mit _JOptionPane_ +# Dialoge mit *JOptionPane* ![](images/screenshot-dialog.png){width="40%"} \bigskip -```java +``` java JOptionPane.showMessageDialog( this, "Krasse Warnung!", @@ -168,21 +157,21 @@ JOptionPane.showMessageDialog( ::: notes Ein Dialog ist ein eigenes Top-Level-Fenster, welches zumindest eine Message zeigt. -Zusätzlich kann man den Fenster-Titel einstellen und ein kleines Icon anzeigen lassen, -was verdeutlichen soll, ob es sich um eine Bestätigung oder Frage oder Warnung etc. -handelt. +Zusätzlich kann man den Fenster-Titel einstellen und ein kleines Icon anzeigen +lassen, was verdeutlichen soll, ob es sich um eine Bestätigung oder Frage oder +Warnung etc. handelt. -Damit der Dialog auch wirklich bedient werden muss, ist er "modal", d.h. er liegt "vor" -der Elternkomponente. Diese wird als Referenz übergeben und bekommt erst wieder den -Fokus, wenn der Dialog geschlossen wurde. +Damit der Dialog auch wirklich bedient werden muss, ist er "modal", d.h. er liegt +"vor" der Elternkomponente. Diese wird als Referenz übergeben und bekommt erst +wieder den Fokus, wenn der Dialog geschlossen wurde. ::: -[Demo: widgets.DialogDemo]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/widgets/DialogDemo.java"} - +[Demo: widgets.DialogDemo]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/widgets/DialogDemo.java"} -# Menüs mit _JMenuBar_, _JMenu_ und _JMenuItem_ +# Menüs mit *JMenuBar*, *JMenu* und *JMenuItem* -```java +``` java JMenuBar menuBar = new JMenuBar(); JMenu menu1 = new JMenu("(M)ein Menü"); JMenuItem m1 = new JMenuItem("Text: A"); @@ -197,51 +186,51 @@ frame.setJMenuBar(menuBar); ``` ::: notes -Eine Menüleiste wird über das Objekt `JMenuBar` realisiert. Diese ist eine Eigenschaft des Frames -und kann nur dort hinzugefügt werden. +Eine Menüleiste wird über das Objekt `JMenuBar` realisiert. Diese ist eine +Eigenschaft des Frames und kann nur dort hinzugefügt werden. -In der Menüleiste kann es mehrere Menüs geben, diese werden mit Objekten vom Typ `JMenu` -erstellt. +In der Menüleiste kann es mehrere Menüs geben, diese werden mit Objekten vom Typ +`JMenu` erstellt. -Wenn man mit der Maus ein Menü ausklappt, wird eine Liste der Menüeinträge angezeigt. Diese -sind vom Typ `JMenuItem` und verhalten sich wie Buttons. +Wenn man mit der Maus ein Menü ausklappt, wird eine Liste der Menüeinträge +angezeigt. Diese sind vom Typ `JMenuItem` und verhalten sich wie Buttons. ::: -[Demo: widgets.MenuDemo]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/widgets/MenuDemo.java"} +[Demo: widgets.MenuDemo]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/widgets/MenuDemo.java"} +# Kontextmenü mit *JPopupMenu* -# Kontextmenü mit _JPopupMenu_ +- Menü kann über anderen Komponenten angezeigt werden -* Menü kann über anderen Komponenten angezeigt werden +- Einträge vom Typ `JMenuItem` hinzufügen (beispielsweise `JRadioButtonMenuItem`) -* Einträge vom Typ `JMenuItem` hinzufügen (beispielsweise `JRadioButtonMenuItem`) - - ```java + ``` java public JMenuItem add(JMenuItem menuItem) ``` -* Menü über der aufrufenden Komponente "`invoker`" anzeigen +- Menü über der aufrufenden Komponente "`invoker`" anzeigen - ```java + ``` java public void show(Component invoker, int x, int y) ``` ::: notes -## Details zu _JMenuItem_ +## Details zu *JMenuItem* -* Erweitert `AbstractButton` -* Reagiert auf `ActionEvent` - => `ActionListener` implementieren für Reaktion auf Menüauswahl +- Erweitert `AbstractButton` +- Reagiert auf `ActionEvent` =\> `ActionListener` implementieren für Reaktion auf + Menüauswahl ## Details zum Kontextmenü **Triggern der Anzeige eines `JPopupMenu`** -* Beispielsweise über `MouseListener` einer (anderen!) Komponente -* Darin Reaktion auf `MouseEvent.isPopupTrigger()` - => `JPopupMenu.show()` aufrufen +- Beispielsweise über `MouseListener` einer (anderen!) Komponente +- Darin Reaktion auf `MouseEvent.isPopupTrigger()` =\> `JPopupMenu.show()` + aufrufen -```java +``` java JFrame myFrame = new JFrame(); JPopupMenu kontextMenu = new JPopupMenu(); @@ -255,15 +244,26 @@ myFrame.addMouseListener(new MouseAdapter() { ``` ::: -[Demo: widgets.PopupDemo]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/widgets/PopupDemo.java"} - +[Demo: widgets.PopupDemo]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/gui/src/widgets/PopupDemo.java"} # Wrap-Up Nützliche Swing-Komponenten: -* Scroll-Bars -* Panel mit Tabs -* Dialogfenster und Dateiauswahl-Dialoge -* Menüleisten und Kontextmenü -* Radiobuttons und Checkboxen, logische Gruppierung +- Scroll-Bars +- Panel mit Tabs +- Dialogfenster und Dateiauswahl-Dialoge +- Menüleisten und Kontextmenü +- Radiobuttons und Checkboxen, logische Gruppierung + +::: readings +- @Java-SE-Tutorial +- @Ullenboom2021 [Kap. 18] +::: + +::: outcomes +- k3: Umgang mit komplexeren Swing-Komponenten: JRadioButton, JFileChooser, + JOptionPane, JTabbedPane, JScrollPane, JMenuBar, JPopupMenu +- k3: Nutzung von ActionListener, MouseListener, KeyListener, FocusListener +::: diff --git a/lecture/java-classic/annotations.md b/lecture/java-classic/annotations.md index 7041e54f4..9acb9979f 100644 --- a/lecture/java-classic/annotations.md +++ b/lecture/java-classic/annotations.md @@ -1,64 +1,35 @@ --- -title: "Annotationen" -author: "Carsten Gips (HSBI)" -readings: - - "@Ullenboom2021 [Kap. 10.8 und 23.4]" - - "@Java-SE-Tutorial" - - "@LernJava" -tldr: | - Annotationen sind Metadaten zum Programm: Sie haben keinen (direkten) Einfluss auf die - Ausführung des annotierten Codes, sondern enthalten Zusatzinformationen über ein Programm, - die selbst nicht Teil des Programms sind. Verschiedene Tools werten Annotationen aus, - beispielsweise der Compiler, Javadoc, JUnit, ... - - - Annotationen können auf Deklarationen (Klassen, Felder, Methoden) angewendet werden und - werden meist auf eine eigene Zeile geschrieben (Konvention). - - Annotationen können relativ einfach selbst erstellt werden: Die Definition ist fast wie - bei einem Interface. Zusätzlich kann man noch über Meta-Annotationen die Sichtbarkeit, - Verwendbarkeit und Dokumentation einschränken. Annotationen können zur Übersetzungszeit - mit einem Annotation-Processor verarbeitet werden oder zur Laufzeit über Reflection - ausgewertet werden. -outcomes: - - k2: "Ich kann den Begriff der 'Annotation' erklären an einem Beispiel" - - k3: "Ich kann `@Override` und auch die Javadoc-Annotationen praktisch anwenden" - - k3: "Ich kann eigene Annotationen erstellen und dabei die Sichtbarkeit und Verwendbarkeit einstellen" - - k3: "Ich kann einen eigenen einfachen Annotation-Processors erstellen" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106512&client_id=FH-Bielefeld" -# name: "Quiz Annotationen (ILIAS)" -youtube: - - link: "https://youtu.be/7u4_I4W_1JY" - name: "VL Annotationen" - - link: "https://youtu.be/wt-Dq_Nv5UU" - name: "Demo Annotationen: `@Override`, `@Deprecated`" - - link: "https://youtu.be/ayWWf_ALObs" - name: "Demo `@NotNull`" - - link: "https://youtu.be/8vTMgYCstLE" - name: "Demo Annotationen selbst gebastelt" - - link: "https://youtu.be/ypHMxunNZpg" - name: "Demo Annotation-Prozessor" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/d438c8d0b87fc2934e71e539aaf32ce01126fc2145f08284235c4c4b2058b0ab9ccba925dbbcfaf90466c70aff29f79acae1f66edeb18bd794d08628545d4e07" - name: "VL Annotationen" -challenges: | - Schreiben Sie drei eigene Annotationen: - - - `@MeineKlasse` darf nur an Klassendefinitionen stehen und speichert den Namen des Autoren ab. - - `@MeineMethode` darf nur an Methoden stehen. - - `@TODO` darf an Methoden und Klassen stehen, ist aber nur in den Source-Dateien sichtbar. - - Implementieren Sie einen Annotation-Prozessor, welcher Ihren Quellcode nach der `@MeineKlasse`-Annotation - durchsucht und dann den Namen der Klasse und den Namen des Autors ausgibt. - - Zeigen Sie die Funktionen anhand einer Demo. +author: Carsten Gips (HSBI) +title: Annotationen --- +::: tldr +Annotationen sind Metadaten zum Programm: Sie haben keinen (direkten) Einfluss auf +die Ausführung des annotierten Codes, sondern enthalten Zusatzinformationen über ein +Programm, die selbst nicht Teil des Programms sind. Verschiedene Tools werten +Annotationen aus, beispielsweise der Compiler, Javadoc, JUnit, ... + +Annotationen können auf Deklarationen (Klassen, Felder, Methoden) angewendet werden +und werden meist auf eine eigene Zeile geschrieben (Konvention). + +Annotationen können relativ einfach selbst erstellt werden: Die Definition ist fast +wie bei einem Interface. Zusätzlich kann man noch über Meta-Annotationen die +Sichtbarkeit, Verwendbarkeit und Dokumentation einschränken. Annotationen können zur +Übersetzungszeit mit einem Annotation-Processor verarbeitet werden oder zur Laufzeit +über Reflection ausgewertet werden. +::: + +::: youtube +- [VL Annotationen](https://youtu.be/7u4_I4W_1JY) +- [Demo Annotationen: `@Override`, `@Deprecated`](https://youtu.be/wt-Dq_Nv5UU) +- [Demo `@NotNull`](https://youtu.be/ayWWf_ALObs) +- [Demo Annotationen selbst gebastelt](https://youtu.be/8vTMgYCstLE) +- [Demo Annotation-Prozessor](https://youtu.be/ypHMxunNZpg) +::: # Was passiert hier? -```java +``` java public class A { public String getInfo() { return "Klasse A"; } } @@ -73,7 +44,8 @@ public class B extends A { } ``` -[[Beispiel: annotations.B]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/annotations/B.java"}]{.notes} +[[Beispiel: annotations.B]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/annotations/B.java"}]{.notes} \pause \bigskip @@ -83,75 +55,83 @@ public class B extends A { ::: notes Tja, da sollte wohl die Methode `B#getInfo` die geerbte Methode `A#getInfo` -**überschreiben**. Dummerweise wird hier die Methode aber nur **überladen** -(mit entsprechenden Folgen beim Aufruf)! +**überschreiben**. Dummerweise wird hier die Methode aber nur **überladen** (mit +entsprechenden Folgen beim Aufruf)! Ein leider relativ häufiges Versehen, welches u.U. schwer zu finden ist. -Annotationen (hier `@Override`) können dagegen helfen - der Compiler "weiß" -dann, dass wir überschreiben wollen und meckert, wenn wir das nicht tun. +Annotationen (hier `@Override`) können dagegen helfen - der Compiler "weiß" dann, +dass wir überschreiben wollen und meckert, wenn wir das nicht tun. IDEs wie Eclipse können diese Annotation bereits beim Erstellen einer Klasse generieren: `Preferences > Java > Code Style > Add @Override annotation ...`. ::: - # Annotationen: Metadaten für Dritte -* **Zusatzinformationen** für Tools, Bibliotheken, ... -* Kein direkter Einfluss auf die Ausführung des annotierten Codes +- **Zusatzinformationen** für Tools, Bibliotheken, ... +- Kein direkter Einfluss auf die Ausführung des annotierten Codes \bigskip -* Beispiele: - * Compiler (JDK): `@Override`, `@Deprecated`, ... - * Javadoc: `@author`, `@version`, `@see`, `@param`, `@return`, ... - * JUnit: `@Test`, `@Before`, `@BeforeClass`, `@After`, `@AfterClass` - * [IntelliJ](https://www.jetbrains.com/help/idea/annotating-source-code.html#jetbrains-annotations): `@NotNull`, `@Nullable` - * [Checker Framework](https://github.com/typetools/checker-framework): `@NonNull`, `@Nullable`, ... - * [Project Lombok](https://github.com/projectlombok/lombok): `@Getter`, `@Setter`, `@NonNull`, ... - * Webservices: `@WebService`, `@WebMethod` - * ... - -[Demo: annotations.B: \@Override, \@Deprecated]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/annotations/B.java"} +- Beispiele: + - Compiler (JDK): `@Override`, `@Deprecated`, ... + - Javadoc: `@author`, `@version`, `@see`, `@param`, `@return`, ... + - JUnit: `@Test`, `@Before`, `@BeforeClass`, `@After`, `@AfterClass` + - [IntelliJ](https://www.jetbrains.com/help/idea/annotating-source-code.html#jetbrains-annotations): + `@NotNull`, `@Nullable` + - [Checker Framework](https://github.com/typetools/checker-framework): + `@NonNull`, `@Nullable`, ... + - [Project Lombok](https://github.com/projectlombok/lombok): `@Getter`, + `@Setter`, `@NonNull`, ... + - Webservices: `@WebService`, `@WebMethod` + - ... + +[Demo: annotations.B: \@Override, \@Deprecated]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/annotations/B.java"} -::::::::: notes +::: notes Jetzt schauen wir uns erst einmal die Auswirkungen von `@Override` und `@Deprecated` -auf den Compiler (via Eclipse) an. -Anschließend lernen Sie die Dokumentation mittels Javadoc-Annotationen kennen. +auf den Compiler (via Eclipse) an. Anschließend lernen Sie die Dokumentation mittels +Javadoc-Annotationen kennen. Das Thema JUnit ist in einer anderen VL dran. Webservices ereilen Sie dann in späteren Semestern :-) ## \@Override -Die mit `@Override` annotierte Methode überschreibt eine Methode aus der Oberklasse oder implementiert eine -Methode einer Schnittstelle. Dies wird durch den Compiler geprüft und ggf. mit einer Fehlermeldung quittiert. +Die mit `@Override` annotierte Methode überschreibt eine Methode aus der Oberklasse +oder implementiert eine Methode einer Schnittstelle. Dies wird durch den Compiler +geprüft und ggf. mit einer Fehlermeldung quittiert. `@Override` ist eine im JDK im Paket `java.lang` enthaltene Annotation. ## \@Deprecated -Das mit `@Deprecated` markierte Element ist veraltet ("*deprecated*") und sollte nicht mehr benutzt werden. -Typischerweise werden so markierte Elemente in zukünftigen Releases aus der API entfernt ... +Das mit `@Deprecated` markierte Element ist veraltet ("*deprecated*") und sollte +nicht mehr benutzt werden. Typischerweise werden so markierte Elemente in +zukünftigen Releases aus der API entfernt ... -Die Annotation `@Deprecated` wird direkt im Code verwendet und entspricht der Annotation `@deprecated` -im Javadoc. Allerdings kann letzteres nur von Javadoc ausgewertet werden. +Die Annotation `@Deprecated` wird direkt im Code verwendet und entspricht der +Annotation `@deprecated` im Javadoc. Allerdings kann letzteres nur von Javadoc +ausgewertet werden. `@Deprecated` ist eine im JDK im Paket `java.lang` enthaltene Annotation. -## Weitere Annotationen aus _java.lang_ - -Im Paket `java.lang` finden sich weitere Annotationen. Mit Hilfe von `@SuppressWarnings` lassen sich bestimmte -Compilerwarnungen unterdrücken (**so etwas sollte man NIE tun!**), und mit `@FunctionalInterface` -lassen sich Schnittstellen auszeichnen, die genau eine (abstrakte) Methode besitzen (Verweis auf spätere Vorlesung). +## Weitere Annotationen aus *java.lang* -Weitere Annotationen aus dem JDK finden sich in den Paketen `java.lang.annotation` und `javax.annotation`. -::::::::: +Im Paket `java.lang` finden sich weitere Annotationen. Mit Hilfe von +`@SuppressWarnings` lassen sich bestimmte Compilerwarnungen unterdrücken (**so etwas +sollte man NIE tun!**), und mit `@FunctionalInterface` lassen sich Schnittstellen +auszeichnen, die genau eine (abstrakte) Methode besitzen (Verweis auf spätere +Vorlesung). +Weitere Annotationen aus dem JDK finden sich in den Paketen `java.lang.annotation` +und `javax.annotation`. +::: # Dokumentation mit Javadoc -```java +``` java /** * Beschreibung Beschreibung Beschreibung * @@ -169,36 +149,38 @@ public boolean setDate(int date) { Die Dokumentation mit Javadoc hatten wir uns bereits in der Einheit ["Javadoc"](../quality/javadoc.md) angesehen. -Hier noch einmal exemplarisch die wichtigsten Elemente, die an -"`public`" sichtbaren Methoden verwendet werden. +Hier noch einmal exemplarisch die wichtigsten Elemente, die an "`public`" sichtbaren +Methoden verwendet werden. ::: -[[Beispiel: annotations.B (Javadoc)]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/annotations/B.java"}]{.notes} - +[[Beispiel: annotations.B (Javadoc)]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/annotations/B.java"}]{.notes} # \@NotNull mit IntelliJ ::: notes -[IntelliJ](https://www.jetbrains.com/help/idea/annotating-source-code.html) bietet im Paket -`org.jetbrains.annotations` u.a. die Annotation `@NotNull` an. +[IntelliJ](https://www.jetbrains.com/help/idea/annotating-source-code.html) bietet +im Paket `org.jetbrains.annotations` u.a. die Annotation `@NotNull` an. -Damit lassen sich Rückgabewerte von Methoden sowie Variablen (Attribute, lokale Variablen, Parameter) markieren: -Diese dürfen nicht `null` werden. +Damit lassen sich Rückgabewerte von Methoden sowie Variablen (Attribute, lokale +Variablen, Parameter) markieren: Diese dürfen nicht `null` werden. -IntelliJ prüft beim Compilieren, dass diese Elemente nicht `null` werden und warnt gegebenenfalls (zur Compilezeit). -Zusätzlich baut IntelliJ entsprechende Assertions in den Code ein, die zur Laufzeit einen `null`-Wert abfangen -und dann das Programm abbrechen. +IntelliJ prüft beim Compilieren, dass diese Elemente nicht `null` werden und warnt +gegebenenfalls (zur Compilezeit). Zusätzlich baut IntelliJ entsprechende Assertions +in den Code ein, die zur Laufzeit einen `null`-Wert abfangen und dann das Programm +abbrechen. -Dadurch können entsprechende Dokumentationen im Javadoc und/oder manuelle Überprüfungen im Code entfallen. -Außerdem hat man durch die Annotation gewissermaßen einen sichtbaren Vertrag (_Contract_) mit den Nutzern -der Methode. Bei einem Aufruf mit `null` würde dieser Contract verletzt und eine entsprechende Exception -geworfen (automatisch) statt einfach das Programm und die JVM "abzuschießen". +Dadurch können entsprechende Dokumentationen im Javadoc und/oder manuelle +Überprüfungen im Code entfallen. Außerdem hat man durch die Annotation gewissermaßen +einen sichtbaren Vertrag (*Contract*) mit den Nutzern der Methode. Bei einem Aufruf +mit `null` würde dieser Contract verletzt und eine entsprechende Exception geworfen +(automatisch) statt einfach das Programm und die JVM "abzuschießen". -Nachteil: Die entsprechende Bibliothek muss bei allen Entwicklern vorhanden und in das Projekt eingebunden -sein. +Nachteil: Die entsprechende Bibliothek muss bei allen Entwicklern vorhanden und in +das Projekt eingebunden sein. ::: -```java +``` java /* o should not be null */ public void bar(Object o) { int i; @@ -212,7 +194,7 @@ public void bar(Object o) { \bigskip \pause -```java +``` java /* o must not be null */ public void foo(@NotNull Object o) { // assert(o != null); // Wirkung (von IntelliJ eingefügt) @@ -220,10 +202,11 @@ public void foo(@NotNull Object o) { } ``` -[Demo: annotations.WuppieAnnotation: \@NotNull]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/annotations/WuppieAnnotation.java"} +[Demo: annotations.WuppieAnnotation: \@NotNull]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/annotations/WuppieAnnotation.java"} ::: notes -## IntelliJ inferiert mit \@NotNull mögliche _null_-Werte +## IntelliJ inferiert mit \@NotNull mögliche *null*-Werte ![](images/screenshot_infer-notnull.png) @@ -232,20 +215,20 @@ public void foo(@NotNull Object o) { ![](images/screenshot_nullpointerexception-notnull.png) ::: - # Eigene Annotationen erstellen ::: notes -In Java kann man sich selbst Annotationen definieren mit dem Schlüsselwort `@interface`. +In Java kann man sich selbst Annotationen definieren mit dem Schlüsselwort +`@interface`. Annotationen können Parameter bekommen und im Javadoc dokumentiert sein. Es gibt die -Möglichkeit zu definieren, wo im Code die eigenen Annotationen verwendet werden dürfen -(Auszeichnung mit der Annotation `@Target`). Ebenso kann über die Annotation `@Retention` -festgelegt werden, wann im Verarbeitungsprozess der Java-Sourcen bzw. des -Bytecodes die -eigenen Annotationen sichtbar sein sollen. +Möglichkeit zu definieren, wo im Code die eigenen Annotationen verwendet werden +dürfen (Auszeichnung mit der Annotation `@Target`). Ebenso kann über die Annotation +`@Retention` festgelegt werden, wann im Verarbeitungsprozess der Java-Sourcen bzw. +des -Bytecodes die eigenen Annotationen sichtbar sein sollen. ::: -```java +``` java public @interface MyFirstAnnotation {} public @interface MyThirdAnnotation { @@ -259,27 +242,29 @@ public @interface MyThirdAnnotation { public class C {} ``` -[Demo: annotations.C]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/annotations/C.java"} +[Demo: annotations.C]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/annotations/C.java"} -::::::::: notes +::: notes ## Definition einer Annotation -Definition einer Annotation wie Interface, aber mit "`@`"-Zeichen vor dem `interface`-Schlüsselwort +Definition einer Annotation wie Interface, aber mit "`@`"-Zeichen vor dem +`interface`-Schlüsselwort ## Parameter für Annotation Parameter für Annotation werden über entsprechende Methoden-Deklaration realisiert -* "Rückgabetyp" der deklarierten "Methode" ist der erlaubte Typ der später +- "Rückgabetyp" der deklarierten "Methode" ist der erlaubte Typ der später verwendeten Parameter -* Name der "Methoden" wird bei der Belegung der Parameter verwendet, +- Name der "Methoden" wird bei der Belegung der Parameter verwendet, beispielsweise `author = ...` -* Vereinfachung: "Methodenname" `value` erlaubt das Weglassen des - Schlüsselworts bei der Verwendung: +- Vereinfachung: "Methodenname" `value` erlaubt das Weglassen des Schlüsselworts + bei der Verwendung: - ```java + ``` java public @interface MySecondAnnotation { String value(); } @@ -291,47 +276,44 @@ Parameter für Annotation werden über entsprechende Methoden-Deklaration realis public class E {} ``` -* Defaultwerte mit dem nachgestellten Schlüsselwort `default` sowie dem +- Defaultwerte mit dem nachgestellten Schlüsselwort `default` sowie dem Defaultwert selbst ## Dokumentation der Annotation mit/im Javadoc -Soll die Annotation in der Javadoc-Doku dargestellt werden, muss sie mit -der Meta-Annotation `@Documented` ausgezeichnet werden (aus +Soll die Annotation in der Javadoc-Doku dargestellt werden, muss sie mit der +Meta-Annotation `@Documented` ausgezeichnet werden (aus `java.lang.annotation.Documented`) -*Hinweis*: Die Annotation wird lediglich in die Doku aufgenommen, d.h. es -erfolgt keine weitere Verarbeitung oder Hervorhebung o.ä. +*Hinweis*: Die Annotation wird lediglich in die Doku aufgenommen, d.h. es erfolgt +keine weitere Verarbeitung oder Hervorhebung o.ä. ## Wann ist eine Annotation sichtbar (Beschränkung der Sichtbarkeit mit `@Retention`) -Annotationen werden vom Compiler und/oder anderen Tools ausgewertet. Man -kann entsprechend die Sichtbarkeit einer Annotation beschränken: Sie kann -ausschließlich im Source-Code verfügbar sein, sie kann in der generierten -Class-Datei eingebettet sein oder sie kann sogar zur Laufzeit (mittels -*Reflection*, vgl. spätere Vorlesung) ausgelesen werden. +Annotationen werden vom Compiler und/oder anderen Tools ausgewertet. Man kann +entsprechend die Sichtbarkeit einer Annotation beschränken: Sie kann ausschließlich +im Source-Code verfügbar sein, sie kann in der generierten Class-Datei eingebettet +sein oder sie kann sogar zur Laufzeit (mittels *Reflection*, vgl. spätere Vorlesung) +ausgelesen werden. Beschränkung der Sichtbarkeit: Meta-Annotation `@Retention` aus `java.lang.annotation.Retention` -* `RetentionPolicy.SOURCE`: Nur Bestandteil der Source-Dateien, wird nicht - in kompilierten Code eingebettet -* `RetentionPolicy.CLASS`: Wird vom Compiler in die Class-Datei eingebettet, - steht aber zur Laufzeit *nicht* zur Verfügung (Standardwert, wenn nichts - angegeben) -* `RetentionPolicy.RUNTIME`: Wird vom Compiler in die Class-Datei - eingebettet und steht zur Laufzeit zur Verfügung und kann via - *Reflection*^[Reflection ist Thema einer späteren Vorlesung] ausgelesen - werden +- `RetentionPolicy.SOURCE`: Nur Bestandteil der Source-Dateien, wird nicht in + kompilierten Code eingebettet +- `RetentionPolicy.CLASS`: Wird vom Compiler in die Class-Datei eingebettet, steht + aber zur Laufzeit *nicht* zur Verfügung (Standardwert, wenn nichts angegeben) +- `RetentionPolicy.RUNTIME`: Wird vom Compiler in die Class-Datei eingebettet und + steht zur Laufzeit zur Verfügung und kann via *Reflection*[^1] ausgelesen werden -Ohne explizite Angabe gilt für die selbst definierte Annotation die -Einstellung `RetentionPolicy.CLASS`. +Ohne explizite Angabe gilt für die selbst definierte Annotation die Einstellung +`RetentionPolicy.CLASS`. ## Wo darf eine Annotation verwendet werden ### Anwendungsmöglichkeiten von Annotationen im Code -```java +``` java @ClassAnnotation public class Wuppie { @InstanceFieldAnnotation @@ -352,56 +334,58 @@ public class Wuppie { ### Einschränkung des Einsatzes eines Annotation mit `@Target` -Für jede Annotation kann eingeschränkt werden, wo (an welchen Java-Elementen) -sie verwendet werden darf. +Für jede Annotation kann eingeschränkt werden, wo (an welchen Java-Elementen) sie +verwendet werden darf. Beschränkung der Verwendung: Meta-Annotation `@Target` aus `java.lang.annotation.Target` -* `ElementType.TYPE`: alle Typdeklarationen: Klassen, Interfaces, Enumerations, ... -* `ElementType.CONSTRUCTOR`: nur Konstruktoren -* `ElementType.METHOD`: nur Methoden -* `ElementType.FIELD`: nur statische Variablen und Objektvariablen -* `ElementType.PARAMETER`: nur Parametervariablen -* `ElementType.PACKAGE`: nur an Package-Deklarationen - -Ohne explizite Angabe ist die selbst definierte Annotation für alle -Elemente verwendbar. -::::::::: +- `ElementType.TYPE`: alle Typdeklarationen: Klassen, Interfaces, Enumerations, + ... +- `ElementType.CONSTRUCTOR`: nur Konstruktoren +- `ElementType.METHOD`: nur Methoden +- `ElementType.FIELD`: nur statische Variablen und Objektvariablen +- `ElementType.PARAMETER`: nur Parametervariablen +- `ElementType.PACKAGE`: nur an Package-Deklarationen +Ohne explizite Angabe ist die selbst definierte Annotation für alle Elemente +verwendbar. +::: -# Annotationen [bei Compilieren]{ .notes} bearbeiten: Java Annotation-Prozessoren +# Annotationen [beim Compilieren]{.notes} bearbeiten: Java Annotation-Prozessoren ::: notes Der dem `javac`-Compiler vorgelegte Source-Code wird eingelesen und in einen -entsprechenden Syntax-Tree (_AST_) transformiert (dazu mehr im Modul -"Compilerbau" :) +entsprechenden Syntax-Tree (*AST*) transformiert (dazu mehr im Modul "Compilerbau" +:) -Anschließend können sogenannte "Annotation Processors" über den AST laufen und -ihre Analysen machen und/oder den AST modifizieren. (Danach kommen die üblichen -weiteren Analysen und die Code-Generierung.) +Anschließend können sogenannte "Annotation Processors" über den AST laufen und ihre +Analysen machen und/oder den AST modifizieren. (Danach kommen die üblichen weiteren +Analysen und die Code-Generierung.) -(Vgl. [OpenJDK: Compilation Overview](https://openjdk.java.net/groups/compiler/doc/compilation-overview/index.html).) +(Vgl. [OpenJDK: Compilation +Overview](https://openjdk.java.net/groups/compiler/doc/compilation-overview/index.html).) An dieser Stelle kann man sich einklinken und einen eigenen Annotation-Prozessor ausführen lassen. Zur Abgrenzung: Diese Auswertung der Annotationen findet zur **Compile-Zeit** statt! In einer späteren Vorlesung werden wir noch über die Auswertung zur **Laufzeit** sprechen: *Reflection*. -Im Prinzip muss man lediglich das Interface `javax.annotation.processing.Processor` -implementieren oder die abstrakte Klasse `javax.annotation.processing.AbstractProcessor` -erweitern. Für die Registrierung im `javac` muss im Projekt (oder Jar-File) die -Datei `META-INF/services/javax.annotation.processing.Processor` angelegt werden, -die den vollständigen Namen des Annotation-Prozessors enthält. Dieser Annotation-Prozessor +Im Prinzip muss man lediglich das Interface `javax.annotation.processing.Processor` +implementieren oder die abstrakte Klasse +`javax.annotation.processing.AbstractProcessor` erweitern. Für die Registrierung im +`javac` muss im Projekt (oder Jar-File) die Datei +`META-INF/services/javax.annotation.processing.Processor` angelegt werden, die den +vollständigen Namen des Annotation-Prozessors enthält. Dieser Annotation-Prozessor wird dann vom `javac` aufgerufen und läuft in einer eigenen JVM. Er kann die -Annotationen, für die er registriert ist, auslesen und verarbeiten und neue Java-Dateien -schreiben, die wiederum eingelesen und compiliert werden. +Annotationen, für die er registriert ist, auslesen und verarbeiten und neue +Java-Dateien schreiben, die wiederum eingelesen und compiliert werden. Im nachfolgenden Beispiel beschränke ich mich auf das Definieren und Registrieren eines einfachen Annotation-Prozessors, der lediglich die Annotationen liest. ::: -```java +``` java @SupportedAnnotationTypes("annotations.MySecondAnnotation") @SupportedSourceVersion(SourceVersion.RELEASE_21) public class Foo extends AbstractProcessor { @@ -418,90 +402,125 @@ public class Foo extends AbstractProcessor { } ``` -[Demo: annotations.C und annotations.Foo, META-INF]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/java-classic/src/META-INF/"} +[Demo: annotations.C und annotations.Foo, META-INF]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/java-classic/src/META-INF/"} -::::::::: notes +::: notes 1. Der Annotation-Processor sollte von `AbstractProcessor` ableiten -2. Über `@SupportedAnnotationTypes` teilt man mit, für welche Annotationen sich der Prozessor interessiert - (d.h. für welche er aufgerufen wird); "`*`" oder eine Liste mit String ist auch möglich -3. Mit `@SupportedSourceVersion` wird die (höchste) unterstützte Java-Version angegeben - (neuere Versionen führen zu einer Warnung) +2. Über `@SupportedAnnotationTypes` teilt man mit, für welche Annotationen sich der + Prozessor interessiert (d.h. für welche er aufgerufen wird); "`*`" oder eine + Liste mit String ist auch möglich +3. Mit `@SupportedSourceVersion` wird die (höchste) unterstützte Java-Version + angegeben (neuere Versionen führen zu einer Warnung) 4. Die Methode `process` erledigt die Arbeit: - * Der erste Parameter enthält alle gefundenen Annotationen, für die der Processor registriert ist - * Der zweite Parameter enthält die damit annotierten Elemente - * Iteration: Die äußere Schleife läuft über alle gefundenen Annotationen, die innere über die - mit der jeweiligen Annotation versehenen Elemente - * Jetzt könnte man mit den Elementen etwas sinnvolles anfangen, beispielsweise alle Attribute - sammeln, die mit `@Getter` markiert sind und für diese neuen Code generieren - * Im Beispiel wird lediglich der eigene Logger (`processingEnv.getMessager()`) aufgerufen, - um beim Compiliervorgang eine Konsolenmeldung zu erzeugen ... -5. Der Annotation-Processor darf keine Exception werfen, da sonst der Compiliervorgang abgebrochen - würde. Zudem wäre der Stack-Trace der des Annotation-Processors und nicht der des compilierten - Programms ... Stattdessen wird ein Boolean zurückgeliefert, um anzudeuten, ob die Verarbeitung - geklappt hat. - -Für ein umfangreicheres Beispiel mit Code-Erzeugung vergleiche beispielsweise die Artikelserie unter + - Der erste Parameter enthält alle gefundenen Annotationen, für die der + Processor registriert ist + - Der zweite Parameter enthält die damit annotierten Elemente + - Iteration: Die äußere Schleife läuft über alle gefundenen Annotationen, die + innere über die mit der jeweiligen Annotation versehenen Elemente + - Jetzt könnte man mit den Elementen etwas sinnvolles anfangen, beispielsweise + alle Attribute sammeln, die mit `@Getter` markiert sind und für diese neuen + Code generieren + - Im Beispiel wird lediglich der eigene Logger (`processingEnv.getMessager()`) + aufgerufen, um beim Compiliervorgang eine Konsolenmeldung zu erzeugen ... +5. Der Annotation-Processor darf keine Exception werfen, da sonst der + Compiliervorgang abgebrochen würde. Zudem wäre der Stack-Trace der des + Annotation-Processors und nicht der des compilierten Programms ... Stattdessen + wird ein Boolean zurückgeliefert, um anzudeuten, ob die Verarbeitung geklappt + hat. + +Für ein umfangreicheres Beispiel mit Code-Erzeugung vergleiche beispielsweise die +Artikelserie unter [cloudogu.com/en/blog/Java-Annotation-Processors_1-Intro](https://cloudogu.com/en/blog/Java-Annotation-Processors_1-Intro). -Siehe auch [OpenJDK: Compilation Overview](https://openjdk.java.net/groups/compiler/doc/compilation-overview/index.html). +Siehe auch [OpenJDK: Compilation +Overview](https://openjdk.java.net/groups/compiler/doc/compilation-overview/index.html). -Im Projekt muss jetzt noch der Ordner `META-INF/services/` angelegt werden mit der Datei -`javax.annotation.processing.Processor`. Deren Inhalt ist für das obige Beispiel die Zeile -`annotations.Foo`. Damit ist der Annotation-Processor `annotations.Foo` für das Übersetzen im eigenen -Projekt registriert. +Im Projekt muss jetzt noch der Ordner `META-INF/services/` angelegt werden mit der +Datei `javax.annotation.processing.Processor`. Deren Inhalt ist für das obige +Beispiel die Zeile `annotations.Foo`. Damit ist der Annotation-Processor +`annotations.Foo` für das Übersetzen im eigenen Projekt registriert. -Zum Compilieren des Annotation-Processors selbst ruft man beispielsweise folgenden Befehl auf: +Zum Compilieren des Annotation-Processors selbst ruft man beispielsweise folgenden +Befehl auf: -``` -javac -cp . -proc:none annotations/Foo.java -``` + javac -cp . -proc:none annotations/Foo.java -Die Option `-proc:none` sorgt für das Beispiel dafür, dass beim Compilieren des Annotation-Processors -dieser nicht bereits aufgerufen wird (was sonst wg. der Registrierung über -`META-INF/services/javax.annotation.processing.Processor` passieren würde). +Die Option `-proc:none` sorgt für das Beispiel dafür, dass beim Compilieren des +Annotation-Processors dieser nicht bereits aufgerufen wird (was sonst wg. der +Registrierung über `META-INF/services/javax.annotation.processing.Processor` +passieren würde). Zum Compilieren der Klasse `C` kann man wie sonst auch den Befehl nutzen: -``` -javac -cp . annotations/C.java -``` + javac -cp . annotations/C.java -Dabei läuft dann der Annotation-Processor `annotations.Foo` und erzeugt beim Verarbeiten von `annotations.C` -die folgende Ausgabe: - -``` -Note: found @MySecondAnnotation at main(java.lang.String[]) -``` -::::::::: +Dabei läuft dann der Annotation-Processor `annotations.Foo` und erzeugt beim +Verarbeiten von `annotations.C` die folgende Ausgabe: + Note: found @MySecondAnnotation at main(java.lang.String[]) +::: # Wrap-Up -* Annotationen: Metadaten zum Programm +- Annotationen: Metadaten zum Programm ::: notes - * **Zusatzinformationen** über ein Programm, aber nicht selbst Teil des Programms - * **Kein (direkter) Einfluss** auf die **Ausführung** des annotierten Codes + - **Zusatzinformationen** über ein Programm, aber nicht selbst Teil des + Programms + - **Kein (direkter) Einfluss** auf die **Ausführung** des annotierten Codes ::: \smallskip -* Typische Anwendungen: Compiler-Hinweise, Javadoc, Tests +- Typische Anwendungen: Compiler-Hinweise, Javadoc, Tests ::: notes - * Compiler: Erkennen von logischen Fehlern, Unterdrücken von - Warnungen => `java.lang`: `@Override`, - `@Deprecated`, `@SuppressWarnings` - * Javadoc: Erkennen von Schlüsselwörtern (`@author`, `@return`, `@param`, ...) - * JUnit: Erkennen von Tests-Methoden (`@Test`) - * ... + - Compiler: Erkennen von logischen Fehlern, Unterdrücken von Warnungen =\> + `java.lang`: `@Override`, `@Deprecated`, `@SuppressWarnings` + - Javadoc: Erkennen von Schlüsselwörtern (`@author`, `@return`, `@param`, ...) + - JUnit: Erkennen von Tests-Methoden (`@Test`) + - ... ::: \bigskip -* Annotationen können auf Deklarationen (Klassen, Felder, Methoden) angewendet werden -* Annotationen können relativ einfach selbst erstellt werden - * Definition fast wie ein Interface - * Einstellung der Sichtbarkeit und Verwendbarkeit und Dokumentation - über Meta-Annotationen -* Verarbeitung von Annotationen zur Compilier-Zeit mit Annotation-Processor -* Verarbeitung von Annotationen zur Laufzeit mit Reflection (siehe spätere VL) +- Annotationen können auf Deklarationen (Klassen, Felder, Methoden) angewendet + werden +- Annotationen können relativ einfach selbst erstellt werden + - Definition fast wie ein Interface + - Einstellung der Sichtbarkeit und Verwendbarkeit und Dokumentation über + Meta-Annotationen +- Verarbeitung von Annotationen zur Compilier-Zeit mit Annotation-Processor +- Verarbeitung von Annotationen zur Laufzeit mit Reflection (siehe spätere VL) + +::: readings +- @Ullenboom2021 [Kap. 10.8 und 23.4] +- @Java-SE-Tutorial +- @LernJava +::: + +::: outcomes +- k2: Ich kann den Begriff der 'Annotation' erklären an einem Beispiel +- k3: Ich kann `@Override` und auch die Javadoc-Annotationen praktisch anwenden +- k3: Ich kann eigene Annotationen erstellen und dabei die Sichtbarkeit und + Verwendbarkeit einstellen +- k3: Ich kann einen eigenen einfachen Annotation-Processors erstellen +::: + +::: challenges +Schreiben Sie drei eigene Annotationen: + +- `@MeineKlasse` darf nur an Klassendefinitionen stehen und speichert den Namen + des Autoren ab. +- `@MeineMethode` darf nur an Methoden stehen. +- `@TODO` darf an Methoden und Klassen stehen, ist aber nur in den Source-Dateien + sichtbar. + +Implementieren Sie einen Annotation-Prozessor, welcher Ihren Quellcode nach der +`@MeineKlasse`-Annotation durchsucht und dann den Namen der Klasse und den Namen des +Autors ausgibt. + +Zeigen Sie die Funktionen anhand einer Demo. +::: + +[^1]: Reflection ist Thema einer späteren Vorlesung diff --git a/lecture/java-classic/collections.md b/lecture/java-classic/collections.md index eec8136f0..903da712e 100644 --- a/lecture/java-classic/collections.md +++ b/lecture/java-classic/collections.md @@ -1,42 +1,29 @@ --- -title: "Java Collections Framework" -author: "André Matutat & Carsten Gips (HSBI)" -readings: - - "@LernJava" -tldr: | - Die Collection-API bietet verschiedene Sammlungen an, mit denen man Objekte speichern kann: Listen, Queues, Mengen, ... - Für diese Typen gibt es jeweils verschiedene Implementierungen mit einem spezifischen Verhalten. Zusätzlich gibt es noch - Maps für das Speichern von Key/Value-Paaren, dabei wird für die Keys eine Hash-Tabelle eingesetzt. - - Die Hilfs-Klasse `Collections` bietet statische Hilfs-Methoden, die auf `Collection`s anwendbar sind. - - Wenn man eigene Klassen in der Collection-API oder in Map benutzen möchte, sollte man den "equals-hashCode-Contract" - berücksichtigen. -outcomes: - - k2: "Was ist der Unterschied zwischen `Collection` und `List`?" - - k2: "Was ist der Unterschied zwischen einer `List`, einer `Queue` und einer `Set`?" - - k2: "Nennen Sie charakteristische Merkmale von `ArrayList`, `LinkedList` und `Vector`." - - k2: "Was ist der Unterschied zwischen einer `Queue` und einem `Stack`?" - - k2: "Was ist eine `Map`? Welche Vertreter kennen Sie?" - - k3: "Erklären Sie die 'Spielregeln' für die eigene Implementierung von `equals()`." - - k3: "Erklären Sie die 'Spielregeln' für die eigene Implementierung von `hashCode()`." - - k3: "Erklären Sie die 'Spielregeln' für die eigene Implementierung von `compareTo()`." - - k3: "Wie müssen und wie sollten `equals()`, `hashCode()` und `compareTo()` miteinander arbeiten?" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106513&client_id=FH-Bielefeld" -# name: "Quiz Collections (ILIAS)" -youtube: - - link: "https://youtu.be/QRYVt9FNY-4" - name: "VL Collections" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/8dca2e36ac9656873b0d6509557d6934b1a8a38c2b730a30b2fef7f67b42dc11ef8fc7bfea4e819bfa026364201a41f38c86d0aeaccfb456b94d1e25feebc2fe" - name: "VL Collections" +author: André Matutat & Carsten Gips (HSBI) +title: Java Collections Framework --- +::: tldr +Die Collection-API bietet verschiedene Sammlungen an, mit denen man Objekte +speichern kann: Listen, Queues, Mengen, ... Für diese Typen gibt es jeweils +verschiedene Implementierungen mit einem spezifischen Verhalten. Zusätzlich gibt es +noch Maps für das Speichern von Key/Value-Paaren, dabei wird für die Keys eine +Hash-Tabelle eingesetzt. + +Die Hilfs-Klasse `Collections` bietet statische Hilfs-Methoden, die auf +`Collection`s anwendbar sind. + +Wenn man eigene Klassen in der Collection-API oder in Map benutzen möchte, sollte +man den "equals-hashCode-Contract" berücksichtigen. +::: + +::: youtube +- [VL Collections](https://youtu.be/QRYVt9FNY-4) +::: # Motivation: Snippet aus einer Klasse im PM-Dungeon -```java +``` java private List entities = new ArrayList<>(); public void add(Entity e){ @@ -53,21 +40,20 @@ Entität geprüft, ob diese bereits in der Liste ist. Hier wird die **falsche Datenstruktur** genutzt! -Eine Liste kann ein Objekt mehrfach enthalten, eine Menge (_Set_) hingegen kann ein -Objekt nur _einmal_ enthalten. +Eine Liste kann ein Objekt mehrfach enthalten, eine Menge (*Set*) hingegen kann ein +Objekt nur *einmal* enthalten. ::: - # Collection-API in Java ![](images/collection.png){width="72%"} ::: notes -_Hinweis_: Die abstrakten (Zwischen-) Klassen wurden im obigen UML aus Gründen der -Übersichtlichkeit _nicht_ aufgeführt. Aus den selben Gründen sind auch nur ausgewählte -Methoden aufgenommen worden. +*Hinweis*: Die abstrakten (Zwischen-) Klassen wurden im obigen UML aus Gründen der +Übersichtlichkeit *nicht* aufgeführt. Aus den selben Gründen sind auch nur +ausgewählte Methoden aufgenommen worden. -_Hinweis_: Blau = Interface, Grün = Klasse. +*Hinweis*: Blau = Interface, Grün = Klasse. `Collection` ist ein zentrales Interface im JDK und stellt die gemeinsame API der Collection-Klassen dar. Klassen, die `Collection` implementieren, speichern und @@ -81,31 +67,33 @@ Reihenfolge der Elemente halten (Reihenfolge des Einfügens, aufsteigende Werte und "unsorted" (ungeordnete) Collections, welche keine bestimmte Reihenfolge halten. Eine Übersicht, welche Collection welche Datenstruktur implementiert, kann unter -["Collection Implementations"](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/doc-files/coll-overview.html) +["Collection +Implementations"](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/doc-files/coll-overview.html) eingesehen werden. -* `List`-Collections sind eine geordnete Liste an Objekten. Per Index-Zugriff können - Objekte an jeder Stelle der Liste zugegriffen (oder hinzugefügt) werden. -* `Queue`-Collections sind eine geordnete Sammlung von Objekten. Objekte können nur - am Ende der Queue hinzugefügt werden und nur am Anfang der Queue (der Head) gelesen - oder entnommen werden ("first in first out"). -* `Set`-Collections sind eine (i.d.R.!) ungeordnete Menge an Objekten, die stets nur - einmal in der Set enthalten sein können. In einem Set kann nicht direkt auf ein Objekt - zugegriffen werden. Es kann aber geprüft werden, ob ein spezifisches Objekt in einer - Set gespeichert ist. - -**Wichtig**: `List`, `Set`, `Queue` und `Map` sind **Interfaces**, definieren -also bestimmte Schnittstellen, die sich so wie aus ADS her bekannt verhalten. Diese können -jeweils mit sehr unterschiedlichen Datenstrukturen implementiert werden und können dadurch -auch intern ein anderes Verhalten haben (sortiert vs. nicht sortiert, Zugriffszeiten, ...). - -Siehe auch [Interface Collection](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Collection.html). +- `List`-Collections sind eine geordnete Liste an Objekten. Per Index-Zugriff + können Objekte an jeder Stelle der Liste zugegriffen (oder hinzugefügt) werden. +- `Queue`-Collections sind eine geordnete Sammlung von Objekten. Objekte können + nur am Ende der Queue hinzugefügt werden und nur am Anfang der Queue (der Head) + gelesen oder entnommen werden ("first in first out"). +- `Set`-Collections sind eine (i.d.R.!) ungeordnete Menge an Objekten, die + stets nur einmal in der Set enthalten sein können. In einem Set kann nicht + direkt auf ein Objekt zugegriffen werden. Es kann aber geprüft werden, ob ein + spezifisches Objekt in einer Set gespeichert ist. + +**Wichtig**: `List`, `Set`, `Queue` und `Map` sind **Interfaces**, +definieren also bestimmte Schnittstellen, die sich so wie aus ADS her bekannt +verhalten. Diese können jeweils mit sehr unterschiedlichen Datenstrukturen +implementiert werden und können dadurch auch intern ein anderes Verhalten haben +(sortiert vs. nicht sortiert, Zugriffszeiten, ...). + +Siehe auch [Interface +Collection](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Collection.html). ::: +# Listen: *ArrayList* -# Listen: _ArrayList_ - -```java +``` java private List entities = new ArrayList<>(); ``` @@ -115,7 +103,8 @@ private List entities = new ArrayList<>(); ![](images/arraylist.png){width="80%"} ::: notes -[Link zu einer netten Animation](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/images/arraylist.gif) +[Link zu einer netten +Animation](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/images/arraylist.gif) Eine `ArrayList` ist von außen betrachtet ein sich dynamisch vergrößerndes Array. @@ -128,23 +117,24 @@ Dank es Arrays kann auf ein Element per Index mit O(1) zugegriffen werden. Wird ein Element aus der Liste gelöscht, rücken alle Nachfolgenden Einträge in der Liste einen Index auf (interner Kopiervorgang). -Deshalb ist eine `ArrayList` effizient in der Abfrage und Manipulation von Einträgen, -aber deutlich weniger effizient beim Hinzufügen und Löschen von Einträgen. +Deshalb ist eine `ArrayList` effizient in der Abfrage und Manipulation von +Einträgen, aber deutlich weniger effizient beim Hinzufügen und Löschen von +Einträgen. -Per Default wird eine `ArrayList` mit einem Array der Länge 10 angelegt, sobald das -erste Element eingefügt wird. Man kann die Startgröße auch im Konstruktoraufruf der -`ArrayList` bestimmen: beispielsweise `new ArrayList<>(20)`. +Per Default wird eine `ArrayList` mit einem Array der Länge 10 angelegt, sobald +das erste Element eingefügt wird. Man kann die Startgröße auch im Konstruktoraufruf +der `ArrayList` bestimmen: beispielsweise `new ArrayList<>(20)`. Die Methoden einer `ArrayList` sind nicht `synchronized`. ::: - -# Listen: _LinkedList_ +# Listen: *LinkedList* ![](images/linkedlist.png){width="80%"} ::: notes -[Link zu einer netten Animation](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/images/linkedlist.gif) +[Link zu einer netten +Animation](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/images/linkedlist.gif) Eine `LinkedList` ist eine Implementierung einer doppelt verketteten Liste (diese kennen Sie bereits aus ADS) in Java. @@ -152,42 +142,40 @@ kennen Sie bereits aus ADS) in Java. Jeder Eintrag wird als Knoten repräsentiert, der den eigentlichen Wert speichert und zusätzlich je einen Verweis auf den Vorgänger- und Nachfolger-Knoten hat. -Der Head der `LinkedList` zeigt auf den Anfang der Liste, der Nachfolger des letzten -Eintrag ist immer `null`. +Der Head der `LinkedList` zeigt auf den Anfang der Liste, der Nachfolger des +letzten Eintrag ist immer `null`. Für den Zugriff auf ein Element muß man die `LinkedList` traversieren und beginnt dabei am Anfang der Liste, deshalb ist ein Zugriff O(n). -Neue Elemente können effizient an das Ende der Liste eingefügt werden, indem der letzte -Eintrag einen Verweis auf den neuen Knoten bekommt: O(1) (sofern man sich nicht nur den -Start der Liste merkt, sondern auch das aktuelle Ende). +Neue Elemente können effizient an das Ende der Liste eingefügt werden, indem der +letzte Eintrag einen Verweis auf den neuen Knoten bekommt: O(1) (sofern man sich +nicht nur den Start der Liste merkt, sondern auch das aktuelle Ende). -Wenn ein Element aus der Liste gelöscht wird, muss dieses zunächst gefundenen werden und -die Liste danach neu verkettete werden: O(n). +Wenn ein Element aus der Liste gelöscht wird, muss dieses zunächst gefundenen werden +und die Liste danach neu verkettete werden: O(n). Die Methoden einer `LinkedList` sind nicht `synchronized`. ::: +# *Vector* und *Stack* -# _Vector_ und _Stack_ - -* `Vector`: - * Ein `Vector` ähnelt einer `ArrayList` - * Das Array eines Vector wird jedoch verdoppelt, wenn es vergrößert wird - * Die Methoden von `Vector` sind `synchronized` +- `Vector`: + - Ein `Vector` ähnelt einer `ArrayList` + - Das Array eines Vector wird jedoch verdoppelt, wenn es vergrößert wird + - Die Methoden von `Vector` sind `synchronized` \bigskip -* `Stack`: - * Schnittstelle: "last in first out"-Prinzip - * `push(T)`: Pushe Element oben auf den Stack - * `pop(): T`: Hole oberstes Element vom Stack - * Tatsächlich aber: `class Stack extends Vector` +- `Stack`: + - Schnittstelle: "last in first out"-Prinzip + - `push(T)`: Pushe Element oben auf den Stack + - `pop(): T`: Hole oberstes Element vom Stack + - Tatsächlich aber: `class Stack extends Vector` +# Iterierbarkeit: *Iterable* und *Iterator* -# Iterierbarkeit: _Iterable_ und _Iterator_ - -```java +``` java private List entities = new ArrayList<>(); for (Entity e : entities) { ... } @@ -200,214 +188,253 @@ entities.forEach(x -> ...); ![](images/iteratoruml.png){width="80%"} ::: notes -Die Klassen aus der Collection-API implementieren das Interface `Iterable` und sind damit -iterierbar. Man kann sie darüber in einer klassischen `for`-Schleife nutzen, oder mit der -Methode `forEach()` direkt über die Sammlung laufen. - -Intern wird dabei ein passender `Iterator` erzeugt, der die Elemente der Sammlung schrittweise -mit der Methode `next()` zurückgibt. Mithilfe eines Cursor merkt sich der Iterator, bei welchem -Eintrag der Datenstruktur er aktuell ist. Mit der Methode `hasNext()`kann geprüft werden, ob noch -ein weiteres Element über den Iterator aus der Datenstruktur verfügbar ist. +Die Klassen aus der Collection-API implementieren das Interface `Iterable` und +sind damit iterierbar. Man kann sie darüber in einer klassischen `for`-Schleife +nutzen, oder mit der Methode `forEach()` direkt über die Sammlung laufen. -Mit `remove()`kann das letzte zurückgegebene Element aus der Datenstruktur entfernt werden. Diese -Methode ist im Interface als Default-Methode implementiert. +Intern wird dabei ein passender `Iterator` erzeugt, der die Elemente der Sammlung +schrittweise mit der Methode `next()` zurückgibt. Mithilfe eines Cursor merkt sich +der Iterator, bei welchem Eintrag der Datenstruktur er aktuell ist. Mit der Methode +`hasNext()`kann geprüft werden, ob noch ein weiteres Element über den Iterator aus +der Datenstruktur verfügbar ist. +Mit `remove()`kann das letzte zurückgegebene Element aus der Datenstruktur entfernt +werden. Diese Methode ist im Interface als Default-Methode implementiert. -Damit kann man die Datenstrukturen auf eine von der Datenstruktur vorgegebene Weise ablaufen, -beispielsweise einen Binärbaum. +Damit kann man die Datenstrukturen auf eine von der Datenstruktur vorgegebene Weise +ablaufen, beispielsweise einen Binärbaum. -[Link zu einer netten Animation](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/images/iterator.gif) +[Link zu einer netten +Animation](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/images/iterator.gif) -Man kann auch selbst für eigene Klassen einen passenden `Iterator` implementieren, der zum Ablaufen -der Elemente der eigenen Klasse genutzt werden kann. Damit die eigene Klasse auch in einer `for`-Schleife -genutzt werden kann, muss sie aber auch noch `Iterable` implementieren. +Man kann auch selbst für eigene Klassen einen passenden `Iterator` +implementieren, der zum Ablaufen der Elemente der eigenen Klasse genutzt werden +kann. Damit die eigene Klasse auch in einer `for`-Schleife genutzt werden kann, muss +sie aber auch noch `Iterable` implementieren. - -[Beispiel: iterator_example.*]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/java-classic/src/collections/iterator_example/"} +[Beispiel: iterator_example.\*]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/java-classic/src/collections/iterator_example/"} ::: - -# Hilfsklasse _Collections_ +# Hilfsklasse *Collections* ![](images/collections.png){width="70%" web_width="50%"} ::: notes -`Collections` ist eine Utility-Klasse mit statischen Methoden, die auf `Collection`s ausgeführt werden. -Diese Methoden nutzen das `Collection`-Interface und/oder die `Iterable`-Schnittstelle. - -Siehe auch [Class Collections](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Collections.html). - -Der Hintergrund für diese in Java nicht unübliche Aufsplittung in ein Interface und eine Utility-Klasse -ist, dass bis vor kurzem Interface nur Schnittstellen definieren konnten. Erst seit einigen Java-Versionen -kann in Interfaces auch Verhalten definiert werden (Default-Methoden). Aus heutiger Sicht würde man also -vermutlich die statischen Methoden in der Klasse `Collections` eher direkt als Default-Methoden im Interface -`Collection` implementieren und bereitstellen, statt eine separate Utility-Klasse zu definieren. +`Collections` ist eine Utility-Klasse mit statischen Methoden, die auf +`Collection`s ausgeführt werden. Diese Methoden nutzen das +`Collection`-Interface und/oder die `Iterable`-Schnittstelle. + +Siehe auch [Class +Collections](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Collections.html). + +Der Hintergrund für diese in Java nicht unübliche Aufsplittung in ein Interface und +eine Utility-Klasse ist, dass bis vor kurzem Interface nur Schnittstellen definieren +konnten. Erst seit einigen Java-Versionen kann in Interfaces auch Verhalten +definiert werden (Default-Methoden). Aus heutiger Sicht würde man also vermutlich +die statischen Methoden in der Klasse `Collections` eher direkt als Default-Methoden +im Interface `Collection` implementieren und bereitstellen, statt eine separate +Utility-Klasse zu definieren. ::: - -# _Map_ +# *Map* ![](images/map.png){width="55%" web_width="50%"} ::: notes -_Hinweis_: Die abstrakten (Zwischen-) Klassen wurden im obigen UML aus Gründen der -Übersichtlichkeit _nicht_ aufgeführt. Aus den selben Gründen sind auch nur ausgewählte -Methoden aufgenommen worden. +*Hinweis*: Die abstrakten (Zwischen-) Klassen wurden im obigen UML aus Gründen der +Übersichtlichkeit *nicht* aufgeführt. Aus den selben Gründen sind auch nur +ausgewählte Methoden aufgenommen worden. -_Hinweis_: Blau = Interface, Grün = Klasse. +*Hinweis*: Blau = Interface, Grün = Klasse. -_Hinweis_: Tatsächlich ist der Typ des Keys in den Methoden `get()` und `remove()` mit `Object` -spezifiziert und nicht mit dem Typ-Parameter `K`. Das ist aus meiner Sicht eine Inkonsistenz in -der API. +*Hinweis*: Tatsächlich ist der Typ des Keys in den Methoden `get()` und `remove()` +mit `Object` spezifiziert und nicht mit dem Typ-Parameter `K`. Das ist aus meiner +Sicht eine Inkonsistenz in der API. -Eine `Map` speichert Objekte als Key/Value-Paar mit den Typen `K` (Key) und `V` (Value). +Eine `Map` speichert Objekte als Key/Value-Paar mit den Typen `K` (Key) und `V` +(Value). -Dabei sind die Keys in einer Map einzigartig und werden verwendet, um auf das jeweilige Value -zuzugreifen. Ein Value kann entsprechend (mit unterschiedlichen Keys) mehrfach im einer Map -enthalten sein. +Dabei sind die Keys in einer Map einzigartig und werden verwendet, um auf das +jeweilige Value zuzugreifen. Ein Value kann entsprechend (mit unterschiedlichen +Keys) mehrfach im einer Map enthalten sein. -Es gibt eine Reihe verschiedener Implementierungen, die unterschiedliche Datenstrukturen -einsetzen, beispielsweise: +Es gibt eine Reihe verschiedener Implementierungen, die unterschiedliche +Datenstrukturen einsetzen, beispielsweise: -* `HashMap` hält keine Ordnung in den Einträgen. Verwendet den Hashwert, um Objekte zu - speichern. Zugriff auf Einträge in einer `HashMap` ist O(1). -* `LinkedHashMap` hält die Einträge in der Reihenfolge, in der sie eingefügt wurden. -* `TreeMap` hält die Einträge in aufsteigender Reihenfolge. +- `HashMap` hält keine Ordnung in den Einträgen. Verwendet den Hashwert, um + Objekte zu speichern. Zugriff auf Einträge in einer `HashMap` ist O(1). +- `LinkedHashMap` hält die Einträge in der Reihenfolge, in der sie eingefügt + wurden. +- `TreeMap` hält die Einträge in aufsteigender Reihenfolge. -Siehe auch [Interface Map](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Map.html). +Siehe auch [Interface +Map](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Map.html). ::: - -# _HashMap_ +# *HashMap* ![](images/hashmap.png){width="72%" web_width="60%"} ::: notes -Eine `HashMap` speichert die Elemente in mehreren einfach verketteten Listen. Dafür -verwendet sie die innere Klasse `Node`. +Eine `HashMap` speichert die Elemente in mehreren einfach verketteten Listen. +Dafür verwendet sie die innere Klasse `Node`. -Die Heads, die auf den Anfang einer Liste zeigen, werden in "Buckets" gespeichert. Initial -besitzt eine HashMap 12 Buckets, diese werden bei Bedarf erweitert. +Die Heads, die auf den Anfang einer Liste zeigen, werden in "Buckets" gespeichert. +Initial besitzt eine HashMap 12 Buckets, diese werden bei Bedarf erweitert. -Um einen Eintrag hinzufügen, wird zunächst aus dem `hashCode()` des Key-Objektes mithilfe der -Hash-Funktion der Index des Buckets berechnet. Ist der Bucket gefunden, wird geprüft, ob das -Objekt dort schon vorkommt: Mit dem `hashCode()` des Key-Objektes werden alle Objekte in der -Liste des Buckets verglichen. Wenn es Einträge mit dem selben `hashCode()` in der Liste gibt, -wird mit `equals` geprüft, ob die Key-Objekte identisch sind. Ist dies der Fall, wird der -existierende Eintrag überschrieben, anderenfalls wird der neue Eintrag an das Ende der Liste -hinzugefügt. +Um einen Eintrag hinzufügen, wird zunächst aus dem `hashCode()` des Key-Objektes +mithilfe der Hash-Funktion der Index des Buckets berechnet. Ist der Bucket gefunden, +wird geprüft, ob das Objekt dort schon vorkommt: Mit dem `hashCode()` des +Key-Objektes werden alle Objekte in der Liste des Buckets verglichen. Wenn es +Einträge mit dem selben `hashCode()` in der Liste gibt, wird mit `equals` geprüft, +ob die Key-Objekte identisch sind. Ist dies der Fall, wird der existierende Eintrag +überschrieben, anderenfalls wird der neue Eintrag an das Ende der Liste hinzugefügt. -Implementierungsdetail: Wenn die Listen zu groß werden, wird die Hashtabelle neu angelegt mit -ungefähr der doppelten Anzahl der Einträge (Buckets) und die alten Einträge per _Re-Hash_ neu -verteilt (vgl. [Class HashMap](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/HashMap.html)). +Implementierungsdetail: Wenn die Listen zu groß werden, wird die Hashtabelle neu +angelegt mit ungefähr der doppelten Anzahl der Einträge (Buckets) und die alten +Einträge per *Re-Hash* neu verteilt (vgl. [Class +HashMap](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/HashMap.html)). `HashMap` Methoden sind nicht `synchronized`. -`HashMap` unterstützt einen `null`-Key. Es darf beliebig viele `null`-Values geben. - -Die Unterklasse `LinkedHashMap` kann Ordnung zwischen den Elementen halten. Dafür wird -eine doppelt verkettete Liste verwendet. +`HashMap` unterstützt einen `null`-Key. Es darf beliebig viele `null`-Values +geben. +Die Unterklasse `LinkedHashMap` kann Ordnung zwischen den Elementen halten. +Dafür wird eine doppelt verkettete Liste verwendet. -[Beispiel: hash_example.HashCodeExample]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/collections/hash_example/HashCodeExample.java"} +[Beispiel: hash_example.HashCodeExample]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/collections/hash_example/HashCodeExample.java"} ::: +# *Hashtable* -# _Hashtable_ +- Nicht zu verwechseln mit der Datenstruktur: Hash-Tabellen (!) +- `Hashtable` ist vergleichbar mit einer `HashMap` +- `Hashtable`-Methoden sind `synchronized` +- Kein Key oder Value darf `null` sein -* Nicht zu verwechseln mit der Datenstruktur: Hash-Tabellen (!) -* `Hashtable` ist vergleichbar mit einer `HashMap` -* `Hashtable`-Methoden sind `synchronized` -* Kein Key oder Value darf `null` sein - - -::::::::: notes -# Spielregeln für _equals()_, _hashCode()_ und _compareTo()_ +::: notes +# Spielregeln für *equals()*, *hashCode()* und *compareTo()* -## _equals()_ +## *equals()* -`boolean equals(Object o)` ist eine Methode Klasse `Object` und wird genutzt, um Objekte auf Gleichheit zu -prüfen. Die Default-Implementierung von `equals()` in `Object` vergleicht die beiden Objekte mit `==`, gibt -also nur dann `true` zurück, wenn die beiden zu vergleichenden Objekte die selbe Objekt-ID haben. +`boolean equals(Object o)` ist eine Methode Klasse `Object` und wird genutzt, um +Objekte auf Gleichheit zu prüfen. Die Default-Implementierung von `equals()` in +`Object` vergleicht die beiden Objekte mit `==`, gibt also nur dann `true` zurück, +wenn die beiden zu vergleichenden Objekte die selbe Objekt-ID haben. -In der Praxis kann es sich anbieten, diese Methode zu überschreiben und eigene Kriterien für Gleichheit -aufzustellen. +In der Praxis kann es sich anbieten, diese Methode zu überschreiben und eigene +Kriterien für Gleichheit aufzustellen. Dabei sind Spielregeln zu beachten (für nicht-`null` Objekte `x`, `y` und `z`): 1. Reflexivität: `x.equals(x) == true` 2. Symmetrie: `x.equals(y) == y.equals(x)` -3. Transitivität: Wenn `x.equals(y) == true` und `y.equals(z) == true`, dann auch `x.equals(z) == true` -4. Konsistenz: Mehrfache Aufrufe von `equals()` mit den selben Werten müssen immer das selbe Ergebnis liefern +3. Transitivität: Wenn `x.equals(y) == true` und `y.equals(z) == true`, dann auch + `x.equals(z) == true` +4. Konsistenz: Mehrfache Aufrufe von `equals()` mit den selben Werten müssen immer + das selbe Ergebnis liefern 5. `x.equals(null) == false` -## _hashCode()_ +## *hashCode()* -Die Methode `int hashCode()` gibt den Hash-Wert eines Objektes zurück. Der Hash-Wert eins Objektes wird genutzt, -um dieses in einen Hash-basierten Container abzulegen bzw. zu finden. +Die Methode `int hashCode()` gibt den Hash-Wert eines Objektes zurück. Der Hash-Wert +eins Objektes wird genutzt, um dieses in einen Hash-basierten Container abzulegen +bzw. zu finden. -Der Rückgabewert der Methode `hashCode()` für ein Objekt bleibt über die Laufzeit einer Anwendung immer identisch, -solange sich die zur Prüfung der Gleichheit genutzten Attribute nicht ändern. +Der Rückgabewert der Methode `hashCode()` für ein Objekt bleibt über die Laufzeit +einer Anwendung immer identisch, solange sich die zur Prüfung der Gleichheit +genutzten Attribute nicht ändern. -## _compareTo()_ +## *compareTo()* -Die Methode `int compareTo()` (Interface `Comparable`) vergleicht Objekte und definiert damit eine Ordnung -auf den Objekten. Während `equals()` für die Prüfung auf Gleichheit eingesetzt wird, wird `compareTo()` für die -Sortierung von Objekten untereinander verwendet. +Die Methode `int compareTo()` (Interface `Comparable`) vergleicht Objekte und +definiert damit eine Ordnung auf den Objekten. Während `equals()` für die Prüfung +auf Gleichheit eingesetzt wird, wird `compareTo()` für die Sortierung von Objekten +untereinander verwendet. Spielregeln: -1. `x.compareTo(y) < 0` wenn `x` "kleiner" als `y` ist -2. `x.compareTo(y) > 0` wenn `x` "größer" als `y` ist -3. `x.compareTo(y) = 0` wenn `x` "gleich" als `y` ist +1. `x.compareTo(y) < 0` wenn `x` "kleiner" als `y` ist +2. `x.compareTo(y) > 0` wenn `x` "größer" als `y` ist +3. `x.compareTo(y) = 0` wenn `x` "gleich" als `y` ist 4. Symmetrie: `signum(x.compareTo(y)) == -signum(y.compareTo(x))` -4. Transitivität: Wenn `x.compareTo(y) > 0` und `y.compareTo(z) > 0`, dann auch `x.compareTo(z) > 0` -5. Wenn `x.compareTo(y) == 0`, dann auch `signum(x.compareTo(z)) == signum(y.compareTo(z))` +5. Transitivität: Wenn `x.compareTo(y) > 0` und `y.compareTo(z) > 0`, dann auch + `x.compareTo(z) > 0` +6. Wenn `x.compareTo(y) == 0`, dann auch + `signum(x.compareTo(z)) == signum(y.compareTo(z))` -## Der _equals()_-_hashCode()_-_compareTo()_-Vertrag -::::::::: +## Der *equals()*-*hashCode()*-*compareTo()*-Vertrag +::: ::: slides -# Der _equals()_-_hashCode()_-_compareTo()_-Vertrag +# Der *equals()*-*hashCode()*-*compareTo()*-Vertrag ::: -**Wird `equals()` überschrieben, sollte auch `hashCode()` (passend) überschrieben werden.** +**Wird `equals()` überschrieben, sollte auch `hashCode()` (passend) überschrieben +werden.** \bigskip \bigskip -1. Wenn `x.equals(y) == true`, dann _muss_ auch `x.hashCode() == y.hashCode()` - -2. Wenn `x.equals(y) == false`, _sollte_ `x.hashCode() != y.hashCode()` sein - [(Unterschiedliche `hashCode()`-Werte für unterschiedliche Objekte verbessern allerdings die Leistung - von Hash-Berechnungen, etwa in einer `HashMap`!)]{.notes} +1. Wenn `x.equals(y) == true`, dann *muss* auch `x.hashCode() == y.hashCode()` -3. [Es wird sehr empfohlen, dass `equals()` und `compareTo()` konsistente Ergebnisse liefern:]{.notes} - `x.compareTo(y) == 0` gdw. `x.equals(y) == true` - [(Dies _muss_ aber nicht zwingend eingehalten werden, sorgt dann aber u.U. für unerwartete Nebeneffekte - beim Umgang mit `Collection` und `Map`!)]{.notes} +2. Wenn `x.equals(y) == false`, *sollte* `x.hashCode() != y.hashCode()` sein + [(Unterschiedliche `hashCode()`-Werte für unterschiedliche Objekte verbessern + allerdings die Leistung von Hash-Berechnungen, etwa in einer + `HashMap`!)]{.notes} +3. [Es wird sehr empfohlen, dass `equals()` und `compareTo()` konsistente + Ergebnisse liefern:]{.notes} `x.compareTo(y) == 0` gdw. `x.equals(y) == true` + [(Dies *muss* aber nicht zwingend eingehalten werden, sorgt dann aber u.U. für + unerwartete Nebeneffekte beim Umgang mit `Collection` und + `Map`!)]{.notes} ::: notes -[Beispiel: hash_example.HashCodeExample]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/collections/hash_example/HashCodeExample.java"} +[Beispiel: hash_example.HashCodeExample]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/collections/hash_example/HashCodeExample.java"} ::: - ::: notes # Überblick ![](images/collections_table.png){width="80%"} -Komplexitätswerte beziehen sich auf den Regelfall. Sonderfälle wie das Vergrößern des Array einer -`ArrayList` können für temporär erhöhte Komplexität sorgen (das ist dem O-Kalkül aber egal). +Komplexitätswerte beziehen sich auf den Regelfall. Sonderfälle wie das Vergrößern +des Array einer `ArrayList` können für temporär erhöhte Komplexität sorgen (das +ist dem O-Kalkül aber egal). ::: - # Wrap-Up -* Interface `Collection`: Schnittstelle für Datenstrukturen/Sammlungen - [zur Verwaltung einer Menge von Objekten]{.notes} -* Klasse `Collections`: Statische Hilfs-Methoden [(anwendbar auf `Collection`s)]{.notes} -* `Iterable` liefert einen `Iterator` zur Iteration über eine `Collection` -* Interface `Map`: Speichern von Key/Value-Paaren -* `equals()`-`hashCode()`-`compareTo()`-Vertrag beachten +- Interface `Collection`: Schnittstelle für Datenstrukturen/Sammlungen [zur + Verwaltung einer Menge von Objekten]{.notes} +- Klasse `Collections`: Statische Hilfs-Methoden [(anwendbar auf + `Collection`s)]{.notes} +- `Iterable` liefert einen `Iterator` zur Iteration über eine + `Collection` +- Interface `Map`: Speichern von Key/Value-Paaren +- `equals()`-`hashCode()`-`compareTo()`-Vertrag beachten + +::: readings +- @LernJava +::: + +::: outcomes +- k2: Was ist der Unterschied zwischen `Collection` und `List`? +- k2: Was ist der Unterschied zwischen einer `List`, einer `Queue` und einer + `Set`? +- k2: Nennen Sie charakteristische Merkmale von `ArrayList`, `LinkedList` + und `Vector`. +- k2: Was ist der Unterschied zwischen einer `Queue` und einem `Stack`? +- k2: Was ist eine `Map`? Welche Vertreter kennen Sie? +- k3: Erklären Sie die 'Spielregeln' für die eigene Implementierung von equals(). +- k3: Erklären Sie die 'Spielregeln' für die eigene Implementierung von + hashCode(). +- k3: Erklären Sie die 'Spielregeln' für die eigene Implementierung von + compareTo(). +- k3: Wie müssen und wie sollten equals(), hashCode() und compareTo() miteinander + arbeiten? +::: diff --git a/lecture/java-classic/configuration.md b/lecture/java-classic/configuration.md index 950011920..7aa26c285 100644 --- a/lecture/java-classic/configuration.md +++ b/lecture/java-classic/configuration.md @@ -1,45 +1,33 @@ --- -title: "Konfiguration eines Programms" -author: "Carsten Gips (HSBI)" -readings: - - "@Java-SE-Tutorial" -tldr: | - Zu Konfiguration von Programmen kann man beim Aufruf Kommandozeilenparameter mitgeben. Diese - sind in der über den Parameter`String[] args` in der `main(String[] args)`-Methode zugreifbar. - - Es gibt oft eine Kurzversion ("-x") und/oder eine Langversion ("--breite"). Zusätzlich können - Parameter noch ein Argument haben ("-x 12" oder "--breite=12"). Parameter können optional oder - verpflichtend sein. - - Um dies nicht manuell auswerten zu müssen, kann man beispielsweise die Bibliothkek Apache - Commons CLI benutzen. - - Ein anderer Weg zur Konfiguration sind Konfigurationsdateien, die man entsprechend einliest. - Hier findet man häufig das "Ini-Format", also zeilenweise "Key=Value"-Paare. Diese kann man - mit der Klasse `java.util.Properties` einlesen, bearbeiten und speichern (auch als XML). -outcomes: - - k3: "Auswertung von Kommandozeilenparametern in einem Programm" - - k3: "Apache Commons CLI zur Verarbeitung von Kommandozeilenparametern" - - k3: "Laden von Konfigurationsdaten mit `java.util.Properties`" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106514&client_id=FH-Bielefeld" -# name: "Quiz Konfiguration (ILIAS)" -youtube: - - link: "https://youtu.be/ImkyRx4UL9M" - name: "VL Konfiguration" - - link: "https://youtu.be/a3XUfDbD9uo" - name: "Demo Setzen von Kommandozeilenparametern (IDE)" - - link: "https://youtu.be/cmMDxdT69ZQ" - name: "Demo Einbinden von Libs in IDE" - - link: "https://youtu.be/DwKNQpLX4xI" - name: "Demo Apache Commons CLI" - - link: "https://youtu.be/or1t_2vld2E" - name: "Demo Properties" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/94963b57dcea201ea1cce6d9d0d61732c823ebc117710448516a259243afd239fdbaf9c10be3bdfa3eb0c0f4e9c4fc3c847eb74b8977bfa8b921d2d02ec325f4" - name: "VL Konfiguration" +author: Carsten Gips (HSBI) +title: Konfiguration eines Programms --- +::: tldr +Zu Konfiguration von Programmen kann man beim Aufruf Kommandozeilenparameter +mitgeben. Diese sind in der über den Parameter`String[] args` in der +`main(String[] args)`-Methode zugreifbar. + +Es gibt oft eine Kurzversion ("-x") und/oder eine Langversion ("--breite"). +Zusätzlich können Parameter noch ein Argument haben ("-x 12" oder "--breite=12"). +Parameter können optional oder verpflichtend sein. + +Um dies nicht manuell auswerten zu müssen, kann man beispielsweise die Bibliothkek +Apache Commons CLI benutzen. + +Ein anderer Weg zur Konfiguration sind Konfigurationsdateien, die man entsprechend +einliest. Hier findet man häufig das "Ini-Format", also zeilenweise +"Key=Value"-Paare. Diese kann man mit der Klasse `java.util.Properties` einlesen, +bearbeiten und speichern (auch als XML). +::: + +::: youtube +- [VL Konfiguration](https://youtu.be/ImkyRx4UL9M) +- [Demo Setzen von Kommandozeilenparametern (IDE)](https://youtu.be/a3XUfDbD9uo) +- [Demo Einbinden von Libs in IDE](https://youtu.be/cmMDxdT69ZQ) +- [Demo Apache Commons CLI](https://youtu.be/DwKNQpLX4xI) +- [Demo Properties](https://youtu.be/or1t_2vld2E) +::: # Wie kann man Programme konfigurieren? @@ -49,73 +37,72 @@ fhmedia: 2. Konfigurationsdatei einlesen und auswerten - # Varianten von Kommandozeilenparameter -* Fixe Reihenfolge +- Fixe Reihenfolge `java MyApp 10 20 hello debug` \smallskip -* Benannte Parameter I +- Benannte Parameter I `java MyApp -x 10 -y 20 -answer hello -d` \smallskip -* Benannte Parameter II +- Benannte Parameter II `java MyApp --breite=10 --hoehe=20 --answer=hello --debug` \bigskip + Häufig Mischung von Kurz- und Langformen ::: notes -Häufig hat man eine Kurzform der Optionen, also etwa "-x". Dabei ist der Name der Option -in der Regel ein Zeichen lang. Es gibt aber auch Abweichungen von dieser Konvention, denken -Sie beispielsweise an `java -version`. - -In der Langform nutzt man dann einen aussagekräftigen Namen und stellt zwei Bindestriche -voran, also beispielsweise "--breite" (als Alternative für "-x"). - -Wenn Optionen Parameter haben, schreibt man in der Kurzform üblicherweise "-x 10" (trennt -also den Parameter mit einem Leerzeichen von der Option) und in der Langform "--breite=10" -(also mit einem "=" zwischen Option und Parameter). Das sind ebenfalls Konventionen, d.h. -man kann prinzipiell auch in der Kurzform das "=" nutzen, also "-x=10", oder in der Langform -mit einem Leerzeichen trennen, also "--breite 10". +Häufig hat man eine Kurzform der Optionen, also etwa "-x". Dabei ist der Name der +Option in der Regel ein Zeichen lang. Es gibt aber auch Abweichungen von dieser +Konvention, denken Sie beispielsweise an `java -version`. + +In der Langform nutzt man dann einen aussagekräftigen Namen und stellt zwei +Bindestriche voran, also beispielsweise "--breite" (als Alternative für "-x"). + +Wenn Optionen Parameter haben, schreibt man in der Kurzform üblicherweise "-x 10" +(trennt also den Parameter mit einem Leerzeichen von der Option) und in der Langform +"--breite=10" (also mit einem "=" zwischen Option und Parameter). Das sind ebenfalls +Konventionen, d.h. man kann prinzipiell auch in der Kurzform das "=" nutzen, also +"-x=10", oder in der Langform mit einem Leerzeichen trennen, also "--breite 10". ::: [Demo IDE und CLI]{.ex href="https://youtu.be/a3XUfDbD9uo"} ::: notes -Hinweis IntelliJ: "`Edit Configurations`" => Kommandozeilenparameter unter "`Build and run`" im entsprechenden Feld eintragen +Hinweis IntelliJ: "`Edit Configurations`" =\> Kommandozeilenparameter unter +"`Build and run`" im entsprechenden Feld eintragen ![](images/ide-cli.png) ::: - # Auswertung Kommandozeilenparameter -* Kommandozeilenparameter [werden]{.notes} als String-Array [an `main()`-Methode übergeben:]{.notes} +- Kommandozeilenparameter [werden]{.notes} als String-Array [an `main()`-Methode + übergeben:]{.notes} - ```java + ``` java public static void main(String[] args) { } public static void main(String... argv) { } ``` - => Müssen "händisch" ausgewertet werden - + =\> Müssen "händisch" ausgewertet werden \bigskip \bigskip -_Anmerkung_: Nur Parameter! Nicht Programmname als erster Eintrag wie in C ... - +*Anmerkung*: Nur Parameter! Nicht Programmname als erster Eintrag wie in C ... # Beispiel Auswertung Kommandozeilenparameter -```java +``` java public static void main(String[] args) { int x = 100; String answer = ""; @@ -134,23 +121,21 @@ public static void main(String[] args) { ::: notes ## Kritik an manueller Auswertung Kommandozeilenparameter -* Umständlich und unübersichtlich -* Große `if-else`-Gebilde in `main()` -* Kurz- und Langform müssen getrennt realisiert werden -* Optionale Parameter müssen anders geprüft werden als Pflichtparameter -* Überlappende Parameternamen schwer aufzufinden -* Prüfung auf korrekten Typ nötig bei Parametern mit Werten -* Hilfe bei Fehlern muss separat realisiert und gepflegt werden +- Umständlich und unübersichtlich +- Große `if-else`-Gebilde in `main()` +- Kurz- und Langform müssen getrennt realisiert werden +- Optionale Parameter müssen anders geprüft werden als Pflichtparameter +- Überlappende Parameternamen schwer aufzufinden +- Prüfung auf korrekten Typ nötig bei Parametern mit Werten +- Hilfe bei Fehlern muss separat realisiert und gepflegt werden ::: - # Apache Commons: CLI **Rad nicht neu erfinden!** -* Apache Commons bietet die CLI-Bibliothek zum Umgang mit - Kommandozeilenparametern an: - [commons.apache.org/cli](https://commons.apache.org/proper/commons-cli/) +- Apache Commons bietet die CLI-Bibliothek zum Umgang mit Kommandozeilenparametern + an: [commons.apache.org/cli](https://commons.apache.org/proper/commons-cli/) \pause \bigskip @@ -158,57 +143,62 @@ public static void main(String[] args) { Annäherung an fremde API: -* Lesen der verfügbaren Doku (PDF, HTML) -* Lesen der verfügbaren Javadoc -* Herunterladen der Bibliothek -* Einbinden ins Projekt - +- Lesen der verfügbaren Doku (PDF, HTML) +- Lesen der verfügbaren Javadoc +- Herunterladen der Bibliothek +- Einbinden ins Projekt # Exkurs: Einbinden fremder Bibliotheken/APIs ::: notes ## Eclipse -* Lib von [commons.apache.org](https://commons.apache.org/proper/commons-cli/download_cli.cgi) +- Lib von + [commons.apache.org](https://commons.apache.org/proper/commons-cli/download_cli.cgi) herunterladen und auspacken -* Neuen Unterordner im Projekt anlegen: `libs/` -* Bibliothek (`.jar`-Files) hinein kopieren -* Projektexplorer, Kontextmenü auf `.jar`-File: "`Add as Library`" -* Alternativ Menü-Leiste: "`Project > Properties > Java Build Path > Libraries > Add JARs`" +- Neuen Unterordner im Projekt anlegen: `libs/` +- Bibliothek (`.jar`-Files) hinein kopieren +- Projektexplorer, Kontextmenü auf `.jar`-File: "`Add as Library`" +- Alternativ Menü-Leiste: + "`Project > Properties > Java Build Path > Libraries > Add JARs`" ## IntelliJ -* Variante 1: - * Lib von [commons.apache.org](https://commons.apache.org/proper/commons-cli/download_cli.cgi) +- Variante 1: + - Lib von + [commons.apache.org](https://commons.apache.org/proper/commons-cli/download_cli.cgi) herunterladen und auspacken - * Neuen Unterordner im Projekt anlegen: `libs/` - * Bibliothek (`.jar`-Files) hinein kopieren - * Variante 1 (a):Projektexplorer, Kontextmenü auf `.jar`-File: "`Build Path > Add to Build Path`" - * Variante 1 (b): Projekteigenschaften, Eintrag "Libraries", "+", "New Project Library", "Java" und Jar-File auswählen -* Variante 2: - * Projekteigenschaften, Eintrag "Libraries", "+", "New Project Library", "From Maven" und - "commons-cli:commons-cli:1.5.0" als Suchstring eingeben und die Suche abschließen + - Neuen Unterordner im Projekt anlegen: `libs/` + - Bibliothek (`.jar`-Files) hinein kopieren + - Variante 1 (a):Projektexplorer, Kontextmenü auf `.jar`-File: + "`Build Path > Add to Build Path`" + - Variante 1 (b): Projekteigenschaften, Eintrag "Libraries", "+", "New Project + Library", "Java" und Jar-File auswählen +- Variante 2: + - Projekteigenschaften, Eintrag "Libraries", "+", "New Project Library", "From + Maven" und "commons-cli:commons-cli:1.5.0" als Suchstring eingeben und die + Suche abschließen ## Gradle oder Ant oder Maven -* Lib auf [Maven Central](https://search.maven.org/) suchen: "commons-cli:commons-cli" als Suchstring eingeben -* Passenden Dependency-Eintrag in das Build-Skript kopieren +- Lib auf [Maven Central](https://search.maven.org/) suchen: + "commons-cli:commons-cli" als Suchstring eingeben +- Passenden Dependency-Eintrag in das Build-Skript kopieren ::: ## Kommandozeilenaufruf -* Class-Path bei Aufruf setzen: - * Unix: `java -cp .:: ` - * Windows: `java -cp .;; ` +- Class-Path bei Aufruf setzen: + - Unix: `java -cp .:: ` + - Windows: `java -cp .;; ` ::: notes - Achtung: Unter Unix (Linux, MacOS) wird ein Doppelpunkt zum Trennen - der Jar-Files eingesetzt, unter Windows ein Semikolon! + Achtung: Unter Unix (Linux, MacOS) wird ein Doppelpunkt zum Trennen der + Jar-Files eingesetzt, unter Windows ein Semikolon! ::: Beispiel: `java -classpath .:/home/user/wuppy.jar MyApp` - ::: notes Vorgriff auf Build-Skripte (spätere VL): Im hier gezeigten Vorgehen werden die Abhängigkeiten manuell aufgelöst, d.h. die Jar-Files werden manuell heruntergeladen @@ -221,7 +211,6 @@ von spezialisierten Servern in der im Skript definierten Version heruntergeladen Dies funktioniert auch bei rekursiven Abhängigkeiten ... ::: - # Überblick Umgang mit Apache Commons CLI **Paket**: `org.apache.commons.cli` @@ -229,51 +218,48 @@ Dies funktioniert auch bei rekursiven Abhängigkeiten ... \bigskip 1. Definition der Optionen - * Je Option eine Instanz der Klasse `Option` - * Alle Optionen in Container `Options` sammeln + - Je Option eine Instanz der Klasse `Option` + - Alle Optionen in Container `Options` sammeln 2. Parsen der Eingaben mit `DefaultParser` 3. Abfragen der Ergebnisse: `CommandLine` 4. Formatierte Hilfe ausgeben: `HelpFormatter` ::: notes -Die Funktionsweise der einzelnen Klassen wird in der Demo kurz angerissen. Schauen Sie bitte -zusätzlich in die Dokumentation. +Die Funktionsweise der einzelnen Klassen wird in der Demo kurz angerissen. Schauen +Sie bitte zusätzlich in die Dokumentation. ::: -[Demo: Einbinden von Libs, cli.Args]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/cli/Args.java"} - +[Demo: Einbinden von Libs, cli.Args]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/cli/Args.java"} # Laden und Speichern von Konfigurationsdaten -``` -#ola - ein Kommentar -hoehe=2 -breite=9 -gewicht=12 -``` + #ola - ein Kommentar + hoehe=2 + breite=9 + gewicht=12 \bigskip -* Konfigurationsdaten sind i.d.R. Schlüssel-Wert-Paare (`String`/`String`) +- Konfigurationsdaten sind i.d.R. Schlüssel-Wert-Paare (`String`/`String`) - => `java.util.Properties` + =\> `java.util.Properties` ::: notes Tatsächlich verbirgt sich ein `Hashtable` dahinter: - ```java + ``` java public class Properties extends Hashtable; ``` ::: - # Laden und Speichern von Konfigurationsdaten (cnt.) \bigskip -* Properties anlegen und modifizieren +- Properties anlegen und modifizieren - ```java + ``` java Properties props = new Properties(); props.setProperty("breite", "9"); props.setProperty("breite", "99"); @@ -282,27 +268,28 @@ gewicht=12 \bigskip -* Properties speichern: `Properties#store` und `Properties#storeToXML` +- Properties speichern: `Properties#store` und `Properties#storeToXML` ::: notes - ```java + ``` java public void store(Writer writer, String comments) public void store(OutputStream out, String comments) public void storeToXML(OutputStream os, String comment, String encoding) ``` ::: -* Properties laden: `Properties#load` und `Properties#loadFromXML` +- Properties laden: `Properties#load` und `Properties#loadFromXML` ::: notes - ```java + ``` java public void load(Reader reader) public void load(InputStream inStream) public void loadFromXML(InputStream in) ``` ::: -[Demo: cli.Props, Hinweis auf "Apache Commons Configuration"]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/cli/Props.java"} +[Demo: cli.Props, Hinweis auf "Apache Commons Configuration"]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/cli/Props.java"} ::: notes `java.util.Properties` sind eine einfache und im JDK bereits eingebaute Möglichkeit, @@ -311,9 +298,19 @@ aber externe Bibliotheken, beispielsweise "Apache Commons Configuration" ([commons.apache.org/configuration](https://commons.apache.org/proper/commons-configuration/)). ::: - # Wrap-Up -* Kommandozeilenparameter als `String[]` in `main()`-Methode -* Manuelle Auswertung komplex => _Apache Commons CLI_ -* Schlüssel-Wert-Paare mit `java.util.Properties` [aus/in Dateien laden/speichern]{.notes} +- Kommandozeilenparameter als `String[]` in `main()`-Methode +- Manuelle Auswertung komplex =\> *Apache Commons CLI* +- Schlüssel-Wert-Paare mit `java.util.Properties` [aus/in Dateien + laden/speichern]{.notes} + +::: readings +- @Java-SE-Tutorial +::: + +::: outcomes +- k3: Auswertung von Kommandozeilenparametern in einem Programm +- k3: Apache Commons CLI zur Verarbeitung von Kommandozeilenparametern +- k3: Laden von Konfigurationsdaten mit java.util.Properties +::: diff --git a/lecture/java-classic/enums.md b/lecture/java-classic/enums.md index 146fc1eca..062bff622 100644 --- a/lecture/java-classic/enums.md +++ b/lecture/java-classic/enums.md @@ -1,49 +1,28 @@ --- -title: "Aufzählungen (Enumerations)" -author: "Carsten Gips (HSBI)" -readings: - - "@Java-SE-Tutorial" - - "@Ullenboom2021 [Kap. 6.4.3 und 10.7]" -tldr: | - Mit Hilfe von `enum` lassen sich Aufzählungstypen definieren (der Compiler erzeugt intern - passende Klassen). Dabei wird den Konstanten eine fortlaufende Nummer zugeordnet, auf die - mit `ordinal()` zugegriffen werden kann. Mit der Methode `values()` kann über die Konstanten - iteriert werden, und mit `name()` kann eine Stringrepräsentation einer Konstanten erzeugt - werden. Es sind keine Instanzen von Enum-Klassen erzeugbar, und die Enum-Konstanten sind - implizit `final` und `static`. - - Es lassen sich auch komplexe Enumerations analog zu Klassendefinition definieren, die eigene - Konstruktoren, Felder und Methoden enthalten. -outcomes: - - k2: "Vorgänge beim Initialisieren von Enum-Klassen (Hinweis: `static`)" - - k3: "Erstellung komplexer Enumerationen mit Feldern und Konstruktoren" - - k3: "Nutzung von `name()`, `ordinal()` und `values()` in Enum-Klassen" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106515&client_id=FH-Bielefeld" -# name: "Quiz Enumerations (ILIAS)" -youtube: - - link: "https://youtu.be/qoeT9jVuQZc" - name: "VL Enumerations" - - link: "https://youtu.be/ZCE9AmTluyk" - name: "Demo Enumerations" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/5c9330cb4425a5b33f89b491d007990642765bf0ca7ea9fd6b8b7536f46691b86bba9bde65cdbce308fb467f2d0183391dd0d1247c142e4e28d9b9eaf8c0ff67" - name: "VL Enumerations" -challenges: | - Im [Dungeon](https://github.com/Dungeon-CampusMinden/Dungeon/blob/master/dungeon/src/contrib/configuration/KeyboardConfig.java) - sollen Key-Codes aus libGDX (Integer-Werte) als Konstanten zugreifbar sein. Zusätzlich soll es es noch einen String geben, der - beschreibt, wo und wie diese Taste im Spiel eingesetzt wird. Aus historischen Gründen ist dies im Dungeon recht komplex gelöst. - - Definieren Sie eine neue Enum-Klasse, die Konstanten für Tasten aufzählt (beispielsweise `ESCAPE`, `W`, `A` oder `LEFT`). Jede - dieser Konstanten soll den der Taste zugeordneten Integerwert speichern können und einen String haben, der als Hilfestring - verstanden werden könnte (nutzen Sie hier einfach Phantasiewerte). Zeigen Sie in einer kleinen Demo, wie Sie mit diesem Enum - arbeiten würden: Zugriff auf die Konstanten, Zugriff auf den Zahlenwert und/oder den String, Übergabe als Funktionsparameter. +author: Carsten Gips (HSBI) +title: Aufzählungen (Enumerations) --- +::: tldr +Mit Hilfe von `enum` lassen sich Aufzählungstypen definieren (der Compiler erzeugt +intern passende Klassen). Dabei wird den Konstanten eine fortlaufende Nummer +zugeordnet, auf die mit `ordinal()` zugegriffen werden kann. Mit der Methode +`values()` kann über die Konstanten iteriert werden, und mit `name()` kann eine +Stringrepräsentation einer Konstanten erzeugt werden. Es sind keine Instanzen von +Enum-Klassen erzeugbar, und die Enum-Konstanten sind implizit `final` und `static`. + +Es lassen sich auch komplexe Enumerations analog zu Klassendefinition definieren, +die eigene Konstruktoren, Felder und Methoden enthalten. +::: + +::: youtube +- [VL Enumerations](https://youtu.be/qoeT9jVuQZc) +- [Demo Enumerations](https://youtu.be/ZCE9AmTluyk) +::: # Motivation -```java +``` java public class Studi { public static final int IFM = 0; public static final int ELM = 1; @@ -64,18 +43,17 @@ public class Studi { ::: notes **Probleme**: -* Keine Typsicherheit -* Konstanten gehören zur Klasse `Studi`, obwohl sie in anderem Kontext - vermutlich auch interessant sind +- Keine Typsicherheit +- Konstanten gehören zur Klasse `Studi`, obwohl sie in anderem Kontext vermutlich + auch interessant sind ::: -[[Probleme: Typsicherheit, Kontext]{.ex}]{.slides} -[[Beispiel enums.v1.Studi]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/enums/v1/Studi.java"}]{.notes} - +[[Probleme: Typsicherheit, Kontext]{.ex}]{.slides} [[Beispiel enums.v1.Studi]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/enums/v1/Studi.java"}]{.notes} # Verbesserung: Einfache Aufzählung -```java +``` java public enum Fach { IFM, ELM, ARC } @@ -93,10 +71,9 @@ public class Studi { } ``` - # Einfache Aufzählungen: Eigenschaften -```java +``` java public enum Fach { IFM, ELM, ARC } @@ -111,32 +88,31 @@ public enum Fach { [[Erinnerung: Bedeutung von static und final]{.ex}]{.slides} - -::::::::: notes -## Wiederholung _static_ +::: notes +## Wiederholung *static* **Attribute**: -* `static` Attribute sind Eigenschaften/Zustände der **Klasse** -* Gelten in jedem von der Klasse erzeugten Objekt -* Unterschiedliche Lebensdauer: - * Objektattribute (Instanzvariablen): ab `new` bis zum Garbage Collector - * Statische Variablen: Laufzeitumgebung (JVM) lädt und initialisiert die Klasse - (`static` Attribute existieren, bis die JVM die Klasse entfernt) +- `static` Attribute sind Eigenschaften/Zustände der **Klasse** +- Gelten in jedem von der Klasse erzeugten Objekt +- Unterschiedliche Lebensdauer: + - Objektattribute (Instanzvariablen): ab `new` bis zum Garbage Collector + - Statische Variablen: Laufzeitumgebung (JVM) lädt und initialisiert die + Klasse (`static` Attribute existieren, bis die JVM die Klasse entfernt) **Methoden**: -* `static` deklarierte Methoden sind **Klassenmethoden** -* Können direkt auf der Klasse aufgerufen werden -* Beispiele: `Math.max()`, `Math.sin()`, `Integer.parseInt()` -* **Achtung**: In Klassenmethoden nur Klassenattribute nutzbar (keine Instanzattribute!), - d.h. keine `this`-Referenz nutzbar +- `static` deklarierte Methoden sind **Klassenmethoden** +- Können direkt auf der Klasse aufgerufen werden +- Beispiele: `Math.max()`, `Math.sin()`, `Integer.parseInt()` +- **Achtung**: In Klassenmethoden nur Klassenattribute nutzbar (keine + Instanzattribute!), d.h. keine `this`-Referenz nutzbar -## Wiederholung _final_: Attribute/Methoden/Klassen nicht änderbar +## Wiederholung *final*: Attribute/Methoden/Klassen nicht änderbar -* Attribute: `final` Attribute können nur einmal gesetzt werden +- Attribute: `final` Attribute können nur einmal gesetzt werden - ```java + ``` java void foo() { int i = 2; final int j = 3; @@ -148,16 +124,19 @@ public enum Fach { } ``` - [Beispiel enums.FinalDemo]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/enums/FinalDemo.java"} + [Beispiel enums.FinalDemo]{.ex + href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/enums/FinalDemo.java"} -* Methoden: `final` deklarierte Methoden können bei Vererbung nicht überschrieben werden -* Klassen: von `final` deklarierten Klassen können keine Unterklassen gebildet werden -::::::::: +- Methoden: `final` deklarierte Methoden können bei Vererbung nicht überschrieben + werden +- Klassen: von `final` deklarierten Klassen können keine Unterklassen gebildet + werden +::: # Einfache Aufzählungen: Eigenschaften (cnt.) -```java +``` java // Referenzen auf Enum-Objekte können null sein Fach f = null; f = Fach.IFM; @@ -181,16 +160,17 @@ switch (f) { ::: notes Außerdem können wir folgende Eigenschaften nutzen (u.a., s.u.): -* Enumerations haben Methode `String toString()` für die Konstanten -* Enumerations haben Methode `final T[] values()` für die Iteration über die Konstanten +- Enumerations haben Methode `String toString()` für die Konstanten +- Enumerations haben Methode `final T[] values()` für die Iteration über die + Konstanten ::: -[Demo: enums.v2.Studi]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/enums/v2/Studi.java"} - +[Demo: enums.v2.Studi]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/enums/v2/Studi.java"} # Enum: Genauer betrachtet -```java +``` java public enum Fach { IFM, ELM, ARC } ``` @@ -198,7 +178,7 @@ public enum Fach { IFM, ELM, ARC } **Compiler sieht (*in etwa*):** -```java +``` java public class Fach extends Enum { public static final Fach IFM = new Fach("IFM", 0); public static final Fach ELM = new Fach("ELM", 1); @@ -208,8 +188,7 @@ public class Fach extends Enum { } ``` -=> **Singleton-Pattern** für Konstanten - +=\> **Singleton-Pattern** für Konstanten # Enum-Klassen: Eigenschaften @@ -239,43 +218,72 @@ public enum Fach { ::: notes ## Konstruktoren und Methoden für Enum-Klassen definierbar -* Kein eigener Aufruf von `super` (!) -* Konstruktoren implizit `private` +- Kein eigener Aufruf von `super` (!) +- Konstruktoren implizit `private` ## Compiler fügt automatisch folgende Methoden hinzu (Auswahl): -* Strings: - * `public final String name()` => Name der Konstanten (`final`!) - * `public String toString()` => Ruft `name()` auf, überschreibbar -* Konstanten: - * `public final T[] values()` => Alle Konstanten der Aufzählung - * `public final int ordinal()` => Interne Nummer der Konstanten - (Reihenfolge des Anlegens der Konstanten!) - * `public static T valueOf(String)` => Zum String passende Konstante - (via `name()`) +- Strings: + - `public final String name()` =\> Name der Konstanten (`final`!) + - `public String toString()` =\> Ruft `name()` auf, überschreibbar +- Konstanten: + - `public final T[] values()` =\> Alle Konstanten der Aufzählung + - `public final int ordinal()` =\> Interne Nummer der Konstanten (Reihenfolge + des Anlegens der Konstanten!) + - `public static T valueOf(String)` =\> Zum String passende Konstante (via + `name()`) **Hinweis**: Diese Methoden gibt es auch bei den "einfachen" Enumerationen (s.o.). ::: -[Demo: enums.v3]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/java-classic/src/enums/v3/"} - +[Demo: enums.v3]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/java-classic/src/enums/v3/"} # Wrap-Up -* Aufzählungen mit Hilfe von `enum` [(Compiler erzeugt intern Klassen)]{.notes} +- Aufzählungen mit Hilfe von `enum` [(Compiler erzeugt intern Klassen)]{.notes} \smallskip -* Komplexe Enumerations analog zu Klassendefinition: Konstruktoren, Felder und Methoden \newline - (keine Instanzen von Enum-Klassen erzeugbar) +- Komplexe Enumerations analog zu Klassendefinition: Konstruktoren, Felder und + Methoden `\newline`{=tex} (keine Instanzen von Enum-Klassen erzeugbar) \bigskip -* Enum-Konstanten sind implizit `final` und `static` -* Compiler stellt Methoden `name()`, `ordinal()` und `values()` zur Verfügung +- Enum-Konstanten sind implizit `final` und `static` + +- Compiler stellt Methoden `name()`, `ordinal()` und `values()` zur Verfügung ::: notes - * Name der Konstanten - * Interne Nummer der Konstanten (Reihenfolge des Anlegens) - * Array mit allen Konstanten der Enum-Klasse + - Name der Konstanten + - Interne Nummer der Konstanten (Reihenfolge des Anlegens) + - Array mit allen Konstanten der Enum-Klasse ::: + +::: readings +- @Java-SE-Tutorial +- @Ullenboom2021 [Kap. 6.4.3 und 10.7] +::: + +::: outcomes +- k2: Vorgänge beim Initialisieren von Enum-Klassen (Hinweis: static) +- k3: Erstellung komplexer Enumerationen mit Feldern und Konstruktoren +- k3: Nutzung von name(), ordinal() und values() in Enum-Klassen +::: + +::: challenges +Im +[Dungeon](https://github.com/Dungeon-CampusMinden/Dungeon/blob/master/dungeon/src/contrib/configuration/KeyboardConfig.java) +sollen Key-Codes aus libGDX (Integer-Werte) als Konstanten zugreifbar sein. +Zusätzlich soll es es noch einen String geben, der beschreibt, wo und wie diese +Taste im Spiel eingesetzt wird. Aus historischen Gründen ist dies im Dungeon recht +komplex gelöst. + +Definieren Sie eine neue Enum-Klasse, die Konstanten für Tasten aufzählt +(beispielsweise `ESCAPE`, `W`, `A` oder `LEFT`). Jede dieser Konstanten soll den der +Taste zugeordneten Integerwert speichern können und einen String haben, der als +Hilfestring verstanden werden könnte (nutzen Sie hier einfach Phantasiewerte). +Zeigen Sie in einer kleinen Demo, wie Sie mit diesem Enum arbeiten würden: Zugriff +auf die Konstanten, Zugriff auf den Zahlenwert und/oder den String, Übergabe als +Funktionsparameter. +::: diff --git a/lecture/java-classic/exceptions.md b/lecture/java-classic/exceptions.md index 8fc3f75a5..f651ac086 100644 --- a/lecture/java-classic/exceptions.md +++ b/lecture/java-classic/exceptions.md @@ -1,110 +1,50 @@ --- -title: "Exception-Handling" -author: "André Matutat & Carsten Gips (HSBI)" -readings: - - "@LernJava" - - "@Ullenboom2021 [Kap. 8]" - - "@Java-SE-Tutorial" -tldr: | - Man unterscheidet in Java zwischen **Exceptions** und **Errors**. Ein Error ist ein - Fehler im System (OS, JVM), von dem man sich nicht wieder erholen kann. Eine Exception - ist ein Fehlerfall innerhalb des Programmes, auf den man innerhalb des Programms - reagieren kann. - - Mit Hilfe von Exceptions lassen sich Fehlerfälle im Programmablauf deklarieren und - behandeln. Methoden können/müssen mit dem Keyword `throws` gefolgt vom Namen der - Exception deklarieren, dass sie im Fehlerfall diese spezifische Exception werfen - (und nicht selbst behandeln). - - Zum Exception-Handling werden die Keywords `try`, `catch` und `finally` verwendet. - Dabei wird im `try`-Block der Code geschrieben, der einen potenziellen Fehler wirft. - Im `catch`-Block wird das Verhalten implementiert, dass im Fehlerfall ausgeführt - werden soll, und im `finally`-Block kann optional Code geschrieben werden, der sowohl - im Erfolgs- als auch Fehlerfall ausgeführt wird. - - Es wird zwischen **checked** Exceptions und **unchecked** Exceptions unterschieden. - Checked Exceptions sind für erwartbare Fehlerfälle gedacht, die nicht vom Programm - ausgeschlossen werden können, wie das Fehlen einer Datei, die eingelesen werden soll. - Checked Exceptions müssen deklariert oder behandelt werden. Dies wird vom Compiler - überprüft. - - Unchecked Exceptions werden für Fehler in der Programmlogik verwendet, etwa das Teilen - durch 0 oder Index-Fehler. Sie deuten auf fehlerhafte Programmierung, fehlerhafte Logik - oder beispielsweise mangelhafte Eingabeprüfung in. Unchecked Exceptions müssen nicht - deklariert oder behandelt werden. Unchecked Exceptions leiten von `RuntimeException` - ab. - - Als Faustregel gilt: Wenn der Aufrufer sich von einer Exception-Situation erholen kann, - sollte man eine checked Exception nutzen. Wenn der Aufrufer vermutlich nichts tun kann, - um sich von dem Problem zu erholen, dann sollte man eine unchecked Exception einsetzen. -outcomes: - - k2: "Unterschied zwischen Error und Exception" - - k2: "Unterschied zwischen checked und unchecked Exceptions" - - k3: "Umgang mit Exceptions" - - k3: "Eigene Exceptions schreiben" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106516&client_id=FH-Bielefeld" -# name: "Quiz Exceptions (ILIAS)" -youtube: - - link: "https://youtu.be/k6EhexEvJDY" - name: "VL Exceptions" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/99634e4bd3c017932803d555405c3921a761c6f7d788e1beaddbc4053d0013f7f2dce08a8c713c005a7352a57de556406554dca14492eaa1a982ed167e20ea5b" - name: "VL Exceptions" -challenges: | - Betrachten Sie die [Vorgaben](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/java-classic/src/challenges/exceptions). - - **Verbessern Sie das Exception-Handling** - - Im package `better_try_catch` finden Sie die Klasse `BetterTryCatchMain`, in der - verschiedene Methoden der Klasse `MyFunctions` aufgerufen werden. - - Erklären Sie, warum das dort implementierte Exception-Handling nicht gut ist und - verbessern Sie es. - - - **Checked vs. unckecked Exceptions** - - Erklären Sie den Unterschied zwischen checked und unchecked Exceptions. - - Im Folgenden werden verschiedene Exceptions beschrieben. Erklären Sie, ob diese - jeweils "checked" oder "unchecked" sein sollten. - - * `IntNotBetweenException` soll geworfen werden, wenn ein Integer-Parameter nicht - im definierten Wertebereich liegt. - * `NoPicturesFoundException` soll geworfen werden, wenn in einem übergebenen - Verzeichnis keine Bilddateien gefunden werden konnten. - * `NotAPrimeNumberException` soll geworfen werden, wenn eine vom User eingegebene - Zahl keine Primzahl ist. - - - **Freigeben von Ressourcen** - - Im Package `finally_resources` finden Sie die Klasse `MyResource`. - - Rufen Sie die Methode `MyResource#doSomething` auf, im Anschluss müssen Sie **immer** - die Methode `MyResource#close` aufrufen. - - 1. Zeigen Sie den Aufruf mit `try-catch-finally`. - 2. Verändern Sie die Vorgaben so, dass Sie den Aufruf mit der "try-with-resources"-Technik - ausführen können. - - - **Where to catch?** - - Erklären Sie, wann und wo eine Exception gefangen und bearbeitet werden sollte. - - Im Package `where_to_catch` finden Sie die Klasse `JustThrow`. Alle Methoden in der Klasse - werfen aufkommende Exceptions bis zur `main` hoch. - - Verändern Sie die Vorgaben so, dass die Exceptions an den passenden Stellen gefangen und - sinnvoll bearbeitet werden. Begründen Sie Ihre Entscheidungen. +author: André Matutat & Carsten Gips (HSBI) +title: Exception-Handling --- +::: tldr +Man unterscheidet in Java zwischen **Exceptions** und **Errors**. Ein Error ist ein +Fehler im System (OS, JVM), von dem man sich nicht wieder erholen kann. Eine +Exception ist ein Fehlerfall innerhalb des Programmes, auf den man innerhalb des +Programms reagieren kann. + +Mit Hilfe von Exceptions lassen sich Fehlerfälle im Programmablauf deklarieren und +behandeln. Methoden können/müssen mit dem Keyword `throws` gefolgt vom Namen der +Exception deklarieren, dass sie im Fehlerfall diese spezifische Exception werfen +(und nicht selbst behandeln). + +Zum Exception-Handling werden die Keywords `try`, `catch` und `finally` verwendet. +Dabei wird im `try`-Block der Code geschrieben, der einen potenziellen Fehler wirft. +Im `catch`-Block wird das Verhalten implementiert, dass im Fehlerfall ausgeführt +werden soll, und im `finally`-Block kann optional Code geschrieben werden, der +sowohl im Erfolgs- als auch Fehlerfall ausgeführt wird. + +Es wird zwischen **checked** Exceptions und **unchecked** Exceptions unterschieden. +Checked Exceptions sind für erwartbare Fehlerfälle gedacht, die nicht vom Programm +ausgeschlossen werden können, wie das Fehlen einer Datei, die eingelesen werden +soll. Checked Exceptions müssen deklariert oder behandelt werden. Dies wird vom +Compiler überprüft. + +Unchecked Exceptions werden für Fehler in der Programmlogik verwendet, etwa das +Teilen durch 0 oder Index-Fehler. Sie deuten auf fehlerhafte Programmierung, +fehlerhafte Logik oder beispielsweise mangelhafte Eingabeprüfung in. Unchecked +Exceptions müssen nicht deklariert oder behandelt werden. Unchecked Exceptions +leiten von `RuntimeException` ab. + +Als Faustregel gilt: Wenn der Aufrufer sich von einer Exception-Situation erholen +kann, sollte man eine checked Exception nutzen. Wenn der Aufrufer vermutlich nichts +tun kann, um sich von dem Problem zu erholen, dann sollte man eine unchecked +Exception einsetzen. +::: + +::: youtube +- [VL Exceptions](https://youtu.be/k6EhexEvJDY) +::: # Fehlerfälle in Java -```java +``` java int div(int a, int b) { return a / b; } @@ -117,10 +57,9 @@ div(3, 0); **Problem**: Programm wird abstürzen, da durch '0' geteilt wird ... ::: - # Lösung? -```java +``` java Optional div(int a, int b) { if (b == 0) return Optional.empty(); return Optional.of(a / b); @@ -138,77 +77,77 @@ if (x.isPresent()) { ::: notes **Probleme**: -* Da `int` nicht `null` sein kann, muss ein `Integer` Objekt erzeugt und zurückgegeben werden: - Overhead wg. Auto-Boxing und -Unboxing! -* Der Aufrufer muss auf `null` prüfen. -* Es wird nicht kommuniziert, warum `null` zurückgegeben wird. Was ist das Problem? -* Was ist, wenn `null` ein gültiger Rückgabewert sein soll? +- Da `int` nicht `null` sein kann, muss ein `Integer` Objekt erzeugt und + zurückgegeben werden: Overhead wg. Auto-Boxing und -Unboxing! +- Der Aufrufer muss auf `null` prüfen. +- Es wird nicht kommuniziert, warum `null` zurückgegeben wird. Was ist das + Problem? +- Was ist, wenn `null` ein gültiger Rückgabewert sein soll? ::: - -# Vererbungsstruktur _Throwable_ +# Vererbungsstruktur *Throwable* ![](images/exception.png){web_width="80%"} -[[Hinweis: checked vs. unchecked]{.ex}]{.slides} - +[[Hinweis: checked vs. unchecked]{.ex}]{.slides} -::::::::: notes -## _Exception_ vs. _Error_ - -* `Error`: - * Wird für Systemfehler verwendet (Betriebssystem, JVM, ...) - * `StackOverflowError` - * `OutOfMemoryError` - * Von einem Error kann man sich nicht erholen - * Sollten nicht behandelt werden - -* `Exception`: - * Ausnahmesituationen bei der Abarbeitung eines Programms - * Können "checked" oder "unchecked" sein - * Von Exceptions kann man sich erholen - -## Unchecked vs. Checked Exceptions - -* "Checked" Exceptions: - * Für erwartbare Fehlerfälle, deren Ursprung nicht im Programm selbst liegt - * `FileNotFoundException` - * `IOException` - * Alle nicht von `RuntimeException` ableitende Exceptions - * Müssen entweder behandelt (`try`/`catch`) oder deklariert (`throws`) werden: +::: notes +## *Exception* vs. *Error* + +- `Error`: + - Wird für Systemfehler verwendet (Betriebssystem, JVM, ...) + - `StackOverflowError` + - `OutOfMemoryError` + - Von einem Error kann man sich nicht erholen + - Sollten nicht behandelt werden +- `Exception`: + - Ausnahmesituationen bei der Abarbeitung eines Programms + - Können "checked" oder "unchecked" sein + - Von Exceptions kann man sich erholen + +## Unchecked vs. Checked Exceptions + +- "Checked" Exceptions: + - Für erwartbare Fehlerfälle, deren Ursprung nicht im Programm selbst liegt + - `FileNotFoundException` + - `IOException` + - Alle nicht von `RuntimeException` ableitende Exceptions + - Müssen entweder behandelt (`try`/`catch`) oder deklariert (`throws`) werden: Dies wird vom Compiler überprüft! - -* "Unchecked" Exceptions: - * Logische Programmierfehler ("Versagen" des Programmcodes) - * `IndexOutOfBoundException` - * `NullPointerException` - * `ArithmeticException` - * `IllegalArgumentException` - * Leiten von `RuntimeException` oder Unterklassen ab - * Müssen nicht deklariert oder behandelt werden +- "Unchecked" Exceptions: + - Logische Programmierfehler ("Versagen" des Programmcodes) + - `IndexOutOfBoundException` + - `NullPointerException` + - `ArithmeticException` + - `IllegalArgumentException` + - Leiten von `RuntimeException` oder Unterklassen ab + - Müssen nicht deklariert oder behandelt werden Beispiele checked Exception: -* Es soll eine Abfrage an eine externe API geschickt werden. Diese ist aber aktuell - nicht zu erreichen. "Erholung": Anfrage noch einmal schicken. -* Es soll eine Datei geöffnet werden. Diese ist aber nicht unter dem angegebenen - Pfad zu finden oder die Berechtigungen stimmen nicht. "Erholung": Aufrufer öffnet - neuen File-Picker, um es noch einmal mit einer anderen Datei zu versuchen. + +- Es soll eine Abfrage an eine externe API geschickt werden. Diese ist aber + aktuell nicht zu erreichen. "Erholung": Anfrage noch einmal schicken. +- Es soll eine Datei geöffnet werden. Diese ist aber nicht unter dem angegebenen + Pfad zu finden oder die Berechtigungen stimmen nicht. "Erholung": Aufrufer + öffnet neuen File-Picker, um es noch einmal mit einer anderen Datei zu + versuchen. Beispiele unchecked Exception: -* Eine `for`-Loop über ein Array ist falsch programmiert und will auf einen Index + +- Eine `for`-Loop über ein Array ist falsch programmiert und will auf einen Index im Array zugreifen, der nicht existiert. Hier kann der Aufrufer nicht Sinnvolles tun, um sich von dieser Situation zu erholen. -* Argumente oder Rückgabewerte einer Methode können `null` sein. Wenn man das nicht - prüft, sondern einfach Methoden auf dem vermeintlichen Objekt aufruft, wird eine - `NullPointerException` ausgelöst, die eine Unterklasse von `RuntimeException` ist - und damit eine unchecked Exception. Auch hier handelt es sich um einen Fehler in - der Programmlogik, von dem sich der Aufrufer nicht sinnvoll erholen kann. -::::::::: - +- Argumente oder Rückgabewerte einer Methode können `null` sein. Wenn man das + nicht prüft, sondern einfach Methoden auf dem vermeintlichen Objekt aufruft, + wird eine `NullPointerException` ausgelöst, die eine Unterklasse von + `RuntimeException` ist und damit eine unchecked Exception. Auch hier handelt es + sich um einen Fehler in der Programmlogik, von dem sich der Aufrufer nicht + sinnvoll erholen kann. +::: -# _Throws_ +# *Throws* -```java +``` java int div(int a, int b) throws ArithmeticException { return a / b; } @@ -220,7 +159,7 @@ Alternativ: \bigskip -```java +``` java int div(int a, int b) throws IllegalArgumentException { if (b == 0) throw new IllegalArgumentException("Can't divide by zero"); return a / b; @@ -228,43 +167,44 @@ int div(int a, int b) throws IllegalArgumentException { ``` ::: notes -Exception können an an den Aufrufer weitergeleitet werden oder selbst geworfen werden. +Exception können an an den Aufrufer weitergeleitet werden oder selbst geworfen +werden. Wenn wie im ersten Beispiel bei einer Operation eine Exception entsteht und nicht gefangen wird, dann wird sie automatisch an den Aufrufer weitergeleitet. Dies wird über die `throws`-Klausel deutlich gemacht (Keyword `throws` plus den/die Namen der -Exception(s), angefügt an die Methodensignatur). Bei unchecked Exceptions _kann_ man -das tun, bei checked Exceptions _muss_ man dies tun. +Exception(s), angefügt an die Methodensignatur). Bei unchecked Exceptions *kann* man +das tun, bei checked Exceptions *muss* man dies tun. Wenn man wie im zweiten Beispiel selbst eine neue Exception werfen will, erzeugt man mit `new` ein neues Objekt der gewünschten Exception und "wirft" diese mit `throw`. -Auch diese Exception kann man dann entweder selbst fangen und bearbeiten (siehe nächste -Folie) oder an den Aufrufer weiterleiten und dies dann entsprechend über die -`throws`-Klausel deklarieren: nicht gefangene checked Exceptions _müssen_ deklariert -werden, nicht gefangene unchecked Exceptions _können_ deklariert werden. +Auch diese Exception kann man dann entweder selbst fangen und bearbeiten (siehe +nächste Folie) oder an den Aufrufer weiterleiten und dies dann entsprechend über die +`throws`-Klausel deklarieren: nicht gefangene checked Exceptions *müssen* deklariert +werden, nicht gefangene unchecked Exceptions *können* deklariert werden. Wenn mehrere Exceptions an den Aufrufer weitergeleitet werden, werden sie in der `throws`-Klausel mit Komma getrennt: `throws Exception1, Exception2, Exception3`. -**Anmerkung**: In beiden obigen Beispielen wurde zur Verdeutlichung, dass die Methode -`div()` eine Exception wirft, diese per `throws`-Klausel deklariert. Da es sich bei -den beiden Beispielen aber jeweils um **unchecked Exceptions** handelt, ist dies im -obigen Beispiel _nicht notwendig_. Der Aufrufer _muss_ auch nicht ein passendes -Exception-Handling einsetzen! +**Anmerkung**: In beiden obigen Beispielen wurde zur Verdeutlichung, dass die +Methode `div()` eine Exception wirft, diese per `throws`-Klausel deklariert. Da es +sich bei den beiden Beispielen aber jeweils um **unchecked Exceptions** handelt, ist +dies im obigen Beispiel *nicht notwendig*. Der Aufrufer *muss* auch nicht ein +passendes Exception-Handling einsetzen! Wenn wir stattdessen eine **checked Exception** werfen würden oder in `div()` eine -Methode aufrufen würden, die eine checked Exception deklariert hat, _muss_ diese +Methode aufrufen würden, die eine checked Exception deklariert hat, *muss* diese checked Exception entweder in `div()` gefangen und bearbeitet werden oder aber per -`throws`-Klausel deklariert werden. Im letzteren Fall _muss_ dann der Aufrufer analog -damit umgehen (fangen oder selbst auch deklarieren). **Dies wird vom Compiler geprüft!** +`throws`-Klausel deklariert werden. Im letzteren Fall *muss* dann der Aufrufer +analog damit umgehen (fangen oder selbst auch deklarieren). **Dies wird vom Compiler +geprüft!** ::: -[[Hinweis: throws und checked vs. unchecked]{.ex}]{.slides} - +[[Hinweis: throws und checked vs. unchecked]{.ex}]{.slides} -# _Try_-_Catch_ +# *Try*-*Catch* -```java +``` java int a = getUserInput(); int b = getUserInput(); @@ -278,19 +218,18 @@ try { ``` ::: notes -* Im `try` Block wird der Code ausgeführt, der einen Fehler werfen könnte. -* Mit `catch` kann eine Exception gefangen und im `catch` Block behandelt werden. +- Im `try` Block wird der Code ausgeführt, der einen Fehler werfen könnte. +- Mit `catch` kann eine Exception gefangen und im `catch` Block behandelt werden. -**Anmerkung**: Das bloße Ausgeben des Stacktrace via `e.printStackTrace()` ist -noch **kein sinnvolles Exception-Handling**! Hier sollte auf die jeweilige Situation +**Anmerkung**: Das bloße Ausgeben des Stacktrace via `e.printStackTrace()` ist noch +**kein sinnvolles Exception-Handling**! Hier sollte auf die jeweilige Situation eingegangen werden und versucht werden, den Fehler zu beheben oder dem Aufrufer geeignet zu melden! ::: +# *Try* und mehrstufiges *Catch* -# _Try_und mehrstufiges _Catch_ - -```java +``` java try { someMethod(a, b, c); } catch (IllegalArgumentException iae) { @@ -301,27 +240,28 @@ try { ``` ::: notes -Eine im `try`-Block auftretende Exception wird der Reihe nach mit den `catch`-Blöcken -gematcht (vergleichbar mit `switch case`). - -**Wichtig**: Dabei muss die Vererbungshierarchie beachtet werden. Die spezialisierteste -Klasse muss ganz oben stehen, die allgemeinste Klasse als letztes. Sonst wird eine -Exception u.U. zu früh in einem nicht dafür gedachten `catch`-Zweig aufgefangen. - -**Wichtig**: Wenn eine Exception nicht durch die `catch`-Zweige aufgefangen wird, dann -wird sie an den Aufrufer weiter geleitet. Im Beispiel würde eine `IOException` nicht durch -die `catch`-Zweige gefangen (`IllegalArgumentException` und `NullPointerException` sind -im falschen Vererbungszweig, und `FileNotFoundException` ist spezieller als `IOException`) -und entsprechend an den Aufrufer weiter gereicht. Da es sich obendrein um eine checked -Exception handelt, müsste man diese per `throws IOException` an der Methode deklarieren. +Eine im `try`-Block auftretende Exception wird der Reihe nach mit den +`catch`-Blöcken gematcht (vergleichbar mit `switch case`). + +**Wichtig**: Dabei muss die Vererbungshierarchie beachtet werden. Die +spezialisierteste Klasse muss ganz oben stehen, die allgemeinste Klasse als letztes. +Sonst wird eine Exception u.U. zu früh in einem nicht dafür gedachten `catch`-Zweig +aufgefangen. + +**Wichtig**: Wenn eine Exception nicht durch die `catch`-Zweige aufgefangen wird, +dann wird sie an den Aufrufer weiter geleitet. Im Beispiel würde eine `IOException` +nicht durch die `catch`-Zweige gefangen (`IllegalArgumentException` und +`NullPointerException` sind im falschen Vererbungszweig, und `FileNotFoundException` +ist spezieller als `IOException`) und entsprechend an den Aufrufer weiter gereicht. +Da es sich obendrein um eine checked Exception handelt, müsste man diese per +`throws IOException` an der Methode deklarieren. ::: [[Hinweis: catch und Vererbungshierarchie]{.ex}]{.slides} +# *Finally* -# _Finally_ - -```java +``` java Scanner myScanner = new Scanner(System.in); try { @@ -335,15 +275,14 @@ try { ``` ::: notes -Der `finally` Block wird sowohl im Fehlerfall als auch im Normalfall aufgerufen. Dies -wird beispielsweise für Aufräumarbeiten genutzt, etwa zum Schließen von Verbindungen -oder Input-Streams. +Der `finally` Block wird sowohl im Fehlerfall als auch im Normalfall aufgerufen. +Dies wird beispielsweise für Aufräumarbeiten genutzt, etwa zum Schließen von +Verbindungen oder Input-Streams. ::: +# *Try*-with-Resources -# _Try_-with-Resources - -```java +``` java try (Scanner myScanner = new Scanner(System.in)) { return 5 / myScanner.nextInt(); } catch (InputMismatchException ime) { @@ -352,14 +291,13 @@ try (Scanner myScanner = new Scanner(System.in)) { ``` ::: notes -Im `try`-Statement können Ressourcen deklariert werden, die am Ende sicher geschlossen -werden. Diese Ressourcen müssen `java.io.Closeable` implementieren. +Im `try`-Statement können Ressourcen deklariert werden, die am Ende sicher +geschlossen werden. Diese Ressourcen müssen `java.io.Closeable` implementieren. ::: - # Eigene Exceptions -```java +``` java // Checked Exception public class MyCheckedException extends Exception { public MyCheckedException(String errorMessage) { @@ -370,7 +308,7 @@ public class MyCheckedException extends Exception { \bigskip -```java +``` java // Unchecked Exception public class MyUncheckedException extends RuntimeException { public MyUncheckedException(String errorMessage) { @@ -384,17 +322,17 @@ Eigene Exceptions können durch Spezialisierung anderer Exception-Klassen realis werden. Dabei kann man direkt von `Exception` oder `RuntimeException` ableiten oder bei Bedarf von spezialisierteren Exception-Klassen. -Wenn die eigene Exception in der Vererbungshierarchie unter `RuntimeException` steht, -handelt es sich um eine _unchecked Exception_, sonst um eine _checked Exception_. +Wenn die eigene Exception in der Vererbungshierarchie unter `RuntimeException` +steht, handelt es sich um eine *unchecked Exception*, sonst um eine *checked +Exception*. -In der Benutzung (werfen, fangen, deklarieren) verhalten sich eigene Exception-Klassen -wie die Exceptions aus dem JDK. +In der Benutzung (werfen, fangen, deklarieren) verhalten sich eigene +Exception-Klassen wie die Exceptions aus dem JDK. ::: +# Stilfrage: Wie viel Code im *Try*? -# Stilfrage: Wie viel Code im _Try_? - -```java +``` java int getFirstLineAsInt(String pathToFile) { FileReader fileReader = new FileReader(pathToFile); BufferedReader bufferedReader = new BufferedReader(fileReader); @@ -404,15 +342,15 @@ int getFirstLineAsInt(String pathToFile) { } ``` -[Zeigen: exceptions.HowMuchTry]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/exceptions/HowMuchTry.java"} - +[Zeigen: exceptions.HowMuchTry]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/exceptions/HowMuchTry.java"} ::: notes Hier lassen sich verschiedene "Ausbaustufen" unterscheiden. ## Handling an den Aufrufer übergeben -```java +``` java int getFirstLineAsIntV1(String pathToFile) throws FileNotFoundException, IOException { FileReader fileReader = new FileReader(pathToFile); BufferedReader bufferedReader = new BufferedReader(fileReader); @@ -428,12 +366,12 @@ auf ein `try`/`catch` verzichten und stattdessen die `FileNotFoundException` (vo `FileReader`) und die `IOException` (vom `bufferedReader.readLine()`) per `throws` deklarieren. -_Anmerkung_: Da `FileNotFoundException` eine Spezialisierung von `IOException` ist, +*Anmerkung*: Da `FileNotFoundException` eine Spezialisierung von `IOException` ist, reicht es aus, lediglich die `IOException` zu deklarieren. ## Jede Exception einzeln fangen und bearbeiten -```java +``` java int getFirstLineAsIntV2(String pathToFile) { FileReader fileReader = null; try { @@ -470,16 +408,17 @@ Allerdings muss man nun mit Behelfsinitialisierungen arbeiten und der Code wird in die Länge gezogen und man erkennt die eigentlichen funktionalen Zusammenhänge nur noch schwer. -_Anmerkung_: Das "Behandeln" der Exceptions ist im obigen Beispiel kein gutes Beispiel -für das Behandeln von Exceptions. Einfach nur einen Stacktrace zu printen und weiter -zu machen, als ob nichts passiert wäre, ist **kein sinnvolles Exception-Handling**. -Wenn Sie solchen Code schreiben oder sehen, ist das ein Anzeichen, dass auf dieser Ebene -nicht sinnvoll mit dem Fehler umgegangen werden kann und dass man ihn besser an den -Aufrufer weiter reichen sollte (siehe nächste Folie). +*Anmerkung*: Das "Behandeln" der Exceptions ist im obigen Beispiel kein gutes +Beispiel für das Behandeln von Exceptions. Einfach nur einen Stacktrace zu printen +und weiter zu machen, als ob nichts passiert wäre, ist **kein sinnvolles +Exception-Handling**. Wenn Sie solchen Code schreiben oder sehen, ist das ein +Anzeichen, dass auf dieser Ebene nicht sinnvoll mit dem Fehler umgegangen werden +kann und dass man ihn besser an den Aufrufer weiter reichen sollte (siehe nächste +Folie). -## Funktionaler Teil in gemeinsames _Try_ und mehrstufiges _Catch_ +## Funktionaler Teil in gemeinsames *Try* und mehrstufiges *Catch* -```java +``` java int getFirstLineAsIntV3(String pathToFile) { try { FileReader fileReader = new FileReader(pathToFile); @@ -498,20 +437,19 @@ int getFirstLineAsIntV3(String pathToFile) { } ``` -Hier wurde der eigentliche funktionale Kern der Methode in ein gemeinsames `try`/`catch` -verpackt und mit einem mehrstufigen `catch` auf die einzelnen Fehler reagiert. Durch die -Art der Exceptions sieht man immer noch, wo der Fehler herkommt. Zusätzlich wird die -eigentliche Funktionalität so leichter erkennbar. +Hier wurde der eigentliche funktionale Kern der Methode in ein gemeinsames +`try`/`catch` verpackt und mit einem mehrstufigen `catch` auf die einzelnen Fehler +reagiert. Durch die Art der Exceptions sieht man immer noch, wo der Fehler herkommt. +Zusätzlich wird die eigentliche Funktionalität so leichter erkennbar. -_Anmerkung_: Auch hier ist das gezeigte Exception-Handling kein gutes Beispiel. Entweder -man macht hier sinnvollere Dinge, oder man überlässt dem Aufrufer die Reaktion auf den -Fehler. +*Anmerkung*: Auch hier ist das gezeigte Exception-Handling kein gutes Beispiel. +Entweder man macht hier sinnvollere Dinge, oder man überlässt dem Aufrufer die +Reaktion auf den Fehler. ::: - # Stilfrage: Wo fange ich die Exception? -```java +``` java private static void methode1(int x) throws IOException { JFileChooser fc = new JFileChooser(); fc.showDialog(null, "ok"); @@ -543,43 +481,103 @@ Ursprung der Fehlerursache zu behandeln. Man sollte sich dabei die Frage stellen kann ich sinnvoll auf den Fehler reagieren? ::: - # Stilfrage: Wann checked, wann unchecked ## "Checked" Exceptions -* Für erwartbare Fehlerfälle, deren Ursprung nicht im Programm selbst liegt -* Aufrufer kann sich von der Exception erholen +- Für erwartbare Fehlerfälle, deren Ursprung nicht im Programm selbst liegt +- Aufrufer kann sich von der Exception erholen \bigskip ## "Unchecked" Exceptions -* Logische Programmierfehler ("Versagen" des Programmcodes) -* Aufrufer kann sich von der Exception vermutlich nicht erholen +- Logische Programmierfehler ("Versagen" des Programmcodes) +- Aufrufer kann sich von der Exception vermutlich nicht erholen ::: notes -Vergleiche ["Unchecked Exceptions — The Controversy"](https://dev.java/learn/exceptions/unchecked-exception-controversy/). +Vergleiche ["Unchecked Exceptions --- The +Controversy"](https://dev.java/learn/exceptions/unchecked-exception-controversy/). ::: - # Wrap-Up -* `Error` und `Exception`: System vs. Programm -* Checked und unchecked Exceptions: `Exception` vs. `RuntimeException` +- `Error` und `Exception`: System vs. Programm +- Checked und unchecked Exceptions: `Exception` vs. `RuntimeException` \smallskip -* `try`: Versuche Code auszuführen -* `catch`: Verhalten im Fehlerfall -* `finally`: Verhalten im Erfolgs- und Fehlerfall +- `try`: Versuche Code auszuführen +- `catch`: Verhalten im Fehlerfall +- `finally`: Verhalten im Erfolgs- und Fehlerfall \smallskip -* `throw`: Wirft eine Exception -* `throws`: Deklariert eine Exception an Methode +- `throw`: Wirft eine Exception +- `throws`: Deklariert eine Exception an Methode \smallskip -* Eigene Exceptions durch Ableiten von anderen Exceptions - [(werden je nach Vererbungshierarchie automatisch checked oder unchecked)]{.notes} +- Eigene Exceptions durch Ableiten von anderen Exceptions [(werden je nach + Vererbungshierarchie automatisch checked oder unchecked)]{.notes} + +::: readings +- @LernJava +- @Ullenboom2021 [Kap. 8] +- @Java-SE-Tutorial +::: + +::: outcomes +- k2: Unterschied zwischen Error und Exception +- k2: Unterschied zwischen checked und unchecked Exceptions +- k3: Umgang mit Exceptions +- k3: Eigene Exceptions schreiben +::: + +::: challenges +Betrachten Sie die +[Vorgaben](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/java-classic/src/challenges/exceptions). + +**Verbessern Sie das Exception-Handling** + +Im package `better_try_catch` finden Sie die Klasse `BetterTryCatchMain`, in der +verschiedene Methoden der Klasse `MyFunctions` aufgerufen werden. + +Erklären Sie, warum das dort implementierte Exception-Handling nicht gut ist und +verbessern Sie es. + +**Checked vs. unckecked Exceptions** + +Erklären Sie den Unterschied zwischen checked und unchecked Exceptions. + +Im Folgenden werden verschiedene Exceptions beschrieben. Erklären Sie, ob diese +jeweils "checked" oder "unchecked" sein sollten. + +- `IntNotBetweenException` soll geworfen werden, wenn ein Integer-Parameter nicht + im definierten Wertebereich liegt. +- `NoPicturesFoundException` soll geworfen werden, wenn in einem übergebenen + Verzeichnis keine Bilddateien gefunden werden konnten. +- `NotAPrimeNumberException` soll geworfen werden, wenn eine vom User eingegebene + Zahl keine Primzahl ist. + +**Freigeben von Ressourcen** + +Im Package `finally_resources` finden Sie die Klasse `MyResource`. + +Rufen Sie die Methode `MyResource#doSomething` auf, im Anschluss müssen Sie +**immer** die Methode `MyResource#close` aufrufen. + +1. Zeigen Sie den Aufruf mit `try-catch-finally`. +2. Verändern Sie die Vorgaben so, dass Sie den Aufruf mit der + "try-with-resources"-Technik ausführen können. + +**Where to catch?** + +Erklären Sie, wann und wo eine Exception gefangen und bearbeitet werden sollte. + +Im Package `where_to_catch` finden Sie die Klasse `JustThrow`. Alle Methoden in der +Klasse werfen aufkommende Exceptions bis zur `main` hoch. + +Verändern Sie die Vorgaben so, dass die Exceptions an den passenden Stellen gefangen +und sinnvoll bearbeitet werden. Begründen Sie Ihre Entscheidungen. +::: diff --git a/lecture/java-classic/generics-bounds-wildcards.md b/lecture/java-classic/generics-bounds-wildcards.md index 11699f5ec..d792fe948 100644 --- a/lecture/java-classic/generics-bounds-wildcards.md +++ b/lecture/java-classic/generics-bounds-wildcards.md @@ -1,90 +1,31 @@ --- +author: Carsten Gips (HSBI) title: "Generics: Bounds & Wildcards" -author: "Carsten Gips (HSBI)" -readings: - - "@Ullenboom2021 [Kap. 11.3]" - - "@LernJava" - - "@Java-SE-Tutorial" - - "@Bloch2018" -tldr: | - Typ-Variablen können weiter eingeschränkt werden, in dem man einen verpflichtenden - Ober- oder Untertyp angibt mit `extends` bzw. `super`. Damit muss der später - bei der Instantiierung verwendete Typ-Parameter entweder die Oberklasse selbst - sein oder davon ableiten (bei `extends`) bzw. der Typ-Parameter muss eine Oberklasse - der angegebenen Schranke sein (`super`). - - Durch die Einschränkung mit `extends` können in der Klasse/Methode auf der Typ-Variablen - alle Methoden des angegebenen Obertyps verwendet werden. - - Ein Wildcard (`?`) als Typ-Parameter steht für einen beliebigen Typ, wobei die - Typ-Variable keinen Namen bekommt und damit innerhalb der Klasse/Methode nicht - zugreifbar ist. -outcomes: - - k3: "Ich kann Wildcards und Bounds bei generischen Klassen/Methoden einsetzen" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106236&client_id=FH-Bielefeld" -# name: "Quiz Generics: Bounds & Wildcards (ILIAS)" -youtube: - - link: "https://youtu.be/OV2vEn2EkWo" - name: "VL Generics: Bounds & Wildcards" - - link: "https://youtu.be/D2hIicsho7I" - name: "Demo Wildcards" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/08ef62f9367140e7ae75cd4e90fde1e63cc3ec6c9e7e4c11a1994065fd45e781f46bc6b3f1fbe4fbe8952db11812d52efe8cd8900fe88843e5eaefad62bbc7d3" - name: "VL Generics: Bounds & Wildcards" -challenges: | - **Spieler, Mannschaften und Ligen** - Modellieren Sie in Java verschiedene Spielertypen sowie generische Mannschaften und Ligen, - die jeweils bestimmte Spieler (-typen) bzw. Mannschaften aufnehmen können. - - 1. Implementieren Sie die Klasse `Spieler`, die das Interface `ISpieler` erfüllt. - - ```java - public interface ISpieler { - String getName(); - } - ``` - - 2. Implementieren Sie die beiden Klassen `FussballSpieler` und `BasketballSpieler` und - sorgen Sie dafür, dass beide Klassen vom Compiler als Spieler betrachtet werden - (geeignete Vererbungshierarchie). - - 3. Betrachten Sie das nicht-generische Interface `IMannschaft`. Erstellen Sie daraus ein - generisches Interface `IMannschaft` mit einer Typ-Variablen. Stellen Sie durch geeignete - Beschränkung der Typ-Variablen sicher, dass nur Mannschaften mit von `ISpieler` abgeleiteten - Spielern gebildet werden können. - - ```java - public interface IMannschaft { - boolean aufnehmen(ISpieler spieler); - boolean rauswerfen(ISpieler spieler); - } - ``` - - 4. Betrachten Sie das nicht-generische Interface `ILiga`. Erstellen Sie daraus ein generisches - Interface `ILiga` mit einer Typvariablen. Stellen Sie durch geeignete Beschränkung der - Typvariablen sicher, dass nur Ligen mit von `IMannschaft` abgeleiteten Mannschaften angelegt - werden können. - - ```java - public interface ILiga { - boolean aufnehmen(IMannschaft mannschaft); - boolean rauswerfen(IMannschaft mannschaft); - } - ``` - - 5. Leiten Sie von `ILiga` das **generische** Interface `IBundesLiga` ab. Stellen Sie durch - geeignete Formulierung der Typvariablen sicher, dass nur Ligen mit Mannschaften angelegt - werden können, deren Spieler vom Typ `FussballSpieler` (oder abgeleitet) sind. - - Realisieren Sie nun noch die Funktionalität von `IBundesLiga` als **nicht-generisches** - Interface `IBundesLiga2`. --- +::: tldr +Typ-Variablen können weiter eingeschränkt werden, in dem man einen verpflichtenden +Ober- oder Untertyp angibt mit `extends` bzw. `super`. Damit muss der später bei der +Instantiierung verwendete Typ-Parameter entweder die Oberklasse selbst sein oder +davon ableiten (bei `extends`) bzw. der Typ-Parameter muss eine Oberklasse der +angegebenen Schranke sein (`super`). + +Durch die Einschränkung mit `extends` können in der Klasse/Methode auf der +Typ-Variablen alle Methoden des angegebenen Obertyps verwendet werden. + +Ein Wildcard (`?`) als Typ-Parameter steht für einen beliebigen Typ, wobei die +Typ-Variable keinen Namen bekommt und damit innerhalb der Klasse/Methode nicht +zugreifbar ist. +::: + +::: youtube +- [VL Generics: Bounds & Wildcards](https://youtu.be/OV2vEn2EkWo) +- [Demo Wildcards](https://youtu.be/D2hIicsho7I) +::: # Bounds: Einschränken der generischen Typen -```java +``` java public class Cps { // Obere Schranke: E muss Number oder Subklasse sein // => Zugriff auf Methoden aus Number moeglich @@ -97,51 +38,53 @@ Cps c; // Fehler!!! \bigskip \smallskip -* Schlüsselwort `extends` gilt hier auch für Interfaces -* Mehrere Interfaces: nach `extends` Klasse oder Interface, danach - mit "`&`" getrennt die restlichen Interfaces: +- Schlüsselwort `extends` gilt hier auch für Interfaces - ```java +- Mehrere Interfaces: nach `extends` Klasse oder Interface, danach mit "`&`" + getrennt die restlichen Interfaces: + + ``` java class Cps {} ``` ::: notes -_Anmerkung_: Der Typ-Parameter ist analog auch mit `super` (nach unten) einschränkbar +*Anmerkung*: Der Typ-Parameter ist analog auch mit `super` (nach unten) +einschränkbar -[Beispiel bounds.Cps]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/bounds/Cps.java"} +[Beispiel bounds.Cps]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/bounds/Cps.java"} ::: - # Wildcards: Dieser Typ ist mir nicht so wichtig \bigskip ::: center -Wildcard mit "`?`" => steht für unbestimmten Typ +Wildcard mit "`?`" =\> steht für unbestimmten Typ ::: \bigskip -```java +``` java public class Wuppie { public void m1(List a) { ... } public void m2(List b) { ... } } ``` -* `m1`: `List` beliebig parametrisierbar \newline - => In `m1` für Objekte in Liste `a` nur Methoden von `Object` nutzbar! - -* `m2`: `List` muss mit `Number` oder Subklasse parametrisiert werden. \newline - => Dadurch für Objekte in Liste `b` alle Methoden von `Number` nutzbar ... +- `m1`: `List` beliebig parametrisierbar `\newline`{=tex} =\> In `m1` für Objekte + in Liste `a` nur Methoden von `Object` nutzbar! +- `m2`: `List` muss mit `Number` oder Subklasse parametrisiert werden. + `\newline`{=tex} =\> Dadurch für Objekte in Liste `b` alle Methoden von `Number` + nutzbar ... ::: notes Weitere Eigenschaften: -* Durch Wildcard kein Zugriff auf den Typ -* Wildcard kann durch upper bound eingeschränkt werden -* Geht nicht bei Klassen-/Interface-Definitionen +- Durch Wildcard kein Zugriff auf den Typ +- Wildcard kann durch upper bound eingeschränkt werden +- Geht nicht bei Klassen-/Interface-Definitionen ::: \bigskip @@ -149,15 +92,14 @@ Weitere Eigenschaften: @Bloch2018: Nur für Parameter und nicht für Rückgabewerte nutzen! - # Hands-On: Ausgabe für generische Listen -Ausgabe für Listen gesucht, die sowohl Elemente der Klasse `A` als auch -Elemente der Klasse `B` enthalten [können]{.notes} +Ausgabe für Listen gesucht, die sowohl Elemente der Klasse `A` als auch Elemente der +Klasse `B` enthalten [können]{.notes} \bigskip -```java +``` java class A { void printInfo() { System.out.println("A"); } } class B extends A { void printInfo() { System.out.println("B"); } } @@ -173,13 +115,14 @@ public class X { } ``` -[**Hinweis**: Dieses Beispiel beinhaltet auch Polymorphie bei/mit generischen Datentypen, bitte vorher - auch das Video zum vierten Teil "Generics und Polymorphie" anschauen]{.notes} +[**Hinweis**: Dieses Beispiel beinhaltet auch Polymorphie bei/mit generischen +Datentypen, bitte vorher auch das Video zum vierten Teil "Generics und Polymorphie" +anschauen]{.notes} -::::::::: notes -## Erster Versuch (_A_ und _B_ und _main()_ wie oben) +::: notes +## Erster Versuch (*A* und *B* und *main()* wie oben) -```java +``` java public class X { public static void printInfo(List list) { for (A a : list) { a.printInfo(); } @@ -187,15 +130,15 @@ public class X { } ``` -=> **So gehts nicht!** Eine `List` ist **keine** `List` -(auch wenn ein `B` ein `A` ist, vgl. spätere Sitzung zu Generics und -Vererbung ...)! +=\> **So gehts nicht!** Eine `List` ist **keine** `List` (auch wenn ein `B` +ein `A` ist, vgl. spätere Sitzung zu Generics und Vererbung ...)! -[Beispiel wildcards.v1.X]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/wildcards/v1/X.java"} +[Beispiel wildcards.v1.X]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/wildcards/v1/X.java"} -## Zweiter Versuch mit Wildcards (_A_ und _B_ und _main()_ wie oben) +## Zweiter Versuch mit Wildcards (*A* und *B* und *main()* wie oben) -```java +``` java public class X { public static void printInfo(List list) { for (Object a : list) { a.printInfo(); } @@ -203,19 +146,18 @@ public class X { } ``` -=> **So gehts auch nicht!** Im Prinzip passt das jetzt -für `List` und `List`. Dummerweise hat man durch das Wildcard -keinen Zugriff mehr auf den Typ-Parameter und muss für den Typ der -Laufvariablen in der `for`-Schleife dann `Object` nehmen. Aber -`Object` kennt unser `printInfo` nicht ... Außerdem könnte man die -Methode `X#printInfo` dank des Wildcards auch mit allen anderen -Typen aufrufen ... +=\> **So gehts auch nicht!** Im Prinzip passt das jetzt für `List` und `List`. +Dummerweise hat man durch das Wildcard keinen Zugriff mehr auf den Typ-Parameter und +muss für den Typ der Laufvariablen in der `for`-Schleife dann `Object` nehmen. Aber +`Object` kennt unser `printInfo` nicht ... Außerdem könnte man die Methode +`X#printInfo` dank des Wildcards auch mit allen anderen Typen aufrufen ... -[Beispiel wildcards.v2.X]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/wildcards/v2/X.java"} +[Beispiel wildcards.v2.X]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/wildcards/v2/X.java"} -## Dritter Versuch (Lösung) mit Wildcards und Bounds (_A_ und _B_ und _main()_ wie oben) +## Dritter Versuch (Lösung) mit Wildcards und Bounds (*A* und *B* und *main()* wie oben) -```java +``` java public class X { public static void printInfo(List list) { for (A a : list) { a.printInfo(); } @@ -223,23 +165,85 @@ public class X { } ``` -Das ist die Lösung. Man erlaubt als Argument nur `List`-Objekte und fordert, -dass sie mit `A` oder einer Unterklasse von `A` parametrisiert sind. D.h. -in der Schleife kann man sich auf den gemeinsamen Obertyp `A` abstützen -und hat dann auch wieder die `printInfo`-Methode zur Verfügung ... -::::::::: - -[Konsole wildcards.v3.X]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/java-classic/src/wildcards/v3"} +Das ist die Lösung. Man erlaubt als Argument nur `List`-Objekte und fordert, dass +sie mit `A` oder einer Unterklasse von `A` parametrisiert sind. D.h. in der Schleife +kann man sich auf den gemeinsamen Obertyp `A` abstützen und hat dann auch wieder die +`printInfo`-Methode zur Verfügung ... +::: +[Konsole wildcards.v3.X]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/java-classic/src/wildcards/v3"} # Wrap-Up -* Ein Wildcard (`?`) als Typ-Parameter steht für einen beliebigen Typ +- Ein Wildcard (`?`) als Typ-Parameter steht für einen beliebigen Typ - Ist in Klasse oder Methode dann aber nicht mehr zugreifbar \smallskip -* Mit Bounds kann man Typ-Parameter nach oben oder nach unten einschränken - (im Sinne einer Vererbungshierarchie) - - `extends`: Der Typ-Parameter muss eine Unterklasse eines bestimmten Typen sein +- Mit Bounds kann man Typ-Parameter nach oben oder nach unten einschränken (im + Sinne einer Vererbungshierarchie) + - `extends`: Der Typ-Parameter muss eine Unterklasse eines bestimmten Typen + sein - `super`: Der Typ-Parameter muss eine Oberklasse eines bestimmten Typen sein + +::: readings +- @Ullenboom2021 [Kap. 11.3] +- @LernJava +- @Java-SE-Tutorial +- @Bloch2018 +::: + +::: outcomes +- k3: Ich kann Wildcards und Bounds bei generischen Klassen/Methoden einsetzen +::: + +::: challenges +**Spieler, Mannschaften und Ligen** Modellieren Sie in Java verschiedene +Spielertypen sowie generische Mannschaften und Ligen, die jeweils bestimmte Spieler +(-typen) bzw. Mannschaften aufnehmen können. + +1. Implementieren Sie die Klasse `Spieler`, die das Interface `ISpieler` erfüllt. + + ``` java + public interface ISpieler { + String getName(); + } + ``` + +2. Implementieren Sie die beiden Klassen `FussballSpieler` und `BasketballSpieler` + und sorgen Sie dafür, dass beide Klassen vom Compiler als Spieler betrachtet + werden (geeignete Vererbungshierarchie). + +3. Betrachten Sie das nicht-generische Interface `IMannschaft`. Erstellen Sie + daraus ein generisches Interface `IMannschaft` mit einer Typ-Variablen. Stellen + Sie durch geeignete Beschränkung der Typ-Variablen sicher, dass nur Mannschaften + mit von `ISpieler` abgeleiteten Spielern gebildet werden können. + + ``` java + public interface IMannschaft { + boolean aufnehmen(ISpieler spieler); + boolean rauswerfen(ISpieler spieler); + } + ``` + +4. Betrachten Sie das nicht-generische Interface `ILiga`. Erstellen Sie daraus ein + generisches Interface `ILiga` mit einer Typvariablen. Stellen Sie durch + geeignete Beschränkung der Typvariablen sicher, dass nur Ligen mit von + `IMannschaft` abgeleiteten Mannschaften angelegt werden können. + + ``` java + public interface ILiga { + boolean aufnehmen(IMannschaft mannschaft); + boolean rauswerfen(IMannschaft mannschaft); + } + ``` + +5. Leiten Sie von `ILiga` das **generische** Interface `IBundesLiga` ab. Stellen + Sie durch geeignete Formulierung der Typvariablen sicher, dass nur Ligen mit + Mannschaften angelegt werden können, deren Spieler vom Typ `FussballSpieler` + (oder abgeleitet) sind. + + Realisieren Sie nun noch die Funktionalität von `IBundesLiga` als + **nicht-generisches** Interface `IBundesLiga2`. +::: diff --git a/lecture/java-classic/generics-classes-methods.md b/lecture/java-classic/generics-classes-methods.md index 00419c7a3..39a220a0a 100644 --- a/lecture/java-classic/generics-classes-methods.md +++ b/lecture/java-classic/generics-classes-methods.md @@ -1,40 +1,27 @@ --- +author: Carsten Gips (HSBI) title: "Generics: Generische Klassen & Methoden" -author: "Carsten Gips (HSBI)" -readings: - - "@Ullenboom2021 [Kap. 11.1]" - - "@LernJava" - - "@Java-SE-Tutorial" - - "@Bloch2018" -tldr: | - Generische Klassen und Methoden sind ein wichtiger Baustein in der Programmierung mit Java. - Dabei werden Typ-Variablen eingeführt, die dann bei der Instantiierung der generischen - Klassen oder beim Aufruf von generischen Methoden mit existierenden Typen konkretisiert - werden ("Typ-Parameter"). - - Syntaktisch definiert man die Typ-Variablen in spitzen Klammern hinter dem Klassennamen bzw. - vor dem Rückgabetyp einer Methode: `public class Stack { }` und `public T foo(T m) { }`. -outcomes: - - k1: "Ich kenne die Begriffe 'generischer Typ', 'parametrisierter Typ', 'formaler Typ-Parameter', 'Typ-Parameter'" - - k3: "Ich kann generische Klassen und Interfaces definieren und praktisch einsetzen" - - k3: "Ich kann generische Methoden definieren und praktisch einsetzen" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106235&client_id=FH-Bielefeld" -# name: "Quiz Generics: Classes & Methods (ILIAS)" -youtube: - - link: "https://youtu.be/k6MFPW-shh8" - name: "VL Generische Klassen & Methoden" - - link: "https://youtu.be/ekXBXge6VvE" - name: "Demo Generische Methoden" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/5497f0d31a9d0ac4d46e781040b1fd9f20ca7e0103cccc1abe0089c7c36a9251de0126f11fe376341a7b077dcdd43e8683f2c165b895da1a70da6c17d5c4576f" - name: "VL Generische Klassen & Methoden" --- +::: tldr +Generische Klassen und Methoden sind ein wichtiger Baustein in der Programmierung +mit Java. Dabei werden Typ-Variablen eingeführt, die dann bei der Instantiierung der +generischen Klassen oder beim Aufruf von generischen Methoden mit existierenden +Typen konkretisiert werden ("Typ-Parameter"). + +Syntaktisch definiert man die Typ-Variablen in spitzen Klammern hinter dem +Klassennamen bzw. vor dem Rückgabetyp einer Methode: `public class Stack { }` und +`public T foo(T m) { }`. +::: + +::: youtube +- [VL Generische Klassen & Methoden](https://youtu.be/k6MFPW-shh8) +- [Demo Generische Methoden](https://youtu.be/ekXBXge6VvE) +::: # Generische Strukturen -```java +``` java Vector speicher = new Vector(); speicher.add(1); speicher.add(2); speicher.add(3); speicher.add("huhu"); @@ -46,22 +33,21 @@ for (Object i : speicher) { summe += (Integer)i; } ::: notes Problem: Nutzung des "*raw*" Typs `Vector` ist nicht typsicher! -* Mögliche Fehler fallen erst zur Laufzeit und u.U. erst sehr spät auf: Offenbar werden im obigen - Beispiel `int`-Werte erwartet, d.h. das Hinzufügen von `"huhu"` ist vermutlich ein Versehen (wird - vom Compiler aber nicht bemerkt) -* Die Iteration über `speicher` kann nur allgemein als `Object` erfolgen, d.h. in der Schleife muss - auf den vermuteten/gewünschten Typ gecastet werden: Hier würde dann der String `"huhu"` Probleme - zur Laufzeit machen +- Mögliche Fehler fallen erst zur Laufzeit und u.U. erst sehr spät auf: Offenbar + werden im obigen Beispiel `int`-Werte erwartet, d.h. das Hinzufügen von `"huhu"` + ist vermutlich ein Versehen (wird vom Compiler aber nicht bemerkt) +- Die Iteration über `speicher` kann nur allgemein als `Object` erfolgen, d.h. in + der Schleife muss auf den vermuteten/gewünschten Typ gecastet werden: Hier würde + dann der String `"huhu"` Probleme zur Laufzeit machen ::: \pause - \bigskip \smallskip \hrule \bigskip -```java +``` java Vector speicher = new Vector(); speicher.add(1); speicher.add(2); speicher.add(3); speicher.add("huhu"); @@ -73,19 +59,19 @@ for (Integer i : speicher) { summe += i; } ::: notes Vorteile beim Einsatz von Generics: -* Datenstrukturen/Algorithmen nur einmal implementieren, aber für unterschiedliche Typen nutzen -* Keine Vererbungshierarchie nötig -* Nutzung ist typsicher, Casting unnötig -* Geht nur für Referenztypen -* Beispiel: Collections-API +- Datenstrukturen/Algorithmen nur einmal implementieren, aber für unterschiedliche + Typen nutzen +- Keine Vererbungshierarchie nötig +- Nutzung ist typsicher, Casting unnötig +- Geht nur für Referenztypen +- Beispiel: Collections-API ::: - # Generische Klassen/Interfaces definieren -* **Definition**: "``" hinter Klassennamen +- **Definition**: "``" hinter Klassennamen - ```java + ``` java public class Stack { public E push(E item) { addElement(item); @@ -94,40 +80,39 @@ Vorteile beim Einsatz von Generics: } ``` - * `Stack` => Generische (parametrisierte) Klasse (auch: "*generischer Typ*") - * `E` => Formaler Typ-Parameter (auch: "*Typ-Variable*") + - `Stack` =\> Generische (parametrisierte) Klasse (auch: "*generischer + Typ*") + - `E` =\> Formaler Typ-Parameter (auch: "*Typ-Variable*") \pause \bigskip \smallskip -* **Einsatz**: +- **Einsatz**: - ```java + ``` java Stack stack = new Stack(); ``` - * `Integer` => Typ-Parameter - * `Stack` => Parametrisierter Typ - + - `Integer` =\> Typ-Parameter + - `Stack` =\> Parametrisierter Typ -::::::::: notes +::: notes # Generische Klassen instantiieren -* Typ-Parameter in spitzen Klammern hinter Klasse bzw. Interface +- Typ-Parameter in spitzen Klammern hinter Klasse bzw. Interface - ```java + ``` java ArrayList il = new ArrayList(); ArrayList dl = new ArrayList(); ``` -::::::::: - +::: # Beispiel I: Einfache generische Klassen \bigskip -```java +``` java class Tutor { // T kann in Tutor *fast* wie Klassenname verwendet werden private T x; @@ -137,7 +122,7 @@ class Tutor { \smallskip -```java +``` java Tutor a = new Tutor(); Tutor b = new Tutor<>(); // ab Java7: "Diamond Operator" @@ -146,25 +131,25 @@ b.foo(1); b.foo("huhu"); // Fehlermeldung vom Compiler ``` -::::::::: notes -[Beispiel: classes.GenericClasses]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/classes/GenericClasses.java"} +::: notes +[Beispiel: classes.GenericClasses]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/classes/GenericClasses.java"} ## Typ-Inferenz -Typ-Parameter kann bei `new()` auf der rechten Seite oft weggelassen werden -=> **Typ-Inferenz** +Typ-Parameter kann bei `new()` auf der rechten Seite oft weggelassen werden =\> +**Typ-Inferenz** -```java +``` java Tutor x = new Tutor<>(); // <>: "Diamantoperator" ``` (gilt seit Java 1.7) -::::::::: - +::: # Beispiel II: Vererbung mit Typparametern -```java +``` java interface Fach { public void machWas(T1 a, T2 b); } @@ -183,17 +168,16 @@ class Properties extends Hashtable { ... } ::: notes Auch Interfaces und abstrakte Klassen können parametrisierbar sein. -Bei der Vererbung sind alle Varianten bzgl. der Typ-Variablen denkbar. -Zu beachten ist dabei vor allem, dass die Typ-Variablen der Oberklasse (gilt -analog für Interfaces) entweder durch Typ-Variablen der Unterklasse oder durch -konkrete Typen spezifiziert sind. Die Typ-Variablen der Oberklasse dürfen -nicht "in der Luft hängen" (siehe auch nächste Folie)! +Bei der Vererbung sind alle Varianten bzgl. der Typ-Variablen denkbar. Zu beachten +ist dabei vor allem, dass die Typ-Variablen der Oberklasse (gilt analog für +Interfaces) entweder durch Typ-Variablen der Unterklasse oder durch konkrete Typen +spezifiziert sind. Die Typ-Variablen der Oberklasse dürfen nicht "in der Luft +hängen" (siehe auch nächste Folie)! ::: - # Beispiel III: Überschreiben/Überladen von Methoden -```java +``` java class Mensch { ... } class Studi { @@ -208,27 +192,27 @@ class Tutor extends Studi { } ``` - # Vorsicht: So geht es nicht! -```java +``` java class Foo extends T { ... } class Fluppie extends Wuppie { ... } ``` ::: notes -* Generische Klasse `Foo` kann nicht selbst vom Typ-Parameter `T` ableiten (warum?) -* Bei Ableiten von generischer Klasse `Wuppie` muss deren Typ-Parameter `S` bestimmt sein: - etwa durch den Typ-Parameter der ableitenden Klasse, beispielsweise `Fluppie` (statt `Fluppie`) +- Generische Klasse `Foo` kann nicht selbst vom Typ-Parameter `T` ableiten + (warum?) +- Bei Ableiten von generischer Klasse `Wuppie` muss deren Typ-Parameter `S` + bestimmt sein: etwa durch den Typ-Parameter der ableitenden Klasse, + beispielsweise `Fluppie` (statt `Fluppie`) ::: - # Generische Methoden definieren -* "``" vor Rückgabetyp +- "``" vor Rückgabetyp - ```java + ``` java public class Mensch { public T myst(T m, T n) { return Math.random() > 0.5 ? m : n; @@ -239,34 +223,33 @@ class Fluppie extends Wuppie { ... } \pause \bigskip -* "Mischen possible": +- "Mischen possible": - ```java + ``` java public class Mensch { public T myst(T m, T n) { ... } public String myst(String m, String n) { ... } } ``` - # Aufruf generischer Methoden -::::::::: notes +::: notes ## Aufruf -* Aufruf mit Typ-Parameter vor Methodennamen, oder -* Inferenz durch Compiler +- Aufruf mit Typ-Parameter vor Methodennamen, oder +- Inferenz durch Compiler ## Finden der richtigen Methode durch den Compiler 1. Zuerst Suche nach exakt passender Methode, -2. danach passend mit Konvertierungen - => Compiler sucht gemeinsame Oberklasse in Typhierarchie +2. danach passend mit Konvertierungen =\> Compiler sucht gemeinsame Oberklasse in + Typhierarchie ## Beispiel -::::::::: +::: -```java +``` java class Mensch { T myst(T m, T n) { ... } } @@ -281,12 +264,14 @@ m.myst("Essen", "lecker"); // String, String => T: String m.myst(1.0, 1); // Double, Integer => T: Number ``` -[Beispiel methods.GenericMethods]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/methods/GenericMethods.java"} +[Beispiel methods.GenericMethods]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/methods/GenericMethods.java"} -::::::::: notes -Reihenfolge der Suche nach passender Methode gilt auch für nicht-generisch überladene Methoden +::: notes +Reihenfolge der Suche nach passender Methode gilt auch für nicht-generisch +überladene Methoden -```java +``` java class Mensch { public T myst(T m, T n) { System.out.println("X#myst: T"); @@ -328,24 +313,38 @@ public class GenericMethods { } } ``` -::::::::: - +::: # Wrap-Up -* Begriffe: - * Generischer Typ: `Stack` - * Formaler Typ-Parameter: `T` - * Parametrisierter Typ:`Stack` - * Typ-Parameter: `Long` - * Raw Type: `Stack` +- Begriffe: + - Generischer Typ: `Stack` + - Formaler Typ-Parameter: `T` + - Parametrisierter Typ:`Stack` + - Typ-Parameter: `Long` + - Raw Type: `Stack` \smallskip -* Generische Klassen: `public class Stack { }` - * "``" hinter Klassennamen +- Generische Klassen: `public class Stack { }` + - "``" hinter Klassennamen \smallskip -* Generische Methoden: `public T foo(T m) { }` - * "``" vor Rückgabewert +- Generische Methoden: `public T foo(T m) { }` + - "``" vor Rückgabewert + +::: readings +- @Ullenboom2021 [Kap. 11.1] +- @LernJava +- @Java-SE-Tutorial +- @Bloch2018 +::: + +::: outcomes +- k1: Ich kenne die Begriffe 'generischer Typ', 'parametrisierter Typ', 'formaler + Typ-Parameter', 'Typ-Parameter' +- k3: Ich kann generische Klassen und Interfaces definieren und praktisch + einsetzen +- k3: Ich kann generische Methoden definieren und praktisch einsetzen +::: diff --git a/lecture/java-classic/generics-polymorphism.md b/lecture/java-classic/generics-polymorphism.md index d148823df..f5936ff40 100644 --- a/lecture/java-classic/generics-polymorphism.md +++ b/lecture/java-classic/generics-polymorphism.md @@ -1,33 +1,22 @@ --- +author: Carsten Gips (HSBI) title: "Generics: Generics und Polymorphie" -author: "Carsten Gips (HSBI)" -readings: - - "@Ullenboom2021 [Kap. 11.5]" - - "@LernJava" - - "@Java-SE-Tutorial" - - "@Bloch2018" -tldr: | - Auch mit generischen Klassen stehen die Mechanismen Vererbung und Überladen zur Verfügung. Dabei muss - aber beachtet werden, dass generische Klassen sich **"invariant"** verhalten: Der Typ selbst folgt der - Vererbungsbeziehung, eine Vererbung des Typ-Parameters begründet _keine_ Vererbungsbeziehung! D.h. - aus `U extends O` folgt **nicht** `A extends A`. - - Bei Arrays ist es genau anders herum: Wenn `U extends O` dann gilt auch `U[] extends O[]` ... (Dies - nennt man "_kovariantes_" Verhalten.) -outcomes: - - k3: "Ich kann Vererbungsbeziehungen mit generischen Klassen bilden" - - k3: "Ich kann mit Arrays und generischen Typen umgehen" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106238&client_id=FH-Bielefeld" -# name: "Quiz Generics und Polymorphie (ILIAS)" -youtube: - - link: "https://youtu.be/RiTA43wTixQ" - name: "VL Generics und Polymorphie" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/a0d6f4ada3f85cd9f78dfde923f045f5fd43819a4930c5ef989ff6acb150c2f53208a061cf87f3c3f03823f6d645e10b1388009bfd9cfe474cfb4bdc93302fc2" - name: "VL Generics und Polymorphie" --- +::: tldr +Auch mit generischen Klassen stehen die Mechanismen Vererbung und Überladen zur +Verfügung. Dabei muss aber beachtet werden, dass generische Klassen sich +**"invariant"** verhalten: Der Typ selbst folgt der Vererbungsbeziehung, eine +Vererbung des Typ-Parameters begründet *keine* Vererbungsbeziehung! D.h. aus +`U extends O` folgt **nicht** `A extends A`. + +Bei Arrays ist es genau anders herum: Wenn `U extends O` dann gilt auch +`U[] extends O[]` ... (Dies nennt man "*kovariantes*" Verhalten.) +::: + +::: youtube +- [VL Generics und Polymorphie](https://youtu.be/RiTA43wTixQ) +::: # Generische Polymorphie @@ -39,7 +28,7 @@ fhmedia: \bigskip ::: notes -```java +``` java class A { ... } class B extends A { ... } @@ -48,7 +37,7 @@ A as = new B(); ``` ::: -```java +``` java class Vector { ... } class Stack extends Vector { ... } @@ -57,15 +46,14 @@ Vector vs = new Stack(); ``` \bigskip -=> Polymorphie bei Generics bezieht sich auf **Typ** (nicht Typ-Parameter) + +=\> Polymorphie bei Generics bezieht sich auf **Typ** (nicht Typ-Parameter) ::: notes -**Invarianz**: Generics sind _invariant_, d.h. ein `HashSet` ist ein -Untertyp von `Set`. Bei der Vererbung muss der Typ-Parameter identisch -sein. +**Invarianz**: Generics sind *invariant*, d.h. ein `HashSet` ist ein +Untertyp von `Set`. Bei der Vererbung muss der Typ-Parameter identisch sein. ::: - # Polymorphie bei Generics bezieht sich nur auf Typ! \bigskip @@ -77,7 +65,7 @@ sein. \bigskip \bigskip -```java +``` java Stack s = new Stack(); // DAS GEHT SO NICHT! // Folgen (wenn obiges gehen wuerde): @@ -92,11 +80,10 @@ s.push(new Double(2.0)); // waere dann auch erlaubt ... ``` ::: notes -* Typ-Löschung => zur Laufzeit keine Typinformationen vorhanden -* Compiler muss Typen prüfen (können)! +- Typ-Löschung =\> zur Laufzeit keine Typinformationen vorhanden +- Compiler muss Typen prüfen (können)! ::: - # Abgrenzung: Polymorphie bei Arrays ::: center @@ -106,76 +93,89 @@ Wenn "`B extends A`" dann "`B[] extends A[]`" \bigskip \bigskip -```java +``` java Object[] x = new String[] {"Hello", "World", ":-)"}; x[0] = "Hallo"; x[0] = new Double(2.0); // Laufzeitfehler String[] y = x; // String[] ist KEIN Object[]!!! ``` + \bigskip -* Arrays besitzen Typinformationen über gespeicherte Elemente -* Prüfung auf Typ-Kompatibilität zur **Laufzeit** (nicht Kompilierzeit!) +- Arrays besitzen Typinformationen über gespeicherte Elemente +- Prüfung auf Typ-Kompatibilität zur **Laufzeit** (nicht Kompilierzeit!) -[Hinweis auf Java-Geschichte [(Java-Insel: "Type Erasure")]{.notes}]{.ex href="https://openbook.rheinwerk-verlag.de/javainsel/11_002.html#u11.2.2"} +[Hinweis auf Java-Geschichte [(Java-Insel: "Type Erasure")]{.notes}]{.ex +href="https://openbook.rheinwerk-verlag.de/javainsel/11_002.html#u11.2.2"} ::: notes -Arrays gab es sehr früh, Generics erst relativ spät (ab Java6) => bei -Arrays fand man das Verhalten natürlich und pragmatisch (trotz der Laufzeit-Überprüfung). +Arrays gab es sehr früh, Generics erst relativ spät (ab Java6) =\> bei Arrays fand +man das Verhalten natürlich und pragmatisch (trotz der Laufzeit-Überprüfung). -Bei der Einführung von Generics musste man Kompatibilität sicherstellen (alter -Code soll auch mit neuen Compilern übersetzt werden können - obwohl im alten -Code Raw-Types verwendet werden). Außerdem wollte man von Laufzeit-Prüfung hin zu +Bei der Einführung von Generics musste man Kompatibilität sicherstellen (alter Code +soll auch mit neuen Compilern übersetzt werden können - obwohl im alten Code +Raw-Types verwendet werden). Außerdem wollte man von Laufzeit-Prüfung hin zu Compiler-Prüfung. Da würde das von Arrays bekannte Verhalten Probleme machen ... -**Kovarianz**: Arrays sind _kovariant_, d.h. ein Array vom Typ `String[]` ist wegen +**Kovarianz**: Arrays sind *kovariant*, d.h. ein Array vom Typ `String[]` ist wegen `String extends Object` ein Untertyp von `Object[]`. -[Beispiel arrays.X]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/arrays/X.java"} +[Beispiel arrays.X]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/arrays/X.java"} ::: +# Arrays vs. parametrisierte Klassen -# Arrays vs. parametrisierte Klassen - -=> Keine Arrays mit parametrisierten Klassen! +=\> Keine Arrays mit parametrisierten Klassen! \bigskip \bigskip -```java +``` java Foo[] x = new Foo[2]; // Compilerfehler Foo y = new Foo(); // OK :) ``` ::: notes -Arrays mit parametrisierten Klassen sind nicht erlaubt! Arrays brauchen zur -Laufzeit Typinformationen, die aber durch die Typ-Löschung entfernt werden. +Arrays mit parametrisierten Klassen sind nicht erlaubt! Arrays brauchen zur Laufzeit +Typinformationen, die aber durch die Typ-Löschung entfernt werden. ::: - -# Diskussion Vererbung vs. Generics +# Diskussion Vererbung vs. Generics **Vererbung:** -* *IS-A*-Beziehung -* Anwendung: Vererbungsbeziehung vorliegend, Eigenschaften verfeinern -* Beispiel: Ein Student *ist eine* Person +- *IS-A*-Beziehung +- Anwendung: Vererbungsbeziehung vorliegend, Eigenschaften verfeinern +- Beispiel: Ein Student *ist eine* Person \bigskip **Generics:** -* Schablone (Template) für viele Datentypen -* Anwendung: Identischer Code für unterschiedliche Typen -* Beispiel: Datenstrukturen, Algorithmen generisch realisieren - +- Schablone (Template) für viele Datentypen +- Anwendung: Identischer Code für unterschiedliche Typen +- Beispiel: Datenstrukturen, Algorithmen generisch realisieren # Wrap-Up -* Generics: Vererbung und Überladen möglich, aber: \newline - **Aus "`U extends O`" folgt **nicht** "`A extends A`"** +- Generics: Vererbung und Überladen möglich, aber: `\newline`{=tex} Aus + "`U extends O`" folgt **nicht** "`A extends A`" \smallskip -* Achtung: Bei Arrays gilt aber: Wenn "`U extends O`" dann gilt auch "`U[] extends O[]`" ... +- Achtung: Bei Arrays gilt aber: Wenn "`U extends O`" dann gilt auch + "`U[] extends O[]`" ... + +::: readings +- @Ullenboom2021 [Kap. 11.5] +- @LernJava +- @Java-SE-Tutorial +- @Bloch2018 +::: + +::: outcomes +- k3: Ich kann Vererbungsbeziehungen mit generischen Klassen bilden +- k3: Ich kann mit Arrays und generischen Typen umgehen +::: diff --git a/lecture/java-classic/generics-type-erasure.md b/lecture/java-classic/generics-type-erasure.md index 432c83793..482b1449f 100644 --- a/lecture/java-classic/generics-type-erasure.md +++ b/lecture/java-classic/generics-type-erasure.md @@ -1,33 +1,22 @@ --- +author: Carsten Gips (HSBI) title: "Generics: Type Erasure" -author: "Carsten Gips (HSBI)" -readings: - - "@Ullenboom2021 [Kap. 11.2 und 11.6]" - - "@LernJava" - - "@Java-SE-Tutorial" - - "@Bloch2018" -tldr: | - Generics existieren eigentlich nur auf Quellcode-Ebene. Nach der Typ-Prüfung etc. - entfernt der Compiler alle generischen Typ-Parameter und alle `<...>` (=> - "Type-Erasure"), d.h. im Byte-Code stehen nur noch Raw-Typen bzw. die oberen - Typ-Schranken der Typ-Parameter, in der Regel `Object`. Zusätzlich baut der Compiler - die nötigen Casts ein. Als Anwender merkt man davon nichts, muss das "Type-Erasure" - wegen der Auswirkungen aber auf dem Radar haben! -outcomes: - - k2: "Ich verstehe 'Typ-Löschung' bei Generics und kann die Auswirkungen erklären" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106237&client_id=FH-Bielefeld" -# name: "Quiz Generics: Type Erasure (ILIAS)" -youtube: - - link: "https://youtu.be/vo0WKkPBMAM" - name: "VL Generics: Type Erasure" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/5fad3671a098d262206f0b0eb995b2d692a6e9914a336b1c28fc99753b0c874a637d310dcdc639afdd200d831b4e3ee5924ea8073b4a32751aebe4fa91c32bef" - name: "VL Generics: Type Erasure" --- +::: tldr +Generics existieren eigentlich nur auf Quellcode-Ebene. Nach der Typ-Prüfung etc. +entfernt der Compiler alle generischen Typ-Parameter und alle `<...>` (=\> +"Type-Erasure"), d.h. im Byte-Code stehen nur noch Raw-Typen bzw. die oberen +Typ-Schranken der Typ-Parameter, in der Regel `Object`. Zusätzlich baut der Compiler +die nötigen Casts ein. Als Anwender merkt man davon nichts, muss das "Type-Erasure" +wegen der Auswirkungen aber auf dem Radar haben! +::: + +::: youtube +- [VL Generics: Type Erasure](https://youtu.be/vo0WKkPBMAM) +::: -# Typ-Löschung (_Type-Erasure_) +# Typ-Löschung (*Type-Erasure*) ::: notes Der Compiler ersetzt nach Prüfung der Typen und ihrer Verwendung alle Typ-Parameter @@ -36,24 +25,23 @@ durch 1. deren obere (Typ-)Schranke und 2. passende explizite Cast-Operationen (im Byte-Code). -Die obere Typ-Schranke ist in der Regel der Typ der ersten Bounds-Klausel -oder `Object`, wenn keine Einschränkungen formuliert sind. +Die obere Typ-Schranke ist in der Regel der Typ der ersten Bounds-Klausel oder +`Object`, wenn keine Einschränkungen formuliert sind. -Bei parametrisierten Typen wie `List` wird der Typ-Parameter entfernt, -es entsteht ein sogenannter *Raw*-Typ (`List`, quasi implizit mit `Object` +Bei parametrisierten Typen wie `List` wird der Typ-Parameter entfernt, es +entsteht ein sogenannter *Raw*-Typ (`List`, quasi implizit mit `Object` parametrisiert). -=> Ergebnis: Nur **eine** (untypisierte) Klasse! Zur Laufzeit gibt es -keine Generics mehr! - -**Hinweis**: In C++ ist man den anderen möglichen Weg gegangen und erzeugt -für jede Instantiierung die passende Klasse. Siehe Modul "Systemprogrammierung" :) +=\> Ergebnis: Nur **eine** (untypisierte) Klasse! Zur Laufzeit gibt es keine +Generics mehr! +**Hinweis**: In C++ ist man den anderen möglichen Weg gegangen und erzeugt für jede +Instantiierung die passende Klasse. Siehe Modul "Systemprogrammierung" :) **Beispiel**: Aus dem folgenden harmlosen Code-Fragment: ::: -```java +``` java class Studi { T myst(T m, T n) { return n; } @@ -65,7 +53,6 @@ class Studi { ``` \pause - \bigskip \hrule \smallskip @@ -74,7 +61,7 @@ class Studi { wird nach der Typ-Löschung durch Compiler (das steht dann quasi im Byte-Code): ::: -```java +``` java class Studi { Object myst(Object m, Object n) { return n; } @@ -86,18 +73,16 @@ class Studi { ``` ::: notes -Die obere Schranke meist `Object` => `new T()` verboten/sinnfrei (s.u.)! +Die obere Schranke meist `Object` =\> `new T()` verboten/sinnfrei (s.u.)! ::: - # Type-Erasure bei Nutzung von Bounds -:::::: columns +::::: columns ::: column - [vor der Typ-Löschung durch Compiler:]{.notes} -```java +``` java class Cps { T myst(T m, T n) { return n; @@ -109,13 +94,12 @@ class Cps { } } ``` - ::: -::: column +::: column [nach der Typ-Löschung durch Compiler:]{.notes} -```java +``` java class Cps { Number myst(Number m, Number n) { return n; @@ -127,45 +111,41 @@ class Cps { } } ``` - ::: -:::::: - +::::: # Raw-Types: Ich mag meine Generics "well done" :-) -Raw-Types: Instanziierung ohne Typ-Parameter => `Object` +Raw-Types: Instanziierung ohne Typ-Parameter =\> `Object` -```java +``` java Stack s = new Stack(); // Stack von Object-Objekten ``` \bigskip -* Wegen Abwärtskompatibilität zu früheren Java-Versionen noch erlaubt. -* Nutzung wird nicht empfohlen! (Warum?) +- Wegen Abwärtskompatibilität zu früheren Java-Versionen noch erlaubt. +- Nutzung wird nicht empfohlen! (Warum?) ::: notes ## Anmerkung -Raw-Types darf man zwar selbst im Quellcode verwenden (so wie im Beispiel -hier), **sollte** die Verwendung aber vermeiden wegen der Typ-Unsicherheit: -Der Compiler sieht im Beispiel nur noch einen Stack für `Object`, d.h. dort -dürfen Objekte aller Typen abgelegt werden - es kann keine Typprüfung -durch den Compiler stattfinden. Auf einem `Stack` kann der Compiler -prüfen, ob dort wirklich nur `String`-Objekte abgelegt werden und ggf. -entsprechend Fehler melden. +Raw-Types darf man zwar selbst im Quellcode verwenden (so wie im Beispiel hier), +**sollte** die Verwendung aber vermeiden wegen der Typ-Unsicherheit: Der Compiler +sieht im Beispiel nur noch einen Stack für `Object`, d.h. dort dürfen Objekte aller +Typen abgelegt werden - es kann keine Typprüfung durch den Compiler stattfinden. Auf +einem `Stack` kann der Compiler prüfen, ob dort wirklich nur +`String`-Objekte abgelegt werden und ggf. entsprechend Fehler melden. -Etwas anderes ist es, dass der Compiler im Zuge von Type-Erasure selbst -Raw-Types in den Byte-Code schreibt. Da hat er vorher bereits die -Typsicherheit geprüft und er baut auch die passenden Casts ein. +Etwas anderes ist es, dass der Compiler im Zuge von Type-Erasure selbst Raw-Types in +den Byte-Code schreibt. Da hat er vorher bereits die Typsicherheit geprüft und er +baut auch die passenden Casts ein. -Das Thema ist eigentlich nur noch aus Kompatibilität zu Java5 oder früher -da, weil es dort noch keine Generics gab (wurden erst mit Java6 eingeführt). +Das Thema ist eigentlich nur noch aus Kompatibilität zu Java5 oder früher da, weil +es dort noch keine Generics gab (wurden erst mit Java6 eingeführt). ::: - -# Folgen der Typ-Löschung: _new_ +# Folgen der Typ-Löschung: *new* ::: center `new` mit parametrisierten Klassen ist nicht erlaubt! @@ -174,7 +154,7 @@ da, weil es dort noch keine Generics gab (wurden erst mit Java6 eingeführt). \bigskip \bigskip -```java +``` java class Fach { public T foo() { return new T(); // nicht erlaubt!!! @@ -183,19 +163,17 @@ class Fach { ``` \bigskip + Grund: Zur Laufzeit keine Klasseninformationen über `T` mehr ::: notes -Im Code steht `return (CAST) new Object();`. Das neue Object -kann man anlegen, aber ein Cast nach irgendeinem anderen Typ -ist sinnfrei: Jede Klasse ist ein Untertyp von `Object`, aber -eben nicht andersherum. Außerdem fehlt dem Objekt vom Typ -`Object` auch sämtliche Information und Verhalten, die der -Cast-Typ eigentlich mitbringt ... +Im Code steht `return (CAST) new Object();`. Das neue Object kann man anlegen, aber +ein Cast nach irgendeinem anderen Typ ist sinnfrei: Jede Klasse ist ein Untertyp von +`Object`, aber eben nicht andersherum. Außerdem fehlt dem Objekt vom Typ `Object` +auch sämtliche Information und Verhalten, die der Cast-Typ eigentlich mitbringt ... ::: - -# Folgen der Typ-Löschung: _static_ +# Folgen der Typ-Löschung: *static* ::: center `static` mit generischen Typen ist nicht erlaubt! @@ -204,7 +182,7 @@ Cast-Typ eigentlich mitbringt ... \bigskip \bigskip -```java +``` java class Fach { static T t; // nicht erlaubt!!! static Fach c; // nicht erlaubt!!! @@ -216,15 +194,15 @@ Fach b; ``` \bigskip -Grund: Compiler generiert nur eine Klasse! Beide Objekte würden -sich die statischen Attribute teilen \newline (Typ zur Laufzeit unklar!). + +Grund: Compiler generiert nur eine Klasse! Beide Objekte würden sich die statischen +Attribute teilen `\newline`{=tex} (Typ zur Laufzeit unklar!). \smallskip *Hinweis*: Generische (statische) Methoden sind erlaubt. - -# Folgen der Typ-Löschung: _instanceof_ +# Folgen der Typ-Löschung: *instanceof* ::: center `instanceof` mit parametrisierten Klassen ist nicht erlaubt! @@ -233,10 +211,9 @@ sich die statischen Attribute teilen \newline (Typ zur Laufzeit unklar!). \bigskip \bigskip -:::::: columns +::::: columns ::: {.column width="60%"} - -```java +``` java class Fach { void printType(Fach p) { if (p instanceof Fach) @@ -246,13 +223,12 @@ class Fach { } } ``` - ::: -::: {.column width="40%"} +::: {.column width="40%"} [Grund: Unsinniger Code nach Typ-Löschung:]{.notes} -```java +``` java class Fach { void printType(Fach p) { if (p instanceof Fach) @@ -262,12 +238,10 @@ void printType(Fach p) { } } ``` - ::: -:::::: - +::::: -# Folgen der Typ-Löschung: _.class_ +# Folgen der Typ-Löschung: *.class* ::: center `.class` mit parametrisierten Klassen ist nicht erlaubt! @@ -276,7 +250,7 @@ void printType(Fach p) { \bigskip \bigskip -```java +``` java boolean x; List a = new ArrayList(); List b = new ArrayList(); @@ -286,15 +260,27 @@ x = (a.getClass() == b.getClass()); // true ``` \bigskip -Grund: Es gibt nur `List.class` (und kein `List.class` bzw. `List.class`)! +Grund: Es gibt nur `List.class` (und kein `List.class` bzw. +`List.class`)! # Wrap-Up - Generics existieren eigentlich nur auf Quellcode-Ebene - "Type-Erasure": - - Compiler entfernt [nach Typ-Prüfungen etc.]{.notes} - generische Typ-Parameter [etc.]{.notes} => im Byte-Code nur noch Raw-Typen - [bzw. die oberen Typ-Schranken der Typ-Parameter, in der Regel `Object`]{.notes} + - Compiler entfernt [nach Typ-Prüfungen etc.]{.notes} generische Typ-Parameter + [etc.]{.notes} =\> im Byte-Code nur noch Raw-Typen [bzw. die oberen + Typ-Schranken der Typ-Parameter, in der Regel `Object`]{.notes} - Compiler baut passende Casts in Byte-Code ein - Transparent für User; Auswirkungen beachten! + +::: readings +- @Ullenboom2021 [Kap. 11.2 und 11.6] +- @LernJava +- @Java-SE-Tutorial +- @Bloch2018 +::: + +::: outcomes +- k2: Ich verstehe 'Typ-Löschung' bei Generics und kann die Auswirkungen erklären +::: diff --git a/lecture/java-classic/readme.md b/lecture/java-classic/readme.md index 494508477..911492ffe 100644 --- a/lecture/java-classic/readme.md +++ b/lecture/java-classic/readme.md @@ -1,5 +1,6 @@ --- -title: "Fortgeschrittene Java-Themen und Umgang mit JVM" -no_pdf: true no_beamer: true +no_pdf: true +title: Fortgeschrittene Java-Themen und Umgang mit JVM --- + diff --git a/lecture/java-classic/reflection.md b/lecture/java-classic/reflection.md index 6ec49de32..042e5ddc2 100644 --- a/lecture/java-classic/reflection.md +++ b/lecture/java-classic/reflection.md @@ -1,64 +1,37 @@ --- -title: "Reflection" -author: "Carsten Gips (HSBI)" -readings: - - "@Java-SE-Tutorial" - - "@Inden2013 [Kap. 8]" -tldr: | - Mit Hilfe der Reflection-API kann man Programme zur **Laufzeit** inspizieren - und Eigenschaften von Elementen wie Klassen oder Methoden abfragen, aber auch - Klassen instantiieren und Methoden aufrufen, die eigentlich auf `private` - gesetzt sind oder die beispielsweise mit einer bestimmten Annotation markiert - sind. - - Die Laufzeitumgebung erzeugt zu jedem Typ ein Objekt der Klasse - `java.lang.Class`. Über dieses `Class`-Objekt einer Klasse können dann Informationen - über diese Klasse abgerufen werden, beispielsweise welche Konstruktoren, - Methoden und Attribute es gibt. - - Man kann über auch Klassen zur Laufzeit nachladen, die zur Compile-Zeit nicht - bekannt waren. Dies bietet sich beispielsweise für User-definierte Plugins an. - - Reflection ist ein mächtiges Werkzeug. Durch das Arbeiten mit Strings und die - Interaktion/Inspektion zur _Laufzeit_ verliert man aber viele Prüfungen, die - der Compiler normalerweise zur Compile-Zeit vornimmt. Auch das Refactoring wird - dadurch eher schwierig. -outcomes: - - k2: "Ich kenne typische Probleme beim Einsatz von Reflection" - - k2: "Ich kann die Bedeutung der verschiedenen Exceptions beim Aufruf von Methoden per Reflection erklären" - - k3: "Ich kann zur Laufzeit mit Reflection Information zu Klassen und Methoden erlangen" - - k3: "Ich kann zur Compilezeit unbekannte Klassen einbinden und deren Konstruktoren und Methoden (mit und ohne Parameter/Rückgabewerte) aufrufen" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106517&client_id=FH-Bielefeld" -# name: "Quiz Reflection (ILIAS)" -youtube: - - link: "https://youtu.be/7wTKl8-KYd0" - name: "VL Reflection" - - link: "https://youtu.be/e7rLH1f0fKM" - name: "Demo Reflection" - - link: "https://youtu.be/HI_ZJFbvoNY" - name: "Demo Class-Loader" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/9ebd01cdab64351e7b52863f4a442d3199d3322eef1ba277ba979ce756d2892a1317ddd6e1fc34e6b4d28b237dec99e56810fe8bb35b9e95db325edbb14d7719" - name: "VL Reflection" -challenges: | - In den [Vorgaben](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/java-classic/src/challenges/reflection) - finden Sie eine einfache Implementierung für einen Taschenrechner mit Java-Swing. - Dieser Taschenrechner kann nur mit `int`-Werten rechnen. - Der Taschenrechner verfügt über keinerlei vordefinierte mathematische Operationen (Addieren, Subtrahieren etc.). - - Erstellen Sie eigene mathematische Operationen, die `IOperation` implementieren. Jede Ihrer Klassen soll mit einer Annotation vermerkt werden, in welcher der Name der jeweiligen Operation gespeichert wird. - - Der Taschenrechner lädt seine Operationen dynamisch über die statische Methode `OperationLoader.loadOperations` ein. - In den Vorgaben ist diese Methode noch nicht ausimplementiert. Implementieren Sie die Funktion so, dass sie mit Hilfe von Reflection Ihre Operationen einliest. Geben Sie dazu den Ordner an, in dem die entsprechenden `.class`-Dateien liegen. (Dieser Ordner soll sich außerhalb Ihres Java-Projekts befinden!) - Verändern Sie nicht die Signatur der Methode. - - Ihre Operation-Klassen dürfen Sie nicht vorher bekannt machen. Diese müssen in einem vom aktuellen Projekt separierten Ordner/Projekt liegen. +author: Carsten Gips (HSBI) +title: Reflection --- +::: tldr +Mit Hilfe der Reflection-API kann man Programme zur **Laufzeit** inspizieren und +Eigenschaften von Elementen wie Klassen oder Methoden abfragen, aber auch Klassen +instantiieren und Methoden aufrufen, die eigentlich auf `private` gesetzt sind oder +die beispielsweise mit einer bestimmten Annotation markiert sind. + +Die Laufzeitumgebung erzeugt zu jedem Typ ein Objekt der Klasse `java.lang.Class`. +Über dieses `Class`-Objekt einer Klasse können dann Informationen über diese Klasse +abgerufen werden, beispielsweise welche Konstruktoren, Methoden und Attribute es +gibt. + +Man kann über auch Klassen zur Laufzeit nachladen, die zur Compile-Zeit nicht +bekannt waren. Dies bietet sich beispielsweise für User-definierte Plugins an. + +Reflection ist ein mächtiges Werkzeug. Durch das Arbeiten mit Strings und die +Interaktion/Inspektion zur *Laufzeit* verliert man aber viele Prüfungen, die der +Compiler normalerweise zur Compile-Zeit vornimmt. Auch das Refactoring wird dadurch +eher schwierig. +::: + +::: youtube +- [VL Reflection](https://youtu.be/7wTKl8-KYd0) +- [Demo Reflection](https://youtu.be/e7rLH1f0fKM) +- [Demo Class-Loader](https://youtu.be/HI_ZJFbvoNY) +::: + # Ausgaben und Einblicke zur Laufzeit -```java +``` java public class FactoryBeispielTest { @Test public void testGetTicket() { @@ -69,23 +42,21 @@ public class FactoryBeispielTest { \bigskip -```java +``` java @Target(value = ElementType.METHOD) @Retention(value = RetentionPolicy.RUNTIME) public @interface Wuppie {} ``` ::: notes -Reflection wird allgemein genutzt, um zur Laufzeit von Programmen -Informationen über Klassen/Methoden/... zu bestimmen. Man könnte -damit auch das Verhalten der laufenden Programme ändern oder Typen -instantiieren und/oder Methoden aufrufen ... +Reflection wird allgemein genutzt, um zur Laufzeit von Programmen Informationen über +Klassen/Methoden/... zu bestimmen. Man könnte damit auch das Verhalten der laufenden +Programme ändern oder Typen instantiieren und/oder Methoden aufrufen ... -Wenn Sie nicht (mehr) wissen, wie man eigene Annotationen definiert, -schauen Sie doch bitte einfach kurz im Handout zu Annotationen nach :-) +Wenn Sie nicht (mehr) wissen, wie man eigene Annotationen definiert, schauen Sie +doch bitte einfach kurz im Handout zu Annotationen nach :-) ::: - # Wer bin ich? ... Informationen über ein Programm (zur Laufzeit) ::: center @@ -94,7 +65,7 @@ schauen Sie doch bitte einfach kurz im Handout zu Annotationen nach :-) \bigskip -```java +``` java // usual way of life Studi heiner = new Studi(); heiner.hello(); @@ -108,23 +79,21 @@ try { ``` ::: notes -Für jeden Typ instantiiert die JVM eine nicht veränderbare Instanz -der Klasse `java.lang.Class`, über die Informationen zu dem Typ -abgefragt werden können. +Für jeden Typ instantiiert die JVM eine nicht veränderbare Instanz der Klasse +`java.lang.Class`, über die Informationen zu dem Typ abgefragt werden können. Dies umfasst u.a.: -* Klassenname -* Implementierte Interfaces -* Methoden -* Attribute -* Annotationen -* ... +- Klassenname +- Implementierte Interfaces +- Methoden +- Attribute +- Annotationen +- ... `java.lang.Class` bildet damit den Einstiegspunkt in die Reflection. ::: - # Vorgehen 1. Gewünschte Klasse über ein `Class`-Objekt laden @@ -139,22 +108,23 @@ Dies umfasst u.a.: 4. Methoden aufrufen ::: notes -Das Vorgehen umfasst vier Schritte: Zunächst die gewünschte Klasse über ein `Class`-Objekt laden -und anschließend Informationen abrufen (etwa welche Methoden vorhanden sind, welche Annotationen -annotiert wurden, ...) und bei Bedarf eine Instanz dieser Klasse erzeugen sowie Methoden aufrufen. - -Ein zweiter wichtiger Anwendungsfall (neben dem Abfragen von Informationen und Aufrufen von Methoden) -ist das Laden von Klassen, die zur Compile-Zeit nicht mit dem eigentlichen Programm verbunden sind. -Auf diesem Weg kann beispielsweise ein Bildbearbeitungsprogramm zur Laufzeit dynamisch Filter aus einem -externen Ordner laden und nutzen, oder der Lexer kann die Tokendefinitionen zur Laufzeit einlesen (d.h. -er könnte mit unterschiedlichen Tokensätzen arbeiten, die zur Compile-Zeit noch gar nicht definiert sind). -Damit werden die Programme dynamischer. +Das Vorgehen umfasst vier Schritte: Zunächst die gewünschte Klasse über ein +`Class`-Objekt laden und anschließend Informationen abrufen (etwa welche Methoden +vorhanden sind, welche Annotationen annotiert wurden, ...) und bei Bedarf eine +Instanz dieser Klasse erzeugen sowie Methoden aufrufen. + +Ein zweiter wichtiger Anwendungsfall (neben dem Abfragen von Informationen und +Aufrufen von Methoden) ist das Laden von Klassen, die zur Compile-Zeit nicht mit dem +eigentlichen Programm verbunden sind. Auf diesem Weg kann beispielsweise ein +Bildbearbeitungsprogramm zur Laufzeit dynamisch Filter aus einem externen Ordner +laden und nutzen, oder der Lexer kann die Tokendefinitionen zur Laufzeit einlesen +(d.h. er könnte mit unterschiedlichen Tokensätzen arbeiten, die zur Compile-Zeit +noch gar nicht definiert sind). Damit werden die Programme dynamischer. ::: +# Schritt 1: *Class*-Objekt erzeugen und Klasse laden -# Schritt 1: _Class_-Objekt erzeugen und Klasse laden - -```java +``` java // Variante 1 (package.MyClass dynamisch zur Laufzeit laden) Class c = Class.forName("package.MyClass"); @@ -168,13 +138,12 @@ Class c = MyClass.class; ``` ::: notes -=> Einstiegspunkt der Reflection API +=\> Einstiegspunkt der Reflection API -Eigentlich wird nur in **Variante 1 die über den String angegebene Klasse -dynamisch von der Laufzeitumgebung (nach-) geladen** (muss also im gestarteten -Programm nicht vorhanden sein). Die angegebene Klasse muss aber in Form von -Byte-Code an der angegebenen Stelle (Ordner `package`, Dateiname `MyClass.class`) -vorhanden sein. +Eigentlich wird nur in **Variante 1 die über den String angegebene Klasse dynamisch +von der Laufzeitumgebung (nach-) geladen** (muss also im gestarteten Programm nicht +vorhanden sein). Die angegebene Klasse muss aber in Form von Byte-Code an der +angegebenen Stelle (Ordner `package`, Dateiname `MyClass.class`) vorhanden sein. Die anderen beiden Varianten setzen voraus, dass die jeweilige Klasse **bereits geladen** ist (also ganz normal mit den restlichen Sourcen zu Byte-Code @@ -184,10 +153,9 @@ Alle drei Varianten ermöglichen die Introspektion der jeweiligen Klassen zur Laufzeit. ::: - # Schritt 2: In die Klasse reinschauen -```java +``` java // Studi-Klasse dynamisch (nach-) laden Class c = Class.forName("reflection.Studi"); @@ -206,30 +174,34 @@ Method[] allMethods = c.getDeclaredMethods(); // all methods (excl. inherited) ``` ::: notes -* `public` Methode laden (auch von Superklasse/Interface geerbt): `Class.getMethod(String, Class[])` -* Beliebige (auch `private`) Methoden (in der Klasse selbst deklariert): `Class.getDeclaredMethod(...)` - -_Anmerkung_: Mit `Class.getDeclaredMethods()` erhalten Sie alle Methoden, -die direkt in der Klasse deklariert werden (ohne geerbte Methoden!), unabhängig -von deren Sichtbarkeit. Mit `Class.getMethods()` erhalten Sie dagegen alle -`public` Methoden, die in der Klasse selbst oder ihren Superklassen bzw. den -implementierten Interfaces deklariert sind. - -Vgl. Javadoc [`getMethods`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Class.html#getMethods()) -und [`getDeclaredMethods`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Class.html#getDeclaredMethods()). +- `public` Methode laden (auch von Superklasse/Interface geerbt): + `Class.getMethod(String, Class[])` +- Beliebige (auch `private`) Methoden (in der Klasse selbst deklariert): + `Class.getDeclaredMethod(...)` + +*Anmerkung*: Mit `Class.getDeclaredMethods()` erhalten Sie alle Methoden, die +direkt in der Klasse deklariert werden (ohne geerbte Methoden!), unabhängig von +deren Sichtbarkeit. Mit `Class.getMethods()` erhalten Sie dagegen alle `public` +Methoden, die in der Klasse selbst oder ihren Superklassen bzw. den implementierten +Interfaces deklariert sind. + +Vgl. Javadoc +[`getMethods`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Class.html#getMethods()) +und +[`getDeclaredMethods`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Class.html#getDeclaredMethods()). Die Methoden-Arrays können Sie nach bestimmten Eigenschaften durchsuchen, bzw. auf -das Vorhandensein einer bestimmten Annotation prüfen (etwa mit `isAnnotationPresent()`) -etc. +das Vorhandensein einer bestimmten Annotation prüfen (etwa mit +`isAnnotationPresent()`) etc. -Analog können Sie weitere Eigenschaften einer Klasse abfragen, beispielsweise Attribute -(`Class.getDeclaredFields()`) oder Konstruktoren (`Class.getDeclaredConstructors()`). +Analog können Sie weitere Eigenschaften einer Klasse abfragen, beispielsweise +Attribute (`Class.getDeclaredFields()`) oder Konstruktoren +(`Class.getDeclaredConstructors()`). ::: - # Schritt 3: Instanz der geladenen Klasse erzeugen -```java +``` java // Class-Objekt erzeugen Class c = Class.forName("reflection.Studi"); @@ -250,29 +222,28 @@ Studi s = (Studi) ctor.newInstance("Beate", 42); ::: notes ## Parameterlose, öffentliche Konstruktoren: -* `Class.newInstance()` (seit Java9 _deprecated_!) -* `Class.getConstructor()` => `Constructor.newInstance()` +- `Class.newInstance()` (seit Java9 *deprecated*!) +- `Class.getConstructor()` =\> `Constructor.newInstance()` ## Sonstige Konstruktoren: Passenden Konstruktor explizit holen: `Class.getDeclaredConstructor(Class[])`, -Parametersatz zusammenbasteln (hier nicht dargestellt) -und aufrufen `Constructor.newInstance(...)` +Parametersatz zusammenbasteln (hier nicht dargestellt) und aufrufen +`Constructor.newInstance(...)` -## Unterschied _new_ und _Constructor.newInstance()_: +## Unterschied *new* und *Constructor.newInstance()*: -`new` ist nicht identisch zu `Constructor.newInstance()`: `new` -kann Dinge wie Typ-Prüfung oder Auto-Boxing mit erledigen, während -man dies bei `Constructor.newInstance()` selbst explizit angeben -oder erledigen muss. +`new` ist nicht identisch zu `Constructor.newInstance()`: `new` kann Dinge wie +Typ-Prüfung oder Auto-Boxing mit erledigen, während man dies bei +`Constructor.newInstance()` selbst explizit angeben oder erledigen muss. -Vgl. [docs.oracle.com/javase/tutorial/reflect/member/ctorTrouble.html](https://docs.oracle.com/javase/tutorial/reflect/member/ctorTrouble.html). +Vgl. +[docs.oracle.com/javase/tutorial/reflect/member/ctorTrouble.html](https://docs.oracle.com/javase/tutorial/reflect/member/ctorTrouble.html). ::: - # Schritt 4: Methoden aufrufen ... -```java +``` java // Studi-Klasse dynamisch (nach-) laden Class c = Class.forName("reflection.Studi"); // Studi-Objekt anlegen (Defaultkonstruktor) @@ -294,12 +265,12 @@ der Parameter und deren Typ und Annotationen fragen etc. ... Schauen Sie am best einmal selbst in die API hinein. ::: -[Demo: reflection.ReflectionDemo]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/reflection/ReflectionDemo.java"} - +[Demo: reflection.ReflectionDemo]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/reflection/ReflectionDemo.java"} # Hinweis: Klassen außerhalb des Classpath laden -```java +``` java File folder = new File("irgendwo"); URL[] ua = new URL[]{folder.toURI().toURL()}; @@ -308,50 +279,87 @@ Class c1 = Class.forName("org.wuppie.Fluppie", true, ucl); Class c2 = ucl.loadClass("org.wuppie.Fluppie"); ``` -[Bemerkung zu Ordnerstruktur und Classpath; Demo: reflection.ClassLoaderDemo]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/reflection/ClassLoaderDemo.java"} +[Bemerkung zu Ordnerstruktur und Classpath; Demo: reflection.ClassLoaderDemo]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/reflection/ClassLoaderDemo.java"} ::: notes -Mit `Class.forName("reflection.Studi")` können Sie die Klasse `Studi` im -Package `reflection` laden. Dabei muss sich aber die entsprechende -`.class`-Datei (samt der der Package-Struktur entsprechenden Ordnerstruktur -darüber) **im Java-Classpath** befinden! - -Mit einem weiteren `ClassLoader` können Sie auch aus Ordnern, die sich -nicht im Classpath befinden, `.class`-Dateien laden. Dies geht dann entweder -wie vorher über `Class.forName()`, wobei hier der neue Class-Loader als -Parameter mitgegeben wird, oder direkt über den neuen Class-Loader mit -dessen Methode `loadClass()`. +Mit `Class.forName("reflection.Studi")` können Sie die Klasse `Studi` im Package +`reflection` laden. Dabei muss sich aber die entsprechende `.class`-Datei (samt der +der Package-Struktur entsprechenden Ordnerstruktur darüber) **im Java-Classpath** +befinden! + +Mit einem weiteren `ClassLoader` können Sie auch aus Ordnern, die sich nicht im +Classpath befinden, `.class`-Dateien laden. Dies geht dann entweder wie vorher über +`Class.forName()`, wobei hier der neue Class-Loader als Parameter mitgegeben wird, +oder direkt über den neuen Class-Loader mit dessen Methode `loadClass()`. ::: - # Licht und Schatten **Nützlich**: -* Erweiterbarkeit: Laden von "externen" [(zur Kompilierzeit unbekannter)]{.notes} +- Erweiterbarkeit: Laden von "externen" [(zur Kompilierzeit unbekannter)]{.notes} Klassen in eine Anwendung -* Klassen-Browser, Debugger und Test-Tools +- Klassen-Browser, Debugger und Test-Tools \bigskip **Nachteile**: -* Verlust von Kapselung, Compiler-Unterstützung und Refactoring -* Performance: Dynamisches Laden von Klassen etc. -* Sicherheitsprobleme/-restriktionen +- Verlust von Kapselung, Compiler-Unterstützung und Refactoring +- Performance: Dynamisches Laden von Klassen etc. +- Sicherheitsprobleme/-restriktionen \bigskip ::: center -[Reflection ist ein nützliches Werkzeug. Aber:]{.notes} **Gibt es eine Lösung ohne Reflection, wähle diese!** +[Reflection ist ein nützliches Werkzeug. Aber:]{.notes} **Gibt es eine Lösung ohne +Reflection, wähle diese!** ::: - # Wrap-Up -* Inspektion von Programmen zur Laufzeit: **Reflection** - * `java.lang.Class`: Metadaten über Klassen - * Je Klasse ein `Class`-Objekt - * Informationen über Konstruktoren, Methoden, Felder - * Anwendung: Laden und Ausführen von zur Compile-Zeit unbekanntem Code - * Vorsicht: Verlust von Refactoring und Compiler-Zusicherungen! +- Inspektion von Programmen zur Laufzeit: **Reflection** + - `java.lang.Class`: Metadaten über Klassen + - Je Klasse ein `Class`-Objekt + - Informationen über Konstruktoren, Methoden, Felder + - Anwendung: Laden und Ausführen von zur Compile-Zeit unbekanntem Code + - Vorsicht: Verlust von Refactoring und Compiler-Zusicherungen! + +::: readings +- @Java-SE-Tutorial +- @Inden2013 [Kap. 8] +::: + +::: outcomes +- k2: Ich kenne typische Probleme beim Einsatz von Reflection +- k2: Ich kann die Bedeutung der verschiedenen Exceptions beim Aufruf von Methoden + per Reflection erklären +- k3: Ich kann zur Laufzeit mit Reflection Information zu Klassen und Methoden + erlangen +- k3: Ich kann zur Compilezeit unbekannte Klassen einbinden und deren + Konstruktoren und Methoden (mit und ohne Parameter/Rückgabewerte) aufrufen +::: + +::: challenges +In den +[Vorgaben](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/java-classic/src/challenges/reflection) +finden Sie eine einfache Implementierung für einen Taschenrechner mit Java-Swing. +Dieser Taschenrechner kann nur mit `int`-Werten rechnen. Der Taschenrechner verfügt +über keinerlei vordefinierte mathematische Operationen (Addieren, Subtrahieren +etc.). + +Erstellen Sie eigene mathematische Operationen, die `IOperation` implementieren. +Jede Ihrer Klassen soll mit einer Annotation vermerkt werden, in welcher der Name +der jeweiligen Operation gespeichert wird. + +Der Taschenrechner lädt seine Operationen dynamisch über die statische Methode +`OperationLoader.loadOperations` ein. In den Vorgaben ist diese Methode noch nicht +ausimplementiert. Implementieren Sie die Funktion so, dass sie mit Hilfe von +Reflection Ihre Operationen einliest. Geben Sie dazu den Ordner an, in dem die +entsprechenden `.class`-Dateien liegen. (Dieser Ordner soll sich außerhalb Ihres +Java-Projekts befinden!) Verändern Sie nicht die Signatur der Methode. + +Ihre Operation-Klassen dürfen Sie nicht vorher bekannt machen. Diese müssen in einem +vom aktuellen Projekt separierten Ordner/Projekt liegen. +::: diff --git a/lecture/java-classic/regexp.md b/lecture/java-classic/regexp.md index cfc337196..d6ae86e44 100644 --- a/lecture/java-classic/regexp.md +++ b/lecture/java-classic/regexp.md @@ -1,99 +1,60 @@ --- -title: "Reguläre Ausdrücke" -author: "Carsten Gips (HSBI)" -readings: - - "@Java-SE-Tutorial" -tldr: | - Mit Hilfe von regulären Ausdrücken kann man den Aufbau von Zeichenketten formal - beschreiben. Dabei lassen sich direkt die gewünschten Zeichen einsetzen, oder - man nutzt Zeichenklassen oder vordefinierte Ausdrücke. Teilausdrücke lassen sich - gruppieren und über _Quantifier_ kann definiert werden, wie oft ein Teilausdruck - vorkommen soll. Die Quantifier sind per Default **greedy** und versuchen so viel - wie möglich zu matchen. - - Auf der Java-Seite stellt man reguläre Ausdrücke zunächst als `String` dar. Dabei - muss darauf geachtet werden, dass ein Backslash im regulären Ausdruck im Java-String - geschützt (_escaped_) werden muss, indem jeweils ein weiterer Backslash voran gestellt - wird. Mit Hilfe der Klasse `java.util.regex.Pattern` lässt sich daraus ein Objekt - mit dem kompilierten regulären Ausdruck erzeugen, was insbesondere bei mehrfacher - Verwendung günstiger in der Laufzeit ist. Dem Pattern-Objekt kann man dann den - Suchstring übergeben und bekommt ein Objekt der Klasse `java.util.regex.Matcher` - (dort sind regulärer Ausdruck/Pattern und der Suchstring kombiniert). Mit den - Methoden `Matcher#find` und `Matcher#matches` kann dann geprüft werden, ob das Pattern - auf den Suchstring passt: `find` sucht dabei nach dem ersten Vorkommen des Patterns - im Suchstring, `match` prüft, ob der gesamte String zum Pattern passt. -outcomes: - - k1: "Ich kenne die wichtigsten Methoden von `java.util.regex.Pattern` und `java.util.regex.Matcher`" - - k2: "Ich kann den Unterschied zwischen `Matcher#find` und `Matcher#matches` erklären" - - k2: "Ich kann zwischen greedy und non-greedy Verhalten bei regulären Ausdrücken unterscheiden" - - k3: "Ich kann einfache reguläre Ausdrücke bilden" - - k3: "Ich kann Zeichenklassen und deren Negation einsetzen" - - k3: "Ich kann die vordefinierten regulären Ausdrücke einsetzen" - - k3: "Ich kann Quantifizierer gezielt einsetzen" - - k3: "Ich kann komplexe Ausdrücke (u.a. mit Gruppen) konstruieren" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106518&client_id=FH-Bielefeld" -# name: "Quiz RegExp (ILIAS)" -youtube: - - link: "https://youtu.be/K9R1Bwa73uI" - name: "VL RegExp" - - link: "https://youtu.be/j_pTZBI6Z3M" - name: "Demo StringSplit" - - link: "https://youtu.be/LYlPL1C_au8" - name: "Demo MatchFind" - - link: "https://youtu.be/xkD9PhCjXyY" - name: "Demo Quantifier" - - link: "https://youtu.be/ANLB2MdC_uY" - name: "Demo Groups" - - link: "https://youtu.be/SRZyT3EAB94" - name: "Demo Backref" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/35288af40bdf53fe210d0f8c4d25fd38d4c5f4cde5c04ffd49500f815723448b0bc16b305c7b05b0d57d670019d52dd5ff9da5f4a43afc0c85ed870b44f93e00" - name: "VL RegExp" -challenges: | - Schreiben Sie eine Methode, die mit Hilfe von regulären Ausdrücken überprüft, ob der eingegebene String - eine nach dem folgenden Schema gebildete EMail-Adresse ist: - - ::: center - `name@firma.domain` - ::: - - Dabei sollen folgende Regeln gelten: - - - Die Bestandteile `name` und `firma` können aus Buchstaben, Ziffern, Unter- und Bindestrichen bestehen. - - Der Bestandteil `name` muss mindestens ein Zeichen lang sein. - - Der Bestandteil `firma` kann entfallen, dann entfällt auch der nachfolgende Punkt (`.`) und der Teil - `domain` folgt direkt auf das `@`-Zeichen. - - Der Bestandteil `domain` besteht aus 2 oder 3 Kleinbuchstaben. - - Hinweis: Sie dürfen keinen Oder-Operator verwenden. - - +author: Carsten Gips (HSBI) +title: Reguläre Ausdrücke --- +::: tldr +Mit Hilfe von regulären Ausdrücken kann man den Aufbau von Zeichenketten formal +beschreiben. Dabei lassen sich direkt die gewünschten Zeichen einsetzen, oder man +nutzt Zeichenklassen oder vordefinierte Ausdrücke. Teilausdrücke lassen sich +gruppieren und über *Quantifier* kann definiert werden, wie oft ein Teilausdruck +vorkommen soll. Die Quantifier sind per Default **greedy** und versuchen so viel wie +möglich zu matchen. + +Auf der Java-Seite stellt man reguläre Ausdrücke zunächst als `String` dar. Dabei +muss darauf geachtet werden, dass ein Backslash im regulären Ausdruck im Java-String +geschützt (*escaped*) werden muss, indem jeweils ein weiterer Backslash voran +gestellt wird. Mit Hilfe der Klasse `java.util.regex.Pattern` lässt sich daraus ein +Objekt mit dem kompilierten regulären Ausdruck erzeugen, was insbesondere bei +mehrfacher Verwendung günstiger in der Laufzeit ist. Dem Pattern-Objekt kann man +dann den Suchstring übergeben und bekommt ein Objekt der Klasse +`java.util.regex.Matcher` (dort sind regulärer Ausdruck/Pattern und der Suchstring +kombiniert). Mit den Methoden `Matcher#find` und `Matcher#matches` kann dann geprüft +werden, ob das Pattern auf den Suchstring passt: `find` sucht dabei nach dem ersten +Vorkommen des Patterns im Suchstring, `match` prüft, ob der gesamte String zum +Pattern passt. +::: + +::: youtube +- [VL RegExp](https://youtu.be/K9R1Bwa73uI) +- [Demo StringSplit](https://youtu.be/j_pTZBI6Z3M) +- [Demo MatchFind](https://youtu.be/LYlPL1C_au8) +- [Demo Quantifier](https://youtu.be/xkD9PhCjXyY) +- [Demo Groups](https://youtu.be/ANLB2MdC_uY) +- [Demo Backref](https://youtu.be/SRZyT3EAB94) +::: # Suchen in Strings Gesucht ist ein Programm zum Extrahieren von Telefonnummern aus E-Mails. \bigskip -=> **Wie geht das?** + +=\> **Wie geht das?** + \bigskip \bigskip \pause ::: notes -Leider gibt es unzählig viele Varianten, wie man eine Telefonnummer (samt -Vorwahl und ggf. Ländervorwahl) aufschreiben kann: +Leider gibt es unzählig viele Varianten, wie man eine Telefonnummer (samt Vorwahl +und ggf. Ländervorwahl) aufschreiben kann: ::: 030 - 123 456 789, 030-123456789, 030/123456789, +49(30)123456-789, +49 (30) 123 456 - 789, ... - # Definition Regulärer Ausdruck > Ein **regulärer Ausdruck** ist eine Zeichenkette, die zur Beschreibung von @@ -102,14 +63,13 @@ Vorwahl und ggf. Ländervorwahl) aufschreiben kann: ::: notes ## Anwendungen -* Finden von Bestandteilen in Zeichenketten -* Aufteilen von Strings in Tokens -* Validierung von textuellen Eingaben - => "Eine Postleitzahl besteht aus 5 Ziffern" -* Compilerbau: Erkennen von Schlüsselwörtern und Strukturen und Syntaxfehlern +- Finden von Bestandteilen in Zeichenketten +- Aufteilen von Strings in Tokens +- Validierung von textuellen Eingaben =\> "Eine Postleitzahl besteht aus 5 + Ziffern" +- Compilerbau: Erkennen von Schlüsselwörtern und Strukturen und Syntaxfehlern ::: - # Einfachste reguläre Ausdrücke | **Zeichenkette** | **Beschreibt** | @@ -125,19 +85,18 @@ Vorwahl und ggf. Ländervorwahl) aufschreiben kann: ## Beispiel ::: -* `abc` => "abc" -* `A.B` => "AAB" oder "A2B" oder ... -* `a\\bc` => "a\\bc" +- `abc` =\> "abc" +- `A.B` =\> "AAB" oder "A2B" oder ... +- `a\\bc` =\> "a\\bc" ::: notes ## Anmerkung In Java-Strings leitet der Backslash eine zu interpretierende Befehlssequenz ein. -Deshalb muss der Backslash i.d.R. geschützt ("escaped") werden. -=> Statt "`\n`" müssen Sie im Java-Code "`\\n`" schreiben! +Deshalb muss der Backslash i.d.R. geschützt ("escaped") werden. =\> Statt "`\n`" +müssen Sie im Java-Code "`\\n`" schreiben! ::: - # Zeichenklassen | **Zeichenkette** | **Beschreibt** | @@ -152,37 +111,35 @@ Deshalb muss der Backslash i.d.R. geschützt ("escaped") werden. ::: notes Zeichenklassen werden über eine Zeichenkette formuliert, die in `[` und `]` eingeschlossen wird. Dabei werden alle Zeichen aufgezählt, die in dieser -Zeichenklasse enthalten sein sollen. Die Zeichenklasse verhält sich von -außen betrachtet wie ein beliebiges Zeichen aus der Menge der aufgezählten -Zeichen. +Zeichenklasse enthalten sein sollen. Die Zeichenklasse verhält sich von außen +betrachtet wie ein beliebiges Zeichen aus der Menge der aufgezählten Zeichen. Beispiel: `[abc]` meint ein "a" oder "b" oder "c" ... -Wenn dem ersten Zeichen der so geformten Zeichenklasse ein `^` vorangestellt -wird, sind alle Zeichen _außer_ den in der Zeichenklasse bezeichneten Zeichen -gemeint (Negation). In der Tabelle oben (erste Zeile) könnte man dem `abc` noch -ein `^` voranstellen und hätte dann *alle* Zeichen *außer* "a", "b" und "c". +Wenn dem ersten Zeichen der so geformten Zeichenklasse ein `^` vorangestellt wird, +sind alle Zeichen *außer* den in der Zeichenklasse bezeichneten Zeichen gemeint +(Negation). In der Tabelle oben (erste Zeile) könnte man dem `abc` noch ein `^` +voranstellen und hätte dann *alle* Zeichen *außer* "a", "b" und "c". -Für den Schnitt kann als zweite Zeichenklasse eine Negation verwendet werden, -damit würde eine Subtraktion erreicht werden: Alle Zeichen in der vorderen -Zeichenklasse abzüglich der Zeichen in der zweiten Zeichenklasse. In der -Tabelle oben (vierte Zeile) würde man dem `def` noch ein `^` voranstellen und -hätte dann die Zeichen "a" bis "z" *ohne* "d", "e" und "f". +Für den Schnitt kann als zweite Zeichenklasse eine Negation verwendet werden, damit +würde eine Subtraktion erreicht werden: Alle Zeichen in der vorderen Zeichenklasse +abzüglich der Zeichen in der zweiten Zeichenklasse. In der Tabelle oben (vierte +Zeile) würde man dem `def` noch ein `^` voranstellen und hätte dann die Zeichen "a" +bis "z" *ohne* "d", "e" und "f". -*Anmerkung*: Das Minus-Zeichen hat in der Zeichenklasse eine besondere Bedeutung -(es bildet einen Range). Deshalb muss es escaped werden, wenn es sich selbst -darstellen soll. +*Anmerkung*: Das Minus-Zeichen hat in der Zeichenklasse eine besondere Bedeutung (es +bildet einen Range). Deshalb muss es escaped werden, wenn es sich selbst darstellen +soll. ::: ::: notes ## Beispiel ::: -* `[abc]` => "a" oder "b" oder "c" -* `[a-c]` => "a" oder "b" oder "c" -* `[a-c][a-c]` => "aa", "ab", "ac", "ba", "bb", "bc", "ca", "cb" oder "cc" -* `A[a-c]` => "Aa", "Ab" oder "Ac" - +- `[abc]` =\> "a" oder "b" oder "c" +- `[a-c]` =\> "a" oder "b" oder "c" +- `[a-c][a-c]` =\> "aa", "ab", "ac", "ba", "bb", "bc", "ca", "cb" oder "cc" +- `A[a-c]` =\> "Aa", "Ab" oder "Ac" # Vordefinierte Ausdrücke @@ -201,46 +158,45 @@ darstellen soll. ## Beispiel ::: -* `\d\d\d\d\d` => "12345" -* `\w\wA` => "aaA", "a0A", "a_A", ... - +- `\d\d\d\d\d` =\> "12345" +- `\w\wA` =\> "aaA", "a0A", "a_A", ... # Nutzung in Java \bigskip -* `java.lang.String`: +- `java.lang.String`: - ```java + ``` java public String[] split(String regex) public boolean matches(String regex) ``` -[Demo: regexp.StringSplit]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/regexp/StringSplit.java"} +[Demo: regexp.StringSplit]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/regexp/StringSplit.java"} \pause -* `java.util.regex.Pattern`: +- `java.util.regex.Pattern`: - ```java + ``` java public static Pattern compile(String regex) public Matcher matcher(CharSequence input) ``` ::: notes - * Schritt 1: Ein Pattern compilieren (erzeugen) mit `Pattern#compile` - => liefert ein Pattern-Objekt für den regulären Ausdruck zurück - * Schritt 2: Dem Pattern-Objekt den zu untersuchenden Zeichenstrom - übergeben mit `Pattern#matcher` => liefert ein Matcher-Objekt - zurück, darin gebunden: Pattern (regulärer Ausdruck) und die zu - untersuchende Zeichenkette + - Schritt 1: Ein Pattern compilieren (erzeugen) mit `Pattern#compile` =\> + liefert ein Pattern-Objekt für den regulären Ausdruck zurück + - Schritt 2: Dem Pattern-Objekt den zu untersuchenden Zeichenstrom übergeben + mit `Pattern#matcher` =\> liefert ein Matcher-Objekt zurück, darin gebunden: + Pattern (regulärer Ausdruck) und die zu untersuchende Zeichenkette ::: \smallskip -* `java.util.regex.Matcher`: +- `java.util.regex.Matcher`: - ```java + ``` java public boolean find() public boolean matches() public int groupCount() @@ -248,8 +204,8 @@ darstellen soll. ``` ::: notes - * Schritt 3: Mit dem Matcher-Objekt kann man die Ergebnisse der - Anwendung des regulären Ausdrucks auf eine Zeichenkette auswerten + - Schritt 3: Mit dem Matcher-Objekt kann man die Ergebnisse der Anwendung des + regulären Ausdrucks auf eine Zeichenkette auswerten Bedeutung der unterschiedlichen Methoden siehe folgende Folien @@ -263,25 +219,25 @@ darstellen soll. In Java-Strings leitet der Backslash eine zu interpretierende Befehlssequenz ein. Deshalb muss der Backslash i.d.R. extra geschützt ("escaped") werden. -=> Statt "`\n`" (regulärer Ausdruck) müssen Sie im Java-String "`\\n`" schreiben! +=\> Statt "`\n`" (regulärer Ausdruck) müssen Sie im Java-String "`\\n`" schreiben! -=> Statt "`a\\bc`" (regulärer Ausdruck, passt auf die Zeichenkette "a\\bc") müssen +=\> Statt "`a\\bc`" (regulärer Ausdruck, passt auf die Zeichenkette "a\\bc") müssen Sie im Java-String "`a\\\\bc`" schreiben! ::: -[Demo: regexp.MatchFind]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/regexp/MatchFind.java"} - +[Demo: regexp.MatchFind]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/regexp/MatchFind.java"} # Unterschied zw. Finden und Matchen -* `Matcher#find`: +- `Matcher#find`: - Regulärer Ausdruck muss im Suchstring **enthalten** sein. \newline - => Suche nach **erstem Vorkommen** + Regulärer Ausdruck muss im Suchstring **enthalten** sein. `\newline`{=tex} =\> + Suche nach **erstem Vorkommen** \smallskip -* `Matcher#matches`: +- `Matcher#matches`: Regulärer Ausdruck muss auf **kompletten** Suchstring passen. @@ -291,10 +247,9 @@ Sie im Java-String "`a\\\\bc`" schreiben! ## Beispiel ::: -* Regulärer Ausdruck: `abc`, Suchstring: "blah blah abc blub" - * `Matcher#find`: erfolgreich - * `Matcher#matches`: kein Match - Suchstring entspricht nicht dem Muster - +- Regulärer Ausdruck: `abc`, Suchstring: "blah blah abc blub" + - `Matcher#find`: erfolgreich + - `Matcher#matches`: kein Match - Suchstring entspricht nicht dem Muster # Quantifizierung @@ -311,13 +266,12 @@ Sie im Java-String "`a\\\\bc`" schreiben! ## Beispiel ::: -* `\d{5}` => "12345" -* `-?\d+\.\d*` => ??? - +- `\d{5}` =\> "12345" +- `-?\d+\.\d*` =\> ??? # Interessante Effekte -```java +``` java Pattern p = Pattern.compile("A.*A"); Matcher m = p.matcher("A 12 A 45 A"); @@ -325,22 +279,22 @@ if (m.matches()) String result = m.group(); // ??? ``` -[Demo: regexp.Quantifier]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/regexp/Quantifier.java"} +[Demo: regexp.Quantifier]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/regexp/Quantifier.java"} ::: notes -`Matcher#group` liefert die Inputsequenz, auf die der Matcher angesprochen hat. -Mit `Matcher#start` und `Matcher#end` kann man sich die Indizes des ersten und -letzten Zeichens des Matches im Eingabezeichenstrom geben lassen. D.h. für einen -Matcher `m` und eine Eingabezeichenkette `s` ist `m.group()` und +`Matcher#group` liefert die Inputsequenz, auf die der Matcher angesprochen hat. Mit +`Matcher#start` und `Matcher#end` kann man sich die Indizes des ersten und letzten +Zeichens des Matches im Eingabezeichenstrom geben lassen. D.h. für einen Matcher `m` +und eine Eingabezeichenkette `s` ist `m.group()` und `s.substring(m.start(), m.end())` äquivalent. -Da bei `Matcher#matches` das Pattern immer auf den gesamten Suchstring passen -muss, verwundert das Ergebnis für `Matcher#group` nicht. Bei `Matcher#find` -wird im Beispiel allerdings ebenfalls der gesamte Suchstring "gefunden" ... -Dies liegt am "*greedy*" Verhalten der Quantifizierer. +Da bei `Matcher#matches` das Pattern immer auf den gesamten Suchstring passen muss, +verwundert das Ergebnis für `Matcher#group` nicht. Bei `Matcher#find` wird im +Beispiel allerdings ebenfalls der gesamte Suchstring "gefunden" ... Dies liegt am +"*greedy*" Verhalten der Quantifizierer. ::: - # Nicht gierige Quantifizierung mit "?" \bigskip @@ -354,23 +308,23 @@ Dies liegt am "*greedy*" Verhalten der Quantifizierer. ## Beispiel ::: -* Suchstring "A 12 A 45 A": - * `A.*A` findet/passt auf "A 12 A 45 A" +- Suchstring "A 12 A 45 A": + - `A.*A` findet/passt auf "A 12 A 45 A" ::: notes normale **greedy** Variante ::: - * `A.*?A` - * findet "A 12 A" - * passt auf "A 12 A 45 A" (!) + - `A.*?A` + + - findet "A 12 A" + - passt auf "A 12 A 45 A" (!) ::: notes - **non-greedy** Variante der Quantifizierung; - `Matcher#matches` muss trotzdem auf den gesamten Suchstring passen! + **non-greedy** Variante der Quantifizierung; `Matcher#matches` muss trotzdem + auf den gesamten Suchstring passen! ::: - # (Fangende) Gruppierungen `Studi{2}` passt nicht auf "StudiStudi" (!) @@ -401,41 +355,42 @@ Gruppierungen durch Klammern verwenden! ## Beispiel ::: -* `(A)(B(C))` +- `(A)(B(C))` - Gruppe 0: `ABC` - Gruppe 1: `A` - Gruppe 2: `BC` - Gruppe 3: `C` ::: notes -Die Gruppen heißen auch "fangende" Gruppen (engl.: _"capturing groups"_). +Die Gruppen heißen auch "fangende" Gruppen (engl.: *"capturing groups"*). + +Damit erreicht man eine Segmentierung des gesamten regulären Ausdrucks, der in +seiner Wirkung aber nicht durch die Gruppierungen geändert wird. Durch die +Gruppierungen von Teilen des regulären Ausdrucks erhält man die Möglichkeit, auf die +entsprechenden Teil-Matches (der Unterausdrücke der einzelnen Gruppen) zuzugreifen: -Damit erreicht man eine Segmentierung des gesamten regulären Ausdrucks, der -in seiner Wirkung aber nicht durch die Gruppierungen geändert wird. Durch die -Gruppierungen von Teilen des regulären Ausdrucks erhält man die Möglichkeit, -auf die entsprechenden Teil-Matches (der Unterausdrücke der einzelnen Gruppen) -zuzugreifen: +- `Matcher#groupCount`: Anzahl der "fangenden" Gruppen im regulären Ausdruck -* `Matcher#groupCount`: Anzahl der "fangenden" Gruppen im regulären Ausdruck -* `Matcher#group(i)`: Liefert die Subsequenz der Eingabezeichenkette zurück, - auf die die jeweilige Gruppe gepasst hat. Dabei wird von links nach rechts +- `Matcher#group(i)`: Liefert die Subsequenz der Eingabezeichenkette zurück, auf + die die jeweilige Gruppe gepasst hat. Dabei wird von links nach rechts durchgezählt, beginnend bei 1(!). - Konvention: Gruppe 0 ist das gesamte Pattern, d.h. `m.group(0) == m.group();` ... + Konvention: Gruppe 0 ist das gesamte Pattern, d.h. `m.group(0) == m.group();` + ... -*Hinweis*: Damit der Zugriff auf die Gruppen klappt, muss auch erst ein Match gemacht -werden, d.h. das Erzeugen des Matcher-Objekts reicht noch nicht, sondern es muss auch -noch ein `matcher.find()` oder `matcher.matches()` ausgeführt werden. Danach kann man -bei Vorliegen eines Matches auf die Gruppen zugreifen. +*Hinweis*: Damit der Zugriff auf die Gruppen klappt, muss auch erst ein Match +gemacht werden, d.h. das Erzeugen des Matcher-Objekts reicht noch nicht, sondern es +muss auch noch ein `matcher.find()` oder `matcher.matches()` ausgeführt werden. +Danach kann man bei Vorliegen eines Matches auf die Gruppen zugreifen. ::: \pause \bigskip -`(Studi){2}` => "StudiStudi" - -[Demo: regexp.Groups]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/regexp/Groups.java"} +`(Studi){2}` =\> "StudiStudi" +[Demo: regexp.Groups]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/regexp/Groups.java"} # Gruppen und Backreferences @@ -450,45 +405,46 @@ Matche zwei Ziffern, gefolgt von den selben zwei Ziffern \bigskip \smallskip -* Verweis auf bereits gematchte Gruppen: `\num` +- Verweis auf bereits gematchte Gruppen: `\num` `num` Nummer der Gruppe (1 ... 9) - => Verweist nicht auf regulären Ausdruck, sondern auf jeweiligen Match! + =\> Verweist nicht auf regulären Ausdruck, sondern auf jeweiligen Match! ::: notes - *Anmerkung*: Laut Literatur/Doku nur 1 ... 9, in Praxis geht auch mehr per Backreference ... + *Anmerkung*: Laut Literatur/Doku nur 1 ... 9, in Praxis geht auch mehr per + Backreference ... ::: \smallskip -* Benennung der Gruppe: `(?X)` +- Benennung der Gruppe: `(?X)` `X` ist regulärer Ausdruck für Gruppe, spitze Klammern wichtig - => Backreference: `\k` - -[Demo: regexp.Backref]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/regexp/Backref.java"} + =\> Backreference: `\k` +[Demo: regexp.Backref]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/regexp/Backref.java"} # Beispiel Gruppen und Backreferences -Regulärer Ausdruck: Namen einer Person matchen, wenn Vor- und Nachname identisch sind. +Regulärer Ausdruck: Namen einer Person matchen, wenn Vor- und Nachname identisch +sind. \pause \bigskip Lösung: `([A-Z][a-zA-Z]*)\s\1` - ::: notes # Umlaute und reguläre Ausdrücke -* Keine vordefinierte Abkürzung für Umlaute (wie etwa `\d`) +- Keine vordefinierte Abkürzung für Umlaute (wie etwa `\d`) -* Umlaute nicht in `[a-z]` enthalten, aber in `[a-ü]` +- Umlaute nicht in `[a-z]` enthalten, aber in `[a-ü]` - ```java + ``` java "helloüA".matches(".*?[ü]A"); "azäöüß".matches("[a-ä]"); "azäöüß".matches("[a-ö]"); @@ -496,25 +452,65 @@ Lösung: `([A-Z][a-zA-Z]*)\s\1` "azäöüß".matches("[a-ß]"); ``` -* Strings sind Unicode-Zeichenketten +- Strings sind Unicode-Zeichenketten - => Nutzung der passenden Unicode Escape Sequence `\uFFFF` + =\> Nutzung der passenden Unicode Escape Sequence `\uFFFF` - ```java + ``` java System.out.println("\u0041 :: A"); System.out.println("helloüA".matches(".*?A")); System.out.println("helloüA".matches(".*?\u0041")); System.out.println("helloü\u0041".matches(".*?A")); ``` -* RegExp vordefinieren und mit Variablen zusammenbauen ala Perl - nicht möglich => Umweg String-Repräsentation +- RegExp vordefinieren und mit Variablen zusammenbauen ala Perl nicht möglich =\> + Umweg String-Repräsentation ::: - # Wrap-Up -* RegExp: Zeichenketten, die andere Zeichenketten beschreiben -* `java.util.regex.Pattern` und `java.util.regex.Matcher` -* Unterschied zwischen `Matcher#find` und `Matcher#matches`! -* Quantifizierung ist möglich, aber **greedy** (Default) +- RegExp: Zeichenketten, die andere Zeichenketten beschreiben +- `java.util.regex.Pattern` und `java.util.regex.Matcher` +- Unterschied zwischen `Matcher#find` und `Matcher#matches`! +- Quantifizierung ist möglich, aber **greedy** (Default) + +::: readings +- @Java-SE-Tutorial +::: + +::: outcomes +- k1: Ich kenne die wichtigsten Methoden von java.util.regex.Pattern und + java.util.regex.Matcher +- k2: Ich kann den Unterschied zwischen Matcher#find und Matcher#matches erklären +- k2: Ich kann zwischen greedy und non-greedy Verhalten bei regulären Ausdrücken + unterscheiden +- k3: Ich kann einfache reguläre Ausdrücke bilden +- k3: Ich kann Zeichenklassen und deren Negation einsetzen +- k3: Ich kann die vordefinierten regulären Ausdrücke einsetzen +- k3: Ich kann Quantifizierer gezielt einsetzen +- k3: Ich kann komplexe Ausdrücke (u.a. mit Gruppen) konstruieren +::: + +:::: challenges +Schreiben Sie eine Methode, die mit Hilfe von regulären Ausdrücken überprüft, ob der +eingegebene String eine nach dem folgenden Schema gebildete EMail-Adresse ist: + +::: center +`name@firma.domain` +::: + +Dabei sollen folgende Regeln gelten: + +- Die Bestandteile `name` und `firma` können aus Buchstaben, Ziffern, Unter- und + Bindestrichen bestehen. +- Der Bestandteil `name` muss mindestens ein Zeichen lang sein. +- Der Bestandteil `firma` kann entfallen, dann entfällt auch der nachfolgende + Punkt (`.`) und der Teil `domain` folgt direkt auf das `@`-Zeichen. +- Der Bestandteil `domain` besteht aus 2 oder 3 Kleinbuchstaben. + +Hinweis: Sie dürfen keinen Oder-Operator verwenden. + + +:::: diff --git a/lecture/java-classic/serialisation.md b/lecture/java-classic/serialisation.md index 917476e5c..26a69b06e 100644 --- a/lecture/java-classic/serialisation.md +++ b/lecture/java-classic/serialisation.md @@ -1,214 +1,46 @@ --- -title: "Serialisierung von Objekten und Zuständen" -author: "Carsten Gips (HSBI)" -readings: - - "@Java-SE-Tutorial" -tldr: | - Objekte lassen sich mit der Methode `void writeObject(Object)` in `ObjectOutputStream` - einfach in einen Datenstrom schreiben. Dies kann beispielsweise eine Datei o.ä. sein. - Mit Hilfe von `Object readObject()` in `ObjectInputStream` lassen sich Objekte aus dem - Datenstrom auch wieder herstellen. Dies nennt man Serialisierung und De-Serialisierung. - - Um Objekte einer Klasse serialisieren zu können, muss diese das leere Interface - `Serializable` implementieren ("Marker-Interface"). Damit wird quasi die Unterstützung - in `Object*Stream` freigeschaltet. - - Wenn ein Objekt serialisiert wird, werden alle Attribute in den Datenstrom geschrieben, - d.h. die Typen der Attribute müssen ihrerseits serialisierbar sein. Dies gilt für alle - primitiven Typen und die meisten eingebauten Typen. Die Serialisierung erfolgt ggf. - rekursiv, Zirkelreferenzen werden erkannt und aufgebrochen. - - `static` und `transient` Attribute werden nicht serialisiert. - - Beim De-Serialisieren wird das neue Objekt von der Laufzeitumgebung aus dem Datenstrom - rekonstruiert. Dies geschieht direkt, es wird kein Konstruktor involviert. - - Beim Serialisieren wird für die Klasse des zu schreibenden Objekts eine `serialVersionUID` - berechnet und mit gespeichert. Beim Einlesen wird dann geprüft, ob die serialisierten - Daten zur aktuellen Version der Klasse passen. Da dies relativ empfindlich gegenüber - Änderungen an einer Klasse ist, wird empfohlen, selbst eine `serialVersionUID` pro - Klasse zu definieren. -outcomes: - - k2: "Was ist ein Marker-Interface und warum ist dies eine der großen Design-Sünden in Java?" - - k2: "Erklären Sie den Prozess der Serialisierung und De-Serialisierung. Worauf müssen Sie achten?" - - k3: "Serialisierung von Objekten und Programmzuständen" - - k3: "Serialisierung eigener Klassen und Typen" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106519&client_id=FH-Bielefeld" -# name: "Quiz Serialisierung (ILIAS)" -youtube: - - link: "https://youtu.be/wnwCMtKpXAQ" - name: "VL Serialisierung" - - link: "https://youtu.be/zpWLPIRPTeQ" - name: "Demo Serialisierung" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/ab8df96555932244456b9ac3dc332fbe39c09029605959af2eea790b5bd5966c5212c9c0184700d6e3785b7af8bdc03e3e6b948bad9392fa01e79309677df68a" - name: "VL Serialisierung" -challenges: | - Implementieren Sie die beiden Klassen entsprechend dem UML-Diagram: - - ![](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/images/uml_serialisierung.png?raw=true) - - Objekte vom Typ `Person` sowie `Address` sollen serialisierbar sein (vgl. Vorlesung). - Dabei soll das Passwort nicht serialisiert bzw. gespeichert werden, alle anderen - Eigenschaften von `Person` sollen serialisierbar sein. - - _Hinweis_: Verwenden Sie zur Umsetzung [java.io.Serializable](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/Serializable.html). - - Erstellen Sie in Ihrem `main()` einige Instanzen von Person und speichern Sie diese in - serialisierter Form und laden (deserialisieren) Sie diese anschließend in neue Variablen. - - Betrachten Sie die ursprünglichen und die wieder deserialisierten Objekte mit Hilfe des - Debuggers. Alternativ können Sie die Objekte auch in übersichtlicher Form über den Logger - ausgeben. - - +author: Carsten Gips (HSBI) +title: Serialisierung von Objekten und Zuständen --- +::: tldr +Objekte lassen sich mit der Methode `void writeObject(Object)` in +`ObjectOutputStream` einfach in einen Datenstrom schreiben. Dies kann beispielsweise +eine Datei o.ä. sein. Mit Hilfe von `Object readObject()` in `ObjectInputStream` +lassen sich Objekte aus dem Datenstrom auch wieder herstellen. Dies nennt man +Serialisierung und De-Serialisierung. + +Um Objekte einer Klasse serialisieren zu können, muss diese das leere Interface +`Serializable` implementieren ("Marker-Interface"). Damit wird quasi die +Unterstützung in `Object*Stream` freigeschaltet. + +Wenn ein Objekt serialisiert wird, werden alle Attribute in den Datenstrom +geschrieben, d.h. die Typen der Attribute müssen ihrerseits serialisierbar sein. +Dies gilt für alle primitiven Typen und die meisten eingebauten Typen. Die +Serialisierung erfolgt ggf. rekursiv, Zirkelreferenzen werden erkannt und +aufgebrochen. + +`static` und `transient` Attribute werden nicht serialisiert. + +Beim De-Serialisieren wird das neue Objekt von der Laufzeitumgebung aus dem +Datenstrom rekonstruiert. Dies geschieht direkt, es wird kein Konstruktor +involviert. + +Beim Serialisieren wird für die Klasse des zu schreibenden Objekts eine +`serialVersionUID` berechnet und mit gespeichert. Beim Einlesen wird dann geprüft, +ob die serialisierten Daten zur aktuellen Version der Klasse passen. Da dies relativ +empfindlich gegenüber Änderungen an einer Klasse ist, wird empfohlen, selbst eine +`serialVersionUID` pro Klasse zu definieren. +::: + +::: youtube +- [VL Serialisierung](https://youtu.be/wnwCMtKpXAQ) +- [Demo Serialisierung](https://youtu.be/zpWLPIRPTeQ) +::: # Motivation: Persistierung von Objekten und Spielzuständen -```java +``` java public class Studi { private final int credits = 42; private String name = "Hilde"; @@ -224,51 +56,47 @@ Wie kann ich Objekte speichern und wieder laden? ::: ::: notes -Ich möchte ein Spiel (einen Lauf) im Dungeon abspeichern, um es später fortsetzen -zu können. Wie kann ich den aktuellen Zustand (also Level, Monster, Held, Inventar, +Ich möchte ein Spiel (einen Lauf) im Dungeon abspeichern, um es später fortsetzen zu +können. Wie kann ich den aktuellen Zustand (also Level, Monster, Held, Inventar, XP/Health/...) so speichern, dass ich später das Spiel nach einem Neustart einfach fortsetzen kann? ::: - # Serialisierung von Objekten -* Klassen müssen Marker-Interface `Serializable` implementieren +- Klassen müssen Marker-Interface `Serializable` implementieren ::: notes - "Marker-Interface": Interface ohne Methoden. Ändert das Verhalten - des Compilers, wenn eine Klasse dieses Interface implementiert: - Weitere Funktionen werden "freigeschaltet", beispielsweise die - Fähigkeit, Klone zu erstellen (`Cloneable`) oder bei `Serializable` - Objekte serialisierbar zu machen. - - Das ist in meinen Augen eine "Design-Sünde" in Java (neben der - Einführung von `null`): Normalerweise definieren Interfaces eine - Schnittstelle, die eine das Interface implementierende Klasse - dann erfüllen muss. Damit agiert das Interface wie ein Typ. Hier - ist das Interface aber leer, es wird also keine Schnittstelle - definiert. Aber es werden damit stattdessen Tooling-Optionen - aktiviert, was Interfaces vom Konzept her eigentlich nicht machen - sollten/dürften - dazu gibt es Annotationen! + "Marker-Interface": Interface ohne Methoden. Ändert das Verhalten des Compilers, + wenn eine Klasse dieses Interface implementiert: Weitere Funktionen werden + "freigeschaltet", beispielsweise die Fähigkeit, Klone zu erstellen (`Cloneable`) + oder bei `Serializable` Objekte serialisierbar zu machen. + + Das ist in meinen Augen eine "Design-Sünde" in Java (neben der Einführung von + `null`): Normalerweise definieren Interfaces eine Schnittstelle, die eine das + Interface implementierende Klasse dann erfüllen muss. Damit agiert das Interface + wie ein Typ. Hier ist das Interface aber leer, es wird also keine Schnittstelle + definiert. Aber es werden damit stattdessen Tooling-Optionen aktiviert, was + Interfaces vom Konzept her eigentlich nicht machen sollten/dürften - dazu gibt + es Annotationen! ::: -* Schreiben von Objekten (samt Zustand) in Streams +- Schreiben von Objekten (samt Zustand) in Streams - ```java + ``` java ObjectOutputStream: void writeObject(Object) ``` ::: notes - Die Serialisierung erfolgt dabei für alle Attribute - (außer `static` und `transient`, s.u.) rekursiv. + Die Serialisierung erfolgt dabei für alle Attribute (außer `static` und + `transient`, s.u.) rekursiv. - Dabei werden auch Zirkelreferenzen automatisch - aufgelöst/unterbrochen. + Dabei werden auch Zirkelreferenzen automatisch aufgelöst/unterbrochen. ::: -* Lesen und "Wiedererwecken" der Objekte aus Streams +- Lesen und "Wiedererwecken" der Objekte aus Streams - ```java + ``` java ObjectInputStream: Object readObject() ``` @@ -276,10 +104,9 @@ fortsetzen kann? Dabei erfolgt KEIN Konstruktor-Aufruf! ::: - # Einfaches Beispiel -```java +``` java public class Studi implements Serializable { private final int credits = 42; private String name = "Hilde"; @@ -302,92 +129,270 @@ public class Studi implements Serializable { } ``` - # Bedingungen für Objekt-Serialisierung -* Klassen implementieren Marker-Interface `Serializable` -* Alle Attribute müssen ebenfalls serialisierbar sein (oder Deklaration "`transient`") -* Alle primitiven Typen sind per Default serialisierbar -* Es wird automatisch rekursiv serialisiert, aber jedes Objekt - nur einmal (bei Mehrfachreferenzierung) -* Serialisierbarkeit vererbt sich - +- Klassen implementieren Marker-Interface `Serializable` +- Alle Attribute müssen ebenfalls serialisierbar sein (oder Deklaration + "`transient`") +- Alle primitiven Typen sind per Default serialisierbar +- Es wird automatisch rekursiv serialisiert, aber jedes Objekt nur einmal (bei + Mehrfachreferenzierung) +- Serialisierbarkeit vererbt sich # Ausnahmen -* Als `static` deklarierte Attribute werden nicht serialisiert -* Als `transient` deklarierte Attribute werden nicht serialisiert -* Nicht serialisierbare Attribut-Typen führen zu `NotSerializableException` - +- Als `static` deklarierte Attribute werden nicht serialisiert +- Als `transient` deklarierte Attribute werden nicht serialisiert +- Nicht serialisierbare Attribut-Typen führen zu `NotSerializableException` # Version-UID -```java +``` java static final long serialVersionUID = 42L; ``` \bigskip -* Dient zum Vergleich der serialisierten Version und der aktuellen Klasse -* Über IDE generieren oder manuell vergeben -* Wenn das Attribut fehlt, wird eine Art Checksumme von der Runtime-Umgebung +- Dient zum Vergleich der serialisierten Version und der aktuellen Klasse +- Über IDE generieren oder manuell vergeben +- Wenn das Attribut fehlt, wird eine Art Checksumme von der Runtime-Umgebung berechnet (basierend auf diversen Eigenschaften der Klasse) ::: notes -Dieser Wert wird beim Einlesen verglichen: Das Objekt wird nur dann wieder de-serialisiert, -wenn die `serialVersionUID` mit der einzulesenden Klasse übereinstimmt! - -Bei automatischer Berechnung der `serialVersionUID` durch die JVM kann jede kleine Änderung an -der Klasse (beispielsweise Refactoring: Änderung der Methodennamen) eine neue `serialVersionUID` -zur Folge haben. Das würde bedeuten, dass bereits serialisierte Objekte nicht mehr eingelesen -werden können, auch wenn sich nur Methoden o.ä. verändert haben und die Attribute noch so vorhanden -sind. Deshalb bietet es sich an, hier selbst eine `serialVersionUID` zu definieren - dann muss -man aber auch selbst darauf achten, diese zu verändern, wenn sich wesentliche strukturelle -Änderungen an der Klasse ergeben! +Dieser Wert wird beim Einlesen verglichen: Das Objekt wird nur dann wieder +de-serialisiert, wenn die `serialVersionUID` mit der einzulesenden Klasse +übereinstimmt! + +Bei automatischer Berechnung der `serialVersionUID` durch die JVM kann jede kleine +Änderung an der Klasse (beispielsweise Refactoring: Änderung der Methodennamen) eine +neue `serialVersionUID` zur Folge haben. Das würde bedeuten, dass bereits +serialisierte Objekte nicht mehr eingelesen werden können, auch wenn sich nur +Methoden o.ä. verändert haben und die Attribute noch so vorhanden sind. Deshalb +bietet es sich an, hier selbst eine `serialVersionUID` zu definieren - dann muss man +aber auch selbst darauf achten, diese zu verändern, wenn sich wesentliche +strukturelle Änderungen an der Klasse ergeben! ::: - ::: notes # Bemerkungen -Es existieren diverse weitere Fallstricke und Probleme, siehe [@Bloch2018] Kapitel 11 "Serialization". +Es existieren diverse weitere Fallstricke und Probleme, siehe [@Bloch2018] Kapitel +11 "Serialization". -Man kann in den `ObjectOutputStream` nicht nur ein Objekt schreiben, sondern mehrere Objekte und -Variablen schreiben lassen. In dieser Reihenfolge muss man diese dann aber auch wieder aus dem -Stream herauslesen (vgl. [Object Streams](https://docs.oracle.com/javase/tutorial/essential/io/objectstreams.html)). +Man kann in den `ObjectOutputStream` nicht nur ein Objekt schreiben, sondern mehrere +Objekte und Variablen schreiben lassen. In dieser Reihenfolge muss man diese dann +aber auch wieder aus dem Stream herauslesen (vgl. [Object +Streams](https://docs.oracle.com/javase/tutorial/essential/io/objectstreams.html)). -Man kann die zu serialisierenden Attribute mit der Annotation `@Serial` markieren. Dies ist in -der Wirkung ähnlich zu `@Override`: Der Compiler prüft dann, ob die markierten Attribute wirklich -serialisierbar sind und würde sonst zur Compile-Zeit einen Fehler werfen. +Man kann die zu serialisierenden Attribute mit der Annotation `@Serial` markieren. +Dies ist in der Wirkung ähnlich zu `@Override`: Der Compiler prüft dann, ob die +markierten Attribute wirklich serialisierbar sind und würde sonst zur Compile-Zeit +einen Fehler werfen. Weitere Links: -* Tutorials: - * https://docs.oracle.com/en/java/javase/17/docs/specs/serialization/input.html - * https://www.baeldung.com/java-serialization -* API: - * https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/ObjectOutputStream.html - * https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/ObjectInputStream.html - * https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/Serializable.html +- Tutorials: + - https://docs.oracle.com/en/java/javase/17/docs/specs/serialization/input.html + - https://www.baeldung.com/java-serialization +- API: + - https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/ObjectOutputStream.html + - https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/ObjectInputStream.html + - https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/Serializable.html ::: -[Demo: serial.SerializableStudi]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/serial/SerializableStudi.java"} - +[Demo: serial.SerializableStudi]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/serial/SerializableStudi.java"} # Wrap-Up -* Markerinterface `Serializable` schaltet Serialisierbarkeit frei +- Markerinterface `Serializable` schaltet Serialisierbarkeit frei \smallskip -* Objekte schreiben: `ObjectOutputStream`: `void writeObject(Object)` -* Objekte lesen: `ObjectInputStream`: `Object readObject()` +- Objekte schreiben: `ObjectOutputStream`: `void writeObject(Object)` +- Objekte lesen: `ObjectInputStream`: `Object readObject()` \smallskip -* Wichtigste Eigenschaften: - * Attribute müssen serialisierbar sein - * `transient` und `static` Attribute werden nicht serialisiert - * De-Serialisierung: KEIN Konstruktor-Aufruf! - * Serialisierbarkeit vererbt sich - * Objekt-Referenz-Graph wird automatisch beachtet +- Wichtigste Eigenschaften: + - Attribute müssen serialisierbar sein + - `transient` und `static` Attribute werden nicht serialisiert + - De-Serialisierung: KEIN Konstruktor-Aufruf! + - Serialisierbarkeit vererbt sich + - Objekt-Referenz-Graph wird automatisch beachtet + +::: readings +- @Java-SE-Tutorial +::: + +::: outcomes +- k2: Was ist ein Marker-Interface und warum ist dies eine der großen + Design-Sünden in Java? +- k2: Erklären Sie den Prozess der Serialisierung und De-Serialisierung. Worauf + müssen Sie achten? +- k3: Serialisierung von Objekten und Programmzuständen +- k3: Serialisierung eigener Klassen und Typen +::: + +::: challenges +Implementieren Sie die beiden Klassen entsprechend dem UML-Diagram: + +![](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/images/uml_serialisierung.png?raw=true) + +Objekte vom Typ `Person` sowie `Address` sollen serialisierbar sein (vgl. +Vorlesung). Dabei soll das Passwort nicht serialisiert bzw. gespeichert werden, alle +anderen Eigenschaften von `Person` sollen serialisierbar sein. + +*Hinweis*: Verwenden Sie zur Umsetzung +[java.io.Serializable](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/Serializable.html). + +Erstellen Sie in Ihrem `main()` einige Instanzen von Person und speichern Sie diese +in serialisierter Form und laden (deserialisieren) Sie diese anschließend in neue +Variablen. + +Betrachten Sie die ursprünglichen und die wieder deserialisierten Objekte mit Hilfe +des Debuggers. Alternativ können Sie die Objekte auch in übersichtlicher Form über +den Logger ausgeben. + + +::: diff --git a/lecture/java-classic/threads-highlevel.md b/lecture/java-classic/threads-highlevel.md index 1cba94ebe..2b9fdde64 100644 --- a/lecture/java-classic/threads-highlevel.md +++ b/lecture/java-classic/threads-highlevel.md @@ -1,64 +1,50 @@ --- +author: Carsten Gips (HSBI) title: "Threads: High-Level Concurrency" -author: "Carsten Gips (HSBI)" -readings: - - "@Ullenboom2021 [Kap. 16]" - - "@Java-SE-Tutorial" - - "@Urma2014 [Kap. 7.2]" -tldr: | - Das Erzeugen von Threads über die Klasse `Thread` oder das Interface `Runnable` und - das Hantieren mit `synchronized` und `wait()`/`notify()` zählt zu den grundlegenden - Dingen beim Multi-Threading mit Java. Auf diesen Konzepten bauen viele weitere - Konzepte auf, die ein flexibleres Arbeiten mit Threads in Java ermöglichen. - - Dazu zählt unter anderem das Arbeiten mit `Lock`-Objekten und dazugehörigen `Conditions`, - was `synchronized` und `wait()`/`notify()` entspricht, aber feingranulareres und - flexibleres Locking bietet. - - Statt Threads immer wieder neu anzulegen (das Anlegen von Objekten bedeutet einen - gewissen Aufwand zur Laufzeit), kann man Threads über sogenannte Thread-Pools - wiederverwenden und über das Executor-Interface benutzen. - - Schließlich bietet sich das Fork/Join-Framework zum rekursiven Zerteilen von Aufgaben - und zur parallelen Bearbeitung der Teilaufgaben an. - - Die in Swing integrierte Klasse `SwingWorker` ermöglicht es, in Swing Berechnungen - in einen parallel ausgeführten Thread auszulagern. -outcomes: - - k3: "Ich kann High-Level-Abstraktionen einsetzen: Lock-Objekten und Conditions, Executor-Interface und Thread-Pools, Fork/Join-Framework, SwingWorker" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106531&client_id=FH-Bielefeld" -# name: "Quiz High-Level Concurrency (ILIAS)" -youtube: - - link: "https://youtu.be/bb_kuuhXC6A" - name: "VL High-Level Concurrency" - - link: "https://youtu.be/1tJRUJddtlw" - name: "Demo Lock-Objekte" - - link: "https://youtu.be/VvzlwZ6n3SI" - name: "Demo Executor" - - link: "https://youtu.be/Wfq_MDFoWYY" - name: "Demo Fork/Join" - - link: "https://youtu.be/Hu3RbqiNy4M" - name: "Demo SwingWorker" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/1d46e6b75b7e2ebe8d15edbda2340505285d886160a3b03f489024b1075f0b5176c68d9993a145fdcdb5e65c17f6be80a7ef1f0c64a72c410536e69965275a59" - name: "VL High-Level Concurrency" --- +::: tldr +Das Erzeugen von Threads über die Klasse `Thread` oder das Interface `Runnable` und +das Hantieren mit `synchronized` und `wait()`/`notify()` zählt zu den grundlegenden +Dingen beim Multi-Threading mit Java. Auf diesen Konzepten bauen viele weitere +Konzepte auf, die ein flexibleres Arbeiten mit Threads in Java ermöglichen. + +Dazu zählt unter anderem das Arbeiten mit `Lock`-Objekten und dazugehörigen +`Conditions`, was `synchronized` und `wait()`/`notify()` entspricht, aber +feingranulareres und flexibleres Locking bietet. + +Statt Threads immer wieder neu anzulegen (das Anlegen von Objekten bedeutet einen +gewissen Aufwand zur Laufzeit), kann man Threads über sogenannte Thread-Pools +wiederverwenden und über das Executor-Interface benutzen. + +Schließlich bietet sich das Fork/Join-Framework zum rekursiven Zerteilen von +Aufgaben und zur parallelen Bearbeitung der Teilaufgaben an. + +Die in Swing integrierte Klasse `SwingWorker` ermöglicht es, in Swing Berechnungen +in einen parallel ausgeführten Thread auszulagern. +::: + +::: youtube +- [VL High-Level Concurrency](https://youtu.be/bb_kuuhXC6A) +- [Demo Lock-Objekte](https://youtu.be/1tJRUJddtlw) +- [Demo Executor](https://youtu.be/VvzlwZ6n3SI) +- [Demo Fork/Join](https://youtu.be/Wfq_MDFoWYY) +- [Demo SwingWorker](https://youtu.be/Hu3RbqiNy4M) +::: # Explizite Lock-Objekte ::: notes Sie kennen bereits die Synchronisierung mit dem Schlüsselwort `synchronized`. -```java +``` java // Synchronisierung der gesamten Methode public synchronized int incrVal() { ... } ``` -```java +``` java // Synchronisierung eines Blocks (eines Teils einer Methode) public int incrVal() { ... @@ -69,13 +55,13 @@ public int incrVal() { } ``` -Dabei wird implizit ein Lock über ein Objekt (das eigene Objekt im ersten Fall, -das Sperrobjekt im zweiten Fall) benutzt. +Dabei wird implizit ein Lock über ein Objekt (das eigene Objekt im ersten Fall, das +Sperrobjekt im zweiten Fall) benutzt. Seit Java5 kann man alternativ auch explizite Lock-Objekte nutzen: ::: -```java +``` java // Synchronisierung eines Teils einer Methode über ein // Lock-Objekt (seit Java 5) // Package `java.util.concurrent.locks` @@ -90,83 +76,81 @@ public int incrVal() { ``` ::: notes -Locks aus dem Paket `java.util.concurrent.locks` arbeiten analog zum -impliziten Locken über `synchronized`. Sie haben darüber hinaus aber einige -Vorteile: - -* Methoden zum Abfragen, ob ein Lock möglich ist: `Lock#tryLock` -* Methoden zum Abfragen der aktuellen Warteschlangengröße: `Lock#getQueueLength` -* Verfeinerung `ReentrantReadWriteLock` mit Methoden `readLock` und `writeLock` - * Locks nur zum Lesen bzw. nur zum Schreiben -* `Lock#newCondition` liefert ein Condition-Objekt zur Benachrichtigung - ala `wait`/`notify`: `await`/`signal` => zusätzliches Timeout beim - Warten möglich +Locks aus dem Paket `java.util.concurrent.locks` arbeiten analog zum impliziten +Locken über `synchronized`. Sie haben darüber hinaus aber einige Vorteile: + +- Methoden zum Abfragen, ob ein Lock möglich ist: `Lock#tryLock` +- Methoden zum Abfragen der aktuellen Warteschlangengröße: `Lock#getQueueLength` +- Verfeinerung `ReentrantReadWriteLock` mit Methoden `readLock` und `writeLock` + - Locks nur zum Lesen bzw. nur zum Schreiben +- `Lock#newCondition` liefert ein Condition-Objekt zur Benachrichtigung ala + `wait`/`notify`: `await`/`signal` =\> zusätzliches Timeout beim Warten möglich Nachteile: -* Bei Exceptions werden implizite Locks durch `synchronized` automatisch - durch das Verlassen der Methode freigegeben. Explizite Locks müssen - **durch den Programmierer** freigegeben werden! => Nutzung des - `finally`-Block! +- Bei Exceptions werden implizite Locks durch `synchronized` automatisch durch das + Verlassen der Methode freigegeben. Explizite Locks müssen **durch den + Programmierer** freigegeben werden! =\> Nutzung des `finally`-Block! ::: -[Demo: lock.*]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/java-classic/src/lock/"} - +[Demo: lock.\*]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/java-classic/src/lock/"} # Thread-Management: Executor-Interface und Thread-Pools -::::::::: notes +::: notes ## Wiederverwendung von Threads -* Normale Threads sind immer Einmal-Threads: Man kann sie nur **einmal** in - ihrem Leben starten (auch wenn das Objekt anschließend noch auf - Nachrichten bzw. Methodenaufrufe reagiert) +- Normale Threads sind immer Einmal-Threads: Man kann sie nur **einmal** in ihrem + Leben starten (auch wenn das Objekt anschließend noch auf Nachrichten bzw. + Methodenaufrufe reagiert) -* Zusätzliches Problem: Threads sind Objekte: - * Threads brauchen relativ viel Arbeitsspeicher - * Erzeugen und Entsorgen von Threads kostet Ressourcen - * Zu viele Threads: Gesamte Anwendung hält an +- Zusätzliches Problem: Threads sind Objekte: -* Idee: Threads wiederverwenden und Thread-Management auslagern - => **Executor-Interface** und **Thread-Pool** + - Threads brauchen relativ viel Arbeitsspeicher + - Erzeugen und Entsorgen von Threads kostet Ressourcen + - Zu viele Threads: Gesamte Anwendung hält an + +- Idee: Threads wiederverwenden und Thread-Management auslagern =\> + **Executor-Interface** und **Thread-Pool** ## Executor-Interface -```java +``` java public interface Executor { void execute(Runnable command); } ``` -* Neue Aufgaben als Runnable an einen Executor via `execute` übergeben -* Executor könnte damit sofort neuen Thread starten (oder alten - wiederverwenden): - `e.execute(r);` => entspricht in der Wirkung `(new Thread(r)).start();` +- Neue Aufgaben als Runnable an einen Executor via `execute` übergeben +- Executor könnte damit sofort neuen Thread starten (oder alten wiederverwenden): + `e.execute(r);` =\> entspricht in der Wirkung `(new Thread(r)).start();` ## Thread-Pool hält Menge von "Worker-Threads" -* Statische Methoden von `java.util.concurrent.Executors` erzeugen - Thread-Pools mit verschiedenen Eigenschaften: - * `Executors#newFixedThreadPool` erzeugt ExecutorService mit - spezifizierter Anzahl von Worker-Threads - * `Executors#newCachedThreadPool` erzeugt Pool mit Threads, die nach - 60 Sekunden Idle wieder entsorgt werden +- Statische Methoden von `java.util.concurrent.Executors` erzeugen Thread-Pools + mit verschiedenen Eigenschaften: + + - `Executors#newFixedThreadPool` erzeugt ExecutorService mit spezifizierter + Anzahl von Worker-Threads + - `Executors#newCachedThreadPool` erzeugt Pool mit Threads, die nach 60 + Sekunden Idle wieder entsorgt werden -* Rückgabe: `ExecutorService` (Thread-Pool) +- Rückgabe: `ExecutorService` (Thread-Pool) - ```java + ``` java public interface ExecutorService extends Executor { ... } ``` -* `Executor#execute` übergibt Runnable dem nächsten freien Worker-Thread - (oder erzeugt ggf. neuen Worker-Thread bzw. hängt Runnable in Warteschlange, - je nach erzeugtem Pool) - -* Methoden zum Beenden eines Thread-Pools (Freigabe): `shutdown()`, `isShutdown()`, ... -::::::::: +- `Executor#execute` übergibt Runnable dem nächsten freien Worker-Thread (oder + erzeugt ggf. neuen Worker-Thread bzw. hängt Runnable in Warteschlange, je nach + erzeugtem Pool) +- Methoden zum Beenden eines Thread-Pools (Freigabe): `shutdown()`, + `isShutdown()`, ... +::: -```java +``` java MyThread x = new MyThread(); // Runnable oder Thread ExecutorService pool = Executors.newCachedThreadPool(); @@ -178,40 +162,40 @@ pool.execute(x); // x.start() pool.shutdown(); // Feierabend :) ``` -[Demo: executor.ExecutorDemo]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/executor/ExecutorDemo.java"} - +[Demo: executor.ExecutorDemo]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/executor/ExecutorDemo.java"} -::::::::: notes +::: notes ## Hintergrund (vereinfacht) -Der Thread-Pool reserviert sich "nackten" Speicher, der der Größe von $n$ -Threads entspricht, und "prägt" die Objektstruktur durch einen Cast direkt auf -(ohne wirkliche neue Objekte zu erzeugen). Dieses Vorgehen ist in der C-Welt -wohlbekannt und schnell (vgl. Thema Speicherverwaltung in der LV -"Systemprogrammierung"). In Java wird dies durch eine wohldefinierte -Schnittstelle vor dem Nutzer verborgen. +Der Thread-Pool reserviert sich "nackten" Speicher, der der Größe von $n$ Threads +entspricht, und "prägt" die Objektstruktur durch einen Cast direkt auf (ohne +wirkliche neue Objekte zu erzeugen). Dieses Vorgehen ist in der C-Welt wohlbekannt +und schnell (vgl. Thema Speicherverwaltung in der LV "Systemprogrammierung"). In +Java wird dies durch eine wohldefinierte Schnittstelle vor dem Nutzer verborgen. ## Ausblick -Hier haben wir nur die absoluten Grundlagen angerissen. Wir können auch -`Callables` anstatt von `Runnables` übergeben, auf Ergebnisse aus der Zukunft -warten (`Futures`), Dinge zeitgesteuert (immer wieder) starten, ... +Hier haben wir nur die absoluten Grundlagen angerissen. Wir können auch `Callables` +anstatt von `Runnables` übergeben, auf Ergebnisse aus der Zukunft warten +(`Futures`), Dinge zeitgesteuert (immer wieder) starten, ... Schauen Sie sich bei Interesse die weiterführende Literatur an, beispielsweise die Oracle-Dokumentation oder auch [@Ullenboom2021] (insbesondere den Abschnitt 16.4 -["Der Ausführer (Executor) kommt"](https://openbook.rheinwerk-verlag.de/javainsel/16_004.html#u16.4)). -::::::::: - +["Der Ausführer (Executor) +kommt"](https://openbook.rheinwerk-verlag.de/javainsel/16_004.html#u16.4)). +::: # Fork/Join-Framework: Teile und Herrsche ::: notes Spezieller Thread-Pool zur rekursiven Bearbeitung parallelisierbarer Tasks -* `java.util.concurrent.ForkJoinPool#invoke` startet Task -* Task muss von `RecursiveTask` erben: +- `java.util.concurrent.ForkJoinPool#invoke` startet Task + +- Task muss von `RecursiveTask` erben: - ```java + ``` java public abstract class RecursiveTask extends ForkJoinTask { protected abstract V compute(); } @@ -220,7 +204,7 @@ Spezieller Thread-Pool zur rekursiven Bearbeitung parallelisierbarer Tasks Prinzipieller Ablauf: ::: -```java +``` java public class RecursiveTask extends ForkJoinTask { protected V compute() { if (task klein genug) { @@ -238,62 +222,65 @@ public class RecursiveTask extends ForkJoinTask { } ``` -[Demo: forkjoin.ForkJoin]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/forkjoin/ForkJoin.java"} - +[Demo: forkjoin.ForkJoin]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/forkjoin/ForkJoin.java"} # Swing und Threads ::: notes ## Lange Berechnungen in Listenern blockieren Swing-GUI -* Problem: Events werden durch **einen** *Event Dispatch Thread* (EDT) +- Problem: Events werden durch **einen** *Event Dispatch Thread* (EDT) **sequentiell** bearbeitet -* Lösung: Berechnungen in neuen Thread auslagern -* **Achtung**: Swing ist **nicht Thread-safe**! Komponenten nicht - durch verschiedene Threads manipulieren! +- Lösung: Berechnungen in neuen Thread auslagern +- **Achtung**: Swing ist **nicht Thread-safe**! Komponenten nicht durch + verschiedene Threads manipulieren! ## Lösung -=> `javax.swing.SwingWorker` ist eine spezielle Thread-Klasse, eng mit Swing/Event-Modell verzahnt. +=\> `javax.swing.SwingWorker` ist eine spezielle Thread-Klasse, eng mit +Swing/Event-Modell verzahnt. ::: -* Implementieren: - * `SwingWorker#doInBackground`: Für die langwierige Berechnung (muss man selbst implementieren) - * `SwingWorker#done`: Wird vom EDT aufgerufen, wenn `doInBackground` fertig ist +- Implementieren: + - `SwingWorker#doInBackground`: Für die langwierige Berechnung (muss man + selbst implementieren) + - `SwingWorker#done`: Wird vom EDT aufgerufen, wenn `doInBackground` fertig + ist \bigskip -* Aufrufen: - * `SwingWorker#execute`: Started neuen Thread nach Anlegen einer Instanz und führt dann automatisch `doInBackground` aus - * `SwingWorker#get`: Return-Wert von `doInBackground` abfragen +- Aufrufen: + - `SwingWorker#execute`: Started neuen Thread nach Anlegen einer Instanz und + führt dann automatisch `doInBackground` aus + - `SwingWorker#get`: Return-Wert von `doInBackground` abfragen ::: notes ## Anmerkungen -* `SwingWorker#done` ist optional: *kann* überschrieben werden - * Beispielweise, wenn nach Beendigung der langwierigen Berechnung +- `SwingWorker#done` ist optional: *kann* überschrieben werden + - Beispielweise, wenn nach Beendigung der langwierigen Berechnung GUI-Bestandteile mit dem Ergebnis aktualisiert werden sollen - -* `SwingWorker` ist eine generische Klasse: - * `T` Typ für das Ergebnis der Berechnung, d.h. Rückgabetyp für +- `SwingWorker` ist eine generische Klasse: + - `T` Typ für das Ergebnis der Berechnung, d.h. Rückgabetyp für `doInBackground` und `get` - * `V` Typ für Zwischenergebnisse + - `V` Typ für Zwischenergebnisse ::: -[Demo: misc.SwingWorkerDemo]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/misc/SwingWorkerDemo.java"} - +[Demo: misc.SwingWorkerDemo]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/misc/SwingWorkerDemo.java"} # Letzte Worte :-) -* Viele weitere Konzepte - * Semaphoren, Monitore, ... - * Leser-Schreiber-Probleme, Verklemmungen, ... +- Viele weitere Konzepte + - Semaphoren, Monitore, ... + - Leser-Schreiber-Probleme, Verklemmungen, ... - => Verweis auf LV "Betriebssysteme" und "Verteilte Systeme" + =\> Verweis auf LV "Betriebssysteme" und "Verteilte Systeme" \bigskip -* **Achtung**: Viele Klassen sind nicht Thread-safe! +- **Achtung**: Viele Klassen sind nicht Thread-safe! ::: notes Es gibt aber meist ein "Gegenstück", welches Thread-safe ist. @@ -301,15 +288,14 @@ public class RecursiveTask extends ForkJoinTask { Beispiel Listen: - * `java.util.ArrayList` ist **nicht** Thread-safe - * `java.util.Vector` ist Thread-sicher + - `java.util.ArrayList` ist **nicht** Thread-safe + - `java.util.Vector` ist Thread-sicher - => Siehe Javadoc in den JDK-Klassen! + =\> Siehe Javadoc in den JDK-Klassen! \bigskip -* Thread-safe bedeutet **Overhead** (Synchronisierung)! - +- Thread-safe bedeutet **Overhead** (Synchronisierung)! # Wrap-Up @@ -317,9 +303,20 @@ Multi-Threading auf höherem Level: Thread-Pools und Fork/Join-Framework \bigskip -* Feingranulareres und flexibleres Locking mit Lock-Objekten und Conditions -* Wiederverwendung von Threads: Thread-Management mit Executor-Interface - und Thread-Pools -* Fork/Join-Framework zum rekursiven Zerteilen von Aufgaben und zur - parallelen Bearbeitung der Teilaufgaben -* `SwingWorker` für die parallele Bearbeitung von Aufgaben in Swing +- Feingranulareres und flexibleres Locking mit Lock-Objekten und Conditions +- Wiederverwendung von Threads: Thread-Management mit Executor-Interface und + Thread-Pools +- Fork/Join-Framework zum rekursiven Zerteilen von Aufgaben und zur parallelen + Bearbeitung der Teilaufgaben +- `SwingWorker` für die parallele Bearbeitung von Aufgaben in Swing + +::: readings +- @Ullenboom2021 [Kap. 16] +- @Java-SE-Tutorial +- @Urma2014 [Kap. 7.2] +::: + +::: outcomes +- k3: Ich kann High-Level-Abstraktionen einsetzen: Lock-Objekten und Conditions, + Executor-Interface und Thread-Pools, Fork/Join-Framework, SwingWorker +::: diff --git a/lecture/java-classic/threads-intro.md b/lecture/java-classic/threads-intro.md index 5d15e1712..5d467bcd4 100644 --- a/lecture/java-classic/threads-intro.md +++ b/lecture/java-classic/threads-intro.md @@ -1,62 +1,49 @@ --- +author: Carsten Gips (HSBI) title: "Threads: Einführung in die nebenläufige Programmierung mit Threads" -author: "Carsten Gips (HSBI)" -readings: - - "@Ullenboom2021 [Kap. 16]" - - "@Java-SE-Tutorial" - - "@Boles2008" -tldr: | - Threads sind weitere Kontrollflussfäden, die von der Java-VM (oder (selten) vom OS) - verwaltet werden. Damit ist sind sie leichtgewichtiger als der Start neuer Prozesse - direkt auf Betriebssystem-Ebene. - - Beim Start eines Java-Programms wird die `main()`-Methode automatisch in einem - (Haupt-) Thread ausgeführt. Alle Anweisungen in einem Thread werden sequentiell - ausgeführt. - - Um einen neuen Thread zu erzeugen, leitet man von `Thread` ab oder implementiert - das Interface `Runnable`. Von diesen eigenen Klassen kann man wie üblich ein neues - Objekt anlegen. Die Methode `run()` enthält dabei den im Thread auszuführenden - Code. Um einen Thread als neuen parallelen Kontrollfluss zu starten, muss man die - geerbte Methode `start()` auf dem Objekt aufrufen. Im Fall der Implementierung von - `Runnable` muss man das Objekt zuvor noch in den Konstruktor von `Thread` stecken - und so ein neues `Thread`-Objekt erzeugen, auf dem man dann `start()` aufrufen kann. - - Threads haben einen Lebenszyklus: Nach dem Erzeugen der Objekte mit `new` wird - der Thread noch nicht ausgeführt. Durch den Aufruf der Methode `start()` gelangt - der Thread in einen Zustand "ausführungsbereit". Sobald er vom Scheduler eine - Zeitscheibe zugeteilt bekommt, wechselt er in den Zustand "rechnend". Von hier kann - er nach Ablauf der Zeitscheibe durch den Scheduler wieder nach "ausführungsbereit" - zurück überführt werden. Dieses Wechselspiel passiert automatisch und i.d.R. schnell, - so dass selbst auf Maschinen mit nur einem Prozessor/Kern der Eindruck einer parallelen - Verarbeitung entsteht. Nach Abarbeitung der `run()`-Methode wird der Thread beendet - und kann nicht wieder neu gestartet werden. Bei Zugriff auf gesperrte Ressourcen - oder durch `sleep()` oder `join()` kann ein Thread blockiert werden. Aus diesem - Zustand gelangt er durch Interrupts oder nach Ablauf der Schlafzeit oder durch `notify` - wieder zurück nach "ausführungsbereit". - - Die Thread-Objekte sind normale Java-Objekte. Man kann hier Attribute und Methoden - haben und diese entsprechend zugreifen/aufrufen. Das klappt auch, wenn der Thread - noch nicht gestartet wurde oder bereits abgearbeitet wurde. -outcomes: - - k2: "Ich kenne den grundsätzlichen Unterschied zw. Threads und Prozessen" - - k2: "Ich kenne den Lebenszyklus von Threads" - - k3: "Ich kann Threads erzeugen und starten" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106529&client_id=FH-Bielefeld" -# name: "Quiz Threads Intro (ILIAS)" -youtube: - - link: "https://youtu.be/ClfXbNPRl_8" - name: "VL Threads Intro" - - link: "https://youtu.be/zcVqFAx5D0E" - name: "Demo Threads Intro: Erzeugen von Threads" - - link: "https://youtu.be/lQ_JSHBGhdU" - name: "Demo Threads Intro: Arbeiten mit Threads (`join()`)" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/41a885ac4baf0e087d393b0fa9d3d6f38cc86debad5b7e563c46dac15c0ba4f214476650ad6fa288708eb8b1ccf12d19f6ab46a8c5cf7a58b61a4cff39ec727b" - name: "VL Threads Intro" --- +::: tldr +Threads sind weitere Kontrollflussfäden, die von der Java-VM (oder (selten) vom OS) +verwaltet werden. Damit ist sind sie leichtgewichtiger als der Start neuer Prozesse +direkt auf Betriebssystem-Ebene. + +Beim Start eines Java-Programms wird die `main()`-Methode automatisch in einem +(Haupt-) Thread ausgeführt. Alle Anweisungen in einem Thread werden sequentiell +ausgeführt. + +Um einen neuen Thread zu erzeugen, leitet man von `Thread` ab oder implementiert das +Interface `Runnable`. Von diesen eigenen Klassen kann man wie üblich ein neues +Objekt anlegen. Die Methode `run()` enthält dabei den im Thread auszuführenden Code. +Um einen Thread als neuen parallelen Kontrollfluss zu starten, muss man die geerbte +Methode `start()` auf dem Objekt aufrufen. Im Fall der Implementierung von +`Runnable` muss man das Objekt zuvor noch in den Konstruktor von `Thread` stecken +und so ein neues `Thread`-Objekt erzeugen, auf dem man dann `start()` aufrufen kann. + +Threads haben einen Lebenszyklus: Nach dem Erzeugen der Objekte mit `new` wird der +Thread noch nicht ausgeführt. Durch den Aufruf der Methode `start()` gelangt der +Thread in einen Zustand "ausführungsbereit". Sobald er vom Scheduler eine +Zeitscheibe zugeteilt bekommt, wechselt er in den Zustand "rechnend". Von hier kann +er nach Ablauf der Zeitscheibe durch den Scheduler wieder nach "ausführungsbereit" +zurück überführt werden. Dieses Wechselspiel passiert automatisch und i.d.R. +schnell, so dass selbst auf Maschinen mit nur einem Prozessor/Kern der Eindruck +einer parallelen Verarbeitung entsteht. Nach Abarbeitung der `run()`-Methode wird +der Thread beendet und kann nicht wieder neu gestartet werden. Bei Zugriff auf +gesperrte Ressourcen oder durch `sleep()` oder `join()` kann ein Thread blockiert +werden. Aus diesem Zustand gelangt er durch Interrupts oder nach Ablauf der +Schlafzeit oder durch `notify` wieder zurück nach "ausführungsbereit". + +Die Thread-Objekte sind normale Java-Objekte. Man kann hier Attribute und Methoden +haben und diese entsprechend zugreifen/aufrufen. Das klappt auch, wenn der Thread +noch nicht gestartet wurde oder bereits abgearbeitet wurde. +::: + +::: youtube +- [VL Threads Intro](https://youtu.be/ClfXbNPRl_8) +- [Demo Threads Intro: Erzeugen von Threads](https://youtu.be/zcVqFAx5D0E) +- [Demo Threads Intro: Arbeiten mit Threads + (`join()`)](https://youtu.be/lQ_JSHBGhdU) +::: # 42 @@ -66,8 +53,8 @@ fhmedia: Wert 42 ausprobieren (ist zeitlich ganz gut) --> -[Demo: misc.SwingWorkerDemo (GUI ausprobieren)]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/misc/SwingWorkerDemo.java"} - +[Demo: misc.SwingWorkerDemo (GUI ausprobieren)]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/misc/SwingWorkerDemo.java"} ::: notes # Einführung in nebenläufige Programmierung @@ -75,18 +62,18 @@ Wert 42 ausprobieren (ist zeitlich ganz gut) ::: notes ## Traditionelle Programmierung -:::: +::: ::: slides # Traditionelle Programmierung ::: ::: notes -* Aufruf einer Methode verlagert Kontrollfluss in diese Methode -* Code hinter Methodenaufruf wird erst **nach Beendigung** der Methode ausgeführt +- Aufruf einer Methode verlagert Kontrollfluss in diese Methode +- Code hinter Methodenaufruf wird erst **nach Beendigung** der Methode ausgeführt ::: -```java +``` java public class Traditional { public static void main(String... args) { Traditional x = new Traditional(); @@ -102,32 +89,32 @@ public class Traditional { } ``` -[Demo: intro.Traditional]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/intro/Traditional.java"} - +[Demo: intro.Traditional]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/intro/Traditional.java"} ::: notes ## Nebenläufige Programmierung -:::: +::: ::: slides # Nebenläufige Programmierung ::: ::: notes -* Erzeugung eines neuen Kontrollflussfadens (Thread) - * **Läuft (quasi-) parallel zu bisherigem Kontrollfluss** -* Threads können unabhängig von einander arbeiten -* Zustandsverwaltung durch Java-VM [(oder Unterstützung durch Betriebssystem)]{.notes} - * Aufruf einer bestimmten Methode erzeugt neuen Kontrollflussfaden - * Der neue Thread arbeitet "parallel" zum bisherigen Thread - * Kontrolle kehrt sofort wieder zurück: Code hinter dem Methodenaufruf - wird ausgeführt ohne auf die Beendigung der aufgerufenen Methode zu - warten - * Verteilung der Threads auf die vorhandenen Prozessorkerne abhängig - von der Java-VM +- Erzeugung eines neuen Kontrollflussfadens (Thread) + - **Läuft (quasi-) parallel zu bisherigem Kontrollfluss** +- Threads können unabhängig von einander arbeiten +- Zustandsverwaltung durch Java-VM [(oder Unterstützung durch + Betriebssystem)]{.notes} + - Aufruf einer bestimmten Methode erzeugt neuen Kontrollflussfaden + - Der neue Thread arbeitet "parallel" zum bisherigen Thread + - Kontrolle kehrt sofort wieder zurück: Code hinter dem Methodenaufruf wird + ausgeführt ohne auf die Beendigung der aufgerufenen Methode zu warten + - Verteilung der Threads auf die vorhandenen Prozessorkerne abhängig von der + Java-VM ::: -```java +``` java public class Threaded extends Thread { public static void main(String... args) { Threaded x = new Threaded(); @@ -144,44 +131,46 @@ public class Threaded extends Thread { } ``` -[Demo: intro.Threaded]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/intro/Threaded.java"} - +[Demo: intro.Threaded]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/intro/Threaded.java"} # Erzeugen von Threads -* Ableiten von `Thread` oder Implementierung von `Runnable` +- Ableiten von `Thread` oder Implementierung von `Runnable` ![](images/ThreadRunnable.png){width="80%" web_width="60%"} -* Methode `run()` implementieren, aber nicht aufrufen -* Methode `start()` aufrufen, aber (i.d.R.) nicht implementieren +- Methode `run()` implementieren, aber nicht aufrufen -[Demo: creation.*]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/creation/"} +- Methode `start()` aufrufen, aber (i.d.R.) nicht implementieren + +[Demo: creation.\*]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/creation/"} ::: notes -## Ableiten von _Thread_ +## Ableiten von *Thread* -* `start()` startet den Thread und sorgt für Ausführung von `run()` -* `start()` nur einmal aufrufen +- `start()` startet den Thread und sorgt für Ausführung von `run()` +- `start()` nur einmal aufrufen -## Implementierung von _Runnable_ +## Implementierung von *Runnable* -* Ebenfalls `run()` implementieren -* Neues `Thread`-Objekt erzeugen, Konstruktor das eigene Runnable übergeben -* Für Thread-Objekt die Methode `start()` aufrufen - * Startet den Thread (das Runnable) und sorgt für Ausführung von `run()` +- Ebenfalls `run()` implementieren +- Neues `Thread`-Objekt erzeugen, Konstruktor das eigene Runnable übergeben +- Für Thread-Objekt die Methode `start()` aufrufen + - Startet den Thread (das Runnable) und sorgt für Ausführung von `run()` -Vorteil von `Runnable`: Ist ein Interface, d.h. man kann noch von einer anderen Klasse erben +Vorteil von `Runnable`: Ist ein Interface, d.h. man kann noch von einer anderen +Klasse erben ::: - # Zustandsmodell von Threads (vereinfacht) ::: notes -Threads haben einen Lebenszyklus: Nach dem Erzeugen der Objekte mit `new` wird -der Thread noch nicht ausgeführt. Er ist sozusagen in einem Zustand "erzeugt". -Man kann bereits mit dem Objekt interagieren, also auf Attribute zugreifen und -Methoden aufrufen. +Threads haben einen Lebenszyklus: Nach dem Erzeugen der Objekte mit `new` wird der +Thread noch nicht ausgeführt. Er ist sozusagen in einem Zustand "erzeugt". Man kann +bereits mit dem Objekt interagieren, also auf Attribute zugreifen und Methoden +aufrufen. Durch den Aufruf der Methode `start()` gelangt der Thread in einen Zustand "ausführungsbereit", er läuft also aus Nutzersicht. Allerdings hat er noch keine @@ -189,8 +178,8 @@ Ressourcen zugeteilt (CPU, ...), so dass er tatsächlich noch nicht rechnet. Sob er vom Scheduler eine Zeitscheibe zugeteilt bekommt, wechselt er in den Zustand "rechnend" und führt den Inhalt der `run()`-Methode aus. Von hier kann er nach Ablauf der Zeitscheibe durch den Scheduler wieder nach "ausführungsbereit" zurück -überführt werden. Dieses Wechselspiel passiert automatisch und i.d.R. schnell, -so dass selbst auf Maschinen mit nur einem Prozessor/Kern der Eindruck einer parallelen +überführt werden. Dieses Wechselspiel passiert automatisch und i.d.R. schnell, so +dass selbst auf Maschinen mit nur einem Prozessor/Kern der Eindruck einer parallelen Verarbeitung entsteht. Nach der Abarbeitung der `run()`-Methode oder bei einer nicht gefangenen Exception @@ -206,75 +195,89 @@ Zustand gelangt der Thread wieder heraus, etwa durch Interrupts (Aufruf der Meth oder durch ein `notify`, und wird wieder zurück nach "ausführungsbereit" versetzt und wartet auf die Zuteilung einer Zeitscheibe durch den Scheduler. -Sie finden in [@Boles2008, Kapitel 5.2 "Thread-Zustände"] eine schöne -ausführliche Darstellung. +Sie finden in [@Boles2008, Kapitel 5.2 "Thread-Zustände"] eine schöne ausführliche +Darstellung. ::: \bigskip ## Threads können wie normale Objekte kommunizieren -* Zugriff auf (`public`) Attribute [(oder eben über Methoden)]{.notes} -* Aufruf von Methoden +- Zugriff auf (`public`) Attribute [(oder eben über Methoden)]{.notes} +- Aufruf von Methoden \bigskip ## Threads können noch mehr -* Eine Zeitlang schlafen: `Thread.sleep()` +- Eine Zeitlang schlafen: `Thread.sleep()` ::: notes - * Statische Methode der Klasse `Thread` (Klassenmethode) - * Aufrufender Thread wird bis zum Ablauf der Zeit oder bis zum Aufruf - der `interrupt()`-Methode des Threads blockiert - * "Moderne" Alternative: `TimeUnit`, beispielsweise `TimeUnit.SECONDS.sleep( 2 );` + - Statische Methode der Klasse `Thread` (Klassenmethode) + - Aufrufender Thread wird bis zum Ablauf der Zeit oder bis zum Aufruf der + `interrupt()`-Methode des Threads blockiert + - "Moderne" Alternative: `TimeUnit`, beispielsweise + `TimeUnit.SECONDS.sleep( 2 );` ::: -* Prozessor abgeben und hinten in Warteschlange einreihen: `yield()` +- Prozessor abgeben und hinten in Warteschlange einreihen: `yield()` -* Andere Threads stören: `otherThreadObj.interrupt()` +- Andere Threads stören: `otherThreadObj.interrupt()` ::: notes - * Die Methoden `sleep()`, `wait()` und `join()` im empfangenden Thread - `otherThreadObj` lösen eine `InterruptedException` aus, wenn sie - durch die Methode `interrupt()` unterbrochen werden. Das heißt, - `interrupt()` beendet diese Methoden mit der Ausnahme. - * Empfangender Thread verlässt ggf. den Zustand "blockiert" und wechselt - in den Zustand "ausführungsbereit" + - Die Methoden `sleep()`, `wait()` und `join()` im empfangenden Thread + `otherThreadObj` lösen eine `InterruptedException` aus, wenn sie durch die + Methode `interrupt()` unterbrochen werden. Das heißt, `interrupt()` beendet + diese Methoden mit der Ausnahme. + - Empfangender Thread verlässt ggf. den Zustand "blockiert" und wechselt in + den Zustand "ausführungsbereit" ::: -* Warten auf das Ende anderer Threads: `otherThreadObj.join()` +- Warten auf das Ende anderer Threads: `otherThreadObj.join()` ::: notes - * Ausführender Thread wird blockiert (also nicht `otherThreadObj`!) - * Blockade des Aufrufers wird beendet, wenn der andere Thread + - Ausführender Thread wird blockiert (also nicht `otherThreadObj`!) + - Blockade des Aufrufers wird beendet, wenn der andere Thread (`otherThreadObj`) beendet wird. ::: \bigskip ::: notes -_Hinweis:_ Ein Thread wird beendet, wenn +*Hinweis:* Ein Thread wird beendet, wenn -* die `run()`-Methode normal endet, oder -* die `run()`-Methode durch eine nicht gefangene Exception beendet wird, oder -* von außen die Methode `stop()` aufgerufen wird (Achtung: Deprecated! - Einen richtigen Ersatz gibt es aber auch nicht.). +- die `run()`-Methode normal endet, oder +- die `run()`-Methode durch eine nicht gefangene Exception beendet wird, oder +- von außen die Methode `stop()` aufgerufen wird (Achtung: Deprecated! Einen + richtigen Ersatz gibt es aber auch nicht.). -_Hinweis:_ Die Methoden `wait()`, `notify()`/`notifyAll()` und die "`synchronized`-Sperre" -werden in der Sitzung ["Threads: Synchronisation"](threads-synchronisation.md) -besprochen. +*Hinweis:* Die Methoden `wait()`, `notify()`/`notifyAll()` und die +"`synchronized`-Sperre" werden in der Sitzung ["Threads: +Synchronisation"](threads-synchronisation.md) besprochen. ::: -[Demo: intro.Join]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/intro/Join.java"} - +[Demo: intro.Join]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/intro/Join.java"} # Wrap-Up -Threads sind weitere Kontrollflussfäden, von Java-VM (oder (selten) von OS) verwaltet +Threads sind weitere Kontrollflussfäden, von Java-VM (oder (selten) von OS) +verwaltet \bigskip -* Ableiten von `Thread` oder implementieren von `Runnable` -* Methode `run` enthält den auszuführenden Code -* Starten des Threads mit `start` (nie mit `run`!) +- Ableiten von `Thread` oder implementieren von `Runnable` +- Methode `run` enthält den auszuführenden Code +- Starten des Threads mit `start` (nie mit `run`!) + +::: readings +- @Ullenboom2021 [Kap. 16] +- @Java-SE-Tutorial +- @Boles2008 +::: + +::: outcomes +- k2: Ich kenne den grundsätzlichen Unterschied zw. Threads und Prozessen +- k2: Ich kenne den Lebenszyklus von Threads +- k3: Ich kann Threads erzeugen und starten +::: diff --git a/lecture/java-classic/threads-synchronisation.md b/lecture/java-classic/threads-synchronisation.md index 6fcb4955c..20f1514d5 100644 --- a/lecture/java-classic/threads-synchronisation.md +++ b/lecture/java-classic/threads-synchronisation.md @@ -1,119 +1,58 @@ --- +author: Carsten Gips (HSBI) title: "Threads: Synchronisation: Verteilter Zugriff auf gemeinsame Ressourcen" -author: "Carsten Gips (HSBI)" -readings: - - "@Java-SE-Tutorial" - - "@Boles2008" -tldr: | - Bei verteiltem Zugriff auf gemeinsame Ressourcen besteht Synchronisierungsbedarf, - insbesondere sollten nicht mehrere Threads gleichzeitig geteilte Daten modifizieren. - Dazu kommt das Problem, dass ein Thread in einer komplexen Folge von Aktionen die - Zeitscheibe verlieren kann und dann später mit veralteten Daten weiter macht. - - Um den Zugriff auf gemeinsame Ressourcen oder den Eintritt in kritische Bereiche - zu schützen und zu synchronisieren, kann man diese Zugriffe oder Bereiche in einen - `synchronized`-Block legen. Dazu benötigt man noch ein beliebiges (gemeinsam - sichtbares) Objekt, welches als Wächter- oder Sperr-Objekt fungiert. Beim Eintritt - in den geschützten Block muss ein Thread einen Lock auf dem Sperr-Objekt erlangen. - Hat bereits ein anderer Thread den Lock, wird der neue Thread so lange blockiert, - bis der Lock wieder "frei" ist. Beim Eintritt in den Bereich wird dann durch den - Thread auf dem Sperr-Objekt der Lock gesetzt und beim Austritt automatisch wieder - aufgehoben. Dies nennt man auch **mehrseitige Synchronisierung** (mehrere Threads - "stimmen" sich quasi untereinander über den Zugriff auf eine Ressource ab). - - Um auf den Eintritt eines Ereignisses oder die Erfüllung einer Bedingung zu warten, - kann man `wait` und `notify` nutzen. In einem `synchronized`-Block prüft man, ob - die Bedingung erfüllt oder ein Ereignis eingetreten ist, und falls ja arbeitet man - damit normal weiter. Falls die Bedingung nicht erfüllt ist oder das Ereignis nicht - eingetreten ist, kann man auf dem im `synchronized`-Block genutzten Sperr-Objekt - die Methode `wait()` aufrufen. Damit wird der Thread in die entsprechende Schlange - auf dem Sperr-Objekt eingereiht und blockiert. Zusätzlich wird der Lock auf dem - Sperr-Objekt freigegeben. Zum "Aufwecken" nutzt man an geeigneter Stelle auf dem - **selben Sperr-Objekt** die Methode `notify()` oder `notifyALl()` (erstere weckt - einen in der Liste des Sperr-Objekts wartenden Thread, die letztere alle). Nach - dem Aufwachen macht der Thread nach seinem `wait()` weiter. Es ist also wichtig, - dass die Bedingung, wegen der ursprünglich das `wait()` aufgerufen wurde, erneut - abgefragt wird und ggf. erneut in das `wait()` gegangen wird. Dies nennt man - **einseitige Synchronisierung**. - - Es gibt darüber hinaus viele weitere Mechanismen und Probleme, die aber den Rahmen - dieser Lehrveranstaltung deutlich übersteigen. Diese werden teilweise in den - Veranstaltungen "Betriebssysteme" und/oder "Verteilte Systeme" besprochen. -outcomes: - - k2: "Ich kann die Notwendigkeit zur Synchronisation erklären" - - k2: "Ich kann den Unterschied zwischen einseitiger und mehrseitiger Synchronisation erklären" - - k3: "Ich kann die Synchronisation mit `synchronized`, `wait`, `notify` und `notifyAll` praktisch einsetzen" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106530&client_id=FH-Bielefeld" -# name: "Quiz Threads Synchronisation (ILIAS)" -youtube: - - link: "https://youtu.be/FtVaobn4NqA" - name: "VL Threads Synchronisation" - - link: "https://youtu.be/SB1ngVkQdLM" - name: "Demo Teaser: Falscher Zugriff auf gemeinsame Ressourcen" - - link: "https://youtu.be/YTV-oT-vmpE" - name: "Demo Mehrseitige Synchronisation (Sperr-Objekt, synchronisierte Methode)" - - link: "https://youtu.be/D4B5xHqCZ-0" - name: "Demo Mehrseitige Synchronisation: Deadlock" - - link: "https://youtu.be/akCl01ZAaGo" - name: "Demo Einseitige Synchronisation" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/08f819d3002d7658801ff15fdb14cbdba82defee0ae97d929f5c4a03eeb0e3e9b751e90f5e0fe0ac3d55a551a53065c04f505f23a6c9f41d69d504474ea28c04" - name: "VL Threads Synchronisation" -challenges: | - **Hamster-Welt** - - In den [Vorgaben](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/java-classic/src/challenges/threads) - finden Sie eine Modellierung für eine Hamsterwelt. - - Es gibt rote und blaue Hamster, die sich unabhängig von einander bewegen können. Es gibt einen Tunnel, den die Hamster betreten - und durchqueren können. In der Vorgabe ist ein kleines Hauptprogramm enthalten, welches einige Hamster anlegt und herumlaufen - lässt. - - - **Teil I: Stau im Tunnel** - - Die Hamster sind sehr neugierig und wollen gern durch den Tunnel gehen, um die Höhle auf der anderen Seite zu erkunden. Leider - mussten sie feststellen, dass immer nur ein Hamster zu einem Zeitpunkt im Tunnel sein darf, sonst wird die Luft zu knapp. - - Ergänzen Sie die Vorgaben, so dass sich immer nur ein paralleler Hamster (egal welcher Farbe) im Tunnel aufhalten kann. Wenn ein - Hamster in den Tunnel will, aber nicht hinein kann, dann soll er am Eingang warten, also nicht noch einmal in seiner Höhle - herumlaufen. (Das passiert eigentlich automatisch, wenn Sie alles richtig machen.) - - - - - **Teil II: Schlaue Hamster** - - Die Hamster sind schlau und haben bemerkt, dass die Einschränkung aus der letzten Aufgabe zu stark war. Sie überleben auch, wenn - sich beliebig viele blaue Hamster oder nur genau ein roter Hamster im Tunnel aufhalten. - - Erweitern Sie die Implementierung aus der letzten Aufgabe, so dass folgende Bedingungen eingehalten werden: - - * Es dürfen sich beliebig viele blaue Hamster gleichzeitig im Tunnel befinden. - - Das bedeutet, dass in diesem Fall zwar weitere blaue Hamster den Tunnel betreten dürfen, aber kein roter Hamster in den Tunnel - hinein darf. - - * Wenn sich ein roter Hamster im Tunnel aufhält, dürfen keine anderen Hamster (unabhängig von deren Farbe) den Tunnel betreten. - - - - --- +::: tldr +Bei verteiltem Zugriff auf gemeinsame Ressourcen besteht Synchronisierungsbedarf, +insbesondere sollten nicht mehrere Threads gleichzeitig geteilte Daten modifizieren. +Dazu kommt das Problem, dass ein Thread in einer komplexen Folge von Aktionen die +Zeitscheibe verlieren kann und dann später mit veralteten Daten weiter macht. + +Um den Zugriff auf gemeinsame Ressourcen oder den Eintritt in kritische Bereiche zu +schützen und zu synchronisieren, kann man diese Zugriffe oder Bereiche in einen +`synchronized`-Block legen. Dazu benötigt man noch ein beliebiges (gemeinsam +sichtbares) Objekt, welches als Wächter- oder Sperr-Objekt fungiert. Beim Eintritt +in den geschützten Block muss ein Thread einen Lock auf dem Sperr-Objekt erlangen. +Hat bereits ein anderer Thread den Lock, wird der neue Thread so lange blockiert, +bis der Lock wieder "frei" ist. Beim Eintritt in den Bereich wird dann durch den +Thread auf dem Sperr-Objekt der Lock gesetzt und beim Austritt automatisch wieder +aufgehoben. Dies nennt man auch **mehrseitige Synchronisierung** (mehrere Threads +"stimmen" sich quasi untereinander über den Zugriff auf eine Ressource ab). + +Um auf den Eintritt eines Ereignisses oder die Erfüllung einer Bedingung zu warten, +kann man `wait` und `notify` nutzen. In einem `synchronized`-Block prüft man, ob die +Bedingung erfüllt oder ein Ereignis eingetreten ist, und falls ja arbeitet man damit +normal weiter. Falls die Bedingung nicht erfüllt ist oder das Ereignis nicht +eingetreten ist, kann man auf dem im `synchronized`-Block genutzten Sperr-Objekt die +Methode `wait()` aufrufen. Damit wird der Thread in die entsprechende Schlange auf +dem Sperr-Objekt eingereiht und blockiert. Zusätzlich wird der Lock auf dem +Sperr-Objekt freigegeben. Zum "Aufwecken" nutzt man an geeigneter Stelle auf dem +**selben Sperr-Objekt** die Methode `notify()` oder `notifyALl()` (erstere weckt +einen in der Liste des Sperr-Objekts wartenden Thread, die letztere alle). Nach dem +Aufwachen macht der Thread nach seinem `wait()` weiter. Es ist also wichtig, dass +die Bedingung, wegen der ursprünglich das `wait()` aufgerufen wurde, erneut +abgefragt wird und ggf. erneut in das `wait()` gegangen wird. Dies nennt man +**einseitige Synchronisierung**. + +Es gibt darüber hinaus viele weitere Mechanismen und Probleme, die aber den Rahmen +dieser Lehrveranstaltung deutlich übersteigen. Diese werden teilweise in den +Veranstaltungen "Betriebssysteme" und/oder "Verteilte Systeme" besprochen. +::: + +::: youtube +- [VL Threads Synchronisation](https://youtu.be/FtVaobn4NqA) +- [Demo Teaser: Falscher Zugriff auf gemeinsame + Ressourcen](https://youtu.be/SB1ngVkQdLM) +- [Demo Mehrseitige Synchronisation (Sperr-Objekt, synchronisierte + Methode)](https://youtu.be/YTV-oT-vmpE) +- [Demo Mehrseitige Synchronisation: Deadlock](https://youtu.be/D4B5xHqCZ-0) +- [Demo Einseitige Synchronisation](https://youtu.be/akCl01ZAaGo) +::: # Motivation: Verteilter Zugriff auf gemeinsame Ressourcen -```java +``` java public class Teaser implements Runnable { private int val = 0; @@ -134,12 +73,12 @@ public class Teaser implements Runnable { } ``` -[Demo: synchronised.Teaser]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/synchronised/Teaser.java"} - +[Demo: synchronised.Teaser]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/synchronised/Teaser.java"} # Zugriff auf gemeinsame Ressourcen: Mehrseitige Synchronisierung -```java +``` java synchronized () { } @@ -147,24 +86,21 @@ synchronized () { \bigskip -=> **"Mehrseitige Synchronisierung"** +=\> **"Mehrseitige Synchronisierung"** ::: notes Fallunterscheidung: Thread T1 führt `synchronized`-Anweisung aus: -* Sperre im Sperr-Objekt nicht gesetzt: +- Sperre im Sperr-Objekt nicht gesetzt: 1. T1 setzt Sperre beim Eintritt, 2. führt den Block aus, und 3. löst Sperre beim Verlassen - -* Sperre durch T1 gesetzt: +- Sperre durch T1 gesetzt: 1. T1 führt den Block aus, und 2. löst Sperre beim Verlassen **nicht** +- Sperre durch T2 gesetzt: =\> T1 wird blockiert, bis T2 die Sperre löst -* Sperre durch T2 gesetzt: - => T1 wird blockiert, bis T2 die Sperre löst - -_Anmerkung_: Das für die Synchronisierung genutzte Objekt nennt man "Wächter-Objekt" +*Anmerkung*: Das für die Synchronisierung genutzte Objekt nennt man "Wächter-Objekt" oder auch "Sperr-Objekt" oder auch "Synchronisations-Objekt". ::: @@ -173,23 +109,24 @@ oder auch "Sperr-Objekt" oder auch "Synchronisations-Objekt". ::: notes Damit könnte man den relevanten Teil der Methode `incrVal()` beispielsweise in einen -geschützten Bereich einschließen und als Sperr-Objekt das eigene Objekt (`this`) einsetzen: +geschützten Bereich einschließen und als Sperr-Objekt das eigene Objekt (`this`) +einsetzen: ::: -```java +``` java private void incrVal() { synchronized (this) { ++val; } } ``` -[Demo: synchronised.ObjSync]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/synchronised/ObjSync.java"} - +[Demo: synchronised.ObjSync]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/synchronised/ObjSync.java"} # Synchronisierte Methoden -:::::: columns -:::{.column width="40%"} -```java +::::::: columns +::: {.column width="40%"} +``` java void f() { synchronized (this) { ... @@ -197,26 +134,32 @@ void f() { } ``` ::: -:::{.column width="5%"} + +:::: {.column width="5%"} + ::: slides \bigskip \bigskip -=> -::: -[... ist äquivalent zu ...]{.notes} + +=\> ::: -:::{.column width="40%"} -```java + +[... ist äquivalent zu ...]{.notes} +:::: + +::: {.column width="40%"} +``` java synchronized void f() { ... } ``` ::: -:::::: +::::::: ::: notes -Kurzschreibweise: Man spart das separate Wächter-Objekt und synchronisiert auf sich selbst ... +Kurzschreibweise: Man spart das separate Wächter-Objekt und synchronisiert auf sich +selbst ... ::: \pause @@ -226,18 +169,18 @@ Kurzschreibweise: Man spart das separate Wächter-Objekt und synchronisiert auf Die Methode `incrVal()` könnte entsprechend so umgeschrieben werden: ::: -```java +``` java private synchronized void incrVal() { ++val; } ``` -[Demo: synchronised.MethodSync]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/synchronised/MethodSync.java"} - +[Demo: synchronised.MethodSync]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/synchronised/MethodSync.java"} # Probleme bei der (mehrseitigen) Synchronisierung: Deadlocks -```java +``` java public class Deadlock { private final String name; @@ -258,11 +201,11 @@ public class Deadlock { ``` ::: notes -Viel hilft hier nicht viel! Durch zu großzügige mehrseitige Synchronisierung -kann es passieren, dass Threads gegenseitig aufeinander warten: Thread A belegt -eine Ressource, die ein anderer Thread B haben möchte und Thread B belegt eine -Ressource, die A gerne bekommen würde. Da es dann nicht weitergeht, nennt man -diese Situation auch "Deadlock" ("Verklemmung"). +Viel hilft hier nicht viel! Durch zu großzügige mehrseitige Synchronisierung kann es +passieren, dass Threads gegenseitig aufeinander warten: Thread A belegt eine +Ressource, die ein anderer Thread B haben möchte und Thread B belegt eine Ressource, +die A gerne bekommen würde. Da es dann nicht weitergeht, nennt man diese Situation +auch "Deadlock" ("Verklemmung"). Im Beispiel ruft der erste Thread für das Objekt `a` die `foo()`-Methode auf und holt sich damit den Lock auf `a`. Um die Methode beenden zu können, muss noch die @@ -280,37 +223,36 @@ holen, den aber aktuell der erste Thread hält. Und schon geht's nicht mehr weiter :-) ::: -[Demo: synchronised.Deadlock]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/synchronised/Deadlock.java"} - +[Demo: synchronised.Deadlock]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/synchronised/Deadlock.java"} # Warten auf andere Threads: Einseitige Synchronisierung ## Problem -* Thread T1 wartet auf Arbeitsergebnis von T2 -* T2 ist noch nicht fertig +- Thread T1 wartet auf Arbeitsergebnis von T2 +- T2 ist noch nicht fertig \bigskip ## Mögliche Lösungen 1. Aktives Warten (Polling): Permanente Abfrage - * Kostet unnötig Rechenzeit + - Kostet unnötig Rechenzeit 2. Schlafen mit `Thread.sleep()` - * Etwas besser; aber wie lange soll man idealerweise schlafen? + - Etwas besser; aber wie lange soll man idealerweise schlafen? 3. Warten mit `T2.join()` - * Macht nur Sinn, wenn T1 auf das _Ende_ von T2 wartet + - Macht nur Sinn, wenn T1 auf das *Ende* von T2 wartet 4. **Einseitige Synchronisierung** mit `wait()` und `notify()` - * Das ist DIE Lösung für das Problem :) + - Das ist DIE Lösung für das Problem :) +# Einseitige Synchronisierung mit *wait* und *notify* -# Einseitige Synchronisierung mit _wait_ und _notify_ - -* **wait**: Warten auf Erfüllung einer Bedingung (Thread blockiert): +- **wait**: Warten auf Erfüllung einer Bedingung (Thread blockiert): \bigskip - ```java + ``` java synchronized (obj) { // Geschützten Bereich betreten while (!condition) { try { @@ -323,27 +265,26 @@ Und schon geht's nicht mehr weiter :-) \bigskip - => Bedingung nach Rückkehr von `wait` erneut prüfen! + =\> Bedingung nach Rückkehr von `wait` erneut prüfen! ::: notes -## Eigenschaften von _wait_ +## Eigenschaften von *wait* -* Thread ruft auf Synchronisations-Objekt die Methode `wait` auf -* Prozessor wird entzogen, Thread blockiert -* Thread wird in interne Warteschlange des Synchronisations-Objekts eingetragen -* Sperre auf Synchronisations-Objekt wird freigegeben +- Thread ruft auf Synchronisations-Objekt die Methode `wait` auf +- Prozessor wird entzogen, Thread blockiert +- Thread wird in interne Warteschlange des Synchronisations-Objekts eingetragen +- Sperre auf Synchronisations-Objekt wird freigegeben -=> Geht nur innerhalb der `synchronized`-Anweisung für das Synchronisations-Objekt! +=\> Geht nur innerhalb der `synchronized`-Anweisung für das Synchronisations-Objekt! ::: +# Einseitige Synchronisierung mit *wait* und *notify* (cnt.) -# Einseitige Synchronisierung mit _wait_ und _notify_ (cnt.) - -* **notify**: Aufwecken von wartenden (blockierten) Threads: +- **notify**: Aufwecken von wartenden (blockierten) Threads: \bigskip - ```java + ``` java synchronized (obj) { obj.notify(); // einen Thread "in" obj aufwecken obj.notifyAll(); // alle Threads "in" obj wecken @@ -351,21 +292,21 @@ Und schon geht's nicht mehr weiter :-) ``` ::: notes -## Eigenschaften von _notify_ bzw. _notifyAll_ +## Eigenschaften von *notify* bzw. *notifyAll* -* Thread ruft auf einem Synchronisations-Objekt die Methode `notify` - oder `notifyAll` auf -* Falls Thread(s) in Warteschlange des Objekts vorhanden, dann - * `notify`: Ein _zufälliger_ Thread wird aus Warteschlange entfernt und - in den Zustand "ausführungsbereit" versetzt - * `notifyAll`: Alle Threads werden aus Warteschlange entfernt und in - den Zustand "ausführungsbereit" versetzt +- Thread ruft auf einem Synchronisations-Objekt die Methode `notify` oder + `notifyAll` auf +- Falls Thread(s) in Warteschlange des Objekts vorhanden, dann + - `notify`: Ein *zufälliger* Thread wird aus Warteschlange entfernt und in den + Zustand "ausführungsbereit" versetzt + - `notifyAll`: Alle Threads werden aus Warteschlange entfernt und in den + Zustand "ausführungsbereit" versetzt -=> Geht nur innerhalb der `synchronized`-Anweisung für das Synchronisations-Objekt! +=\> Geht nur innerhalb der `synchronized`-Anweisung für das Synchronisations-Objekt! ::: -[Demo: synchronised.Staffel]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/synchronised/Staffel.java"} - +[Demo: synchronised.Staffel]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/synchronised/Staffel.java"} # Wrap-Up @@ -374,9 +315,78 @@ Synchronisierungsbedarf bei verteiltem Zugriff auf gemeinsame Ressourcen: \bigskip \smallskip -* Vorsicht mit konkurrierendem Ressourcenzugriff: \newline - Synchronisieren mit `synchronized` => **Mehrseitige Synchronisierung** +- Vorsicht mit konkurrierendem Ressourcenzugriff: `\newline`{=tex} Synchronisieren + mit `synchronized` =\> **Mehrseitige Synchronisierung** \smallskip -* Warten auf Ereignisse mit `wait` und `notify`/`notifyAll` => **Einseitige Synchronisierung** +- Warten auf Ereignisse mit `wait` und `notify`/`notifyAll` =\> **Einseitige + Synchronisierung** + +::: readings +- @Java-SE-Tutorial +- @Boles2008 +::: + +::: outcomes +- k2: Ich kann die Notwendigkeit zur Synchronisation erklären +- k2: Ich kann den Unterschied zwischen einseitiger und mehrseitiger + Synchronisation erklären +- k3: Ich kann die Synchronisation mit synchronized, wait, notify und notifyAll + praktisch einsetzen +::: + +::: challenges +**Hamster-Welt** + +In den +[Vorgaben](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/java-classic/src/challenges/threads) +finden Sie eine Modellierung für eine Hamsterwelt. + +Es gibt rote und blaue Hamster, die sich unabhängig von einander bewegen können. Es +gibt einen Tunnel, den die Hamster betreten und durchqueren können. In der Vorgabe +ist ein kleines Hauptprogramm enthalten, welches einige Hamster anlegt und +herumlaufen lässt. + +**Teil I: Stau im Tunnel** + +Die Hamster sind sehr neugierig und wollen gern durch den Tunnel gehen, um die Höhle +auf der anderen Seite zu erkunden. Leider mussten sie feststellen, dass immer nur +ein Hamster zu einem Zeitpunkt im Tunnel sein darf, sonst wird die Luft zu knapp. + +Ergänzen Sie die Vorgaben, so dass sich immer nur ein paralleler Hamster (egal +welcher Farbe) im Tunnel aufhalten kann. Wenn ein Hamster in den Tunnel will, aber +nicht hinein kann, dann soll er am Eingang warten, also nicht noch einmal in seiner +Höhle herumlaufen. (Das passiert eigentlich automatisch, wenn Sie alles richtig +machen.) + + + +**Teil II: Schlaue Hamster** + +Die Hamster sind schlau und haben bemerkt, dass die Einschränkung aus der letzten +Aufgabe zu stark war. Sie überleben auch, wenn sich beliebig viele blaue Hamster +oder nur genau ein roter Hamster im Tunnel aufhalten. + +Erweitern Sie die Implementierung aus der letzten Aufgabe, so dass folgende +Bedingungen eingehalten werden: + +- Es dürfen sich beliebig viele blaue Hamster gleichzeitig im Tunnel befinden. + + Das bedeutet, dass in diesem Fall zwar weitere blaue Hamster den Tunnel betreten + dürfen, aber kein roter Hamster in den Tunnel hinein darf. + +- Wenn sich ein roter Hamster im Tunnel aufhält, dürfen keine anderen Hamster + (unabhängig von deren Farbe) den Tunnel betreten. + + + +::: diff --git a/lecture/java-modern/defaultmethods.md b/lecture/java-modern/defaultmethods.md index 1bef88846..f22fc0d92 100644 --- a/lecture/java-modern/defaultmethods.md +++ b/lecture/java-modern/defaultmethods.md @@ -1,77 +1,43 @@ --- +author: Carsten Gips (HSBI) title: "Interfaces: Default-Methoden" -author: "Carsten Gips (HSBI)" -readings: - - "@Java-SE-Tutorial" - - "@Urma2014 [Kap. 9]" -tldr: | - Seit Java8 können Methoden in Interfaces auch fertig implementiert sein: Sogenannte - **Default-Methoden**. - - Dazu werden die Methoden mit dem neuen Schlüsselwort `default` gekennzeichnet. Die - Implementierung wird an die das Interface implementierenden Klassen (oder Interfaces) - vererbt und kann bei Bedarf überschrieben werden. - - Da eine Klasse von einer anderen Klasse erben darf, aber mehrere Interfaces implementieren - kann, könnte es zu einer Mehrfachvererbung einer Methode kommen: Eine Methode könnte - beispielsweise in verschiedenen Interfaces als Default-Methode angeboten werden, und wenn - eine Klasse diese Interfaces implementiert, steht eine Methode mit der selben Signatur - auf einmal mehrfach zur Verfügung. Dies muss (u.U. manuell) aufgelöst werden. - - Auflösung von Mehrfachvererbung: - * Regel 1: Klassen gewinnen - * Regel 2: Sub-Interfaces gewinnen - * Regel 3: Methode explizit auswählen - - Aktuell ist der Unterschied zu abstrakten Klassen: Interfaces können **keinen Zustand** - haben, d.h. keine Attribute/Felder. -outcomes: - - k2: "Ich weiss, dass in Interfaces Default-Methoden erstellt werden können" - - k2: "Ich kann den Unterschied zwischen Interfaces mit Default-Methoden und abstrakten Klassen erklären" - - k2: "Ich verstehe das Problem der Mehrfachvererbung bei Interfaces mit Default-Methoden" - - k3: "Ich kann Interfaces mit Default-Methoden erstellen und einsetzen" - - k3: "Ich habe die Regeln zum Auflösen der Mehrfachvererbung verstanden und kann sie in der Praxis nutzen" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106522&client_id=FH-Bielefeld" -# name: "Quiz Default-Methoden (ILIAS)" -youtube: - - link: "https://youtu.be/qQ8BPkL9X5o" - name: "VL Default-Methoden" - - link: "https://youtu.be/gm6ttKlAEJc" - name: "Demo Regel 1" - - link: "https://youtu.be/3j9i7iMVmMM" - name: "Demo Regel 2" - - link: "https://youtu.be/J3gJnwz8Rf0" - name: "Demo Regel 3" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/a86bc2fc9d692f6ca307cfabd09c7860c06b8fd53b60e281586c8b51414b7139d061328b2160a988045952f92fc13a921b6148c0dd2f790ea7324634c491006e" - name: "VL Default-Methoden" -challenges: | - Erklären Sie die Code-Schnipsel in der - [Vorgabe](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/java-modern/src/challenges/defaults) - und die jeweils entstehenden Ausgaben. - - --- +::: tldr +Seit Java8 können Methoden in Interfaces auch fertig implementiert sein: Sogenannte +**Default-Methoden**. + +Dazu werden die Methoden mit dem neuen Schlüsselwort `default` gekennzeichnet. Die +Implementierung wird an die das Interface implementierenden Klassen (oder +Interfaces) vererbt und kann bei Bedarf überschrieben werden. + +Da eine Klasse von einer anderen Klasse erben darf, aber mehrere Interfaces +implementieren kann, könnte es zu einer Mehrfachvererbung einer Methode kommen: Eine +Methode könnte beispielsweise in verschiedenen Interfaces als Default-Methode +angeboten werden, und wenn eine Klasse diese Interfaces implementiert, steht eine +Methode mit der selben Signatur auf einmal mehrfach zur Verfügung. Dies muss (u.U. +manuell) aufgelöst werden. + +Auflösung von Mehrfachvererbung: + +- Regel 1: Klassen gewinnen +- Regel 2: Sub-Interfaces gewinnen +- Regel 3: Methode explizit auswählen + +Aktuell ist der Unterschied zu abstrakten Klassen: Interfaces können **keinen +Zustand** haben, d.h. keine Attribute/Felder. +::: + +::: youtube +- [VL Default-Methoden](https://youtu.be/qQ8BPkL9X5o) +- [Demo Regel 1](https://youtu.be/gm6ttKlAEJc) +- [Demo Regel 2](https://youtu.be/3j9i7iMVmMM) +- [Demo Regel 3](https://youtu.be/J3gJnwz8Rf0) +::: # Problem: Etablierte API (Interfaces) erweitern -```java +``` java interface Klausur { void anmelden(Studi s); void abmelden(Studi s); @@ -81,24 +47,23 @@ interface Klausur { \bigskip \pause -=> Nachträglich noch `void schreiben(Studi s);` ergänzen? +=\> Nachträglich noch `void schreiben(Studi s);` ergänzen? ::: notes -Wenn ein Interface nachträglich erweitert wird, müssen alle Kunden (also -alle Klassen, die das Interface implementieren) auf die neuen Signaturen -angepasst werden. Dies kann viel Aufwand verursachen und API-Änderungen -damit unmöglich machen. +Wenn ein Interface nachträglich erweitert wird, müssen alle Kunden (also alle +Klassen, die das Interface implementieren) auf die neuen Signaturen angepasst +werden. Dies kann viel Aufwand verursachen und API-Änderungen damit unmöglich +machen. ::: - # Default-Methoden: Interfaces mit Implementierung ::: notes -Seit Java8 können Interfaces auch Methoden implementieren. -Es gibt zwei Varianten: Default-Methoden und statische Methoden. +Seit Java8 können Interfaces auch Methoden implementieren. Es gibt zwei Varianten: +Default-Methoden und statische Methoden. ::: -```java +``` java interface Klausur { void anmelden(Studi s); void abmelden(Studi s); @@ -116,38 +81,37 @@ interface Klausur { ::: notes Methoden können in Interfaces seit Java8 implementiert werden. Für Default-Methoden muss das Schlüsselwort `default` vor die Signatur gesetzt werden. Klassen, die das -Interface implementieren, können diese Default-Implementierung erben oder selbst -neu implementieren (überschreiben). Alternativ kann die Klasse eine Default-Methode -neu _deklarieren_ und wird damit zur abstrakten Klasse. +Interface implementieren, können diese Default-Implementierung erben oder selbst neu +implementieren (überschreiben). Alternativ kann die Klasse eine Default-Methode neu +*deklarieren* und wird damit zur abstrakten Klasse. Dies ähnelt abstrakten Klassen. Allerdings kann in abstrakten Klassen neben dem -Verhalten (implementierten Methoden) auch Zustand über die Attribute gespeichert werden. +Verhalten (implementierten Methoden) auch Zustand über die Attribute gespeichert +werden. ::: - ::: notes # Problem: Mehrfachvererbung Drei Regeln zum Auflösen bei Konflikten: -1. **Klassen gewinnen**: - Methoden aus Klasse oder Superklasse haben höhere Priorität als Default-Methoden -2. **Sub-Interfaces gewinnen**: - Methode aus am meisten spezialisiertem Interface mit Default-Methode wird gewählt - Beispiel: Wenn `B extends A` dann ist `B` spezialisierter als `A` -3. Sonst: Klasse muss **Methode explizit auswählen**: - Methode überschreiben und gewünschte (geerbte) Variante aufrufen: - `X.super.m(...)` (`X` ist das gewünschte Interface) +1. **Klassen gewinnen**: Methoden aus Klasse oder Superklasse haben höhere + Priorität als Default-Methoden +2. **Sub-Interfaces gewinnen**: Methode aus am meisten spezialisiertem Interface + mit Default-Methode wird gewählt Beispiel: Wenn `B extends A` dann ist `B` + spezialisierter als `A` +3. Sonst: Klasse muss **Methode explizit auswählen**: Methode überschreiben und + gewünschte (geerbte) Variante aufrufen: `X.super.m(...)` (`X` ist das gewünschte + Interface) Auf den folgenden Folien wird dies anhand kleiner Beispiele verdeutlicht. ::: [[Hinweis: Mehrfachvererbung]{.ex}]{.slides} - # Auflösung Mehrfachvererbung: 1. Klassen gewinnen -```java +``` java interface A { default String hello() { return "A"; } } @@ -165,20 +129,21 @@ public class DefaultTest1 { } ``` -[Demo: defaultmethods.rule1.DefaultTest1]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/defaultmethods/rule1/DefaultTest1.java"} +[Demo: defaultmethods.rule1.DefaultTest1]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/defaultmethods/rule1/DefaultTest1.java"} ::: notes -Die Klasse `E` erbt sowohl von Klasse `C` als auch vom Interface `A` die Methode `hello()` -(Mehrfachvererbung). In diesem Fall "gewinnt" die Implementierung aus Klasse `C`. +Die Klasse `E` erbt sowohl von Klasse `C` als auch vom Interface `A` die Methode +`hello()` (Mehrfachvererbung). In diesem Fall "gewinnt" die Implementierung aus +Klasse `C`. -**1. Regel**: Klassen gewinnen immer. Deklarationen einer Methode in einer Klasse oder -einer Oberklasse haben Vorrang von allen Default-Methoden. +**1. Regel**: Klassen gewinnen immer. Deklarationen einer Methode in einer Klasse +oder einer Oberklasse haben Vorrang von allen Default-Methoden. ::: - # Auflösung Mehrfachvererbung: 2. Sub-Interfaces gewinnen -```java +``` java interface A { default String hello() { return "A"; } } @@ -196,21 +161,21 @@ public class DefaultTest2 { } ``` -[Demo: defaultmethods.rule2.DefaultTest2]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/defaultmethods/rule2/DefaultTest2.java"} +[Demo: defaultmethods.rule2.DefaultTest2]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/defaultmethods/rule2/DefaultTest2.java"} ::: notes -Die Klasse `D` erbt sowohl vom Interface `A` als auch vom Interface `B` die Methode `hello()` -(Mehrfachvererbung). In diesem Fall "gewinnt" die Implementierung aus Klasse `B`: Interface -`B` ist spezialisierter als `A`. +Die Klasse `D` erbt sowohl vom Interface `A` als auch vom Interface `B` die Methode +`hello()` (Mehrfachvererbung). In diesem Fall "gewinnt" die Implementierung aus +Klasse `B`: Interface `B` ist spezialisierter als `A`. -**2. Regel**: Falls Regel 1 nicht zutrifft, gewinnt die Default-Methode, die am meisten -spezialisiert ist. +**2. Regel**: Falls Regel 1 nicht zutrifft, gewinnt die Default-Methode, die am +meisten spezialisiert ist. ::: - # Auflösung Mehrfachvererbung: 3. Methode explizit auswählen -```java +``` java interface A { default String hello() { return "A"; } } @@ -230,25 +195,27 @@ public class DefaultTest3 { } ``` -[Demo: defaultmethods.rule3.DefaultTest3]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/defaultmethods/rule3/DefaultTest3.java"} +[Demo: defaultmethods.rule3.DefaultTest3]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/defaultmethods/rule3/DefaultTest3.java"} ::: notes -Die Klasse `D` erbt sowohl vom Interface `A` als auch vom Interface `B` die Methode `hello()` -(Mehrfachvererbung). In diesem Fall _muss_ zur Auflösung die Methode in `D` neu implementiert -werden und die gewünschte geerbte Methode explizit aufgerufen werden. (Wenn dies unterlassen -wird, führt das selbst bei Nicht-Nutzung der Methode `hello()` zu einem Compiler-Fehler!) - -*Achtung*: Der Aufruf der Default-Methode aus Interface `A` erfolgt mit `A.super.hello();` -(nicht einfach durch `A.hello();`)! - -**3. Regel**: Falls weder Regel 1 noch 2 zutreffen bzw. die Auflösung noch uneindeutig ist, -muss man manuell durch die explizite Angabe der gewünschten Methode auflösen. +Die Klasse `D` erbt sowohl vom Interface `A` als auch vom Interface `B` die Methode +`hello()` (Mehrfachvererbung). In diesem Fall *muss* zur Auflösung die Methode in +`D` neu implementiert werden und die gewünschte geerbte Methode explizit aufgerufen +werden. (Wenn dies unterlassen wird, führt das selbst bei Nicht-Nutzung der Methode +`hello()` zu einem Compiler-Fehler!) + +*Achtung*: Der Aufruf der Default-Methode aus Interface `A` erfolgt mit +`A.super.hello();` (nicht einfach durch `A.hello();`)! + +**3. Regel**: Falls weder Regel 1 noch 2 zutreffen bzw. die Auflösung noch +uneindeutig ist, muss man manuell durch die explizite Angabe der gewünschten Methode +auflösen. ::: - # Quiz: Was kommt hier raus? -```java +``` java interface A { default String hello() { return "A"; } } @@ -270,18 +237,18 @@ public class DefaultTest { ``` ::: notes -Die Klasse `D` erbt sowohl von Klasse `C` als auch von den Interfaces `A` und `B` die Methode -`hello()` (Mehrfachvererbung). In diesem Fall "gewinnt" die Implementierung aus Klasse `C`: Klassen -gewinnen immer (Regel 1). +Die Klasse `D` erbt sowohl von Klasse `C` als auch von den Interfaces `A` und `B` +die Methode `hello()` (Mehrfachvererbung). In diesem Fall "gewinnt" die +Implementierung aus Klasse `C`: Klassen gewinnen immer (Regel 1). -[Beispiel: defaultmethods.quiz.DefaultTest]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/defaultmethods/quiz/DefaultTest.java"} +[Beispiel: defaultmethods.quiz.DefaultTest]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/defaultmethods/quiz/DefaultTest.java"} ::: - ::: notes # Statische Methoden in Interfaces -```java +``` java public interface Collection extends Iterable { boolean add(E e); ... @@ -293,18 +260,20 @@ public class Collections { } ``` -Typisches Pattern in Java: Interface plus Utility-Klasse (Companion-Klasse) mit statischen Hilfsmethoden -zum einfacheren Umgang mit Instanzen des Interfaces (mit Objekten, deren Klasse das Interface implementiert). -Beispiel: `Collections` ist eine Hilfs-Klasse zum Umgang mit `Collection`-Objekten. +Typisches Pattern in Java: Interface plus Utility-Klasse (Companion-Klasse) mit +statischen Hilfsmethoden zum einfacheren Umgang mit Instanzen des Interfaces (mit +Objekten, deren Klasse das Interface implementiert). Beispiel: `Collections` ist +eine Hilfs-Klasse zum Umgang mit `Collection`-Objekten. -Seit Java8 können in Interfaces neben Default-Methoden auch statische Methoden implementiert werden. +Seit Java8 können in Interfaces neben Default-Methoden auch statische Methoden +implementiert werden. +Die Hilfsmethoden können jetzt ins Interface wandern =\> Utility-Klassen werden +obsolet ... Aus Kompatibilitätsgründen würde man die bisherige Companion-Klasse +weiterhin anbieten, wobei die Implementierungen auf die statischen Methoden im +Interface verweisen (*SKIZZE, nicht real!*): -Die Hilfsmethoden können jetzt ins Interface wandern => Utility-Klassen werden obsolet ... Aus -Kompatibilitätsgründen würde man die bisherige Companion-Klasse weiterhin anbieten, wobei die Implementierungen -auf die statischen Methoden im Interface verweisen (*SKIZZE, nicht real!*): - -```java +``` java public interface CollectionX extends Iterable { boolean add(E e); static boolean addAll(CollectionX c, T... elements) { ... } @@ -319,14 +288,14 @@ public class CollectionsX { ``` ::: +# Interfaces vs. Abstrakte Klassen -# Interfaces vs. Abstrakte Klassen +- **Abstrakte Klassen**: Schnittstelle und Verhalten und Zustand -* **Abstrakte Klassen**: Schnittstelle und Verhalten und Zustand +- **Interfaces**: -* **Interfaces**: - * vor Java 8 nur Schnittstelle - * ab Java 8 Schnittstelle und Verhalten + - vor Java 8 nur Schnittstelle + - ab Java 8 Schnittstelle und Verhalten ::: notes Unterschied zu abstrakten Klassen: Kein Zustand, d.h. keine Attribute @@ -334,11 +303,10 @@ public class CollectionsX { \bigskip -* Design: - * Interfaces sind beinahe wie abstrakte Klassen, nur ohne Zustand - * Klassen können nur von **einer** (abstrakten) Klasse erben, aber - **viele** Interfaces implementieren - +- Design: + - Interfaces sind beinahe wie abstrakte Klassen, nur ohne Zustand + - Klassen können nur von **einer** (abstrakten) Klasse erben, aber **viele** + Interfaces implementieren # Wrap-Up @@ -346,10 +314,49 @@ Seit Java8: Interfaces mit Implementierung: **Default-Methoden** \bigskip -* Methoden mit dem Schlüsselwort `default` können Implementierung im Interface haben -* Die Implementierung wird vererbt und kann bei Bedarf überschrieben werden -* Auflösung von Mehrfachvererbung: - * Regel 1: Klassen gewinnen - * Regel 2: Sub-Interfaces gewinnen - * Regel 3: Methode explizit auswählen -* Unterschied zu abstrakten Klassen: **Kein Zustand** +- Methoden mit dem Schlüsselwort `default` können Implementierung im Interface + haben +- Die Implementierung wird vererbt und kann bei Bedarf überschrieben werden +- Auflösung von Mehrfachvererbung: + - Regel 1: Klassen gewinnen + - Regel 2: Sub-Interfaces gewinnen + - Regel 3: Methode explizit auswählen +- Unterschied zu abstrakten Klassen: **Kein Zustand** + +::: readings +- @Java-SE-Tutorial +- @Urma2014 [Kap. 9] +::: + +::: outcomes +- k2: Ich weiss, dass in Interfaces Default-Methoden erstellt werden können +- k2: Ich kann den Unterschied zwischen Interfaces mit Default-Methoden und + abstrakten Klassen erklären +- k2: Ich verstehe das Problem der Mehrfachvererbung bei Interfaces mit + Default-Methoden +- k3: Ich kann Interfaces mit Default-Methoden erstellen und einsetzen +- k3: Ich habe die Regeln zum Auflösen der Mehrfachvererbung verstanden und kann + sie in der Praxis nutzen +::: + +::: challenges +Erklären Sie die Code-Schnipsel in der +[Vorgabe](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/java-modern/src/challenges/defaults) +und die jeweils entstehenden Ausgaben. + + +::: diff --git a/lecture/java-modern/lambdas.md b/lecture/java-modern/lambdas.md index bb5926525..1e634a532 100644 --- a/lecture/java-modern/lambdas.md +++ b/lecture/java-modern/lambdas.md @@ -1,242 +1,53 @@ --- -title: "Lambda-Ausdrücke und funktionale Interfaces" -author: "Carsten Gips (HSBI)" -readings: - - "@Java-SE-Tutorial" - - "@Urma2014 [Kap. 3]" - - "@Ullenboom2021 [Kap. 12]" -tldr: | - Mit einer anonymen inneren Klasse erstellt man gewissermaßen ein Objekt einer "Wegwerf"-Klasse: - Man leitet _on-the-fly_ von einem Interface ab oder erweitert eine Klasse und implementiert die - benötigten Methoden und erzeugt von dieser Klasse sofort eine Instanz (Objekt). Diese neue Klasse - ist im restlichen Code nicht sichtbar. - - Anonyme innere Klassen sind beispielsweise in Swing recht nützlich, wenn man einer Komponente einen - Listener mitgeben will: Hier erzeugt man eine anonyme innere Klasse basierend auf dem passenden - Listener-Interface, implementiert die entsprechenden Methoden und übergibt das mit dieser Klasse - erzeugte Objekt als neuen Listener der Swing-Komponente. - - Mit Java 8 können unter gewissen Bedingungen diese anonymen inneren Klassen zu Lambda-Ausdrücken - (und Methoden-Referenzen) vereinfacht werden. Dazu muss die anonyme innere Klasse ein sogenanntes - **funktionales Interface** implementieren. - - Funktionale Interfaces sind Interfaces mit _genau einer abstrakten Methode_. Es können beliebig - viele Default-Methoden im Interface enthalten sein, und es können `public` sichtbare abstrakte - Methoden von `java.lang.Object` geerbt/überschrieben werden. - - Die Lambda-Ausdrücke entsprechen einer anonymen Methode: Die Parameter werden aufgelistet (in - Klammern), und hinter einem Pfeil kommt entweder _ein_ Ausdruck (Wert - gleichzeitig Rückgabewert - des Lambda-Ausdrucks) oder beliebig viele Anweisungen (in geschweiften Klammern, mit Semikolon): - * Form 1: `(parameters) -> expression` - * Form 2: `(parameters) -> { statements; }` - - Der Lambda-Ausdruck muss von der Signatur her genau der einen abstrakten Methode im unterliegenden - funktionalen Interface entsprechen. -outcomes: - - k2: "Ich kenne die Definition 'Funktionales Interface'" - - k3: "Ich kann innere und anonyme Klassen praktisch einsetzen" - - k3: "Ich kann eigene funktionale Interfaces erstellen" - - k3: "Ich kann Lambda-Ausdrücke formulieren und einsetzen" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106523&client_id=FH-Bielefeld" -# name: "Quiz Lambda-Ausdrücke und funktionale Interfaces (ILIAS)" -youtube: - - link: "https://youtu.be/Wd8KG7xtp4c" - name: "VL Lambda-Ausdrücke und funktionale Interfaces" - - link: "https://youtu.be/QEXpQwRYoYc" - name: "Demo Anonyme innere Klasse" - - link: "https://youtu.be/2LJIxsVw4pM" - name: "Demo Lambda-Ausdruck" - - link: "https://youtu.be/93O1oDL5_5c" - name: "Demo Funktionale Interfaces selbst definiert" - - link: "https://youtu.be/jzEw8IH8Mfc" - name: "Demo Vordefinierte funktionale Interfaces im JDK" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/d2398cc8e1279e6b6bf1df06bd30b731e714d04d10e15b87a5f89aa07cbaf97978bb12f82ea0c7eff8a3133eb65134521933218fb94856fb6e8a6dc187dded28" - name: "VL Lambda-Ausdrücke und funktionale Interfaces" -challenges: | - **Beispiel aus einem Code-Review im [Dungeon-CampusMinden/Dungeon](https://github.com/Dungeon-CampusMinden/Dungeon)** - - Erklären Sie folgenden Code: - - ```java - public interface IFightAI { - void fight(Entity entity); - } - - public class AIComponent extends Component { - private final IFightAI fightAI; - - fightAI = - entity1 -> { - System.out.println("TIME TO FIGHT!"); - // todo replace with melee skill - }; - } - ``` - - - - - **Spielen mit Lambdas** - - Sie finden in einem Spiel folgenden Code: - - ```java - public class Main { - public static void main(String[] args) { - DoorTile door = new DoorTile(); - Entity lever1 = new Entity(), lever2 = new Entity(), lever3 = new Entity(); - - // ganz viel Code - - if (!door.isOpen() && (lever1.isOn() && (lever2.isOn() || lever3.isOn()))) door.open(); - - // ganz viel Code - } - } - - class DoorTile { - public boolean isOpen() { return false; } - public void open() { } - } - class Entity { - public boolean isOn() { return false; } - } - ``` - - Dabei stört, dass die Verknüpfung der konkreten Objekte und Zustände zum Öffnen der konkreten - Tür fest (und zudem mitten) im Programm hinterlegt ist. - - Schreiben Sie diesen Code um: Definieren Sie eine statische Hilfsmethode, die ein Door-Tile - und drei Entitäten als Argument entgegen nimmt und dafür einen Lambda-Ausdruck zurückliefert, - mit dem (a) die gezeigte Bedingung überprüft werden kann, und mit dem (falls die Bedingung - erfüllt ist) (b) die Aktion (`door.open()`) ausgeführt werden kann. Statt der gezeigten fest - codierten `if`-Abfrage soll dieser Lambda-Ausdruck ausgewertet werden: `doorhandle.test().accept();`. - - Damit haben Sie sich eine "Factory-Method" geschrieben (Entwurfsmuster), mit der diese Bedingung - dynamisch erzeugt werden kann (auch für andere Objekte). - - Hinweis: Der Lambda-Ausdruck wird "zweistufig" sein müssen ... - - - - - **Sortieren mit Lambdas und funktionalen Interfaces** - - Betrachten Sie die Klasse [Student](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/challenges/lambda/Student.java). - - 1. Definieren Sie eine Methode, die das Sortieren einer `Student`-Liste erlaubt. Übergeben Sie die Liste als Parameter. - 2. Schaffen Sie es, das Sortierkriterium ebenfalls als Parameter zu übergeben (als Lambda-Ausdruck)? - 3. Definieren Sie eine weitere Methode, die wieder eine `Student`-Liste als Parameter bekommt und liefern sie das erste - `Student`-Objekt zurück, welches einer als Lambda-Ausdruck übergebenen Bedingung genügt. - 4. Definieren Sie noch eine Methode, die wieder eine `Student`-Liste als Parameter bekommt sowie einen Lambda-Ausdruck, - welcher aus einem `Student`-Objekt ein Objekt eines anderen Typen `T` berechnet. Wenden Sie in der Methode den - Lambda-Ausdruck auf jedes Objekt der Liste an und geben sie die resultierende neue Liste als Ergebnis zurück. - - Verwenden Sie in dieser Aufgabe jeweils Lambda-Ausdrücke. Rufen Sie alle drei/vier Methoden an einem kleinen Beispiel auf. - - +author: Carsten Gips (HSBI) +title: Lambda-Ausdrücke und funktionale Interfaces --- +::: tldr +Mit einer anonymen inneren Klasse erstellt man gewissermaßen ein Objekt einer +"Wegwerf"-Klasse: Man leitet *on-the-fly* von einem Interface ab oder erweitert eine +Klasse und implementiert die benötigten Methoden und erzeugt von dieser Klasse +sofort eine Instanz (Objekt). Diese neue Klasse ist im restlichen Code nicht +sichtbar. + +Anonyme innere Klassen sind beispielsweise in Swing recht nützlich, wenn man einer +Komponente einen Listener mitgeben will: Hier erzeugt man eine anonyme innere Klasse +basierend auf dem passenden Listener-Interface, implementiert die entsprechenden +Methoden und übergibt das mit dieser Klasse erzeugte Objekt als neuen Listener der +Swing-Komponente. + +Mit Java 8 können unter gewissen Bedingungen diese anonymen inneren Klassen zu +Lambda-Ausdrücken (und Methoden-Referenzen) vereinfacht werden. Dazu muss die +anonyme innere Klasse ein sogenanntes **funktionales Interface** implementieren. + +Funktionale Interfaces sind Interfaces mit *genau einer abstrakten Methode*. Es +können beliebig viele Default-Methoden im Interface enthalten sein, und es können +`public` sichtbare abstrakte Methoden von `java.lang.Object` geerbt/überschrieben +werden. + +Die Lambda-Ausdrücke entsprechen einer anonymen Methode: Die Parameter werden +aufgelistet (in Klammern), und hinter einem Pfeil kommt entweder *ein* Ausdruck +(Wert - gleichzeitig Rückgabewert des Lambda-Ausdrucks) oder beliebig viele +Anweisungen (in geschweiften Klammern, mit Semikolon): + +- Form 1: `(parameters) -> expression` +- Form 2: `(parameters) -> { statements; }` + +Der Lambda-Ausdruck muss von der Signatur her genau der einen abstrakten Methode im +unterliegenden funktionalen Interface entsprechen. +::: + +::: youtube +- [VL Lambda-Ausdrücke und funktionale Interfaces](https://youtu.be/Wd8KG7xtp4c) +- [Demo Anonyme innere Klasse](https://youtu.be/QEXpQwRYoYc) +- [Demo Lambda-Ausdruck](https://youtu.be/2LJIxsVw4pM) +- [Demo Funktionale Interfaces selbst definiert](https://youtu.be/93O1oDL5_5c) +- [Demo Vordefinierte funktionale Interfaces im JDK](https://youtu.be/jzEw8IH8Mfc) +::: # Problem: Sortieren einer Studi-Liste -```java +``` java List sl = new ArrayList<>(); // Liste sortieren? @@ -246,15 +57,15 @@ sl.sort(???); // Parameter: java.util.Comparator \pause \bigskip -```java +``` java public class MyCompare implements Comparator { @Override public int compare(Studi o1, Studi o2) { return o1.getCredits() - o2.getCredits(); } } -```` +``` -```java +``` java // Liste sortieren? MyCompare mc = new MyCompare(); sl.sort(mc); @@ -267,43 +78,43 @@ erzeugen und dieses dann der `sort()`-Methode übergeben. Die Klasse bekommt wie in Java üblich eine eigene Datei und ist damit in der Package-Struktur offen sichtbar und "verstopft" mir damit die Strukturen: Diese -Klasse ist doch nur eine Hilfsklasse ... Noch schlimmer: Ich brauche einen Namen -für diese Klasse! +Klasse ist doch nur eine Hilfsklasse ... Noch schlimmer: Ich brauche einen Namen für +diese Klasse! -Den ersten Punkt könnte man über verschachtelte Klassen lösen: Die Hilfsklasse -wird innerhalb der Klasse definiert, die das Objekt benötigt. Für den zweiten -Punkt brauchen wir mehr Anlauf ... +Den ersten Punkt könnte man über verschachtelte Klassen lösen: Die Hilfsklasse wird +innerhalb der Klasse definiert, die das Objekt benötigt. Für den zweiten Punkt +brauchen wir mehr Anlauf ... ::: - -::::::::: notes -# Erinnerung: Verschachtelte Klassen ("_Nested Classes_") +::: notes +# Erinnerung: Verschachtelte Klassen ("*Nested Classes*") Man kann Klassen innerhalb von Klassen definieren: Verschachtelte Klassen. -* Implizite Referenz auf Instanz der äußeren Klasse, Zugriff auf **alle** Elemente -* **Begriffe**: - * "normale" innere Klassen: "_inner classes_" - * statische innere Klassen: "_static nested classes_" -* Einsatzzweck: - * Hilfsklassen: Zusätzliche Funktionalität kapseln; Nutzung **nur** in äußerer Klasse - * Kapselung von Rückgabewerten +- Implizite Referenz auf Instanz der äußeren Klasse, Zugriff auf **alle** Elemente +- **Begriffe**: + - "normale" innere Klassen: "*inner classes*" + - statische innere Klassen: "*static nested classes*" +- Einsatzzweck: + - Hilfsklassen: Zusätzliche Funktionalität kapseln; Nutzung **nur** in äußerer + Klasse + - Kapselung von Rückgabewerten Sichtbarkeit: Wird u.U. von äußerer Klasse "überstimmt" -## Innere Klassen ("_Inner Classes_") +## Innere Klassen ("*Inner Classes*") -* Objekt der äußeren Klasse muss existieren -* Innere Klasse ist normales Member der äußeren Klasse -* Implizite Referenz auf Instanz äußerer Klasse -* Zugriff auf **alle** Elemente der äußeren Klasse -* Sonderfall: Definition innerhalb von Methoden ("local classes") - * Nur innerhalb der Methode sichtbar - * Kennt zusätzlich `final` Attribute der Methode +- Objekt der äußeren Klasse muss existieren +- Innere Klasse ist normales Member der äußeren Klasse +- Implizite Referenz auf Instanz äußerer Klasse +- Zugriff auf **alle** Elemente der äußeren Klasse +- Sonderfall: Definition innerhalb von Methoden ("local classes") + - Nur innerhalb der Methode sichtbar + - Kennt zusätzlich `final` Attribute der Methode Beispiel: -```java +``` java public class Outer { ... private class Inner { @@ -314,16 +125,17 @@ public class Outer { } ``` -[Beispiel mit Iterator als innere Klasse: nested.StudiListNested]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/nested/StudiListNested.java"} +[Beispiel mit Iterator als innere Klasse: nested.StudiListNested]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/nested/StudiListNested.java"} -## Statische innere Klassen ("_Static Nested Classes_") +## Statische innere Klassen ("*Static Nested Classes*") -* Keine implizite Referenz auf Objekt -* Nur Zugriff auf Klassenmethoden und -attribute +- Keine implizite Referenz auf Objekt +- Nur Zugriff auf Klassenmethoden und -attribute Beispiel: -```java +``` java class Outer { ... static class StaticNested { @@ -333,12 +145,11 @@ class Outer { Outer.StaticNested nested = new Outer.StaticNested(); ``` -::::::::: - +::: # Lösung: Comparator als anonyme innere Klasse -```java +``` java List sl = new ArrayList<>(); // Parametrisierung mit anonymer Klasse @@ -352,24 +163,25 @@ sl.sort( ``` ::: notes -=> Instanz einer anonymen inneren Klasse, die das Interface `Comparator` implementiert - -* Für spezielle, einmalige Aufgabe: nur eine Instanz möglich -* Kein Name, kein Konstruktor, oft nur eine Methode -* Müssen Interface implementieren oder andere Klasse erweitern - * Achtung Schreibweise: ohne `implements` oder `extends`! -* Konstruktor kann auch Parameter aufweisen -* Zugriff auf alle Attribute der äußeren Klasse plus alle `final` lokalen +=\> Instanz einer anonymen inneren Klasse, die das Interface `Comparator` +implementiert + +- Für spezielle, einmalige Aufgabe: nur eine Instanz möglich +- Kein Name, kein Konstruktor, oft nur eine Methode +- Müssen Interface implementieren oder andere Klasse erweitern + - Achtung Schreibweise: ohne `implements` oder `extends`! +- Konstruktor kann auch Parameter aufweisen +- Zugriff auf alle Attribute der äußeren Klasse plus alle `final` lokalen Variablen -* Nutzung typischerweise bei GUIs: Event-Handler etc. +- Nutzung typischerweise bei GUIs: Event-Handler etc. ::: -[Demo: nested.DemoAnonymousInnerClass]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/nested/DemoAnonymousInnerClass.java"} - +[Demo: nested.DemoAnonymousInnerClass]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/nested/DemoAnonymousInnerClass.java"} # Vereinfachung mit Lambda-Ausdruck -```java +``` java List sl = new ArrayList<>(); // Parametrisierung mit anonymer Klasse @@ -390,16 +202,16 @@ sl.sort( (Studi o1, Studi o2) -> o1.getCredits() - o2.getCredits() ); ::: notes **Anmerkung**: Damit für den Parameter alternativ auch ein Lambda-Ausdruck verwendet -werden kann, muss der erwartete Parameter vom Typ her ein "**funktionales Interface**" -(s.u.) sein! +werden kann, muss der erwartete Parameter vom Typ her ein "**funktionales +Interface**" (s.u.) sein! ::: -[Demo: nested.DemoLambda]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/nested/DemoLambda.java"} - +[Demo: nested.DemoLambda]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/nested/DemoLambda.java"} # Syntax für Lambdas -```java +``` java (Studi o1, Studi o2) -> o1.getCredits() - o2.getCredits() ``` @@ -410,11 +222,11 @@ Ein Lambda-Ausdruck ist eine Funktion ohne Namen und besteht aus drei Teilen: 2. Pfeil 3. Funktionskörper (rechte Seite) -Falls es _genau einen_ Parameter gibt, _können_ die runden Klammern um den Parameter +Falls es *genau einen* Parameter gibt, *können* die runden Klammern um den Parameter entfallen. -Dabei kann der Funktionskörper aus _einem Ausdruck_ ("_expression_") bestehen oder -einer _Menge von Anweisungen_ ("_statements"_), die dann in geschweifte Klammern +Dabei kann der Funktionskörper aus *einem Ausdruck* ("*expression*") bestehen oder +einer *Menge von Anweisungen* ("*statements"*), die dann in geschweifte Klammern eingeschlossen werden müssen (Block mit Anweisungen). Der Wert des Ausdrucks ist zugleich der Rückgabewert des Lambda-Ausdrucks. @@ -425,12 +237,11 @@ Der Wert des Ausdrucks ist zugleich der Rückgabewert des Lambda-Ausdrucks. Varianten: -* **`(parameters) -> expression`** +- **`(parameters) -> expression`** \smallskip -* **`(parameters) -> { statements; }`** - +- **`(parameters) -> { statements; }`** # Quiz: Welches sind keine gültigen Lambda-Ausdrücke? @@ -447,21 +258,20 @@ Varianten: 11. `s -> s.getCps() > 100 && s.getCps() < 300` 12. `s -> { return s.getCps() > 100 && s.getCps() < 300; }` -:::::: notes +:::: notes ::: details Auflösung: (4) und (5) -`return` ist eine Anweisung, d.h. bei (4) fehlen die geschweiften -Klammern. `"foo"` ist ein String und als solcher ein Ausdruck, d.h. hier sind -die geschweiften Klammern zu viel (oder man ergänze den String mit einem `return`, -also `return "foo";` ...). +`return` ist eine Anweisung, d.h. bei (4) fehlen die geschweiften Klammern. `"foo"` +ist ein String und als solcher ein Ausdruck, d.h. hier sind die geschweiften +Klammern zu viel (oder man ergänze den String mit einem `return`, also +`return "foo";` ...). ::: -:::::: - +:::: -# Definition "Funktionales Interface" ("_functional interfaces_") +# Definition "Funktionales Interface" ("*functional interfaces*") -```java +``` java @FunctionalInterface public interface Wuppie { int wuppie(T obj); @@ -472,44 +282,44 @@ public interface Wuppie { \bigskip -`Wuppie` ist ein **funktionales Interface** -("_functional interface_") [(seit Java 8)]{.notes} +`Wuppie` ist ein **funktionales Interface** ("*functional interface*") [(seit +Java 8)]{.notes} -* Hat **genau _eine_ abstrakte Methode** -* Hat evtl. weitere Default-Methoden -* Hat evtl. weitere abstrakte Methoden, die `public` Methoden von +- Hat **genau *eine* abstrakte Methode** +- Hat evtl. weitere Default-Methoden +- Hat evtl. weitere abstrakte Methoden, die `public` Methoden von `java.lang.Object` überschreiben ::: notes -Die Annotation `@FunctionalInterface` selbst ist nur für den Compiler: Falls -das Interface _kein_ funktionales Interface ist, würde er beim Vorhandensein -dieser Annotation einen Fehler werfen. Oder anders herum: Allein durch das -Annotieren mit `@FunctionalInterface` wird aus einem Interface noch kein -funktionales Interface! Vergleichbar mit `@Override` ... - -**Während man für eine anonyme Klasse lediglich ein "normales" Interface -(oder eine Klasse) benötigt, braucht man für Lambda-Ausdrücke zwingend ein -passendes funktionales Interface!** - -_Anmerkung_: Es scheint keine einheitliche deutsche Übersetzung für den Begriff -_functional interface_ zu geben. Es wird häufig mit "funktionales Interface", +Die Annotation `@FunctionalInterface` selbst ist nur für den Compiler: Falls das +Interface *kein* funktionales Interface ist, würde er beim Vorhandensein dieser +Annotation einen Fehler werfen. Oder anders herum: Allein durch das Annotieren mit +`@FunctionalInterface` wird aus einem Interface noch kein funktionales Interface! +Vergleichbar mit `@Override` ... + +**Während man für eine anonyme Klasse lediglich ein "normales" Interface (oder eine +Klasse) benötigt, braucht man für Lambda-Ausdrücke zwingend ein passendes +funktionales Interface!** + +*Anmerkung*: Es scheint keine einheitliche deutsche Übersetzung für den Begriff +*functional interface* zu geben. Es wird häufig mit "funktionales Interface", manchmal aber auch mit "Funktionsinterface" übersetzt. -Das in den obigen Beispielen eingesetzte Interface `java.util.Comparator` -ist also ein funktionales Interface: Es hat nur _eine_ eigene abstrakte Methode +Das in den obigen Beispielen eingesetzte Interface `java.util.Comparator` ist +also ein funktionales Interface: Es hat nur *eine* eigene abstrakte Methode `int compare(T o1, T o2);`. -Im Package [java.util.function](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/function/package-summary.html) +Im Package +[java.util.function](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/function/package-summary.html) sind einige wichtige funktionale Interfaces bereits vordefiniert, beispielsweise -`Predicate` (Test, ob eine Bedingung erfüllt ist) und `Function` (verarbeite -einen Wert und liefere einen passenden Ergebniswert). Diese kann man auch in -eigenen Projekten nutzen! +`Predicate` (Test, ob eine Bedingung erfüllt ist) und `Function` (verarbeite einen +Wert und liefere einen passenden Ergebniswert). Diese kann man auch in eigenen +Projekten nutzen! ::: - # Quiz: Welches ist kein funktionales Interface? -```java +``` java public interface Wuppie { int wuppie(int a); } @@ -526,21 +336,21 @@ public interface Bar extends Wuppie { } ``` -:::::: notes +:::: notes ::: details Auflösung: -* `Wuppie` hat _genau eine_ abstrakte Methode => funktionales Interface -* `Fluppie` hat zwei abstrakte Methoden => **kein** funktionales Interface -* `Foo` hat gar keine abstrakte Methode => **kein** funktionales Interface -* `Bar` hat _genau eine_ abstrakte Methode (und eine Default-Methode) => funktionales Interface +- `Wuppie` hat *genau eine* abstrakte Methode =\> funktionales Interface +- `Fluppie` hat zwei abstrakte Methoden =\> **kein** funktionales Interface +- `Foo` hat gar keine abstrakte Methode =\> **kein** funktionales Interface +- `Bar` hat *genau eine* abstrakte Methode (und eine Default-Methode) =\> + funktionales Interface ::: -:::::: - +:::: # Lambdas und funktionale Interfaces: Typprüfung -```java +``` java interface java.util.Comparator { int compare(T o1, T o2); // abstrakte Methode } @@ -548,7 +358,7 @@ interface java.util.Comparator { \bigskip -```java +``` java // Verwendung ohne weitere Typinferenz Comparator c1 = (Studi o1, Studi o2) -> o1.getCredits() - o2.getCredits(); @@ -557,34 +367,234 @@ Comparator c2 = (o1, o2) -> o1.getCredits() - o2.getCredits(); ``` ::: notes -Der Compiler prüft in etwa folgende Schritte, wenn er über einen Lambda-Ausdruck stolpert: +Der Compiler prüft in etwa folgende Schritte, wenn er über einen Lambda-Ausdruck +stolpert: 1. In welchem Kontext habe ich den Lambda-Ausdruck gesehen? 2. OK, der Zieltyp ist hier `Comparator`. 3. Wie lautet die **eine** abstrakte Methode im `Comparator`-Interface? 4. OK, das ist `int compare(T o1, T o2);` -5. Da `T` hier an `Studi` gebunden ist, muss der Lambda-Ausdruck - der Methode `int compare(Studi o1, Studi o2);` entsprechen: - 2x `Studi` als Parameter und als Ergebnis ein `int` +5. Da `T` hier an `Studi` gebunden ist, muss der Lambda-Ausdruck der Methode + `int compare(Studi o1, Studi o2);` entsprechen: 2x `Studi` als Parameter und als + Ergebnis ein `int` 6. Ergebnis: a) Cool, passt zum Lambda-Ausdruck `c1`. Fertig. - b) D.h. in `c2` müssen `o1` und `o2` vom Typ `Studi` sein. - Cool, passt zum Lambda-Ausdruck `c2`. Fertig. + b) D.h. in `c2` müssen `o1` und `o2` vom Typ `Studi` sein. Cool, passt zum + Lambda-Ausdruck `c2`. Fertig. ::: - # Wrap-Up -* Anonyme Klassen: "Wegwerf"-Innere Klassen - * Müssen Interface implementieren oder Klasse erweitern +- Anonyme Klassen: "Wegwerf"-Innere Klassen + - Müssen Interface implementieren oder Klasse erweitern \smallskip -* Java8: **Lambda-Ausdrücke** statt anonymer Klassen (**funktionales Interface nötig**) - * Zwei mögliche Formen: - * Form 1: `(parameters) -> expression` - * Form 2: `(parameters) -> { statements; }` - * Im jeweiligen Kontext muss ein **funktionales Interface** verwendet werden, +- Java8: **Lambda-Ausdrücke** statt anonymer Klassen (**funktionales Interface + nötig**) + - Zwei mögliche Formen: + - Form 1: `(parameters) -> expression` + - Form 2: `(parameters) -> { statements; }` + - Im jeweiligen Kontext muss ein **funktionales Interface** verwendet werden, d.h. ein Interface mit **genau** einer abstrakten Methode - * Der Lambda-Ausdruck muss von der Signatur her dieser einen abstrakten Methode - entsprechen + - Der Lambda-Ausdruck muss von der Signatur her dieser einen abstrakten + Methode entsprechen + +::: readings +- @Java-SE-Tutorial +- @Urma2014 [Kap. 3] +- @Ullenboom2021 [Kap. 12] +::: + +::: outcomes +- k2: Ich kenne die Definition 'Funktionales Interface' +- k3: Ich kann innere und anonyme Klassen praktisch einsetzen +- k3: Ich kann eigene funktionale Interfaces erstellen +- k3: Ich kann Lambda-Ausdrücke formulieren und einsetzen +::: + +::: challenges +**Beispiel aus einem Code-Review im +[Dungeon-CampusMinden/Dungeon](https://github.com/Dungeon-CampusMinden/Dungeon)** + +Erklären Sie folgenden Code: + +``` java +public interface IFightAI { + void fight(Entity entity); +} + +public class AIComponent extends Component { + private final IFightAI fightAI; + + fightAI = + entity1 -> { + System.out.println("TIME TO FIGHT!"); + // todo replace with melee skill + }; +} +``` + + + +**Spielen mit Lambdas** + +Sie finden in einem Spiel folgenden Code: + +``` java +public class Main { + public static void main(String[] args) { + DoorTile door = new DoorTile(); + Entity lever1 = new Entity(), lever2 = new Entity(), lever3 = new Entity(); + + // ganz viel Code + + if (!door.isOpen() && (lever1.isOn() && (lever2.isOn() || lever3.isOn()))) door.open(); + + // ganz viel Code + } +} + +class DoorTile { + public boolean isOpen() { return false; } + public void open() { } +} +class Entity { + public boolean isOn() { return false; } +} +``` + +Dabei stört, dass die Verknüpfung der konkreten Objekte und Zustände zum Öffnen der +konkreten Tür fest (und zudem mitten) im Programm hinterlegt ist. + +Schreiben Sie diesen Code um: Definieren Sie eine statische Hilfsmethode, die ein +Door-Tile und drei Entitäten als Argument entgegen nimmt und dafür einen +Lambda-Ausdruck zurückliefert, mit dem (a) die gezeigte Bedingung überprüft werden +kann, und mit dem (falls die Bedingung erfüllt ist) (b) die Aktion (`door.open()`) +ausgeführt werden kann. Statt der gezeigten fest codierten `if`-Abfrage soll dieser +Lambda-Ausdruck ausgewertet werden: `doorhandle.test().accept();`. + +Damit haben Sie sich eine "Factory-Method" geschrieben (Entwurfsmuster), mit der +diese Bedingung dynamisch erzeugt werden kann (auch für andere Objekte). + +Hinweis: Der Lambda-Ausdruck wird "zweistufig" sein müssen ... + + + +**Sortieren mit Lambdas und funktionalen Interfaces** + +Betrachten Sie die Klasse +[Student](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/challenges/lambda/Student.java). + +1. Definieren Sie eine Methode, die das Sortieren einer `Student`-Liste erlaubt. + Übergeben Sie die Liste als Parameter. +2. Schaffen Sie es, das Sortierkriterium ebenfalls als Parameter zu übergeben (als + Lambda-Ausdruck)? +3. Definieren Sie eine weitere Methode, die wieder eine `Student`-Liste als + Parameter bekommt und liefern sie das erste `Student`-Objekt zurück, welches + einer als Lambda-Ausdruck übergebenen Bedingung genügt. +4. Definieren Sie noch eine Methode, die wieder eine `Student`-Liste als Parameter + bekommt sowie einen Lambda-Ausdruck, welcher aus einem `Student`-Objekt ein + Objekt eines anderen Typen `T` berechnet. Wenden Sie in der Methode den + Lambda-Ausdruck auf jedes Objekt der Liste an und geben sie die resultierende + neue Liste als Ergebnis zurück. + +Verwenden Sie in dieser Aufgabe jeweils Lambda-Ausdrücke. Rufen Sie alle drei/vier +Methoden an einem kleinen Beispiel auf. + + +::: diff --git a/lecture/java-modern/methodreferences.md b/lecture/java-modern/methodreferences.md index db8c8cc58..ff0b4c9fb 100644 --- a/lecture/java-modern/methodreferences.md +++ b/lecture/java-modern/methodreferences.md @@ -1,119 +1,39 @@ --- -title: "Methoden-Referenzen" -author: "Carsten Gips (HSBI)" -readings: - - "@Java-SE-Tutorial" - - "@Urma2014 [Kap. 3]" -tldr: | - Seit Java8 können **Referenzen auf Methoden** statt anonymer Klassen eingesetzt werden - (**funktionales Interface nötig**). - - Dabei gibt es drei mögliche Formen: - * Form 1: Referenz auf eine statische Methode: `ClassName::staticMethodName` - (wird verwendet wie `(args) -> ClassName.staticMethodName(args)`) - * Form 2: Referenz auf eine Instanz-Methode eines Objekts: `objectref::instanceMethodName` - (wird verwendet wie `(args) -> objectref.instanceMethodName(args)`) - * Form 3: Referenz auf eine Instanz-Methode eines Typs: `ClassName::instanceMethodName` - (wird verwendet wie `(o1, args) -> o1.instanceMethodName(args)`) - - Im jeweiligen Kontext muss ein passendes funktionales Interface verwendet werden, d.h. ein - Interface mit **genau** einer abstrakten Methode. Die Methoden-Referenz muss von der - Syntax her dieser einen abstrakten Methode entsprechen (bei der dritten Form wird die - Methode auf dem ersten Parameter aufgerufen). -outcomes: - - k2: "Ich verstehe die Definition von 'Funktionalen Interfaces' und kann sie erklären" - - k3: "Ich kann Methoden-Referenzen lesen und selbst formulieren" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106524&client_id=FH-Bielefeld" -# name: "Quiz Methoden-Referenzen (ILIAS)" -youtube: - - link: "https://youtu.be/z0mfvvrsRzc" - name: "VL Methoden-Referenzen" - - link: "https://youtu.be/YFdPcxE_1Eo" - name: "Demo Referenz auf statische Methode" - - link: "https://youtu.be/ImJTywhXrJo" - name: "Demo Referenz auf Instanz-Methode (Objekt)" - - link: "https://youtu.be/DVz2x27WHU8" - name: "Demo Referenz auf Instanz-Methode (Typ)" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/662003c5cb2cdef08b5d35cefd49b05f561fa26471cf3da22c4ff4310596909d0e21300133fc2fac353dfc4a391c8bb9af0dd47293efabfa8c3464429534d719" - name: "VL Methoden-Referenzen" -challenges: | - Betrachten Sie den folgenden Java-Code: - - ```java - public class Cat { - int gewicht; - public Cat(int gewicht) { this.gewicht = gewicht; } - - public static void main(String... args) { - List clouder = new ArrayList<>(); - clouder.add(new Cat(100)); clouder.add(new Cat(1)); clouder.add(new Cat(10)); - - clouder.sort(...); - } - } - ``` - - 1. Ergänzen Sie den Methodenaufruf `clouder.sort(...);` mit einer geeigneten - anonymen Klasse, daß der `clouder` aufsteigend nach Gewicht sortiert wird. - 2. Statt einer anonymen Klasse kann man auch Lambda-Ausdrücke einsetzen. Geben - Sie eine konkrete Form an. - 3. Statt einer anonymen Klasse kann man auch Methodenreferenzen einsetzen. Dafür - gibt es mehrere Formen. Geben Sie für zwei Formen der Methodenreferenz sowohl - den Aufruf als auch die Implementierung der entsprechenden Methoden in der - Klasse `Cat` an. - - +author: Carsten Gips (HSBI) +title: Methoden-Referenzen --- +::: tldr +Seit Java8 können **Referenzen auf Methoden** statt anonymer Klassen eingesetzt +werden (**funktionales Interface nötig**). + +Dabei gibt es drei mögliche Formen: + +- Form 1: Referenz auf eine statische Methode: `ClassName::staticMethodName` (wird + verwendet wie `(args) -> ClassName.staticMethodName(args)`) +- Form 2: Referenz auf eine Instanz-Methode eines Objekts: + `objectref::instanceMethodName` (wird verwendet wie + `(args) -> objectref.instanceMethodName(args)`) +- Form 3: Referenz auf eine Instanz-Methode eines Typs: + `ClassName::instanceMethodName` (wird verwendet wie + `(o1, args) -> o1.instanceMethodName(args)`) + +Im jeweiligen Kontext muss ein passendes funktionales Interface verwendet werden, +d.h. ein Interface mit **genau** einer abstrakten Methode. Die Methoden-Referenz +muss von der Syntax her dieser einen abstrakten Methode entsprechen (bei der dritten +Form wird die Methode auf dem ersten Parameter aufgerufen). +::: + +::: youtube +- [VL Methoden-Referenzen](https://youtu.be/z0mfvvrsRzc) +- [Demo Referenz auf statische Methode](https://youtu.be/YFdPcxE_1Eo) +- [Demo Referenz auf Instanz-Methode (Objekt)](https://youtu.be/ImJTywhXrJo) +- [Demo Referenz auf Instanz-Methode (Typ)](https://youtu.be/DVz2x27WHU8) +::: # Beispiel: Sortierung einer Liste -```java +``` java List sl = new ArrayList(); // Anonyme innere Klasse @@ -137,60 +57,57 @@ Collections.sort(sl, Studi::cmpCpsClass); Für das obige Beispiel wird davon ausgegangen, dass in der Klasse `Studi` eine statische Methode `cmpCpsClass()` existiert: -```java +``` java public static int cmpCpsClass(Studi s1, Studi s2) { return s1.getCps() - s2.getCps(); } ``` Wenn man im Lambda-Ausdruck nur Methoden der eigenen Klasse aufruft, kann man das -auch direkt per _Methoden-Referenz_ abkürzen! +auch direkt per *Methoden-Referenz* abkürzen! -* Erinnerung: `Comparator` ist ein funktionales Interface -* Instanzen können wie üblich durch Ableiten bzw. anonyme Klassen erzeugt werden -* Alternativ kann seit Java8 auch ein passender Lambda-Ausdruck verwendet werden -* Ab Java8: Referenzen auf passende Methoden (Signatur!) können ein funktionales +- Erinnerung: `Comparator` ist ein funktionales Interface +- Instanzen können wie üblich durch Ableiten bzw. anonyme Klassen erzeugt werden +- Alternativ kann seit Java8 auch ein passender Lambda-Ausdruck verwendet werden +- Ab Java8: Referenzen auf passende Methoden (Signatur!) können ein funktionales Interface "implementieren" - * Die statische Methode `static int cmpCpsClass(Studi s1, Studi s2)` hat die + - Die statische Methode `static int cmpCpsClass(Studi s1, Studi s2)` hat die selbe Signatur wie `int compare(Studi s1, Studi s2)` aus `Comparator` - * Kann deshalb wie eine Instanz von `Comparator` genutzt werden - * Name der Methode spielt dabei keine Rolle + - Kann deshalb wie eine Instanz von `Comparator` genutzt werden + - Name der Methode spielt dabei keine Rolle ::: - ::: notes # Überblick: Arten von Methoden-Referenzen 1. Referenz auf eine statische Methode - * Form: `ClassName::staticMethodName` - * Wirkung: Aufruf mit `(args) -> ClassName.staticMethodName(args)` + - Form: `ClassName::staticMethodName` + - Wirkung: Aufruf mit `(args) -> ClassName.staticMethodName(args)` \smallskip 2. Referenz auf Instanz-Methode eines bestimmten Objekts - * Form: `objectref::instanceMethodName` - * Wirkung: Aufruf mit `(args) -> objectref.instanceMethodName(args)` + - Form: `objectref::instanceMethodName` + - Wirkung: Aufruf mit `(args) -> objectref.instanceMethodName(args)` \smallskip 3. Referenz auf Instanz-Methode eines bestimmten Typs - * Form: `ClassName::instanceMethodName` - * Wirkung: Aufruf mit `(arg0, rest) -> arg0.instanceMethodName(rest)` \newline - (`arg0` ist vom Typ `ClassName`) - + - Form: `ClassName::instanceMethodName` + - Wirkung: Aufruf mit `(arg0, rest) -> arg0.instanceMethodName(rest)` + `\newline`{=tex} (`arg0` ist vom Typ `ClassName`) -_Anmerkung_: Analog zur Referenz auf eine statische Methode gibt es noch die -Form der Referenz auf einen Konstruktor: `ClassName::new`. Für Referenzen auf +*Anmerkung*: Analog zur Referenz auf eine statische Methode gibt es noch die Form +der Referenz auf einen Konstruktor: `ClassName::new`. Für Referenzen auf Konstruktoren mit mehr als 2 Parametern muss ein eigenes passendes funktionales Interface mit entsprechend vielen Parametern definiert werden ... ::: -[[Hinweis: Klassen- vs. Instanz-Methoden]{.ex}]{.slides} - +[[Hinweis: Klassen- vs. Instanz-Methoden]{.ex}]{.slides} # Methoden-Referenz 1: Referenz auf statische Methode -```java +``` java public class Studi { public static int cmpCpsClass(Studi s1, Studi s2) { return s1.getCredits() - s2.getCredits(); @@ -208,23 +125,23 @@ public class Studi { } ``` -[Demo: methodreferences.DemoStaticMethodReference]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/methodreferences/DemoStaticMethodReference.java"} +[Demo: methodreferences.DemoStaticMethodReference]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/methodreferences/DemoStaticMethodReference.java"} ::: notes -`Collections.sort()` erwartet in diesem Szenario als zweiten Parameter eine Instanz von -`Comparator` mit einer Methode `int compare(Studi o1, Studi o2)`. - -Die übergebene Referenz auf die **statische Methode `cmpCpsClass` der Klasse `Studi`** -hat die **selbe Signatur** und wird deshalb von `Collections.sort()` genauso genutzt wie -die eigentlich erwartete Methode `Comparator#compare(Studi o1, Studi o2)`, d.h. -statt `compare(o1, o2)` wird nun für jeden Vergleich **`Studi.cmpCpsClass(o1, o2)`** -aufgerufen. -::: +`Collections.sort()` erwartet in diesem Szenario als zweiten Parameter eine Instanz +von `Comparator` mit einer Methode `int compare(Studi o1, Studi o2)`. +Die übergebene Referenz auf die **statische Methode `cmpCpsClass` der Klasse +`Studi`** hat die **selbe Signatur** und wird deshalb von `Collections.sort()` +genauso genutzt wie die eigentlich erwartete Methode +`Comparator#compare(Studi o1, Studi o2)`, d.h. statt `compare(o1, o2)` wird +nun für jeden Vergleich **`Studi.cmpCpsClass(o1, o2)`** aufgerufen. +::: # Methoden-Referenz 2: Referenz auf Instanz-Methode (Objekt) -```java +``` java public class Studi { public int cmpCpsInstance(Studi s1, Studi s2) { return s1.getCredits() - s2.getCredits(); @@ -243,23 +160,23 @@ public class Studi { } ``` -[Demo: methodreferences.DemoInstanceMethodReferenceObject]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/methodreferences/DemoInstanceMethodReferenceObject.java"} +[Demo: methodreferences.DemoInstanceMethodReferenceObject]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/methodreferences/DemoInstanceMethodReferenceObject.java"} ::: notes -`Collections.sort()` erwartet in diesem Szenario als zweites Argument wieder eine Instanz -von `Comparator` mit einer Methode `int compare(Studi o1, Studi o2)`. - -Die übergebene Referenz auf die **Instanz-Methode `cmpCpsInstance` des Objekts `holger`** -hat die selbe Signatur und wird entsprechend von `Collections.sort()` genauso genutzt wie -die eigentlich erwartete Methode `Comparator#compare(Studi o1, Studi o2)`, d.h. -statt `compare(o1, o2)` wird nun für jeden Vergleich **`holger.cmpCpsInstance(o1, o2)`** -aufgerufen. +`Collections.sort()` erwartet in diesem Szenario als zweites Argument wieder eine +Instanz von `Comparator` mit einer Methode `int compare(Studi o1, Studi o2)`. + +Die übergebene Referenz auf die **Instanz-Methode `cmpCpsInstance` des Objekts +`holger`** hat die selbe Signatur und wird entsprechend von `Collections.sort()` +genauso genutzt wie die eigentlich erwartete Methode +`Comparator#compare(Studi o1, Studi o2)`, d.h. statt `compare(o1, o2)` wird +nun für jeden Vergleich **`holger.cmpCpsInstance(o1, o2)`** aufgerufen. ::: - # Methoden-Referenz 3: Referenz auf Instanz-Methode (Typ) -```java +``` java public class Studi { public int cmpCpsInstance(Studi studi) { return this.getCredits() - studi.getCredits(); @@ -277,25 +194,26 @@ public class Studi { } ``` -[Demo: methodreferences.DemoInstanceMethodReferenceType]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/methodreferences/DemoInstanceMethodReferenceType.java"} +[Demo: methodreferences.DemoInstanceMethodReferenceType]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/methodreferences/DemoInstanceMethodReferenceType.java"} ::: notes -`Collections.sort()` erwartet in diesem Szenario als zweites Argument wieder eine Instanz -von `Comparator` mit einer Methode `int compare(Studi o1, Studi o2)`. +`Collections.sort()` erwartet in diesem Szenario als zweites Argument wieder eine +Instanz von `Comparator` mit einer Methode `int compare(Studi o1, Studi o2)`. -Die übergebene Referenz auf die **Instanz-Methode `cmpCpsInstance` des Typs `Studi`** hat -die Signatur `int cmpCpsInstance(Studi studi)` und wird von `Collections.sort()` so genutzt: -Statt `compare(o1, o2)` wird nun für jeden Vergleich **`o1.cmpCpsInstance(o2)`** -aufgerufen. +Die übergebene Referenz auf die **Instanz-Methode `cmpCpsInstance` des Typs +`Studi`** hat die Signatur `int cmpCpsInstance(Studi studi)` und wird von +`Collections.sort()` so genutzt: Statt `compare(o1, o2)` wird nun für jeden +Vergleich **`o1.cmpCpsInstance(o2)`** aufgerufen. ::: - # Ausblick: Threads ::: notes -Erinnerung an bzw. Vorgriff auf ["Threads: Intro"](../java-classic/threads-intro.md): +Erinnerung an bzw. Vorgriff auf ["Threads: +Intro"](../java-classic/threads-intro.md): -```java +``` java public interface Runnable { void run(); } @@ -304,7 +222,7 @@ public interface Runnable { Damit lassen sich Threads auf verschiedene Arten erzeugen: ::: -```java +``` java public class ThreadStarter { public static void wuppie() { System.out.println("wuppie(): wuppie"); } } @@ -321,8 +239,8 @@ Thread t2 = new Thread(() -> System.out.println("t2: wuppie")); Thread t3 = new Thread(ThreadStarter::wuppie); ``` -[Beispiel: methodreferences.ThreadStarter]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/methodreferences/ThreadStarter.java"} - +[Beispiel: methodreferences.ThreadStarter]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/methodreferences/ThreadStarter.java"} # Ausblick: Datenstrukturen als Streams @@ -330,7 +248,7 @@ Thread t3 = new Thread(ThreadStarter::wuppie); Erinnerung an bzw. Vorgriff auf ["Stream-API"](stream-api.md): ::: -```java +``` java class X { public static boolean gtFour(int x) { return (x > 4) ? true : false; } } @@ -345,37 +263,124 @@ List wordLengths = words.stream() .collect(toList()); ``` -[Beispiel: methodreferences.CollectionStreams]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/methodreferences/CollectionStreams.java"} +[Beispiel: methodreferences.CollectionStreams]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/methodreferences/CollectionStreams.java"} ::: notes -* Collections können als Datenstrom betrachtet werden: `stream()` - * Iteration über die Collection, analog zu externer Iteration mit `foreach` -* Daten aus dem Strom filtern: `filter`, braucht Prädikat -* Auf alle Daten eine Funktion anwenden: `map` -* Daten im Strom sortieren: `sort` (auch mit Comparator) -* Daten wieder einsammeln mit `collect` +- Collections können als Datenstrom betrachtet werden: `stream()` + - Iteration über die Collection, analog zu externer Iteration mit `foreach` +- Daten aus dem Strom filtern: `filter`, braucht Prädikat +- Auf alle Daten eine Funktion anwenden: `map` +- Daten im Strom sortieren: `sort` (auch mit Comparator) +- Daten wieder einsammeln mit `collect` -=> Typische Elemente **funktionaler Programmierung** +=\> Typische Elemente **funktionaler Programmierung** -=> Verweis auf Wahlfach "Spezielle Methoden der Programmierung" +=\> Verweis auf Wahlfach "Spezielle Methoden der Programmierung" ::: - # Wrap-Up -Seit Java8: **Methoden-Referenzen** statt anonymer Klassen (**funktionales Interface nötig**) +Seit Java8: **Methoden-Referenzen** statt anonymer Klassen (**funktionales Interface +nötig**) \bigskip -* Drei mögliche Formen: - * Form 1: Referenz auf statische Methode: `ClassName::staticMethodName` \newline - (verwendet wie `(args) -> ClassName.staticMethodName(args)`) - * Form 2: Referenz auf Instanz-Methode eines Objekts: `objectref::instanceMethodName` \newline - (verwendet wie `(args) -> objectref.instanceMethodName(args)`) - * Form 3: Referenz auf Instanz-Methode eines Typs: `ClassName::instanceMethodName` \newline - (verwendet wie `(o1, args) -> o1.instanceMethodName(args)`) +- Drei mögliche Formen: + - Form 1: Referenz auf statische Methode: `ClassName::staticMethodName` + `\newline`{=tex} (verwendet wie + `(args) -> ClassName.staticMethodName(args)`) + - Form 2: Referenz auf Instanz-Methode eines Objekts: + `objectref::instanceMethodName` `\newline`{=tex} (verwendet wie + `(args) -> objectref.instanceMethodName(args)`) + - Form 3: Referenz auf Instanz-Methode eines Typs: + `ClassName::instanceMethodName` `\newline`{=tex} (verwendet wie + `(o1, args) -> o1.instanceMethodName(args)`) \smallskip -* Im jeweiligen Kontext muss ein passendes funktionales Interface verwendet werden +- Im jeweiligen Kontext muss ein passendes funktionales Interface verwendet werden [(d.h. ein Interface mit **genau** einer abstrakten Methode)]{.notes} + +::: readings +- @Java-SE-Tutorial +- @Urma2014 [Kap. 3] +::: + +::: outcomes +- k2: Ich verstehe die Definition von 'Funktionalen Interfaces' und kann sie + erklären +- k3: Ich kann Methoden-Referenzen lesen und selbst formulieren +::: + +::: challenges +Betrachten Sie den folgenden Java-Code: + +``` java +public class Cat { + int gewicht; + public Cat(int gewicht) { this.gewicht = gewicht; } + + public static void main(String... args) { + List clouder = new ArrayList<>(); + clouder.add(new Cat(100)); clouder.add(new Cat(1)); clouder.add(new Cat(10)); + + clouder.sort(...); + } +} +``` + +1. Ergänzen Sie den Methodenaufruf `clouder.sort(...);` mit einer geeigneten + anonymen Klasse, daß der `clouder` aufsteigend nach Gewicht sortiert wird. +2. Statt einer anonymen Klasse kann man auch Lambda-Ausdrücke einsetzen. Geben Sie + eine konkrete Form an. +3. Statt einer anonymen Klasse kann man auch Methodenreferenzen einsetzen. Dafür + gibt es mehrere Formen. Geben Sie für zwei Formen der Methodenreferenz sowohl + den Aufruf als auch die Implementierung der entsprechenden Methoden in der + Klasse `Cat` an. + + +::: diff --git a/lecture/java-modern/optional.md b/lecture/java-modern/optional.md index 858873e5e..192a81dac 100644 --- a/lecture/java-modern/optional.md +++ b/lecture/java-modern/optional.md @@ -1,214 +1,45 @@ --- -title: "Optional" -author: "Carsten Gips (HSBI)" -readings: - - "@LernJava" - - "@Ullenboom2021 [Kap. 12.6]" -tldr: | - Häufig hat man in Methoden den Fall, dass es keinen Wert gibt, und man liefert dann - `null` als "kein Wert vorhanden" zurück. Dies führt dazu, dass die Aufrufer eine - entsprechende `null`-Prüfung für die Rückgabewerte durchführen müssen, bevor sie - das Ergebnis nutzen können. - - `Optional` schließt elegant den Fall "kein Wert vorhanden" ein: Es kann mit der Methode - `Optional.ofNullable()` das Argument in ein Optional verpacken (Argument != `null`) - oder ein `Optional.empty()` zurückliefern ("leeres" Optional, wenn Argument == `null`). - - Man kann Optionals prüfen mit `isEmpty()` und `ifPresent()` und dann direkt mit - `ifPresent()`, `orElse()` und `orElseThrow()` auf den verpackten Wert zugreifen. - Besser ist aber der Zugriff über die Stream-API von `Optional`: `map()`, `filter`, - `flatMap()`, ... Dabei gibt es keine terminalen Operationen - es handelt sich ja auch - nicht um einen Stream, nur die Optik erinnert daran. - - - `Optional` ist vor allem für Rückgabewerte gedacht, die den Fall "kein Wert vorhanden" - einschließen sollen. Attribute, Parameter und Sammlungen sollten nicht `Optional`-Referenzen - speichern, sondern "richtige" (unverpackte) Werte (und eben zur Not `null`). `Optional` - ist kein Ersatz für `null`-Prüfung von Methoden-Parametern (nutzen Sie hier beispielsweise - passende Annotationen). `Optional` ist auch kein Ersatz für vernünftiges Exception-Handling - im Fall, dass etwas Unerwartetes passiert ist. Liefern Sie **niemals** `null` zurück, wenn - der Rückgabetyp der Methode ein `Optional` ist! -outcomes: - - k2: "Ich kann erklären, warum Optionals vor allem für Rückgabewerte gedacht sind" - - k2: "Ich kann erklären, warum kein `null` zurückgeliefert werden darf, wenn der Rückgabetyp ein `Optional` ist" - - k3: "Ich kann (ggf. leere) Optionals mit `Optional.ofNullable()` erzeugen" - - k3: "Ich kann auf Optionals klassisch über die direkten Hilfsmethoden der Klasse zugreifen" - - k3: "Ich kann auf Optionals elegant per Stream-API zugreifen" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106526&client_id=FH-Bielefeld" -# name: "Quiz Optional (ILIAS)" -youtube: - - link: "https://youtu.be/JDG_hUSBfSA" - name: "VL Optional" - - link: "https://youtu.be/vL2c0iB4uSk" - name: "Demo Optional" - - link: "https://youtu.be/vyN-vOV9_CU" - name: "Demo Optional: Beispiel aus der Praxis im PM-Dungeon" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/b62e544bff3a9a25982052bd761b9971f7e67caca90e3ed66c2b794b95d35c2c479f8d72189159345ac018e34ec55866c5558a256ce63b5e9c43a30fc8787d1b" - name: "VL Optional" -challenges: | - **Optional und Stream-API** - - 1. Erklären Sie den folgenden Code-Schnipsel aus dem [Dungeon](https://github.com/Dungeon-CampusMinden/Dungeon/pull/1831): - - ```java - Skill fireball = - new Skill( - new FireballSkill( - () -> - hero.fetch(CollideComponent.class) - .map(cc -> cc - .center(hero) - .add(viewDirection.toPoint())) - .orElseThrow( - () -> MissingComponentException.build( - hero, - CollideComponent.class)), - FIREBALL_RANGE, - FIREBALL_SPEED, - FIREBALL_DMG), - 1); - ``` - - Hinweise: - - `Entity#fetch`: ` Optional fetch(final Class klass)` - - `CollideComponent#center`: `Point center(final Entity entity)` - - `Point#add`: `Point add(final Point other)` - - - - 2. Was würde sich ändern, wenn statt `map` ein `flatMap` verwendet würde? Wie ist das bei richtigen - Streams? - - - - 3. Was passiert im folgenden Beispiel? Warum funktioniert das auch ohne terminale Stream-Operation? - - ```java - Game.hero() - .flatMap(e -> e.fetch(AmmunitionComponent.class)) - .map(AmmunitionComponent::resetCurrentAmmunition); - ``` - - Hinweis: `Game.hero()`: `static Optional hero()`. - - - - 4. Können Sie die beiden obigen Beispiele in "klassischer" Schreibweise umformulieren? - - - - - **String-Handling** - - Können Sie den folgenden Code so umschreiben, dass Sie statt der `if`-Abfragen und der einzelnen direkten Methodenaufrufe - die Stream-API und `Optional` nutzen? - - ```java - String format(final String text, String replacement) { - if (text.isEmpty()) { - return ""; - } - - final String trimmed = text.trim(); - final String withSpacesReplaced = trimmed.replaceAll(" +", replacement); - - return replacement + withSpacesReplaced + replacement; - } - ``` - - Ein Aufruf `format(" Hello World ... ", "_");` liefert den String "`_Hello_World_..._`". - - +author: Carsten Gips (HSBI) +title: Optional --- +::: tldr +Häufig hat man in Methoden den Fall, dass es keinen Wert gibt, und man liefert dann +`null` als "kein Wert vorhanden" zurück. Dies führt dazu, dass die Aufrufer eine +entsprechende `null`-Prüfung für die Rückgabewerte durchführen müssen, bevor sie das +Ergebnis nutzen können. + +`Optional` schließt elegant den Fall "kein Wert vorhanden" ein: Es kann mit der +Methode `Optional.ofNullable()` das Argument in ein Optional verpacken (Argument != +`null`) oder ein `Optional.empty()` zurückliefern ("leeres" Optional, wenn Argument +== `null`). + +Man kann Optionals prüfen mit `isEmpty()` und `ifPresent()` und dann direkt mit +`ifPresent()`, `orElse()` und `orElseThrow()` auf den verpackten Wert zugreifen. +Besser ist aber der Zugriff über die Stream-API von `Optional`: `map()`, `filter`, +`flatMap()`, ... Dabei gibt es keine terminalen Operationen - es handelt sich ja +auch nicht um einen Stream, nur die Optik erinnert daran. + +`Optional` ist vor allem für Rückgabewerte gedacht, die den Fall "kein Wert +vorhanden" einschließen sollen. Attribute, Parameter und Sammlungen sollten nicht +`Optional`-Referenzen speichern, sondern "richtige" (unverpackte) Werte (und eben +zur Not `null`). `Optional` ist kein Ersatz für `null`-Prüfung von +Methoden-Parametern (nutzen Sie hier beispielsweise passende Annotationen). +`Optional` ist auch kein Ersatz für vernünftiges Exception-Handling im Fall, dass +etwas Unerwartetes passiert ist. Liefern Sie **niemals** `null` zurück, wenn der +Rückgabetyp der Methode ein `Optional` ist! +::: + +::: youtube +- [VL Optional](https://youtu.be/JDG_hUSBfSA) +- [Demo Optional](https://youtu.be/vL2c0iB4uSk) +- [Demo Optional: Beispiel aus der Praxis im + PM-Dungeon](https://youtu.be/vyN-vOV9_CU) +::: # Motivation -```java +``` java public class LSF { private Set sl; @@ -240,27 +71,27 @@ public static void main(String... args) { ::: notes ## Problem: `null` wird an (zu) vielen Stellen genutzt -* Es gibt keinen Wert ("not found") -* Felder wurden (noch) nicht initialisiert -* Es ist ein Problem oder etwas Unerwartetes aufgetreten +- Es gibt keinen Wert ("not found") +- Felder wurden (noch) nicht initialisiert +- Es ist ein Problem oder etwas Unerwartetes aufgetreten -=> Parameter und Rückgabewerte müssen stets auf `null` geprüft werden -(oder Annotationen wie `@NotNull` eingesetzt werden ...) +=\> Parameter und Rückgabewerte müssen stets auf `null` geprüft werden (oder +Annotationen wie `@NotNull` eingesetzt werden ...) ## Lösung -* `Optional` für Rückgabewerte, die "kein Wert vorhanden" mit einschließen +- `Optional` für Rückgabewerte, die "kein Wert vorhanden" mit einschließen (statt `null` bei Abwesenheit von Werten) -* `@NotNull`/`@Nullable` für Parameter einsetzen (oder separate Prüfung) -* Exceptions werfen in Fällen, wo ein Problem aufgetreten ist +- `@NotNull`/`@Nullable` für Parameter einsetzen (oder separate Prüfung) +- Exceptions werfen in Fällen, wo ein Problem aufgetreten ist ## Anmerkungen -* Verwendung von `null` auf Attribut-Ebene (Klassen-interne Verwendung) ist okay! -* `Optional` ist **kein** Ersatz für `null`-Checks! -* `null` ist **kein** Ersatz für vernünftiges Error-Handling! - Das häufig zu beobachtende "Irgendwas Unerwartetes ist passiert, hier ist `null`" - ist ein **Anti-Pattern**! +- Verwendung von `null` auf Attribut-Ebene (Klassen-interne Verwendung) ist okay! +- `Optional` ist **kein** Ersatz für `null`-Checks! +- `null` ist **kein** Ersatz für vernünftiges Error-Handling! Das häufig zu + beobachtende "Irgendwas Unerwartetes ist passiert, hier ist `null`" ist ein + **Anti-Pattern**! ## Beispiel aus der Praxis im PM-Dungeon @@ -275,67 +106,68 @@ Die Methode `AITools#calculateNewPath` soll in der Umgebung einer als Parameter betretbar ist und einen Pfad von der Position der Entität zu diesem Feld an den Aufrufer zurückliefern. -Zunächst wird in der Entität nach einer `PositionComponent` und einer `VelocityComponent` -gesucht. Wenn es (eine) diese(r) Components nicht in der Entität gibt, wird der Wert -`null` an den Aufrufer von `AITools#calculateNewPath` zurückgeliefert. -(_Anmerkung_: Interessanterweise wird in der Methode nicht mit der `VelocityComponent` -gearbeitet.) +Zunächst wird in der Entität nach einer `PositionComponent` und einer +`VelocityComponent` gesucht. Wenn es (eine) diese(r) Components nicht in der Entität +gibt, wird der Wert `null` an den Aufrufer von `AITools#calculateNewPath` +zurückgeliefert. (*Anmerkung*: Interessanterweise wird in der Methode nicht mit der +`VelocityComponent` gearbeitet.) Dann wird in der `PositionComponent` die Position der Entität im aktuellen Level abgerufen. In einer Schleife werden alle Felder im gegebenen Radius in eine Liste -gespeichert. -(_Anmerkung_: Da dies über die `float`-Werte passiert und nicht über die Feld-Indizes -wird ein `Tile` u.U. recht oft in der Liste abgelegt. Können Sie sich hier einfache -Verbesserungen überlegen?) +gespeichert. (*Anmerkung*: Da dies über die `float`-Werte passiert und nicht über +die Feld-Indizes wird ein `Tile` u.U. recht oft in der Liste abgelegt. Können Sie +sich hier einfache Verbesserungen überlegen?) Da `level.getTileAt()` offenbar als Antwort auch `null` zurückliefern kann, werden -nun zunächst per `tiles.removeIf(Objects::isNull);` all diese `null`-Werte wieder aus -der Liste entfernt. Danach erfolgt die Prüfung, ob die verbleibenden Felder betretbar -sind und nicht-betretbare Felder werden entfernt. +nun zunächst per `tiles.removeIf(Objects::isNull);` all diese `null`-Werte wieder +aus der Liste entfernt. Danach erfolgt die Prüfung, ob die verbleibenden Felder +betretbar sind und nicht-betretbare Felder werden entfernt. Aus den verbleibenden (betretbaren) Feldern in der Liste wird nun eines zufällig -ausgewählt und per `level.findPath()` ein Pfad von der Position der Entität zu diesem -Feld berechnet und zurückgeliefert. -(_Anmerkung_: Hier wird ein zufälliges Tile in der Liste der umgebenden Felder gewählt, -von diesem die Koordinaten bestimmt, und dann noch einmal aus dem Level das dazugehörige -Feld geholt - dabei hatte man die Referenz auf das Feld bereits in der Liste. Können -Sie sich hier eine einfache Verbesserung überlegen?) +ausgewählt und per `level.findPath()` ein Pfad von der Position der Entität zu +diesem Feld berechnet und zurückgeliefert. (*Anmerkung*: Hier wird ein zufälliges +Tile in der Liste der umgebenden Felder gewählt, von diesem die Koordinaten +bestimmt, und dann noch einmal aus dem Level das dazugehörige Feld geholt - dabei +hatte man die Referenz auf das Feld bereits in der Liste. Können Sie sich hier eine +einfache Verbesserung überlegen?) Zusammengefasst: -* Die als Parameter `entity` übergebene Referenz darf offenbar _nicht_ `null` sein. - Die ersten beiden Statements in der Methode rufen auf dieser Referenz Methoden - auf, was bei einer `null`-Referenz zu einer `NullPointer`-Exception führen - würde. Hier wäre `null` ein Fehlerzustand. -* `entity.getComponent()` kann offenbar `null` zurückliefern, wenn die gesuchte +- Die als Parameter `entity` übergebene Referenz darf offenbar *nicht* `null` + sein. Die ersten beiden Statements in der Methode rufen auf dieser Referenz + Methoden auf, was bei einer `null`-Referenz zu einer `NullPointer`-Exception + führen würde. Hier wäre `null` ein Fehlerzustand. +- `entity.getComponent()` kann offenbar `null` zurückliefern, wenn die gesuchte Component nicht vorhanden ist. Hier wird `null` als "kein Wert vorhanden" genutzt, was dann nachfolgende `null`-Checks notwendig macht. -* Wenn es die gewünschten Components nicht gibt, wird dem Aufrufer der Methode - `null` zurückgeliefert. Hier ist nicht ganz klar, ob das einfach nur "kein - Wert vorhanden" ist oder eigentlich ein Fehlerzustand? -* `level.getTileAt()` kann offenbar `null` zurückliefern, wenn kein Feld an der +- Wenn es die gewünschten Components nicht gibt, wird dem Aufrufer der Methode + `null` zurückgeliefert. Hier ist nicht ganz klar, ob das einfach nur "kein Wert + vorhanden" ist oder eigentlich ein Fehlerzustand? +- `level.getTileAt()` kann offenbar `null` zurückliefern, wenn kein Feld an der Position vorhanden ist. Hier wird `null` wieder als "kein Wert vorhanden" genutzt, was dann nachfolgende `null`-Checks notwendig macht (Entfernen aller `null`-Referenzen aus der Liste). -* `level.findPath()` kann auch wieder `null` zurückliefern, wenn kein Pfad berechnet - werden konnte. Hier ist wieder nicht ganz klar, ob das einfach nur "kein Wert - vorhanden" ist oder eigentlich ein Fehlerzustand? Man könnte beispielsweise in - diesem Fall ein anderes Feld probieren? - -Der Aufrufer bekommt also eine `NullPointer`-Exception, wenn der übergebene Parameter -`entity` nicht vorhanden ist oder den Wert `null`, wenn in der Methode etwas schief -lief oder schlicht kein Pfad berechnet werden konnte oder tatsächlich einen Pfad. -Damit wird der Aufrufer gezwungen, den Rückgabewert vor der Verwendung zu untersuchen. - -**Allein in dieser einen kurzen Methode macht `null` so viele extra Prüfungen notwendig -und den Code dadurch schwerer lesbar und fehleranfälliger! `null` wird als (unvollständige) -Initialisierung und als Rückgabewert und für den Fehlerfall genutzt, zusätzlich ist -die Semantik von `null` nicht immer klar.** -(_Anmerkung_: Der Gebrauch von `null` hat nicht wirklich etwas mit "der Natur eines ECS" -zu tun. Die Methode wurde mittlerweile komplett überarbeitet und ist in der hier gezeigten +- `level.findPath()` kann auch wieder `null` zurückliefern, wenn kein Pfad + berechnet werden konnte. Hier ist wieder nicht ganz klar, ob das einfach nur + "kein Wert vorhanden" ist oder eigentlich ein Fehlerzustand? Man könnte + beispielsweise in diesem Fall ein anderes Feld probieren? + +Der Aufrufer bekommt also eine `NullPointer`-Exception, wenn der übergebene +Parameter `entity` nicht vorhanden ist oder den Wert `null`, wenn in der Methode +etwas schief lief oder schlicht kein Pfad berechnet werden konnte oder tatsächlich +einen Pfad. Damit wird der Aufrufer gezwungen, den Rückgabewert vor der Verwendung +zu untersuchen. + +**Allein in dieser einen kurzen Methode macht `null` so viele extra Prüfungen +notwendig und den Code dadurch schwerer lesbar und fehleranfälliger! `null` wird als +(unvollständige) Initialisierung und als Rückgabewert und für den Fehlerfall +genutzt, zusätzlich ist die Semantik von `null` nicht immer klar.** (*Anmerkung*: +Der Gebrauch von `null` hat nicht wirklich etwas mit "der Natur eines ECS" zu tun. +Die Methode wurde mittlerweile komplett überarbeitet und ist in der hier gezeigten Form glücklicherweise nicht mehr zu finden.) -Entsprechend hat sich in diesem [Review](https://github.com/Dungeon-CampusMinden/Dungeon/pull/128#pullrequestreview-1254025874) +Entsprechend hat sich in diesem +[Review](https://github.com/Dungeon-CampusMinden/Dungeon/pull/128#pullrequestreview-1254025874) die nachfolgende Diskussion ergeben: ![](images/screenshot_review2.png){width="80%"} @@ -343,20 +175,19 @@ die nachfolgende Diskussion ergeben: ![](images/screenshot_review3.png){width="80%"} ::: - -# Erzeugen von _Optional_-Objekten +# Erzeugen von *Optional*-Objekten Konstruktor ist `private` ... -* "Kein Wert": `Optional.empty()` -* Verpacken eines non-`null` Elements: `Optional.of()` \newline +- "Kein Wert": `Optional.empty()` +- Verpacken eines non-`null` Elements: `Optional.of()` `\newline`{=tex} (`NullPointerException` wenn Argument `null`!) \bigskip -* Verpacken eines "unsicheren"/beliebigen Elements: `Optional.ofNullable()` - * Liefert verpacktes Element, oder - * `Optional.empty()`, falls Element `null` war +- Verpacken eines "unsicheren"/beliebigen Elements: `Optional.ofNullable()` + - Liefert verpacktes Element, oder + - `Optional.empty()`, falls Element `null` war ::: notes Es sollte in der Praxis eigentlich nur wenige Fälle geben, wo ein Aufruf von @@ -365,13 +196,12 @@ Es sollte in der Praxis eigentlich nur wenige Fälle geben, wo ein Aufruf von Stattdessen sollte stets `Optional.ofNullable()` verwendet werden. ::: -**`null` kann nicht nicht in `Optional` verpackt werden!** -[(Das wäre dann eben `Optional.empty()`.)]{.notes} +**`null` kann nicht nicht in `Optional` verpackt werden!** [(Das wäre dann eben +`Optional.empty()`.)]{.notes} +# LSF liefert jetzt *Optional* zurück -# LSF liefert jetzt _Optional_ zurück - -```java +``` java public class LSF { private Set sl; @@ -396,35 +226,34 @@ Das Beispiel soll verdeutlichen, dass man im Fehlerfall nicht einfach `null` ode `Optional.empty()` zurückliefern soll, sondern eine passende Exception werfen soll. Wenn die Liste aber leer ist, stellt dies keinen Fehler dar! Es handelt sich um den -Fall "kein Wert vorhanden". In diesem Fall wird statt `null` nun ein `Optional.empty()` -zurückgeliefert, also ein Objekt, auf dem der Aufrufer die üblichen Methoden aufrufen -kann. +Fall "kein Wert vorhanden". In diesem Fall wird statt `null` nun ein +`Optional.empty()` zurückgeliefert, also ein Objekt, auf dem der Aufrufer die +üblichen Methoden aufrufen kann. ::: - -# Zugriff auf _Optional_-Objekte +# Zugriff auf *Optional*-Objekte ::: notes In der funktionalen Programmierung gibt es schon lange das Konzept von `Optional`, in Haskell ist dies beispielsweise die Monade `Maybe`. Allerdings ist die Einbettung in die Sprache von vornherein mit berücksichtigt worden, insbesondere kann man hier -sehr gut mit _Pattern Matching_ in der Funktionsdefinition auf den verpackten Inhalt +sehr gut mit *Pattern Matching* in der Funktionsdefinition auf den verpackten Inhalt reagieren. -In Java gibt es die Methode `Optional#isEmpty()`, die einen Boolean zurückliefert und -prüft, ob es sich um ein leeres `Optional` handelt oder ob hier ein Wert "verpackt" ist. +In Java gibt es die Methode `Optional#isEmpty()`, die einen Boolean zurückliefert +und prüft, ob es sich um ein leeres `Optional` handelt oder ob hier ein Wert +"verpackt" ist. Für den direkten Zugriff auf die Werte gibt es die Methoden `Optional#orElseThrow()` und `Optional#orElse()`. Damit kann man auf den verpackten Wert zugreifen, oder es wird eine Exception geworfen bzw. ein Ersatzwert geliefert. -Zusätzlich gibt es `Optional#isPresent()`, die als Parameter ein `java.util.function.Consumer` -erwartet, also ein funktionales Interface mit einer Methode `void accept(T)`, die das -Objekt verarbeitet. +Zusätzlich gibt es `Optional#isPresent()`, die als Parameter ein +`java.util.function.Consumer` erwartet, also ein funktionales Interface mit einer +Methode `void accept(T)`, die das Objekt verarbeitet. ::: - -```java +``` java Studi best; // Testen und dann verwenden @@ -448,18 +277,18 @@ best = lsf.getBestStudi().orElseThrow(); ::: notes Es gibt noch eine Methode `get()`, die so verhält wie `orElseThrow()`. Da man diese Methode vom Namen her schnell mit einem Getter verwechselt, ist sie mittlerweile -_deprecated_. +*deprecated*. -_Anmerkung_: Da `getBestStudi()` eine `NullPointerException` werfen kann, sollte der +*Anmerkung*: Da `getBestStudi()` eine `NullPointerException` werfen kann, sollte der Aufruf möglicherweise in ein `try/catch` verpackt werden. Dito für `orElseThrow()`. -[Beispiel: optional.traditional.Demo]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/optional/traditional/Demo.java"} +[Beispiel: optional.traditional.Demo]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/optional/traditional/Demo.java"} ::: - # Einsatz mit Stream-API -```java +``` java public class LSF { ... public Optional getBestStudi() throws NullPointerException { @@ -480,50 +309,50 @@ public static void main(String... args) { ``` ::: notes -[Beispiel: optional.streams.Demo]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/optional/streams/Demo.java"} +[Beispiel: optional.streams.Demo]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/optional/streams/Demo.java"} Im Beispiel wird in `getBestStudi()` die Sammlung als Stream betrachtet, über die Methode `sorted()` und den Lamda-Ausdruck für den `Comparator` sortiert ("falsch" -herum: absteigend in den Credits der Studis in der Sammlung), und `findFirst()` -ist die terminale Operation auf dem Stream, die ein `Optional` zurückliefert: +herum: absteigend in den Credits der Studis in der Sammlung), und `findFirst()` ist +die terminale Operation auf dem Stream, die ein `Optional` zurückliefert: entweder den Studi mit den meisten Credits (verpackt in `Optional`) oder `Optional.empty()`, wenn es überhaupt keine Studis in der Sammlung gab. In `main()` wird dieses `Optional` mit den Stream-Methoden von `Optional` bearbeitet, zunächst mit `Optional#map()`. Man braucht nicht selbst prüfen, ob das -von `getBestStudi()` erhaltene Objekt leer ist oder nicht, da dies von `Optional#map()` -erledigt wird: Es wendet die Methodenreferenz auf den verpackten Wert an (sofern -dieser vorhanden ist) und liefert damit den Namen des Studis als `Optional` -verpackt zurück. Wenn es keinen Wert, also nur `Optional.empty()` von `getBestStudi()` -gab, dann ist der Rückgabewert von `Optional#map()` ein `Optional.empty()`. Wenn -der Name, also der Rückgabewert von `Studi::name`, `null` war, dann wird ebenfalls -ein `Optional.empty()` zurückgeliefert. Dadurch wirft `orElseThrow()` dann eine -`NoSuchElementException`. Man kann also direkt mit dem String `name` weiterarbeiten -ohne extra `null`-Prüfung - allerdings will man noch ein Exception-Handling einbauen -(dies fehlt im obigen Beispiel aus Gründen der Übersicht) ... +von `getBestStudi()` erhaltene Objekt leer ist oder nicht, da dies von +`Optional#map()` erledigt wird: Es wendet die Methodenreferenz auf den verpackten +Wert an (sofern dieser vorhanden ist) und liefert damit den Namen des Studis als +`Optional` verpackt zurück. Wenn es keinen Wert, also nur `Optional.empty()` +von `getBestStudi()` gab, dann ist der Rückgabewert von `Optional#map()` ein +`Optional.empty()`. Wenn der Name, also der Rückgabewert von `Studi::name`, `null` +war, dann wird ebenfalls ein `Optional.empty()` zurückgeliefert. Dadurch wirft +`orElseThrow()` dann eine `NoSuchElementException`. Man kann also direkt mit dem +String `name` weiterarbeiten ohne extra `null`-Prüfung - allerdings will man noch +ein Exception-Handling einbauen (dies fehlt im obigen Beispiel aus Gründen der +Übersicht) ... ::: - ::: notes -# Weitere _Optionals_ +# Weitere *Optionals* Für die drei primitiven Datentypen `int`, `long` und `double` gibt es passende -Wrapper-Klassen von `Optional`: `OptionalInt`, `OptionalLong` und `OptionalDouble`. +Wrapper-Klassen von `Optional`: `OptionalInt`, `OptionalLong` und +`OptionalDouble`. -Diese verhalten sich analog zu `Optional`, haben aber keine Methode `ofNullable()`, -da dies hier keinen Sinn ergeben würde: Die drei primitiven Datentypen repräsentieren -Werte - diese können nicht `null` sein. +Diese verhalten sich analog zu `Optional`, haben aber keine Methode +`ofNullable()`, da dies hier keinen Sinn ergeben würde: Die drei primitiven +Datentypen repräsentieren Werte - diese können nicht `null` sein. ::: - -# Regeln für _Optional_ +# Regeln für *Optional* 1. Nutze `Optional` nur als Rückgabe für "kein Wert vorhanden" ::: notes - `Optional` ist nicht als Ersatz für eine `null`-Prüfung o.ä. - gedacht, sondern als Repräsentation, um auch ein "kein Wert - vorhanden" zurückliefern zu können. + `Optional` ist nicht als Ersatz für eine `null`-Prüfung o.ä. gedacht, sondern + als Repräsentation, um auch ein "kein Wert vorhanden" zurückliefern zu können. ::: \bigskip @@ -531,9 +360,8 @@ Werte - diese können nicht `null` sein. 2. Nutze nie `null` für eine `Optional`-Variable oder einen `Optional`-Rückgabewert ::: notes - Wenn man ein `Optional` als Rückgabe bekommt, sollte das - niemals selbst eine `null`-Referenz sein. Das macht das - gesamte Konzept kaputt! + Wenn man ein `Optional` als Rückgabe bekommt, sollte das niemals selbst eine + `null`-Referenz sein. Das macht das gesamte Konzept kaputt! Nutzen Sie stattdessen `Optional.empty()`. ::: @@ -541,22 +369,20 @@ Werte - diese können nicht `null` sein. 3. Nutze `Optional.ofNullable()` zum Erzeugen eines `Optional` ::: notes - Diese Methode verhält sich "freundlich" und erzeugt automatisch - ein `Optional.empty()`, wenn das Argument `null` ist. Es gibt - also keinen Grund, dies mit einer Fallunterscheidung selbst - erledigen zu wollen. - - Bevorzugen Sie `Optional.ofNullable()` vor einer manuellen - Fallunterscheidung und dem entsprechenden Einsatz von - `Optional.of()` und `Optional.empty()`. + Diese Methode verhält sich "freundlich" und erzeugt automatisch ein + `Optional.empty()`, wenn das Argument `null` ist. Es gibt also keinen Grund, + dies mit einer Fallunterscheidung selbst erledigen zu wollen. + + Bevorzugen Sie `Optional.ofNullable()` vor einer manuellen Fallunterscheidung + und dem entsprechenden Einsatz von `Optional.of()` und `Optional.empty()`. ::: 4. Erzeuge keine `Optional` als Ersatz für die Prüfung auf `null` ::: notes - Wenn Sie auf `null` prüfen müssen, müssen Sie auf `null` prüfen. - Der ersatzweise Einsatz von `Optional` macht es nur komplexer - - prüfen müssen Sie hinterher ja immer noch. + Wenn Sie auf `null` prüfen müssen, müssen Sie auf `null` prüfen. Der ersatzweise + Einsatz von `Optional` macht es nur komplexer - prüfen müssen Sie hinterher ja + immer noch. ::: 5. Nutze `Optional` nicht in Attributen, Methoden-Parametern und Sammlungen @@ -564,58 +390,234 @@ Werte - diese können nicht `null` sein. ::: notes Nutzen Sie `Optional` vor allem für Rückgabewerte. - Attribute sollten immer direkt einen Wert haben oder `null`, - analog Parameter von Methoden o.ä. ... Hier hilft `Optional` - nicht, Sie müssten ja trotzdem eine `null`-Prüfung machen, - nur eben dann über den `Optional`, wodurch dies komplexer und - schlechter lesbar wird. + Attribute sollten immer direkt einen Wert haben oder `null`, analog Parameter + von Methoden o.ä. ... Hier hilft `Optional` nicht, Sie müssten ja trotzdem eine + `null`-Prüfung machen, nur eben dann über den `Optional`, wodurch dies komplexer + und schlechter lesbar wird. - Aus einem ähnlichen Grund sollten Sie auch in Sammlungen - keine `Optional` speichern! + Aus einem ähnlichen Grund sollten Sie auch in Sammlungen keine `Optional` + speichern! ::: 6. Vermeide den direkten Zugriff (`ifPresent()`, `orElseThrow()` ...) ::: notes - Der direkte Zugriff auf ein `Optional` entspricht dem - Prüfen auf `null` und dann dem Auspacken. Dies ist nicht - nur Overhead, sondern auch schlechter lesbar. + Der direkte Zugriff auf ein `Optional` entspricht dem Prüfen auf `null` und dann + dem Auspacken. Dies ist nicht nur Overhead, sondern auch schlechter lesbar. - Vermeiden Sie den direkten Zugriff und nutzen Sie `Optional` - mit den Stream-Methoden. So ist dies von den Designern - gedacht. + Vermeiden Sie den direkten Zugriff und nutzen Sie `Optional` mit den + Stream-Methoden. So ist dies von den Designern gedacht. ::: - ::: notes # Interessante Links -* ["Using Optionals"](https://dev.java/learn/api/streams/optionals/) -* ["What You Might Not Know About Optional"](https://medium.com/javarevisited/what-you-might-not-know-about-optional-7238e3c05f63) -* ["Experienced Developers Use These 7 Java Optional Tips to Remove Code Clutter"](https://medium.com/javarevisited/experienced-developers-use-these-7-java-optional-tips-to-remove-code-clutter-6e8b1a639861) -* ["Code Smells: Null"](https://blog.jetbrains.com/idea/2017/08/code-smells-null/) -* ["Class Optional"](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Optional.html) +- ["Using Optionals"](https://dev.java/learn/api/streams/optionals/) +- ["What You Might Not Know About + Optional"](https://medium.com/javarevisited/what-you-might-not-know-about-optional-7238e3c05f63) +- ["Experienced Developers Use These 7 Java Optional Tips to Remove Code + Clutter"](https://medium.com/javarevisited/experienced-developers-use-these-7-java-optional-tips-to-remove-code-clutter-6e8b1a639861) +- ["Code Smells: Null"](https://blog.jetbrains.com/idea/2017/08/code-smells-null/) +- ["Class + Optional"](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Optional.html) ::: - # Wrap-Up `Optional` als Rückgabe für "kein Wert vorhanden" \bigskip -* `Optional.ofNullable()`: Erzeugen eines `Optional` - * Entweder Objekt "verpackt" (Argument != `null`) - * Oder `Optional.empty()` (Argument == `null`) -* Prüfen mit `isEmpty()` und `ifPresent()` -* Direkter Zugriff mit `ifPresent()`, `orElse()` und `orElseThrow()` -* Stream-API: `map()`, `filter()`, `flatMap()`, ... +- `Optional.ofNullable()`: Erzeugen eines `Optional` + - Entweder Objekt "verpackt" (Argument != `null`) + - Oder `Optional.empty()` (Argument == `null`) +- Prüfen mit `isEmpty()` und `ifPresent()` +- Direkter Zugriff mit `ifPresent()`, `orElse()` und `orElseThrow()` +- Stream-API: `map()`, `filter()`, `flatMap()`, ... \bigskip -* Attribute, Parameter und Sammlungen: nicht `Optional` nutzen -* Kein Ersatz für `null`-Prüfung! +- Attribute, Parameter und Sammlungen: nicht `Optional` nutzen +- Kein Ersatz für `null`-Prüfung! ::: notes Schöne Doku: ["Using Optionals"](https://dev.java/learn/api/streams/optionals/). ::: + +::: readings +- @LernJava +- @Ullenboom2021 [Kap. 12.6] +::: + +::: outcomes +- k2: Ich kann erklären, warum Optionals vor allem für Rückgabewerte gedacht sind +- k2: Ich kann erklären, warum kein null zurückgeliefert werden darf, wenn der + Rückgabetyp ein Optional\ ist +- k3: Ich kann (ggf. leere) Optionals mit Optional.ofNullable() erzeugen +- k3: Ich kann auf Optionals klassisch über die direkten Hilfsmethoden der Klasse + zugreifen +- k3: Ich kann auf Optionals elegant per Stream-API zugreifen +::: + +::: challenges +**Optional und Stream-API** + +1. Erklären Sie den folgenden Code-Schnipsel aus dem + [Dungeon](https://github.com/Dungeon-CampusMinden/Dungeon/pull/1831): + + ``` java + Skill fireball = + new Skill( + new FireballSkill( + () -> + hero.fetch(CollideComponent.class) + .map(cc -> cc + .center(hero) + .add(viewDirection.toPoint())) + .orElseThrow( + () -> MissingComponentException.build( + hero, + CollideComponent.class)), + FIREBALL_RANGE, + FIREBALL_SPEED, + FIREBALL_DMG), + 1); + ``` + + Hinweise: + + - `Entity#fetch`: + ` Optional fetch(final Class klass)` + - `CollideComponent#center`: `Point center(final Entity entity)` + - `Point#add`: `Point add(final Point other)` + + + +2. Was würde sich ändern, wenn statt `map` ein `flatMap` verwendet würde? Wie ist + das bei richtigen Streams? + + + +3. Was passiert im folgenden Beispiel? Warum funktioniert das auch ohne terminale + Stream-Operation? + + ``` java + Game.hero() + .flatMap(e -> e.fetch(AmmunitionComponent.class)) + .map(AmmunitionComponent::resetCurrentAmmunition); + ``` + + Hinweis: `Game.hero()`: `static Optional hero()`. + + + +4. Können Sie die beiden obigen Beispiele in "klassischer" Schreibweise + umformulieren? + + + +**String-Handling** + +Können Sie den folgenden Code so umschreiben, dass Sie statt der `if`-Abfragen und +der einzelnen direkten Methodenaufrufe die Stream-API und `Optional` nutzen? + +``` java +String format(final String text, String replacement) { + if (text.isEmpty()) { + return ""; + } + + final String trimmed = text.trim(); + final String withSpacesReplaced = trimmed.replaceAll(" +", replacement); + + return replacement + withSpacesReplaced + replacement; +} +``` + +Ein Aufruf `format(" Hello World ... ", "_");` liefert den String +"`_Hello_World_..._`". + + +::: diff --git a/lecture/java-modern/readme.md b/lecture/java-modern/readme.md index fcce9ea96..e5c5fafc9 100644 --- a/lecture/java-modern/readme.md +++ b/lecture/java-modern/readme.md @@ -1,5 +1,6 @@ --- -title: "Modern Java: Funktionaler Stil und Stream-API" -no_pdf: true no_beamer: true +no_pdf: true +title: "Modern Java: Funktionaler Stil und Stream-API" --- + diff --git a/lecture/java-modern/records.md b/lecture/java-modern/records.md index 65f1aca59..a5f2fc877 100644 --- a/lecture/java-modern/records.md +++ b/lecture/java-modern/records.md @@ -1,101 +1,46 @@ --- -title: "Record-Klassen" -author: "Carsten Gips (HSBI)" -readings: - - "@LernJava" -tldr: | - Häufig schreibt man relativ viel _Boiler Plate Code_, um einfach ein paar Daten plus den - Konstruktor und die Zugriffsmethoden zu kapseln. Und selbst wenn die IDE dies zum Teil - abnehmen kann - lesen muss man diesen Overhead trotzdem noch. - - Für den Fall von Klassen mit `final` Attributen wurden in Java14 die **Record-Klassen** - eingeführt. Statt dem Schlüsselwort `class` wird das neue Schlüsselwort `record` verwendet. - Nach dem Klassennamen kommen in runden Klammern die "Komponenten" - eine Auflistung der - Parameter für den Standardkonstruktor (Typ, Name). Daraus wird automatisch ein "kanonischer - Konstruktor" mit exakt diesen Parametern generiert. Es werden zusätzlich `private final` - Attribute generiert für jede Komponente, und diese werden durch den kanonischen Konstruktor - gesetzt. Außerdem wird für jedes Attribut automatisch ein Getter mit dem Namen des Attributs - generiert (also ohne den Präfix "get"). - - Beispiel: - ```java - public record StudiR(String name, int credits) {} - ``` - - Der Konstruktor und die Getter können überschrieben werden, es können auch eigene Methoden - definiert werden (eigene Konstruktoren _müssen_ den kanonischen Konstruktor aufrufen). Es - gibt außer den über die Komponenten definierten Attribute keine weiteren Attribute. Da eine - Record-Klasse intern von `java.lang.Record` ableitet, kann eine Record-Klasse nicht von - weiteren Klassen ableiten (erben). Man kann aber beliebig viele Interfaces implementieren. - Record-Klassen sind implizit final, d.h. man nicht von Record-Klassen erben. -outcomes: - - k2: "Ich verstehe, dass Record-Klassen implizit `final` sind" - - k2: "Ich weiss, dass Record-Klassen einen kanonischen Konstruktor haben" - - k2: "Ich verstehe, dass die Attribute in Record-Klassen implizit `final` sind und automatisch angelegt und über den Konstruktor gesetzt werden" - - k2: "Ich weiss, dass die Getter in Record-Klassen so benannt sind wie die Namen der Komponenten, also keinen Präfix 'get' haben" - - k2: "Ich weiss, dass der kanonische Konstruktor ergänzt werden kann" - - k2: "Ich weiss, dass weitere Methoden definiert werden können" - - k2: "Ich verstehe, dass Record-Klassen nicht von anderen Klassen erben können, aber Interfaces implementieren können" - - k3: "Ich kann Record-Klassen praktisch einsetzen" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106527&client_id=FH-Bielefeld" -# name: "Quiz Record-Klassen (ILIAS)" -youtube: - - link: "https://youtu.be/5RMhdCsZL6Y" - name: "VL Record-Klassen" - - link: "https://youtu.be/jWBAXWH0MUc" - name: "Demo Record-Klassen" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/461caa1e97e1655109ce66647c169cf01b35d4b268bfaa64a6289c505f247a5ebfde054a98b08a1b5235195ff21b1fffa4d12e3968c7a68a0f001b0dabe6b695" - name: "VL Record-Klassen" -challenges: | - Betrachen Sie den folgenden Code: - - ```java - public interface Person { - String getName(); - Date getBirthday(); - } - - public class Student implements Person { - private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd.MM.yyyy"); - - private final String name; - private final Date birthday; +author: Carsten Gips (HSBI) +title: Record-Klassen +--- - public Student(String name, String birthday) throws ParseException { - this.name = name; - this.birthday = DATE_FORMAT.parse(birthday); - } +::: tldr +Häufig schreibt man relativ viel *Boiler Plate Code*, um einfach ein paar Daten plus +den Konstruktor und die Zugriffsmethoden zu kapseln. Und selbst wenn die IDE dies +zum Teil abnehmen kann - lesen muss man diesen Overhead trotzdem noch. - public String getName() { return name; } - public Date getBirthday() { return birthday; } - } - ``` +Für den Fall von Klassen mit `final` Attributen wurden in Java14 die +**Record-Klassen** eingeführt. Statt dem Schlüsselwort `class` wird das neue +Schlüsselwort `record` verwendet. Nach dem Klassennamen kommen in runden Klammern +die "Komponenten" - eine Auflistung der Parameter für den Standardkonstruktor (Typ, +Name). Daraus wird automatisch ein "kanonischer Konstruktor" mit exakt diesen +Parametern generiert. Es werden zusätzlich `private final` Attribute generiert für +jede Komponente, und diese werden durch den kanonischen Konstruktor gesetzt. +Außerdem wird für jedes Attribut automatisch ein Getter mit dem Namen des Attributs +generiert (also ohne den Präfix "get"). - Schreiben Sie die Klasse `Student` in eine Record-Klasse um. Was müssen Sie zusätzlich noch tun, - damit die aktuelle API erhalten bleibt? +Beispiel: - ---- +Der Konstruktor und die Getter können überschrieben werden, es können auch eigene +Methoden definiert werden (eigene Konstruktoren *müssen* den kanonischen Konstruktor +aufrufen). Es gibt außer den über die Komponenten definierten Attribute keine +weiteren Attribute. Da eine Record-Klasse intern von `java.lang.Record` ableitet, +kann eine Record-Klasse nicht von weiteren Klassen ableiten (erben). Man kann aber +beliebig viele Interfaces implementieren. Record-Klassen sind implizit final, d.h. +man nicht von Record-Klassen erben. +::: +::: youtube +- [VL Record-Klassen](https://youtu.be/5RMhdCsZL6Y) +- [Demo Record-Klassen](https://youtu.be/jWBAXWH0MUc) +::: # Motivation; Klasse Studi -```java +``` java public class Studi { private final String name; private final int credits; @@ -115,55 +60,54 @@ public class Studi { } ``` - # Klasse Studi als Record -```java +``` java public record StudiR(String name, int credits) {} ``` \bigskip \pause -* Immutable Klasse mit Feldern `String name` und `int credits` \newline - => "`(String name, int credits)`" werden "Komponenten" des Records genannt -* Standardkonstruktor setzt diese Felder ("Kanonischer Konstruktor") -* Getter für beide Felder: +- Immutable Klasse mit Feldern `String name` und `int credits` `\newline`{=tex} + =\> "`(String name, int credits)`" werden "Komponenten" des Records genannt - ```java +- Standardkonstruktor setzt diese Felder ("Kanonischer Konstruktor") + +- Getter für beide Felder: + + ``` java public String name() { return this.name; } public int credits() { return this.credits; } ``` ::: notes -Record-Klassen wurden in Java14 eingeführt und werden immer wieder in -neuen Releases erweitert/ergänzt. +Record-Klassen wurden in Java14 eingeführt und werden immer wieder in neuen Releases +erweitert/ergänzt. -Der kanonische Konstruktor hat das Aussehen wie die Record-Deklaration, im -Beispiel also `public StudiR(String name, int credits)`. Dabei werden die -Komponenten über eine Kopie der Werte initialisiert. +Der kanonische Konstruktor hat das Aussehen wie die Record-Deklaration, im Beispiel +also `public StudiR(String name, int credits)`. Dabei werden die Komponenten über +eine Kopie der Werte initialisiert. -Für die Komponenten werden automatisch private Attribute mit dem selben -Namen angelegt. +Für die Komponenten werden automatisch private Attribute mit dem selben Namen +angelegt. Für die Komponenten werden automatisch Getter angelegt. Achtung: Die Namen entsprechen denen der Komponenten, es fehlt also der übliche "get"-Präfix! ::: - # Eigenschaften und Einschränkungen von Record-Klassen -* Records erweitern implizit die Klasse `java.lang.Record`: \newline - Keine andere Klassen mehr erweiterbar! (Interfaces kein Problem) +- Records erweitern implizit die Klasse `java.lang.Record`: `\newline`{=tex} Keine + andere Klassen mehr erweiterbar! (Interfaces kein Problem) -* Record-Klassen sind implizit final +- Record-Klassen sind implizit final -* Keine weiteren (Instanz-) Attribute definierbar (nur die Komponenten) +- Keine weiteren (Instanz-) Attribute definierbar (nur die Komponenten) -* Keine Setter definierbar für die Komponenten: Attribute sind final - -* Statische Attribute mit Initialisierung erlaubt +- Keine Setter definierbar für die Komponenten: Attribute sind final +- Statische Attribute mit Initialisierung erlaubt # Records: Prüfungen im Konstruktor @@ -171,7 +115,7 @@ entsprechen denen der Komponenten, es fehlt also der übliche "get"-Präfix! Der Konstruktor ist erweiterbar: ::: -```java +``` java public record StudiS(String name, int credits) { public StudiS(String name, int credits) { if (name == null) { throw new IllegalArgumentException("Name cannot be null!"); } @@ -186,11 +130,10 @@ public record StudiS(String name, int credits) { ::: notes In dieser Form muss man die Attribute selbst setzen. - Alternativ kann man die "kompakte" Form nutzen: ::: -```java +``` java public record StudiT(String name, int credits) { public StudiT { if (name == null) { throw new IllegalArgumentException("Name cannot be null!"); } @@ -204,26 +147,24 @@ public record StudiT(String name, int credits) { In der kompakten Form kann man nur die Werte der Parameter des Konstruktors ändern. Das Setzen der Attribute ergänzt der Compiler nach dem eigenen Code. +Es sind weitere Konstruktoren definierbar, diese *müssen* den kanonischen +Konstruktor aufrufen: -Es sind weitere Konstruktoren definierbar, diese _müssen_ den kanonischen Konstruktor -aufrufen: - -```java +``` java public StudiT() { this("", 42); } ``` ::: - # Getter und Methoden ::: notes -Getter werden vom Compiler automatisch generiert. Dabei entsprechen die Methoden-Namen -den Namen der Attribute: +Getter werden vom Compiler automatisch generiert. Dabei entsprechen die +Methoden-Namen den Namen der Attribute: ::: -```java +``` java public record StudiR(String name, int credits) {} public static void main(String... args) { @@ -238,7 +179,7 @@ public static void main(String... args) { Getter überschreibbar und man kann weitere Methoden definieren: ::: -```java +``` java public record StudiT(String name, int credits) { public int credits() { return credits + 42; } public void wuppie() { System.out.println("WUPPIE"); } @@ -246,11 +187,10 @@ public record StudiT(String name, int credits) { ``` ::: notes -Die Komponenten/Attribute sind aber `final` und können nicht über Methoden -geändert werden! +Die Komponenten/Attribute sind aber `final` und können nicht über Methoden geändert +werden! ::: - ::: notes # Beispiel aus den Challenges @@ -263,23 +203,85 @@ Getter für die einzelnen Eigenschaften. Das braucht 18 Zeilen Code (ohne Kommen Leerzeilen). Zudem erzeugt der Boilerplate-Code relativ viel "visual noise", so dass der eigentliche Kern der Klasse schwerer zu erkennen ist. -In einem Refactoring wurde diese Klasse durch eine äquivalente Record-Klasse ersetzt, -die nur noch 2 Zeilen Code (je nach Code-Style auch nur 1 Zeile) benötigt. Gleichzeitig -wurde die Les- und Wartbarkeit deutlich verbessert. +In einem Refactoring wurde diese Klasse durch eine äquivalente Record-Klasse +ersetzt, die nur noch 2 Zeilen Code (je nach Code-Style auch nur 1 Zeile) benötigt. +Gleichzeitig wurde die Les- und Wartbarkeit deutlich verbessert. ![](images/screenshot_katze.png) ::: - # Wrap-Up -* Records sind immutable Klassen: - * `final` Attribute (entsprechend den Komponenten) - * Kanonischer Konstruktor - * Automatische Getter (Namen wie Komponenten) -* Konstruktoren und Methoden können ergänzt/überschrieben werden -* Keine Vererbung von Klassen möglich (kein `extends`) +- Records sind immutable Klassen: + - `final` Attribute (entsprechend den Komponenten) + - Kanonischer Konstruktor + - Automatische Getter (Namen wie Komponenten) +- Konstruktoren und Methoden können ergänzt/überschrieben werden +- Keine Vererbung von Klassen möglich (kein `extends`) ::: notes -Schöne Doku: ["Using Record to Model Immutable Data"](https://dev.java/learn/using-record-to-model-immutable-data/). +Schöne Doku: ["Using Record to Model Immutable +Data"](https://dev.java/learn/using-record-to-model-immutable-data/). +::: + +::: readings +- @LernJava +::: + +::: outcomes +- k2: Ich verstehe, dass Record-Klassen implizit final sind +- k2: Ich weiss, dass Record-Klassen einen kanonischen Konstruktor haben +- k2: Ich verstehe, dass die Attribute in Record-Klassen implizit final sind und + automatisch angelegt und über den Konstruktor gesetzt werden +- k2: Ich weiss, dass die Getter in Record-Klassen so benannt sind wie die Namen + der Komponenten, also keinen Präfix 'get' haben +- k2: Ich weiss, dass der kanonische Konstruktor ergänzt werden kann +- k2: Ich weiss, dass weitere Methoden definiert werden können +- k2: Ich verstehe, dass Record-Klassen nicht von anderen Klassen erben können, + aber Interfaces implementieren können +- k3: Ich kann Record-Klassen praktisch einsetzen +::: + +::: challenges +Betrachen Sie den folgenden Code: + +``` java +public interface Person { + String getName(); + Date getBirthday(); +} + +public class Student implements Person { + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd.MM.yyyy"); + + private final String name; + private final Date birthday; + + public Student(String name, String birthday) throws ParseException { + this.name = name; + this.birthday = DATE_FORMAT.parse(birthday); + } + + public String getName() { return name; } + public Date getBirthday() { return birthday; } +} +``` + +Schreiben Sie die Klasse `Student` in eine Record-Klasse um. Was müssen Sie +zusätzlich noch tun, damit die aktuelle API erhalten bleibt? + + ::: diff --git a/lecture/java-modern/stream-api.md b/lecture/java-modern/stream-api.md index 64c20efa1..3822258ed 100644 --- a/lecture/java-modern/stream-api.md +++ b/lecture/java-modern/stream-api.md @@ -1,175 +1,57 @@ --- -title: "Stream-API" -author: "Carsten Gips (HSBI)" -readings: - - "@LernJava" - - "@Ullenboom2021 [Kap. 17.3-17.6]" -tldr: | - Mit der Collection-API existiert in Java die Möglichkeit, Daten auf verschiedenste Weisen zu - speichern (`Collection`). Mit der Stream-API gibt es die Möglichkeit, diese Daten in einer - Art Pipeline zu verarbeiten. Ein `Stream` ist eine Folge von Objekten vom Typ `T`. Die - Verarbeitung der Daten ist "lazy", d.h. sie erfolgt erst auf Anforderung (durch die terminale - Operation). - - Ein Stream hat eine Datenquelle und kann beispielsweise über `Collection#stream()` oder - `Stream.of()` angelegt werden. Streams speichern keine Daten. Die Daten werden aus der - verbundenen Datenquelle geholt. - - Auf einem Stream kann man eine Folge von intermediären Operationen wie `peek()`, `map()`, - `flatMap()`, `filter()`, `sorted()` ... durchführen. Alle diese Operationen arbeiten auf - dem Stream und erzeugen einen neuen Stream als Ergebnis. Dadurch kann die typische - Pipeline-artige Verkettung der Operationen ermöglicht werden. Die intermediären Operationen - werden erst ausgeführt, wenn der Stream durch eine terminale Operation geschlossen wird. - - Terminale Operationen wie `count()`, `forEach()`, `allMatch()` oder `collect()` - * `collect(Collectors.toList())` (bzw. direkt mit `stream.toList()` (ab Java16)) - * `collect(Collectors.toSet())` - * `collect(Collectors.toCollection(LinkedList::new))` (als `Supplier`) - - stoßen die Verarbeitung des Streams an und schließen den Stream damit ab. - - Wir können hier nur die absoluten Grundlagen betrachten. Die Stream-API ist sehr groß und - mächtig und lohnt die weitere selbstständige Auseinandersetzung :-) -outcomes: - - k2: "Ich verstehe, dass Streams die Daten nicht sofort verarbeiten ('lazy' Verarbeitung)" - - k2: "Ich verstehe, dass ich mit `map()` den Typ (und Inhalt) von Objekten im Stream, aber nicht die Anzahl verändere" - - k2: "Ich verstehe, dass ich mit `filter()` die Anzahl der Objekte im Stream, aber nicht deren Typ (und Inhalt) verändere" - - k2: "Ich verstehe, warum Streams nicht in Attributen gehalten oder als Parameter herumgereicht werden sollten" - - k3: "Ich kann einen Stream erzeugen" - - k3: "Ich kann verschiedene intermediäre Operationen verketten" - - k3: "Ich kann mit einer terminalen Operation einen Stream abschließen und damit die Berechnung durchführen" - - k3: "Ich kann `flatMap()` einsetzen" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106528&client_id=FH-Bielefeld" -# name: "Quiz Stream-API (ILIAS)" -youtube: - - link: "https://youtu.be/zZMyk0u5hJk" - name: "VL Stream-API" - - link: "https://youtu.be/KBP72tCkBt8" - name: "Demo Stream-API" - - link: "https://youtu.be/jzEw8IH8Mfc" - name: "Demo Vordefinierte funktionale Interfaces im JDK" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/429bb37eaea02582785bfb46a92d68a3ed76cb18bdc98ec0f04ae438cecf82a595e1e46947d2ffcc2fd868d67ca1ed3beba73f216ae4886f2a9492167c006784" - name: "VL Stream-API" -challenges: | - Betrachten Sie den folgenden Java-Code: - - ```java - record Cat(int weight){}; - - public class Main { - public static void main(String... args) { - List clouder = new ArrayList<>(); - clouder.add(new Cat(100)); clouder.add(new Cat(1)); clouder.add(new Cat(10)); - - sumOverWeight(8, clouder); - } - - private static int sumOverWeight(int threshold, List cats) { - int result = 0; - for (Cat c : cats) { - int weight = c.weight(); - if (weight > threshold) { - result += weight; - } - } - return result; - } - } - ``` - - Schreiben Sie die Methode `sumOverWeight` unter Beibehaltung der Funktionalität so - um, dass statt der `for`-Schleife und der `if`-Abfrage Streams und Stream-Operationen - eingesetzt werden. Nutzen Sie passende Lambda-Ausdrücke und nach Möglichkeit - Methodenreferenzen. - - +author: Carsten Gips (HSBI) +title: Stream-API +--- +::: tldr +Mit der Collection-API existiert in Java die Möglichkeit, Daten auf verschiedenste +Weisen zu speichern (`Collection`). Mit der Stream-API gibt es die Möglichkeit, +diese Daten in einer Art Pipeline zu verarbeiten. Ein `Stream` ist eine Folge von +Objekten vom Typ `T`. Die Verarbeitung der Daten ist "lazy", d.h. sie erfolgt erst +auf Anforderung (durch die terminale Operation). - Betrachten Sie den folgenden Java-Code: +Ein Stream hat eine Datenquelle und kann beispielsweise über `Collection#stream()` +oder `Stream.of()` angelegt werden. Streams speichern keine Daten. Die Daten werden +aus der verbundenen Datenquelle geholt. - ```java - public class Main { - public static String getParameterNamesJson(String[] parameterNames) { - if (parameterNames.length == 0) { - return "[]"; - } +Auf einem Stream kann man eine Folge von intermediären Operationen wie `peek()`, +`map()`, `flatMap()`, `filter()`, `sorted()` ... durchführen. Alle diese Operationen +arbeiten auf dem Stream und erzeugen einen neuen Stream als Ergebnis. Dadurch kann +die typische Pipeline-artige Verkettung der Operationen ermöglicht werden. Die +intermediären Operationen werden erst ausgeführt, wenn der Stream durch eine +terminale Operation geschlossen wird. - StringBuilder sb = new StringBuilder(); - sb.append("["); - for (int i = 0; i < parameterNames.length; i++) { - if (i > 0) { - sb.append(", "); - } - sb.append("\"").append(escapeJson(parameterNames[i])).append("\""); - } - sb.append("]"); - return sb.toString(); - } +Terminale Operationen wie `count()`, `forEach()`, `allMatch()` oder `collect()` - private static String escapeJson(String parameterName) { - // does something or another ... - } - } - ``` - - Schreiben Sie die Methode `getParameterNamesJson` unter Beibehaltung der Funktionalität - so um, dass statt der `for`-Schleife und der `if`-Abfrage Streams und Stream-Operationen - eingesetzt werden. Nutzen Sie passende Lambda-Ausdrücke und nach Möglichkeit auch - Methodenreferenzen. - - ---- +Wir können hier nur die absoluten Grundlagen betrachten. Die Stream-API ist sehr +groß und mächtig und lohnt die weitere selbstständige Auseinandersetzung :-) +::: +::: youtube +- [VL Stream-API](https://youtu.be/zZMyk0u5hJk) +- [Demo Stream-API](https://youtu.be/KBP72tCkBt8) +- [Demo Vordefinierte funktionale Interfaces im JDK](https://youtu.be/jzEw8IH8Mfc) +::: # Motivation ::: notes -Es wurden Studis, Studiengänge und Fachbereiche modelliert (aus Gründen der Übersichtlichkeit -einfach als Record-Klassen). +Es wurden Studis, Studiengänge und Fachbereiche modelliert (aus Gründen der +Übersichtlichkeit einfach als Record-Klassen). -Nun soll pro Fachbereich die Anzahl der Studis ermittelt werden, die bereits 100 ECTS -oder mehr haben. Dazu könnte man über alle Studiengänge im Fachbereich iterieren, und -in der inneren Schleife über alle Studis im Studiengang. Dann filtert man alle Studis, -deren ECTS größer 100 sind und erhöht jeweils den Zähler: +Nun soll pro Fachbereich die Anzahl der Studis ermittelt werden, die bereits 100 +ECTS oder mehr haben. Dazu könnte man über alle Studiengänge im Fachbereich +iterieren, und in der inneren Schleife über alle Studis im Studiengang. Dann filtert +man alle Studis, deren ECTS größer 100 sind und erhöht jeweils den Zähler: ::: -```java +``` java public record Studi(String name, int credits) {} public record Studiengang(String name, List studis) {} public record Fachbereich(String name, List studiengaenge) {} @@ -190,10 +72,9 @@ Dies ist ein Beispiel, welches klassisch in OO-Manier als Iteration über Klasse realisiert ist. (Inhaltlich ist es vermutlich nicht sooo sinnvoll.) ::: - # Innere Schleife mit Streams umgeschrieben -```java +``` java private static long getCountSG(Studiengang sg) { return sg.studis().stream() .map(Studi::credits) @@ -210,64 +91,59 @@ private static long getCountFB2(Fachbereich fb) { } ``` -::::::::: notes +::: notes ## Erklärung des Beispiels Im Beispiel wurde die innere Schleife in einen Stream ausgelagert. -Mit der Methode `Collection#stream()` wird aus der Collection ein -neuer Stream erzeugt. Auf diesem wird für jedes Element durch die -Methode `map()` die Methode `Studi#credits()` angewendet, was aus -einem Strom von `Studi` einen Strom von `Integer` macht. Mit `filter()` -wird auf jedes Element das Prädikat `c -> c > 100` angewendet und -alle Elemente aus dem Strom entfernt, die der Bedingung nicht -entsprechen. Am Ende wird mit `count()` gezählt, wie viele Elemente -im Strom enthalten sind. +Mit der Methode `Collection#stream()` wird aus der Collection ein neuer Stream +erzeugt. Auf diesem wird für jedes Element durch die Methode `map()` die Methode +`Studi#credits()` angewendet, was aus einem Strom von `Studi` einen Strom von +`Integer` macht. Mit `filter()` wird auf jedes Element das Prädikat `c -> c > 100` +angewendet und alle Elemente aus dem Strom entfernt, die der Bedingung nicht +entsprechen. Am Ende wird mit `count()` gezählt, wie viele Elemente im Strom +enthalten sind. ## Was ist ein Stream? -Ein "Stream" ist ein Strom (Folge) von Daten oder Objekten. In Java wird -die Collections-API für die Speicherung von Daten (Objekten) verwendet. -Die Stream-API dient zur Iteration über diese Daten und entsprechend -zur Verarbeitung der Daten. In Java speichert ein Stream keine Daten. - -Das Konzept kommt aus der funktionalen Programmierung und wurde in Java -nachträglich eingebaut (wobei dieser Prozess noch lange nicht abgeschlossen -zu sein scheint). - -In der funktionalen Programmierung kennt man die Konzepte "map", "filter" -und "reduce": Die Funktion "map()" erhält als Parameter eine Funktion und -wendet diese auf alle Elemente eines Streams an. Die Funktion "filter()" -bekommt ein Prädikat als Parameter und prüft jedes Element im Stream, ob -es dem Prädikat genügt (also ob das Prädikat mit dem jeweiligen Element -zu `true` evaluiert - die anderen Objekte werden entfernt). Mit "reduce()" -kann man Streams zu einem einzigen Wert zusammenfassen (denken Sie etwa -an das Aufsummieren aller Elemente eines Integer-Streams). Zusätzlich kann -man in der funktionalen Programmierung ohne Probleme unendliche Ströme -darstellen: Die Auswertung erfolgt nur bei Bedarf und auch dann auch nur -so weit wie nötig. Dies nennt man auch "_lazy evaluation_". - -Die Streams in Java versuchen, diese Konzepte aus der funktionalen Programmierung -in die objektorientierte Programmierung zu übertragen. Ein Stream in Java -hat eine Datenquelle, von wo die Daten gezogen werden - ein Stream speichert -selbst keine Daten. Es gibt "intermediäre Operationen" auf einem Stream, -die die Elemente verarbeiten und das Ergebnis als Stream zurückliefern. Daraus -ergibt sich typische Pipeline-artige Verkettung der Operationen. Allerdings -werden diese Operationen erst durchgeführt, wenn eine "terminale Operation" den -Stream "abschließt". Ein Stream ohne eine terminale Operation macht also -tatsächlich _nichts_. - -Die Operationen auf dem Stream sind üblicherweise zustandslos, können aber -durchaus auch einen Zustand haben. Dies verhindert üblicherweise die parallele -Verarbeitung der Streams. Operationen sollten aber nach Möglichkeit keine -_Seiteneffekte_ haben, d.h. keine Daten außerhalb des Streams modifizieren. -Operationen dürfen auf keinen Fall die Datenquelle des Streams modifizieren! -::::::::: - +Ein "Stream" ist ein Strom (Folge) von Daten oder Objekten. In Java wird die +Collections-API für die Speicherung von Daten (Objekten) verwendet. Die Stream-API +dient zur Iteration über diese Daten und entsprechend zur Verarbeitung der Daten. In +Java speichert ein Stream keine Daten. + +Das Konzept kommt aus der funktionalen Programmierung und wurde in Java nachträglich +eingebaut (wobei dieser Prozess noch lange nicht abgeschlossen zu sein scheint). + +In der funktionalen Programmierung kennt man die Konzepte "map", "filter" und +"reduce": Die Funktion "map()" erhält als Parameter eine Funktion und wendet diese +auf alle Elemente eines Streams an. Die Funktion "filter()" bekommt ein Prädikat als +Parameter und prüft jedes Element im Stream, ob es dem Prädikat genügt (also ob das +Prädikat mit dem jeweiligen Element zu `true` evaluiert - die anderen Objekte werden +entfernt). Mit "reduce()" kann man Streams zu einem einzigen Wert zusammenfassen +(denken Sie etwa an das Aufsummieren aller Elemente eines Integer-Streams). +Zusätzlich kann man in der funktionalen Programmierung ohne Probleme unendliche +Ströme darstellen: Die Auswertung erfolgt nur bei Bedarf und auch dann auch nur so +weit wie nötig. Dies nennt man auch "*lazy evaluation*". + +Die Streams in Java versuchen, diese Konzepte aus der funktionalen Programmierung in +die objektorientierte Programmierung zu übertragen. Ein Stream in Java hat eine +Datenquelle, von wo die Daten gezogen werden - ein Stream speichert selbst keine +Daten. Es gibt "intermediäre Operationen" auf einem Stream, die die Elemente +verarbeiten und das Ergebnis als Stream zurückliefern. Daraus ergibt sich typische +Pipeline-artige Verkettung der Operationen. Allerdings werden diese Operationen erst +durchgeführt, wenn eine "terminale Operation" den Stream "abschließt". Ein Stream +ohne eine terminale Operation macht also tatsächlich *nichts*. + +Die Operationen auf dem Stream sind üblicherweise zustandslos, können aber durchaus +auch einen Zustand haben. Dies verhindert üblicherweise die parallele Verarbeitung +der Streams. Operationen sollten aber nach Möglichkeit keine *Seiteneffekte* haben, +d.h. keine Daten außerhalb des Streams modifizieren. Operationen dürfen auf keinen +Fall die Datenquelle des Streams modifizieren! +::: # Erzeugen von Streams -```java +``` java List l1 = List.of("Hello", "World", "foo", "bar", "wuppie"); Stream s1 = l1.stream(); @@ -281,8 +157,8 @@ Stream s4 = pattern.splitAsStream("Hello world! foo bar wuppie!"); ``` ::: notes -Dies sind möglicherweise die wichtigsten Möglichkeiten, in Java einen Stream -zu erzeugen. +Dies sind möglicherweise die wichtigsten Möglichkeiten, in Java einen Stream zu +erzeugen. Ausgehend von einer Klasse aus der Collection-API kann man die Methode `Collection#stream()` aufrufen und bekommt einen seriellen Stream. @@ -291,8 +167,8 @@ Alternativ bietet das Interface `Stream` verschiedene statische Methoden wie `Stream.of()` an, mit deren Hilfe Streams angelegt werden können. Dies funktioniert auch mit Arrays ... -Und schließlich kann man per `Stream.generate()` einen Stream anlegen, wobei -als Argument ein "Supplier" (Interface `java.util.function.Supplier`) übergeben +Und schließlich kann man per `Stream.generate()` einen Stream anlegen, wobei als +Argument ein "Supplier" (Interface `java.util.function.Supplier`) übergeben werden muss. Dieses Argument wird dann benutzt, um die Daten für den Stream zu generieren. @@ -305,10 +181,9 @@ Streams anlegen, wenn dadurch tatsächlich Vorteile durch die Parallelisierung z erwarten sind (Overhead!). ::: - # Intermediäre Operationen auf Streams -```java +``` java private static void dummy(Studiengang sg) { sg.studis().stream() .peek(s -> System.out.println("Looking at: " + s.name())) @@ -325,58 +200,58 @@ private static void dummy(Studiengang sg) { An diesem (weitestgehend sinnfreien) Beispiel werden einige intermediäre Operationen demonstriert. -Die Methode `peek()` liefert einen Stream zurück, die aus den Elementen des Eingabestroms -bestehen. Auf jedes Element wird die Methode `void accept(T)` des `Consumer` angewendet -(Argument der Methode), was aber nicht zu einer Änderung der Daten führt. -**Hinweis**: Diese Methode dient vor allem zu Debug-Zwecken! Durch den Seiteneffekt kann -die Methode eine schlechtere Laufzeit zur Folge haben oder sogar eine sonst mögliche -parallele Verarbeitung verhindern oder durch eine parallele Verarbeitung verwirrende -Ergebnisse zeigen! - -Die Methode `map()` liefert ebenfalls einen Stream zurück, der durch die Anwendung der Methode -`R apply(T)` der als Argument übergebenen `Function` auf jedes Element des Eingabestroms -entsteht. Damit lassen sich die Elemente des ursprünglichen Streams verändern; für jedes Element -gibt es im Ergebnis-Stream ebenfalls ein Element (der Typ ändert sich, aber nicht die Anzahl -der Elemente). - -Mit der Methode `filter()` wird ein Stream erzeugt, der alle Objekte des Eingabe-Streams -enthält, auf denen die Anwendung der Methode `boolean test(T)` des Arguments `Predicate` -zu `true` evaluiert (der Typ und Inhalt der Elemente ändert sich nicht, aber die Anzahl der -Elemente). - -Mit `sorted()` wird ein Stream erzeugt, der die Elemente des Eingabe-Streams sortiert -(existiert auch mit einem `Comparator` als Parameter). - -Diese Methoden sind alles **intermediäre** Operationen. Diese arbeiten auf einem Stream und -erzeugen einen neuen Stream und werden erst dann ausgeführt, wenn eine terminale Operation den -Stream abschließt. - -Dabei sind die gezeigten intermediären Methoden bis auf `sorted()` ohne inneren Zustand. -`sorted()` ist eine Operation mit innerem Zustand (wird für das Sortieren benötigt). Dies -kann ordentlich in Speicher und Zeit zuschlagen und u.U. nicht/nur schlecht parallelisierbar -sein. Betrachten Sie den fiktiven parallelen Stream `stream.parallel().sorted().skip(42)`: -Hier müssen erst _alle_ Elemente sortiert werden, bevor mit `skip(42)` die ersten 42 Elemente -entfernt werden. Dies kann auch nicht mehr parallel durchgeführt werden. - - -Die Methode `forEach()` schließlich ist eine **terminale** Operation, die auf jedes Element des -Eingabe-Streams die Methode `void accept(T)` des übergebenen `Consumer` anwendet. Diese -Methode ist eine **terminale Operation**, d.h. sie führt zur Auswertung der anderen _intermediären_ -Operationen und schließt den Stream ab. +Die Methode `peek()` liefert einen Stream zurück, die aus den Elementen des +Eingabestroms bestehen. Auf jedes Element wird die Methode `void accept(T)` des +`Consumer` angewendet (Argument der Methode), was aber nicht zu einer Änderung +der Daten führt. **Hinweis**: Diese Methode dient vor allem zu Debug-Zwecken! Durch +den Seiteneffekt kann die Methode eine schlechtere Laufzeit zur Folge haben oder +sogar eine sonst mögliche parallele Verarbeitung verhindern oder durch eine +parallele Verarbeitung verwirrende Ergebnisse zeigen! + +Die Methode `map()` liefert ebenfalls einen Stream zurück, der durch die Anwendung +der Methode `R apply(T)` der als Argument übergebenen `Function` auf jedes +Element des Eingabestroms entsteht. Damit lassen sich die Elemente des +ursprünglichen Streams verändern; für jedes Element gibt es im Ergebnis-Stream +ebenfalls ein Element (der Typ ändert sich, aber nicht die Anzahl der Elemente). + +Mit der Methode `filter()` wird ein Stream erzeugt, der alle Objekte des +Eingabe-Streams enthält, auf denen die Anwendung der Methode `boolean test(T)` des +Arguments `Predicate` zu `true` evaluiert (der Typ und Inhalt der Elemente ändert +sich nicht, aber die Anzahl der Elemente). + +Mit `sorted()` wird ein Stream erzeugt, der die Elemente des Eingabe-Streams +sortiert (existiert auch mit einem `Comparator` als Parameter). + +Diese Methoden sind alles **intermediäre** Operationen. Diese arbeiten auf einem +Stream und erzeugen einen neuen Stream und werden erst dann ausgeführt, wenn eine +terminale Operation den Stream abschließt. + +Dabei sind die gezeigten intermediären Methoden bis auf `sorted()` ohne inneren +Zustand. `sorted()` ist eine Operation mit innerem Zustand (wird für das Sortieren +benötigt). Dies kann ordentlich in Speicher und Zeit zuschlagen und u.U. nicht/nur +schlecht parallelisierbar sein. Betrachten Sie den fiktiven parallelen Stream +`stream.parallel().sorted().skip(42)`: Hier müssen erst *alle* Elemente sortiert +werden, bevor mit `skip(42)` die ersten 42 Elemente entfernt werden. Dies kann auch +nicht mehr parallel durchgeführt werden. + +Die Methode `forEach()` schließlich ist eine **terminale** Operation, die auf jedes +Element des Eingabe-Streams die Methode `void accept(T)` des übergebenen +`Consumer` anwendet. Diese Methode ist eine **terminale Operation**, d.h. sie +führt zur Auswertung der anderen *intermediären* Operationen und schließt den Stream +ab. ::: - # Was tun, wenn eine Methode Streams zurückliefert ::: notes Wir konnten vorhin nur die innere Schleife in eine Stream-basierte Verarbeitung -umbauen. Das Problem ist: Die äußere Schleife würde einen Stream liefern (Stream -von Studiengängen), auf dem wir die `map`-Funktion anwenden müssten und darin dann -für jeden Studiengang einen (inneren) Stream mit den Studis eines Studiengangs +umbauen. Das Problem ist: Die äußere Schleife würde einen Stream liefern (Stream von +Studiengängen), auf dem wir die `map`-Funktion anwenden müssten und darin dann für +jeden Studiengang einen (inneren) Stream mit den Studis eines Studiengangs verarbeiten müssten. ::: -```java +``` java private static long getCountSG(Studiengang sg) { return sg.studis().stream().map(Studi::credits).filter(c -> c > 100).count(); } @@ -391,8 +266,8 @@ private static long getCountFB2(Fachbereich fb) { ``` ::: notes -Dafür ist die Methode `flatMap()` die Lösung. Diese Methode bekommt als Argument -ein Objekt vom Typ `Function>` mit einer +Dafür ist die Methode `flatMap()` die Lösung. Diese Methode bekommt als Argument ein +Objekt vom Typ `Function>` mit einer Methode `Stream apply(T)`. Die Methode `flatMap()` verarbeitet den Stream in zwei Schritten: @@ -403,12 +278,12 @@ Stream in zwei Schritten: `Stream>`. 2. "Klopfe den verschachtelten Stream wieder flach", d.h. nimm die einzelnen - `Studi`-Objekte aus den `Stream`-Objekten und setze diese stattdessen - in den Stream. Das Ergebnis ist dann wie gewünscht ein `Stream` (Stream - mit `Studi`-Objekten). + `Studi`-Objekte aus den `Stream`-Objekten und setze diese stattdessen in + den Stream. Das Ergebnis ist dann wie gewünscht ein `Stream` (Stream mit + `Studi`-Objekten). ::: -```java +``` java private static long getCountFB3(Fachbereich fb) { return fb.studiengaenge().stream() .flatMap(sg -> sg.studis().stream()) @@ -422,7 +297,7 @@ private static long getCountFB3(Fachbereich fb) { Zum direkten Vergleich hier noch einmal der ursprüngliche Code mit zwei verschachtelten Schleifen und entsprechenden Hilfsvariablen: -```java +``` java private static long getCountFB(Fachbereich fb) { long count = 0; for (Studiengang sg : fb.studiengaenge()) { @@ -442,10 +317,9 @@ weshalb man im Ergebnis wie bei `map` aus einem `stream` einen `stream` erhält. ::: - # Streams abschließen: Terminale Operationen -```java +``` java Stream s = Stream.of("Hello", "World", "foo", "bar", "wuppie"); long count = s.count(); @@ -460,90 +334,225 @@ List s4 = s.collect(Collectors.toCollection(LinkedList::new)); ``` ::: notes -Streams müssen mit **_einer_ terminalen Operation** abgeschlossen werden, damit die Verarbeitung -tatsächlich angestoßen wird (_lazy evaluation_).^[Anmerkung: Das obige Beispiel dient als Überblick -gebräuchlicher terminaler Operationen, es ist nicht als lauffähiges Programm gedacht! Auf einem -Stream kann immer nur **eine** terminale Operation ausgeführt werden - d.h. nach der Ausführung -von `s.count()` wäre der Stream `s` verarbeitet und es können keine weiteren Operationen auf diesem -Stream durchgeführt werden. Dito für die anderen gezeigten terminalen Operationen.] +Streams müssen mit ***einer* terminalen Operation** abgeschlossen werden, damit die +Verarbeitung tatsächlich angestoßen wird (*lazy evaluation*).[^1] -Es gibt viele verschiedene terminale Operationen. Wir haben bereits `count()` und `forEach()` -gesehen. In der Sitzung zu ["Optionals"](optional.md) werden wir noch `findFirst()` näher kennenlernen. +Es gibt viele verschiedene terminale Operationen. Wir haben bereits `count()` und +`forEach()` gesehen. In der Sitzung zu ["Optionals"](optional.md) werden wir noch +`findFirst()` näher kennenlernen. -Daneben gibt es beispielsweise noch `allMatch()`, `anyMatch()` und `noneMatch()`, die jeweils -ein Prädikat testen und einen Boolean zurückliefern (matchen alle, mind. eines oder keines der -Objekte im Stream). +Daneben gibt es beispielsweise noch `allMatch()`, `anyMatch()` und `noneMatch()`, +die jeweils ein Prädikat testen und einen Boolean zurückliefern (matchen alle, mind. +eines oder keines der Objekte im Stream). -Mit `min()` und `max()` kann man sich das kleinste und das größte Element des Streams liefern -lassen. Beide Methoden benötigen dazu einen `Comparator` als Parameter. +Mit `min()` und `max()` kann man sich das kleinste und das größte Element des +Streams liefern lassen. Beide Methoden benötigen dazu einen `Comparator` als +Parameter. -Mit der Methode `collect()` kann man eine der drei Methoden aus `Collectors` über den Stream -laufen lassen und eine `Collection` erzeugen lassen: +Mit der Methode `collect()` kann man eine der drei Methoden aus `Collectors` über +den Stream laufen lassen und eine `Collection` erzeugen lassen: -1. `toList()` sammelt die Elemente in ein `List`-Objekt (bzw. direkt mit `stream.toList()` (ab Java16)) +1. `toList()` sammelt die Elemente in ein `List`-Objekt (bzw. direkt mit + `stream.toList()` (ab Java16)) 2. `toSet()` sammelt die Elemente in ein `Set`-Objekt -3. `toCollection()` sammelt die Elemente durch Anwendung der Methode `T get()` des übergebenen - `Supplier`-Objekts auf +3. `toCollection()` sammelt die Elemente durch Anwendung der Methode `T get()` des + übergebenen `Supplier`-Objekts auf +Die ist nur die sprichwörtliche "Spitze des Eisbergs"! Es gibt viele weitere +Möglichkeiten, sowohl bei den intermediären als auch den terminalen Operationen. +Schauen Sie in die Dokumentation! -Die ist nur die sprichwörtliche "Spitze des Eisbergs"! Es gibt viele weitere Möglichkeiten, sowohl -bei den intermediären als auch den terminalen Operationen. Schauen Sie in die Dokumentation! - -[Demo: streams.Demo]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/streams/Demo.java"} +[Demo: streams.Demo]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-modern/src/streams/Demo.java"} ::: - # Spielregeln -* Operationen dürfen nicht die Stream-Quelle modifizieren - -* Operationen können die Werte im Stream ändern (`map`) oder die Anzahl (`filter`) +- Operationen dürfen nicht die Stream-Quelle modifizieren -* Keine Streams in Attributen/Variablen speichern oder als Argumente übergeben: Sie könnten bereits "gebraucht" sein! +- Operationen können die Werte im Stream ändern (`map`) oder die Anzahl (`filter`) - => Ein Stream sollte immer sofort nach der Erzeugung benutzt werden +- Keine Streams in Attributen/Variablen speichern oder als Argumente übergeben: + Sie könnten bereits "gebraucht" sein! -* Operationen auf einem Stream sollten keine Seiteneffekte (Veränderungen von Variablen/Attributen außerhalb des Streams) haben - [(dies verhindert u.U. die parallele Verarbeitung)]{.notes} + =\> Ein Stream sollte immer sofort nach der Erzeugung benutzt werden +- Operationen auf einem Stream sollten keine Seiteneffekte (Veränderungen von + Variablen/Attributen außerhalb des Streams) haben [(dies verhindert u.U. die + parallele Verarbeitung)]{.notes} # Wrap-Up -`Stream`: Folge von Objekten vom Typ `T`, Verarbeitung "lazy" -[(Gegenstück zu `Collection`: Dort werden Daten **gespeichert**, hier werden Daten **verarbeitet**)]{.notes} +`Stream`: Folge von Objekten vom Typ `T`, Verarbeitung "lazy" [(Gegenstück zu +`Collection`: Dort werden Daten **gespeichert**, hier werden Daten +**verarbeitet**)]{.notes} -:::::: notes +:::: notes ::: tip **Fließband-Metapher** -Einen Stream kann man sich vielleicht wie ein Fließband in einer Fabrik vorstellen: Die Daten -werden auf dem Fließband in eine Richtung transportiert und durchlaufen verschiedene Stationen, -wo auf den Daten gearbeitet wird. In manchen Stationen werden Objekte vom Fließband geschubst -(Daten herausgefiltert), in manchen Stationen werden die Objekte bearbeitet (Daten verändert), -in manchen Stationen werden aus mehreren Teilen neue Objekte gebaut ... +Einen Stream kann man sich vielleicht wie ein Fließband in einer Fabrik vorstellen: +Die Daten werden auf dem Fließband in eine Richtung transportiert und durchlaufen +verschiedene Stationen, wo auf den Daten gearbeitet wird. In manchen Stationen +werden Objekte vom Fließband geschubst (Daten herausgefiltert), in manchen Stationen +werden die Objekte bearbeitet (Daten verändert), in manchen Stationen werden aus +mehreren Teilen neue Objekte gebaut ... -Es ist nur eine Metapher! Sie endet spätestens damit, dass die Streams *lazy* sind und dass -sämtliche Operationen erst dann ausgeführt werden, wenn eine terminale Operation den Stream -abschließt. +Es ist nur eine Metapher! Sie endet spätestens damit, dass die Streams *lazy* sind +und dass sämtliche Operationen erst dann ausgeführt werden, wenn eine terminale +Operation den Stream abschließt. ::: -:::::: +:::: \bigskip -* Neuen Stream anlegen: `Collection#stream()` oder `Stream.of()` ... -* Intermediäre Operationen: `peek()`, `map()`, `flatMap()`, `filter()`, `sorted()` ... -* Terminale Operationen: `count()`, `forEach()`, `allMatch()`, `collect()` ... - * `collect(Collectors.toList())` - * `collect(Collectors.toSet())` - * `collect(Collectors.toCollection())` (mit `Supplier`) +- Neuen Stream anlegen: `Collection#stream()` oder `Stream.of()` ... +- Intermediäre Operationen: `peek()`, `map()`, `flatMap()`, `filter()`, `sorted()` + ... +- Terminale Operationen: `count()`, `forEach()`, `allMatch()`, `collect()` ... + - `collect(Collectors.toList())` + - `collect(Collectors.toSet())` + - `collect(Collectors.toCollection())` (mit `Supplier`) \smallskip -* Streams speichern keine Daten -* Intermediäre Operationen laufen erst bei Abschluss des Streams los -* Terminale Operation führt zur Verarbeitung und Abschluss des Streams +- Streams speichern keine Daten +- Intermediäre Operationen laufen erst bei Abschluss des Streams los +- Terminale Operation führt zur Verarbeitung und Abschluss des Streams ::: notes Schöne Doku: ["The Stream API"](https://dev.java/learn/api/streams/), und auch -["Package java.util.stream"](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/stream/package-summary.html). +["Package +java.util.stream"](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/stream/package-summary.html). +::: + +::: readings +- @LernJava +- @Ullenboom2021 [Kap. 17.3-17.6] ::: + +::: outcomes +- k2: Ich verstehe, dass Streams die Daten nicht sofort verarbeiten ('lazy' + Verarbeitung) +- k2: Ich verstehe, dass ich mit map() den Typ (und Inhalt) von Objekten im + Stream, aber nicht die Anzahl verändere +- k2: Ich verstehe, dass ich mit filter() die Anzahl der Objekte im Stream, aber + nicht deren Typ (und Inhalt) verändere +- k2: Ich verstehe, warum Streams nicht in Attributen gehalten oder als Parameter + herumgereicht werden sollten +- k3: Ich kann einen Stream erzeugen +- k3: Ich kann verschiedene intermediäre Operationen verketten +- k3: Ich kann mit einer terminalen Operation einen Stream abschließen und damit + die Berechnung durchführen +- k3: Ich kann flatMap() einsetzen +::: + +::: challenges +Betrachten Sie den folgenden Java-Code: + +``` java +record Cat(int weight){}; + +public class Main { + public static void main(String... args) { + List clouder = new ArrayList<>(); + clouder.add(new Cat(100)); clouder.add(new Cat(1)); clouder.add(new Cat(10)); + + sumOverWeight(8, clouder); + } + + private static int sumOverWeight(int threshold, List cats) { + int result = 0; + for (Cat c : cats) { + int weight = c.weight(); + if (weight > threshold) { + result += weight; + } + } + return result; + } +} +``` + +Schreiben Sie die Methode `sumOverWeight` unter Beibehaltung der Funktionalität so +um, dass statt der `for`-Schleife und der `if`-Abfrage Streams und +Stream-Operationen eingesetzt werden. Nutzen Sie passende Lambda-Ausdrücke und nach +Möglichkeit Methodenreferenzen. + + + +Betrachten Sie den folgenden Java-Code: + +``` java +public class Main { + public static String getParameterNamesJson(String[] parameterNames) { + if (parameterNames.length == 0) { + return "[]"; + } + + StringBuilder sb = new StringBuilder(); + sb.append("["); + for (int i = 0; i < parameterNames.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append("\"").append(escapeJson(parameterNames[i])).append("\""); + } + sb.append("]"); + return sb.toString(); + } + + private static String escapeJson(String parameterName) { + // does something or another ... + } +} +``` + +Schreiben Sie die Methode `getParameterNamesJson` unter Beibehaltung der +Funktionalität so um, dass statt der `for`-Schleife und der `if`-Abfrage Streams und +Stream-Operationen eingesetzt werden. Nutzen Sie passende Lambda-Ausdrücke und nach +Möglichkeit auch Methodenreferenzen. + + +::: + +[^1]: Anmerkung: Das obige Beispiel dient als Überblick gebräuchlicher terminaler + Operationen, es ist nicht als lauffähiges Programm gedacht! Auf einem Stream + kann immer nur **eine** terminale Operation ausgeführt werden - d.h. nach der + Ausführung von `s.count()` wäre der Stream `s` verarbeitet und es können keine + weiteren Operationen auf diesem Stream durchgeführt werden. Dito für die anderen + gezeigten terminalen Operationen. diff --git a/lecture/misc/dungeon.md b/lecture/misc/dungeon.md index eb1e91d76..1b2879af4 100644 --- a/lecture/misc/dungeon.md +++ b/lecture/misc/dungeon.md @@ -1,58 +1,55 @@ --- +author: Carsten Gips (HSBI) title: "Frameworks: How-To Dungeon" -author: "Carsten Gips (HSBI)" -tldr: | - Der PM-Dungeon ist ein Framework zum Entwickeln von Rogue-like Dungeon-Crawlern, also einfachen 2D-Spielen in Java. - Das Framework bietet die wichtigsten benötigten Grundstrukturen für ein Computer-Spiel: Es hat eine Game-Loop, kann - Level generieren und darstellen und hat eine Entity-Component-System-Struktur (ECS), über die die Spielinhalte - erstellt werden können. Im Hintergrund arbeitet die Open-Source-Bibliothek libGDX. - - Sie können das Projekt direkt von GitHub clonen und über den im Projekt integrierten Gradle-Wrapper starten. Dazu - brauchen Sie Java 21 LTS (in einer 64-bit Version). Sie können das Projekt als Gradle-Projekt in Ihre IDE laden. - - Die Starter-Klassen (z.B. `starter.Starter` im "dungeon"-Subprojekt oder `starter.DevDungeon` im "devDungeon"-Subprojekt) - sind die zentralen Einstiegspunkte. Hier finden Sie "unseren" Teil der Game-Loop (der in der eigentlichen Game-Loop von - libGDX aufgerufen wird), hier finden Sie die Konfiguration und die `main()`-Methode. - - Im ECS werden die im Spiel befindlichen Elemente als _Entitäten_ modelliert. Diese Entitäten sind lediglich Container - für _Components_, die dann ihrerseits die entsprechenden Eigenschaften der Entitäten modellieren. Entitäten haben - normalerweise über die Components hinaus keine weiteren Eigenschaften (Attribute, Methoden). Das Game kennt alle zum - aktuellen Zeitpunkt "lebenden" Entitäten. - - Components gruppieren Eigenschaften, beispielsweise für Positionen oder Lebenspunkte. Components haben normalerweise - keine Methoden (halten also nur Werte/Attribute). Jede Component-Instanz ist immer einer konkreten Entität zugeordnet - und kann ohne diese nicht existieren. - - _Systeme_ implementieren das Verhalten im ECS. Das Game kennt alle aktiven Systeme und ruft in jedem Durchlauf der - Game-Loop die `execute()`-Methode der Systeme auf. Üblicherweise holt sich dann ein System alle Entitäten vom Game und - iteriert darüber und fragt ab, ob die betrachtete Entität die notwendigen Components hat - falls ja, dann kann das - System auf dieser Entität die entsprechenden Operationen ausführen (Animation, Bewegung, ...); falls nein, wird diese - Entität ignoriert und mit der Iteration fortgefahren. - - Wir programmieren in dieser Einheit einen einfachen Helden. Der Held ist eine `Entity` und braucht verschiedene - Components, um im Spiel angezeigt zu werden und bewegt werden zu können. -outcomes: - - k2: "Überblick über die wichtigsten Strukturen im PM-Dungeon" - - k2: "Aufbau eines ECS: Entitäten, Komponenten, Systeme" - - k3: "Herunterladen und installieren des PM-Dungeon" - - k3: "Laden in der IDE" - - k3: "Erstellen eines Helden mit Animation und Bewegung" -youtube: - - link: "https://youtu.be/kGKDcKIDHok" - name: "VL How-To Dungeon: Teil 1 (Überblick)" - - link: "https://youtu.be/uPNt7OWpMBs" - name: "VL How-To Dungeon: Teil 2 (Hero)" - - link: "https://youtu.be/_YVYoFgvU04" - name: "VL How-To Dungeon: Teil 3 (Monster und Feuerbälle)" -tibav: - - link: "https://av.tib.eu/media/67783" - name: "VL How-To Dungeon: Teil 1 (Überblick)" - - link: "https://av.tib.eu/media/67785" - name: "VL How-To Dungeon: Teil 2 (Hero)" - - link: "https://av.tib.eu/media/67784" - name: "VL How-To Dungeon: Teil 3 (Monster und Feuerbälle)" --- +::: tldr +Der PM-Dungeon ist ein Framework zum Entwickeln von Rogue-like Dungeon-Crawlern, +also einfachen 2D-Spielen in Java. Das Framework bietet die wichtigsten benötigten +Grundstrukturen für ein Computer-Spiel: Es hat eine Game-Loop, kann Level generieren +und darstellen und hat eine Entity-Component-System-Struktur (ECS), über die die +Spielinhalte erstellt werden können. Im Hintergrund arbeitet die +Open-Source-Bibliothek libGDX. + +Sie können das Projekt direkt von GitHub clonen und über den im Projekt integrierten +Gradle-Wrapper starten. Dazu brauchen Sie Java 21 LTS (in einer 64-bit Version). Sie +können das Projekt als Gradle-Projekt in Ihre IDE laden. + +Die Starter-Klassen (z.B. `starter.Starter` im "dungeon"-Subprojekt oder +`starter.DevDungeon` im "devDungeon"-Subprojekt) sind die zentralen Einstiegspunkte. +Hier finden Sie "unseren" Teil der Game-Loop (der in der eigentlichen Game-Loop von +libGDX aufgerufen wird), hier finden Sie die Konfiguration und die `main()`-Methode. + +Im ECS werden die im Spiel befindlichen Elemente als *Entitäten* modelliert. Diese +Entitäten sind lediglich Container für *Components*, die dann ihrerseits die +entsprechenden Eigenschaften der Entitäten modellieren. Entitäten haben +normalerweise über die Components hinaus keine weiteren Eigenschaften (Attribute, +Methoden). Das Game kennt alle zum aktuellen Zeitpunkt "lebenden" Entitäten. + +Components gruppieren Eigenschaften, beispielsweise für Positionen oder +Lebenspunkte. Components haben normalerweise keine Methoden (halten also nur +Werte/Attribute). Jede Component-Instanz ist immer einer konkreten Entität +zugeordnet und kann ohne diese nicht existieren. + +*Systeme* implementieren das Verhalten im ECS. Das Game kennt alle aktiven Systeme +und ruft in jedem Durchlauf der Game-Loop die `execute()`-Methode der Systeme auf. +Üblicherweise holt sich dann ein System alle Entitäten vom Game und iteriert darüber +und fragt ab, ob die betrachtete Entität die notwendigen Components hat - falls ja, +dann kann das System auf dieser Entität die entsprechenden Operationen ausführen +(Animation, Bewegung, ...); falls nein, wird diese Entität ignoriert und mit der +Iteration fortgefahren. + +Wir programmieren in dieser Einheit einen einfachen Helden. Der Held ist eine +`Entity` und braucht verschiedene Components, um im Spiel angezeigt zu werden und +bewegt werden zu können. +::: + +::: youtube +- [VL How-To Dungeon: Teil 1 (Überblick)](https://youtu.be/kGKDcKIDHok) +- [VL How-To Dungeon: Teil 2 (Hero)](https://youtu.be/uPNt7OWpMBs) +- [VL How-To Dungeon: Teil 3 (Monster und + Feuerbälle)](https://youtu.be/_YVYoFgvU04) +::: +author: Carsten Gips (HSBI) +title: Command-Pattern --- +::: tldr +Das **Command-Pattern** ist die objektorientierte Antwort auf Callback-Funktionen: +Man kapselt Befehle in einem Objekt. + +1. Die `Command`-Objekte haben eine Methode `execute()` und führen dabei Aktion auf + einem bzw. "ihrem" Receiver aus. + +2. `Receiver` sind Objekte, auf denen Aktionen ausgeführt werden, im Dungeon + könnten dies etwa Hero, Monster, ... sein. Receiver müssen keine der anderen + Akteure in diesem Pattern kennen. + +3. Damit die `Command`-Objekte aufgerufen werden, gibt es einen `Invoker`, der + `Command`-Objekte hat und zu gegebener Zeit auf diesen die Methode `execute()` + aufruft. Der Invoker muss dabei die konkreten Kommandos und die Receiver nicht + kennen (nur die `Command`-Schnittstelle). + +4. Zusätzlich gibt es einen `Client`, der die anderen Akteure kennt und alles + zusammen baut. +::: + +::: youtube +- [VL Command-Pattern](https://youtu.be/F7RJ7YCVMS4) +::: # Motivation ::: notes -Irgendwo im Dungeon wird es ein Objekt einer Klasse ähnlich wie `InputHandler` -geben mit einer Methode ähnlich zu `handleInput()`: +Irgendwo im Dungeon wird es ein Objekt einer Klasse ähnlich wie `InputHandler` geben +mit einer Methode ähnlich zu `handleInput()`: ::: -```java +``` java public class InputHandler { public void handleInput() { switch (keyPressed()) { @@ -85,10 +58,9 @@ zugeordnet und erlauben keinerlei Konfiguration. [[Problem: Starre Zuordnung]{.ex}]{.slides} - # Auflösen der starren Zuordnung über Zwischenobjekte -```java +``` java public interface Command { void execute(); } public class Jump implements Command { @@ -112,64 +84,65 @@ public class InputHandler { ``` ::: notes -Die starre Zuordnung "Button : Aktion" wird aufgelöst und über Zwischenobjekte konfigurierbar -gemacht. +Die starre Zuordnung "Button : Aktion" wird aufgelöst und über Zwischenobjekte +konfigurierbar gemacht. -Für die Zwischenobjekte wird ein Typ `Command` eingeführt, der nur eine `execute()`-Methode -hat. Für jede gewünschte Aktion wird eine Klasse davon abgeleitet, diese Klassen können auch -einen Zustand pflegen. +Für die Zwischenobjekte wird ein Typ `Command` eingeführt, der nur eine +`execute()`-Methode hat. Für jede gewünschte Aktion wird eine Klasse davon +abgeleitet, diese Klassen können auch einen Zustand pflegen. -Den Buttons wird nun an geeigneter Stelle (Konstruktor, Methoden, ...) je ein Objekt der -jeweiligen Command-Unterklassen zugeordnet. Wenn ein Button betätigt wird, wird auf dem -Objekt die Methode `execute()` aufgerufen. +Den Buttons wird nun an geeigneter Stelle (Konstruktor, Methoden, ...) je ein Objekt +der jeweiligen Command-Unterklassen zugeordnet. Wenn ein Button betätigt wird, wird +auf dem Objekt die Methode `execute()` aufgerufen. -Damit die Kommandos nicht nur auf den Helden wirken können, kann man den Kommando-Objekten -beispielsweise noch eine Entität mitgeben, auf der das Kommando ausgeführt werden soll. Im -Beispiel oben wurde dafür der `hero` genutzt. +Damit die Kommandos nicht nur auf den Helden wirken können, kann man den +Kommando-Objekten beispielsweise noch eine Entität mitgeben, auf der das Kommando +ausgeführt werden soll. Im Beispiel oben wurde dafür der `hero` genutzt. ::: - # Command: Objektorientierte Antwort auf Callback-Funktionen ![](images/command.png){web_width="80%"} ::: notes -Im Command-Pattern gibt es vier beteiligte Parteien: Client, Receiver, Command und Invoker. +Im Command-Pattern gibt es vier beteiligte Parteien: Client, Receiver, Command und +Invoker. -Ein Command ist die objektorientierte Abstraktion eines Befehls. Es hat möglicherweise -einen Zustand, und und kennt "seinen" Receiver und kann beim Aufruf der `execute()`-Methode -eine vorher verabredete Methode auf diesem Receiver-Objekt ausführen. +Ein Command ist die objektorientierte Abstraktion eines Befehls. Es hat +möglicherweise einen Zustand, und und kennt "seinen" Receiver und kann beim Aufruf +der `execute()`-Methode eine vorher verabredete Methode auf diesem Receiver-Objekt +ausführen. -Ein Receiver ist eine Klasse, die Aktionen durchführen kann. Sie kennt die anderen Akteure -nicht. +Ein Receiver ist eine Klasse, die Aktionen durchführen kann. Sie kennt die anderen +Akteure nicht. -Der Invoker (manchmal auch "Caller" genannt) ist eine Klasse, die Commands aggregiert und die -die Commandos "ausführt", indem hier die `execute()`-Methode aufgerufen wird. Diese Klasse -kennt nur das `Command`-Interface und keine spezifischen Kommandos (also keine der Sub-Klassen). -Es kann zusätzlich eine gewisse Buchführung übernehmen, etwa um eine Undo-Funktionalität zu -realisieren. - -Der Client ist ein Programmteil, der ein Command-Objekt aufbaut und dabei einen passenden -Receiver übergibt und der das Command-Objekt dann zum Aufruf an den Invoker weiterreicht. +Der Invoker (manchmal auch "Caller" genannt) ist eine Klasse, die Commands +aggregiert und die die Commandos "ausführt", indem hier die `execute()`-Methode +aufgerufen wird. Diese Klasse kennt nur das `Command`-Interface und keine +spezifischen Kommandos (also keine der Sub-Klassen). Es kann zusätzlich eine gewisse +Buchführung übernehmen, etwa um eine Undo-Funktionalität zu realisieren. +Der Client ist ein Programmteil, der ein Command-Objekt aufbaut und dabei einen +passenden Receiver übergibt und der das Command-Objekt dann zum Aufruf an den +Invoker weiterreicht. In unserem Beispiel lassen sich die einzelnen Teile so sortieren: -* Client: Klasse `InputHandler` (erzeugt neue `Command`-Objekte im obigen Code) bzw. `main()`, - wenn man die `Command`-Objekte dort erstellt und an den Konstruktor von `InputHandler` - weiterreicht -* Receiver: Objekt `hero` der Klasse `Hero` (auf diesem wird eine Aktion ausgeführt) -* Command: `Jump` und `Move` -* Invoker: `InputHandler` (in der Methode `handleInput()`) +- Client: Klasse `InputHandler` (erzeugt neue `Command`-Objekte im obigen Code) + bzw. `main()`, wenn man die `Command`-Objekte dort erstellt und an den + Konstruktor von `InputHandler` weiterreicht +- Receiver: Objekt `hero` der Klasse `Hero` (auf diesem wird eine Aktion + ausgeführt) +- Command: `Jump` und `Move` +- Invoker: `InputHandler` (in der Methode `handleInput()`) ::: - # Undo ::: notes Wir könnten das `Command`-Interface um ein paar Methoden erweitern: -```java +``` java public interface Command { void execute(); void undo(); @@ -177,11 +150,11 @@ public interface Command { } ``` -Jetzt kann jedes Command-Objekt eine neue Instanz erzeugen mit der -Entity, die dann dieses Kommando empfangen soll: +Jetzt kann jedes Command-Objekt eine neue Instanz erzeugen mit der Entity, die dann +dieses Kommando empfangen soll: ::: -```java +``` java public class Move implements Command { private Entity e; private int x, y, oldX, oldY; @@ -210,35 +183,68 @@ public class InputHandler { ``` ::: notes -Über den Konstruktor von `InputHandler` (im Beispiel nicht gezeigt) würde man -wie vorher die `Command`-Objekte für die Buttons setzen. Es würde aber in jedem -Aufruf von `handleInput()` abgefragt, was gerade die selektierte Entität ist und -für diese eine neue Instanz des zur Tastatureingabe passenden `Command`-Objekts -erzeugt. Dieses wird nun in einem Stack gespeichert und danach ausgeführt. - -Wenn der Button "U" gedrückt wird, wird das letzte `Command`-Objekt aus dem -Stack genommen (Achtung: Im echten Leben müsste man erst einmal schauen, ob hier -noch was drin ist!) und auf diesem die Methode `undo()` aufgerufen. Für das -Kommando `Move` ist hier skizziert, wie ein Undo aussehen könnte: Man muss einfach -bei jedem `execute()` die alte Position der Entität speichern, dann kann man -sie bei einem `undo()` wieder auf diese Position verschieben. Da für jeden Move -ein neues Objekt angelegt wird und dieses nur einmal benutzt wird, braucht man -keine weitere Buchhaltung ... +Über den Konstruktor von `InputHandler` (im Beispiel nicht gezeigt) würde man wie +vorher die `Command`-Objekte für die Buttons setzen. Es würde aber in jedem Aufruf +von `handleInput()` abgefragt, was gerade die selektierte Entität ist und für diese +eine neue Instanz des zur Tastatureingabe passenden `Command`-Objekts erzeugt. +Dieses wird nun in einem Stack gespeichert und danach ausgeführt. + +Wenn der Button "U" gedrückt wird, wird das letzte `Command`-Objekt aus dem Stack +genommen (Achtung: Im echten Leben müsste man erst einmal schauen, ob hier noch was +drin ist!) und auf diesem die Methode `undo()` aufgerufen. Für das Kommando `Move` +ist hier skizziert, wie ein Undo aussehen könnte: Man muss einfach bei jedem +`execute()` die alte Position der Entität speichern, dann kann man sie bei einem +`undo()` wieder auf diese Position verschieben. Da für jeden Move ein neues Objekt +angelegt wird und dieses nur einmal benutzt wird, braucht man keine weitere +Buchhaltung ... ::: - # Wrap-Up **Command-Pattern**: Kapsele Befehle in ein Objekt \bigskip -* `Command`-Objekte haben eine Methode `execute()` und führen darin Aktion auf Receiver aus -* `Receiver` sind Objekte, auf denen Aktionen ausgeführt werden (Hero, Monster, ...) -* `Invoker` hat `Command`-Objekte und ruft darauf `execute()` auf -* `Client` kennt alle und baut alles zusammen +- `Command`-Objekte haben eine Methode `execute()` und führen darin Aktion auf + Receiver aus +- `Receiver` sind Objekte, auf denen Aktionen ausgeführt werden (Hero, Monster, + ...) +- `Invoker` hat `Command`-Objekte und ruft darauf `execute()` auf +- `Client` kennt alle und baut alles zusammen \bigskip \bigskip **Objektorientierte Antwort auf Callback-Funktionen** + +::: readings +- @Gamma2011 +- @Nystrom2014 [Kap. 2] +::: + +::: outcomes +- k2: Ich kann den Aufbau des Command-Patterns erklären +- k3: Ich kann das Command-Pattern auf konkrete Beispiele, etwa den PM-Dungeon, + anwenden +::: + +::: challenges +Schreiben Sie für den `Dwarf` in den +[Vorgaben](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/pattern/src/challenges/command) +einen Controller, welcher das Command-Pattern verwendet. + +- "W" führt Springen aus +- "A" bewegt den Zwerg nach links +- "D" bewegt den Zwerg nach rechts +- "S" führt Ducken aus + +Schreiben Sie zusätzlich für den `Cursor` einen Controller, welcher das +Command-Pattern mit Historie erfüllt (ebenfalls über die Tasten "W", "A", "S" und +"D"). + +Schreiben Sie eine Demo, um die Funktionalität Ihres Programmes zu demonstrieren. + + +::: diff --git a/lecture/pattern/factory-method.md b/lecture/pattern/factory-method.md index ee3d92d2c..8461cc36d 100644 --- a/lecture/pattern/factory-method.md +++ b/lecture/pattern/factory-method.md @@ -1,119 +1,119 @@ --- -title: "Factory-Method-Pattern" -author: "Carsten Gips (HSBI)" -readings: - - "@Eilebrecht2013" - - "@Gamma2011" - - "@Kleuker2018" -tldr: | - Oft ist es wünschenswert, dass Nutzer nicht direkt Objekte von bestimmten Klassen anlegen (können). - Hier kann eine "Fabrik-Methode" (**Factory-Method**) helfen, der man die gewünschten Parameter - übergibt und die daraus dann das passende Objekt (der richtigen Klasse) erzeugt und zurückliefert. - - Dadurch erreicht man eine höhere Entkoppelung, die Nutzer müssen nur noch das Interface oder die - abstrakte Klasse, also den Obertyp des Ergebnisses kennen. Außerdem lassen sich so leicht die - konkreten Klassen austauschen. - - Dieses Entwurfsmuster kommt häufig zusammen mit dem _Singleton-Pattern_ vor, wo es nur eine einzige - Instanz einer Klasse geben soll. Über eine Fabrik-Methode kann man diese Instanz ggf. erzeugen und - dann die Referenz darauf zurückliefern. -outcomes: - - k3: "Entwurfsmuster Factory-Methode anwenden" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106533&client_id=FH-Bielefeld" -# name: "Quiz Factory-Method-Pattern (ILIAS)" -youtube: - - link: "https://youtu.be/mJWe-2BS2W0" - name: "VL Factory-Method-Pattern" - - link: "https://youtu.be/14rt1YIoiME" - name: "Demo Factory-Method-Pattern" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/54f1c0ac6b5a7670788efdd88e63dd9eb5de4179d342bce82f5c04268c469beba149891305c81181f1d23c2cf89194f06cdac809396d2e7bff7607078a1a621e" - name: "VL Factory-Method-Pattern" -challenges: | - Ein Kunde kommt in unser Computergeschäft und möchte bei uns einen Computer - bestellen. Dabei gibt er an, wie er diesen vorwiegend nutzen möchte bzw. für - welchen Zweck er gedacht ist ("_stationär_" oder "_mobil_"). Nach reichlicher - Überlegung, ob er den neuen Rechner zu Hause stehen haben möchte oder lieber - keinen weiteren Rechner, egal ob "_mobil_" oder "_stationär_", bei sich im Weg - herumstehen haben will, teilt er Ihnen seine Entscheidung darüber mit - ("_stationär_" oder "_mobil_" vs. "_nicht daheim_"). Bei diesem Gespräch merkt er - beiläufig an, dass es ein Rechner mit "_viel Wumms_" sein könnte oder vielleicht - doch besser etwas Kleines, was leise vor sich hin schnurrt ("_viel Wumms_" vs. - "_leise schnurrend_"). - - Je nach gewünschter Konfiguration soll ein den oben genannten Auswahlkriterien - entsprechender Rechner mit den aus der unten stehenden Konfigurationsmatrix zu - entnehmenden Eigenschaften automatisch erzeugt werden. Die Größe des installierten - `RAM`, die Anzahl der eingebauten `CPU`-Kerne mit ihrer jeweiligen Taktrate, - sowie die Art und Größe der installierten Festplatte (`HDD` oder `SSD`) sollte - dabei zu dem gewählten Paket passend gesetzt werden. - - Implementieren Sie eine "Computerfabrik" (Klasse `ComputerFactory`), die Ihnen - den richtig konfigurierten Rechner zusammenbaut. Nutzen Sie dabei das - "Factory-Method-Pattern" zum Erzeugen der Objekte der einzelnen Subklassen. Dabei - soll Ihre Computerfabrik anhand der ihr übergebenen Konfiguration eigenständig - entscheiden, welche Art von Computer dabei erstellt werden soll. - - Implementieren Sie dazu in Ihrer Factory die Factory-Methode `buildComputer`, - welche das jeweils passend konfigurierte Objekt zurückgibt. - - ```java - public class ComputerFactory { - ... +author: Carsten Gips (HSBI) +title: Factory-Method-Pattern +--- - public static Computer buildComputer(..."stationär",..."viel Wumms") { - ... - return myComputer; - } - } - ``` +::: tldr +Oft ist es wünschenswert, dass Nutzer nicht direkt Objekte von bestimmten Klassen +anlegen (können). Hier kann eine "Fabrik-Methode" (**Factory-Method**) helfen, der +man die gewünschten Parameter übergibt und die daraus dann das passende Objekt (der +richtigen Klasse) erzeugt und zurückliefert. - **Konfigurationsmatrix** +Dadurch erreicht man eine höhere Entkoppelung, die Nutzer müssen nur noch das +Interface oder die abstrakte Klasse, also den Obertyp des Ergebnisses kennen. +Außerdem lassen sich so leicht die konkreten Klassen austauschen. - | | "stationär" (`DesktopComputer `) | "mobil" (`LaptopComputer`) | "nicht daheim" (`CloudComputer`) | - |:------------------:|:--------------------------------------:|:------------------------------------:|:------------------------------------------:| - | "leise schnurrend" | 8 Cores, 1.21GHZ, 16GB RAM, 256GB HDD | 4 Cores, 1.21GHZ, 8GB RAM, 256GB HDD | 8 Cores, 1.21GHZ, 24GB RAM, 1000GB HDD | - | "viel Wumms" | 16 Cores, 4.2GHZ, 32GB RAM, 2000GB SSD | 8 Cores, 2.4GHZ, 16GB RAM, 256GB SSD | 42 Cores, 9.001GHZ, 128GB RAM, 10000GB SSD | ---- +Dieses Entwurfsmuster kommt häufig zusammen mit dem *Singleton-Pattern* vor, wo es +nur eine einzige Instanz einer Klasse geben soll. Über eine Fabrik-Methode kann man +diese Instanz ggf. erzeugen und dann die Referenz darauf zurückliefern. +::: +::: youtube +- [VL Factory-Method-Pattern](https://youtu.be/mJWe-2BS2W0) +- [Demo Factory-Method-Pattern](https://youtu.be/14rt1YIoiME) +::: # Motivation: Ticket-App -* Nutzer geben Fahrtziel an (und nicht die Ticketart!) +- Nutzer geben Fahrtziel an (und nicht die Ticketart!) \bigskip -* Ticket-App bucht passendes Ticket - * User muss nicht die konkreten Ticketarten kennen - * Ticketarten lassen sich leicht austauschen +- Ticket-App bucht passendes Ticket + - User muss nicht die konkreten Ticketarten kennen + - Ticketarten lassen sich leicht austauschen \bigskip \bigskip -=> **Factory-Method-Pattern**: Objekte sollen nicht direkt durch den Nutzer erzeugt werden - +=\> **Factory-Method-Pattern**: Objekte sollen nicht direkt durch den Nutzer erzeugt +werden # Factory-Method-Pattern ![](images/factorymethod.png){width="80%"} - # Hands-On: Ticket-App -Implementieren Sie eine Ticket-App, die verschiedene Tickets mit -Hilfe des Factory-Method Entwurfsmusters generiert. - -[UML; Konsole: factory.FactoryBeispiel]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/pattern/src/factory/FactoryBeispiel.java"} +Implementieren Sie eine Ticket-App, die verschiedene Tickets mit Hilfe des +Factory-Method Entwurfsmusters generiert. +[UML; Konsole: factory.FactoryBeispiel]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/pattern/src/factory/FactoryBeispiel.java"} # Wrap-Up -* Konkrete Objekte sollen nicht direkt über Konstruktor erzeugt werden -* (Statische) Hilfsmethode, die aus Parameter das "richtige" Objekte erzeugt +- Konkrete Objekte sollen nicht direkt über Konstruktor erzeugt werden +- (Statische) Hilfsmethode, die aus Parameter das "richtige" Objekte erzeugt \smallskip -* Vorteil: - * Nutzer kennt nur das Interface - * Konkrete Klassen lassen sich leicht austauschen +- Vorteil: + - Nutzer kennt nur das Interface + - Konkrete Klassen lassen sich leicht austauschen + +::: readings +- @Eilebrecht2013 +- @Gamma2011 +- @Kleuker2018 +::: + +::: outcomes +- k3: Entwurfsmuster Factory-Methode anwenden +::: + +::: challenges +Ein Kunde kommt in unser Computergeschäft und möchte bei uns einen Computer +bestellen. Dabei gibt er an, wie er diesen vorwiegend nutzen möchte bzw. für welchen +Zweck er gedacht ist ("*stationär*" oder "*mobil*"). Nach reichlicher Überlegung, ob +er den neuen Rechner zu Hause stehen haben möchte oder lieber keinen weiteren +Rechner, egal ob "*mobil*" oder "*stationär*", bei sich im Weg herumstehen haben +will, teilt er Ihnen seine Entscheidung darüber mit ("*stationär*" oder "*mobil*" +vs. "*nicht daheim*"). Bei diesem Gespräch merkt er beiläufig an, dass es ein +Rechner mit "*viel Wumms*" sein könnte oder vielleicht doch besser etwas Kleines, +was leise vor sich hin schnurrt ("*viel Wumms*" vs. "*leise schnurrend*"). + +Je nach gewünschter Konfiguration soll ein den oben genannten Auswahlkriterien +entsprechender Rechner mit den aus der unten stehenden Konfigurationsmatrix zu +entnehmenden Eigenschaften automatisch erzeugt werden. Die Größe des installierten +`RAM`, die Anzahl der eingebauten `CPU`-Kerne mit ihrer jeweiligen Taktrate, sowie +die Art und Größe der installierten Festplatte (`HDD` oder `SSD`) sollte dabei zu +dem gewählten Paket passend gesetzt werden. + +Implementieren Sie eine "Computerfabrik" (Klasse `ComputerFactory`), die Ihnen den +richtig konfigurierten Rechner zusammenbaut. Nutzen Sie dabei das +"Factory-Method-Pattern" zum Erzeugen der Objekte der einzelnen Subklassen. Dabei +soll Ihre Computerfabrik anhand der ihr übergebenen Konfiguration eigenständig +entscheiden, welche Art von Computer dabei erstellt werden soll. + +Implementieren Sie dazu in Ihrer Factory die Factory-Methode `buildComputer`, welche +das jeweils passend konfigurierte Objekt zurückgibt. + +``` java +public class ComputerFactory { + ... + + public static Computer buildComputer(..."stationär",..."viel Wumms") { + ... + return myComputer; + } +} +``` + +**Konfigurationsmatrix** + +| | "stationär" (`DesktopComputer`) | "mobil" (`LaptopComputer`) | "nicht daheim" (`CloudComputer`) | +|:-----------:|:----------------------:|:--------------------:|:------------------------:| +| "leise schnurrend" | 8 Cores, 1.21GHZ, 16GB RAM, 256GB HDD | 4 Cores, 1.21GHZ, 8GB RAM, 256GB HDD | 8 Cores, 1.21GHZ, 24GB RAM, 1000GB HDD | +| "viel Wumms" | 16 Cores, 4.2GHZ, 32GB RAM, 2000GB SSD | 8 Cores, 2.4GHZ, 16GB RAM, 256GB SSD | 42 Cores, 9.001GHZ, 128GB RAM, 10000GB SSD | +::: diff --git a/lecture/pattern/flyweight.md b/lecture/pattern/flyweight.md index 944af694f..fa135654a 100644 --- a/lecture/pattern/flyweight.md +++ b/lecture/pattern/flyweight.md @@ -1,46 +1,24 @@ --- -title: "Flyweight-Pattern" -author: "Carsten Gips (HSBI)" -readings: - - "@Nystrom2014 [Kap. 3]" -tldr: | - Das Flyweight-Pattern dient der Steigerung der (Speicher-) Effizienz, indem gemeinsame - Daten durch gemeinsam genutzte Objekte repräsentiert werden. - - Den sogenannten _Intrinsic State_, also die Eigenschaften, die sich alle Objekte teilen, - werden in gemeinsam genutzte Objekte ausgelagert, und diese werden in den ursprünglichen - Klassen bzw. Objekten nur referenziert. So werden diese Eigenschaften nur einmal in den - Speicher geladen. - - Den sogenannten _Extrinsic State_, also alle individuellen Eigenschaften, werden - entsprechend individuell je Objekt modelliert/eingestellt. -outcomes: - - k2: "Unterscheiden von Intrinsic State und Extrinsic State" - - k2: "Verschieben des Intrinsic States in gemeinsam genutzte Objekte" - - k2: "Erklären der Ähnlichkeit zum Type-Object-Pattern" - - k3: "Praktischer Einsatz des Flyweight-Patterns" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106534&client_id=FH-Bielefeld" -# name: "Quiz Flyweight-Pattern (ILIAS)" -youtube: - - link: "https://youtu.be/Oo8TO8z5wQI" - name: "VL Flyweight-Pattern" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/48c5b1437b5f1ff4023295d0a95c12d893510d0980ff5c6a37ab4e5814990c82b769ff505e727fc3d1ee6b71facb92955639400c01d269ad74e69d9788470c53" - name: "VL Flyweight-Pattern" -challenges: | - In den [Vorgaben](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/pattern/src/challenges/flyweight) - finden Sie ein Modellierung eines Schachspiels. - - Identifizieren Sie die Stellen im Vorgabe-Code, wo Sie das Flyweight-Pattern - sinnvoll anwenden können und bauen Sie dieses Pattern über ein Refactoring ein. - Begründen Sie, wie Sie das Pattern eingesetzt haben und warum Sie welche Elemente - _immutable_ oder _mutable_ deklariert haben. - - Wieso eignet sich das Flyweight-Pattern besonders im Bereich von Computerspielen? - Geben Sie mögliche Vor- und Nachteile an und begründen Sie Ihre Antwort. +author: Carsten Gips (HSBI) +title: Flyweight-Pattern --- +::: tldr +Das Flyweight-Pattern dient der Steigerung der (Speicher-) Effizienz, indem +gemeinsame Daten durch gemeinsam genutzte Objekte repräsentiert werden. + +Den sogenannten *Intrinsic State*, also die Eigenschaften, die sich alle Objekte +teilen, werden in gemeinsam genutzte Objekte ausgelagert, und diese werden in den +ursprünglichen Klassen bzw. Objekten nur referenziert. So werden diese Eigenschaften +nur einmal in den Speicher geladen. + +Den sogenannten *Extrinsic State*, also alle individuellen Eigenschaften, werden +entsprechend individuell je Objekt modelliert/eingestellt. +::: + +::: youtube +- [VL Flyweight-Pattern](https://youtu.be/Oo8TO8z5wQI) +::: # Motivation: Modellierung eines Levels @@ -48,7 +26,7 @@ challenges: | ## Variante I: Einsatz eines Enums für die Felder ::: -```java +``` java public enum Tile { WATER, FLOOR, WALL, ... } public class Level { @@ -73,16 +51,15 @@ public class Level { ``` ::: notes -Ein Level kann als Array mit Feldern modelliert werden. Die Felder selbst könnten mit -Hilfe eines Enums repräsentiert werden. +Ein Level kann als Array mit Feldern modelliert werden. Die Felder selbst könnten +mit Hilfe eines Enums repräsentiert werden. Allerdings muss dann bei jedem Zugriff auf ein Feld und dessen Eigenschaften eine entsprechende `switch/case`-Fallunterscheidung eingebaut werden. Damit verstreut man -die Eigenschaften über die gesamte Klasse, und bei jeder Änderung am Enum für die Tiles -müssen _alle_ `switch/case`-Blöcke entsprechend angepasst werden. +die Eigenschaften über die gesamte Klasse, und bei jeder Änderung am Enum für die +Tiles müssen *alle* `switch/case`-Blöcke entsprechend angepasst werden. ::: - ::: slides # Motivation: Modellierung eines Levels (cnt.) ::: @@ -91,7 +68,7 @@ müssen _alle_ `switch/case`-Blöcke entsprechend angepasst werden. ## Variante II: Einsatz einer Klasse/Klassenhierarchie für die Felder ::: -```java +``` java public abstract class Tile { protected boolean isAccessible; protected Texture texture; @@ -116,27 +93,28 @@ public class Level { ``` ::: notes -Hier werden die Felder über eine Klassenhierarchie mit gemeinsamer Basisklasse modelliert. +Hier werden die Felder über eine Klassenhierarchie mit gemeinsamer Basisklasse +modelliert. -Allerdings wird hier die Klassenhierarchie unter Umständen sehr schnell sehr umfangreich. -Außerdem werden Eigenschaften wie Texturen beim Anlegen der Tile-Objekte immer wieder neu -geladen und entsprechend mehrfach im Speicher gehalten (großer Speicherbedarf). +Allerdings wird hier die Klassenhierarchie unter Umständen sehr schnell sehr +umfangreich. Außerdem werden Eigenschaften wie Texturen beim Anlegen der +Tile-Objekte immer wieder neu geladen und entsprechend mehrfach im Speicher gehalten +(großer Speicherbedarf). ::: - # Flyweight: Nutze gemeinsame Eigenschaften gemeinsam ::: notes -Idee: Eigenschaften, die nicht an einem konkreten Objekt hängen, werden in gemeinsam genutzte -Objekte ausgelagert (Shared Objects/Memory). +Idee: Eigenschaften, die nicht an einem konkreten Objekt hängen, werden in gemeinsam +genutzte Objekte ausgelagert (Shared Objects/Memory). -Ziel: Erhöhung der Speichereffizienz (geringerer Bedarf an Hauptspeicher, geringere Bandbreite -bei der Übertragung der Daten/Objekt an die GPU, ...). +Ziel: Erhöhung der Speichereffizienz (geringerer Bedarf an Hauptspeicher, geringere +Bandbreite bei der Übertragung der Daten/Objekt an die GPU, ...). ## Lösungsvorschlag I ::: -```java +``` java public final class Tile { private final boolean isAccessible; private final Texture texture; @@ -161,15 +139,15 @@ public class Level { ``` ::: notes -Man legt die verschiedenen Tiles nur je _einmal_ an und nutzt dann Referenzen auf diese Objekte. -Dadurch werden die speicherintensiven Elemente wie Texturen o.ä. nur je einmal geladen und im -Speicher vorgehalten. +Man legt die verschiedenen Tiles nur je *einmal* an und nutzt dann Referenzen auf +diese Objekte. Dadurch werden die speicherintensiven Elemente wie Texturen o.ä. nur +je einmal geladen und im Speicher vorgehalten. -Bei dieser Modellierung können die einzelnen Felder aber keine individuellen Eigenschaften haben, -wie etwa, ob ein Feld bereits durch den Helden untersucht/betreten wurde o.ä. ... +Bei dieser Modellierung können die einzelnen Felder aber keine individuellen +Eigenschaften haben, wie etwa, ob ein Feld bereits durch den Helden +untersucht/betreten wurde o.ä. ... ::: - ::: slides # Flyweight: Nutze gemeinsame Eigenschaften gemeinsam (cnt.) ::: @@ -178,7 +156,7 @@ wie etwa, ob ein Feld bereits durch den Helden untersucht/betreten wurde o.ä. . ## Lösungsvorschlag II ::: -```java +``` java public final class TileModel { private final boolean isAccessible; private final Texture texture; @@ -208,28 +186,28 @@ public class Level { ``` ::: notes -In dieser Variante werden die Eigenschaften eines `Tile` in Eigenschaften aufgeteilt, die von den -Tiles geteilt werden können (im Beispiel Textur und Betretbarkeit) und in Eigenschaften, die je -Feld individuell modelliert werden müssen (im Beispiel: wurde das Feld bereits betreten?). - -Entsprechend könnte man für das Level-Beispiel ein `TileModel` anlegen, welches die gemeinsamen -Eigenschaften verwaltet. Man erzeugt dann im Level die nötigen Modelle je genau einmal und nutzt -sie, um damit dann die konkreten Felder zu erzeugen und im Level-Array zu referenzieren. Damit -werden Tile-Modelle von Tiles der gleichen "Klasse" gemeinsam genutzt und die Texturen u.ä. nur -je einmal im Speicher repräsentiert. +In dieser Variante werden die Eigenschaften eines `Tile` in Eigenschaften +aufgeteilt, die von den Tiles geteilt werden können (im Beispiel Textur und +Betretbarkeit) und in Eigenschaften, die je Feld individuell modelliert werden +müssen (im Beispiel: wurde das Feld bereits betreten?). + +Entsprechend könnte man für das Level-Beispiel ein `TileModel` anlegen, welches die +gemeinsamen Eigenschaften verwaltet. Man erzeugt dann im Level die nötigen Modelle +je genau einmal und nutzt sie, um damit dann die konkreten Felder zu erzeugen und im +Level-Array zu referenzieren. Damit werden Tile-Modelle von Tiles der gleichen +"Klasse" gemeinsam genutzt und die Texturen u.ä. nur je einmal im Speicher +repräsentiert. ::: - # Flyweight-Pattern: Begriffe -* **Intrinsic** State: invariant, Kontext-unabhängig, gemeinsam nutzbar \newline - => auslagern in gemeinsame Objekte +- **Intrinsic** State: invariant, Kontext-unabhängig, gemeinsam nutzbar + `\newline`{=tex} =\> auslagern in gemeinsame Objekte \bigskip -* **Extrinsic** State: variant, Kontext-abhängig und kann nicht geteilt werden \newline - => individuell modellieren - +- **Extrinsic** State: variant, Kontext-abhängig und kann nicht geteilt werden + `\newline`{=tex} =\> individuell modellieren # Flyweight-Pattern: Klassische Modellierung @@ -238,41 +216,46 @@ je einmal im Speicher repräsentiert. [[Hinweis zum Beispiel: -Interface, -Factory, +Composite]{.ex}]{.slides} ::: notes -Im klassischen Flyweight-Pattern der "Gang of Four" [@Gamma2011] wird ein gemeinsames Interface -erstellt, von dem die einzelnen Fliegengewicht-Klassen ableiten. Der Nutzer kennt nur dieses -Interface und nicht direkt die implementierenden Klassen. +Im klassischen Flyweight-Pattern der "Gang of Four" [@Gamma2011] wird ein +gemeinsames Interface erstellt, von dem die einzelnen Fliegengewicht-Klassen +ableiten. Der Nutzer kennt nur dieses Interface und nicht direkt die +implementierenden Klassen. -Das Interface wird von zwei Arten von Klassen implementiert: Klassen, die nur intrinsischen -Zustand modellieren, und Klassen, die extrinsischen Zustand modellieren. +Das Interface wird von zwei Arten von Klassen implementiert: Klassen, die nur +intrinsischen Zustand modellieren, und Klassen, die extrinsischen Zustand +modellieren. -Für die Klassen, die den intrinsischen Zustand modellieren, werden die Objekte gemeinsam genutzt -(nicht im Diagramm darstellbar) und deshalb eine Factory davor geschaltet, die die Objekte der -entsprechenden Fliegengewicht-Klassen erzeugt und dabei darauf achtet, dass diese Objekte nur -einmal angelegt und bei erneuter Anfrage einfach nur wieder zurückgeliefert werden. +Für die Klassen, die den intrinsischen Zustand modellieren, werden die Objekte +gemeinsam genutzt (nicht im Diagramm darstellbar) und deshalb eine Factory davor +geschaltet, die die Objekte der entsprechenden Fliegengewicht-Klassen erzeugt und +dabei darauf achtet, dass diese Objekte nur einmal angelegt und bei erneuter Anfrage +einfach nur wieder zurückgeliefert werden. -Zusätzlich gibt es Klassen, die extrinsischen Zustand modellieren und deshalb nicht unter den -Nutzern geteilt werden können und deren Objekte bei jeder Anfrage neu erstellt werden. Aber -auch diese werden von der Factory erzeugt/verwaltet. +Zusätzlich gibt es Klassen, die extrinsischen Zustand modellieren und deshalb nicht +unter den Nutzern geteilt werden können und deren Objekte bei jeder Anfrage neu +erstellt werden. Aber auch diese werden von der Factory erzeugt/verwaltet. ## Kombination mit dem Composite-Pattern -In der Praxis kann man das Pattern so direkt meist nicht einsetzen, sondern verbindet es mit -dem Composite-Pattern: +In der Praxis kann man das Pattern so direkt meist nicht einsetzen, sondern +verbindet es mit dem Composite-Pattern: ![](images/composite.png){width="40%"} -Ein Element kann eine einfache Komponente sein (im obigen Beispiel war das die Klasse `TileModel`) -oder eine zusammengesetzte Komponente, die ihrerseits andere Komponenten speichert (im obigen -Beispiel war das die Klasse `Tile`, die ein Objekt vom Typ `TileModel` referenziert - allerdings -fehlt im obigen Beispiel das gemeinsame Interface ...). +Ein Element kann eine einfache Komponente sein (im obigen Beispiel war das die +Klasse `TileModel`) oder eine zusammengesetzte Komponente, die ihrerseits andere +Komponenten speichert (im obigen Beispiel war das die Klasse `Tile`, die ein Objekt +vom Typ `TileModel` referenziert - allerdings fehlt im obigen Beispiel das +gemeinsame Interface ...). ## Level-Beispiel mit Flyweight (vollständig) und Composite -Im obigen Beispiel wurde zum Flyweight-Pattern noch das Composite-Pattern hinzugenommen, aber -es wurde aus Gründen der Übersichtlichkeit auf ein gemeinsames Interface und auf die Factory -verzichtet. Wenn man es anpassen würde, dann würde das Beispiel ungefähr so aussehen: +Im obigen Beispiel wurde zum Flyweight-Pattern noch das Composite-Pattern +hinzugenommen, aber es wurde aus Gründen der Übersichtlichkeit auf ein gemeinsames +Interface und auf die Factory verzichtet. Wenn man es anpassen würde, dann würde das +Beispiel ungefähr so aussehen: -```java +``` java public interface ITile { public boolean isAccessible(); } @@ -334,28 +317,53 @@ public class Level { ``` ::: - ::: notes # Verwandtschaft zum Type-Object-Pattern -Das [Flyweight-Pattern](https://gameprogrammingpatterns.com/flyweight.html) ist sehr ähnlich zum -[Type-Object-Pattern](type-object.md). In beiden Pattern teilen -sich mehrere Objekte gemeinsame Daten, die über Referenzen auf gemeinsame Hilfsobjekte eingebunden -werden. Die Zielrichtung unterscheidet sich aber deutlich: - -* Beim Flyweight-Pattern ist das Ziel vor allem die Erhöhung der Speichereffizienz, und die - dort geteilten Daten müssen nicht unbedingt den "Typ" des nutzenden Objekts definieren. -* Beim Type-Objekt-Pattern ist das Ziel die Flexibilität auf Code-Ebene, indem man die Anzahl - der Klassen minimiert und die Typen in ein eigenes Objekt-Modell verschiebt. Das Teilen von - Speicher ist hier nur ein Nebeneffekt. +Das [Flyweight-Pattern](https://gameprogrammingpatterns.com/flyweight.html) ist sehr +ähnlich zum [Type-Object-Pattern](type-object.md). In beiden Pattern teilen sich +mehrere Objekte gemeinsame Daten, die über Referenzen auf gemeinsame Hilfsobjekte +eingebunden werden. Die Zielrichtung unterscheidet sich aber deutlich: + +- Beim Flyweight-Pattern ist das Ziel vor allem die Erhöhung der + Speichereffizienz, und die dort geteilten Daten müssen nicht unbedingt den "Typ" + des nutzenden Objekts definieren. +- Beim Type-Objekt-Pattern ist das Ziel die Flexibilität auf Code-Ebene, indem man + die Anzahl der Klassen minimiert und die Typen in ein eigenes Objekt-Modell + verschiebt. Das Teilen von Speicher ist hier nur ein Nebeneffekt. ::: - # Wrap-Up -Flyweight-Pattern: Steigerung der (Speicher-) Effizienz durch gemeinsame Nutzung von Objekten +Flyweight-Pattern: Steigerung der (Speicher-) Effizienz durch gemeinsame Nutzung von +Objekten \bigskip -* Lagere _Intrinsic State_ in gemeinsam genutzte Objekte aus -* Modelliere _Extrinsic State_ individuell +- Lagere *Intrinsic State* in gemeinsam genutzte Objekte aus +- Modelliere *Extrinsic State* individuell + +::: readings +- @Nystrom2014 [Kap. 3] +::: + +::: outcomes +- k2: Unterscheiden von Intrinsic State und Extrinsic State +- k2: Verschieben des Intrinsic States in gemeinsam genutzte Objekte +- k2: Erklären der Ähnlichkeit zum Type-Object-Pattern +- k3: Praktischer Einsatz des Flyweight-Patterns +::: + +::: challenges +In den +[Vorgaben](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/pattern/src/challenges/flyweight) +finden Sie ein Modellierung eines Schachspiels. + +Identifizieren Sie die Stellen im Vorgabe-Code, wo Sie das Flyweight-Pattern +sinnvoll anwenden können und bauen Sie dieses Pattern über ein Refactoring ein. +Begründen Sie, wie Sie das Pattern eingesetzt haben und warum Sie welche Elemente +*immutable* oder *mutable* deklariert haben. + +Wieso eignet sich das Flyweight-Pattern besonders im Bereich von Computerspielen? +Geben Sie mögliche Vor- und Nachteile an und begründen Sie Ihre Antwort. +::: diff --git a/lecture/pattern/observer.md b/lecture/pattern/observer.md index df311f002..71ccefb07 100644 --- a/lecture/pattern/observer.md +++ b/lecture/pattern/observer.md @@ -1,91 +1,28 @@ --- -title: "Observer-Pattern" -author: "Carsten Gips (HSBI)" -readings: - - "@Nystrom2014 [Kap. 4]" - - "@Gamma2011" -tldr: | - Eine Reihe von Objekten möchte über eine Änderung in einem anderen ("zentralen") Objekt informiert werden. - Dazu könnte das "zentrale" Objekt eine Zugriffsmethode anbieten, die die anderen Objekte regelmäßig - abrufen ("pollen"). - - Mit dem Observer-Pattern kann man das aktive Polling vermeiden. Die interessierten Objekte "registrieren" - sich beim "zentralen" Objekt. Sobald dieses eine Änderung erfährt oder Informationen bereitstehen o.ä., - wird das "zentrale" Objekt alle registrierten Objekte über den Aufruf einer Methode benachrichtigen. Dazu - müssen diese eine gemeinsame Schnittstelle implementieren. - - Das "zentrale" Objekt, welches abgefragt wird, nennt man "_Observable_" oder "_Subject_". Die Objekte, die - die Information abfragen möchten, nennt man "_Observer_". -outcomes: - - k2: "Ich kenne den Aufbau des Observer-Patterns und kann dies an einem Beispiel erklären" - - k3: "Ich kann das Observer-Pattern auf konkrete Beispiele (etwa den PM-Dungeon) anwenden" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106535&client_id=FH-Bielefeld" -# name: "Quiz Observer-Pattern (ILIAS)" -youtube: - - link: "https://youtu.be/833lHcoxeog" - name: "VL Observer-Pattern" - - link: "https://youtu.be/0mgB8RfcNuM" - name: "Demo Observer-Pattern" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/e00888ac91978bb3694491a722e61bba5d836d403d7f94e7d3ef6b28c07dae841b5852488bdf8f64e1628a58a2e5f3410dbb08699ded346ec2da34fd877a831f" - name: "VL Observer-Pattern" -challenges: | - **Observer: Restaurant** - - Stellen Sie sich ein Restaurant vor, in welchem man nicht eine komplette Mahlzeit bestellt, sondern aus - einzelnen Komponenten auswählen kann. Die Kunden bestellen also die gewünschten Komponenten, suchen sich - einen Tisch und warten auf die Fertigstellung ihrer Bestellung. Da die Küche leider nur sehr klein ist, - werden immer alle Bestellungen einer bestimmten Komponente zusammen bearbeitet - also beispielsweise werden - alle bestellten Salate angerichtet oder die alle bestellten Pommes-Portionen zubereitet. Sobald eine solche - Komponente fertig ist, werden alle Kunden aufgerufen, die diese Komponente bestellt haben ... - - Modellieren Sie dies in Java. Nutzen Sie dazu das Observer-Pattern, welches Sie ggf. leicht anpassen - müssen. - - - - - **Observer: Einzel- und Großhandel** - - In den [Vorgaben](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/pattern/src/challenges/observer) - finden Sie ein Modell für eine Lieferkette zwischen Großhandel und Einzelhandel. - - Wenn beim Einzelhändler eine Bestellung von einem Kunden eingeht (`Einzelhandel#bestellen`), speichert - dieser den `Auftrag` zunächst in einer Liste ab. In regelmäßigen Abständen (`Einzelhandel#loop`) sendet - der Einzelhändler die offenen Bestellungen an seinen Großhändler (`Grosshandel#bestellen`). Hat der - Großhändler die benötigte Ware vorrätig, sendet er diese an den Einzelhändler (`Einzelhandel#empfangen`). - Dieser kann dann den Auftrag gegenüber seinem Kunden erfüllen (keine Methode vorgesehen). - - Anders als der Einzelhandel speichert der Großhandel keine Aufträge ab. Ist die benötigte Ware bei einer - Bestellung also nicht oder nicht in ausreichender Zahl auf Lager, wird diese nicht geliefert und der - Einzelhandel muss (später) eine neue Bestellung aufgeben. - - Der Großhandel bekommt regelmäßig (`Grosshandel#loop`) neue Ware für die am wenigsten vorrätigen Positionen. - - Im aktuellen Modell wird der Einzelhandel nicht über den neuen Lagerbestand des Großhändlers informiert - und kann daher nur "zufällig" neue Bestellanfragen an den Großhändler senden. - - Verbessern Sie das Modell, indem Sie das Observer-Pattern integrieren. Wer ist Observer? Wer ist Observable? - Welche Informationen werden bei einem `update` mitgeliefert? - - Bauen Sie in alle Aktionen vom Einzelhändler und vom Großhändler passendes Logging ein. - - _Anmerkung_: Sie dürfen nur die Vorgaben-Klassen `Einzelhandel` und `Grosshandel` verändern, die anderen - Vorgaben-Klassen dürfen Sie nicht bearbeiten. Sie können zusätzlich benötigte eigene Klassen/Interfaces - implementieren. +author: Carsten Gips (HSBI) +title: Observer-Pattern --- +::: tldr +Eine Reihe von Objekten möchte über eine Änderung in einem anderen ("zentralen") +Objekt informiert werden. Dazu könnte das "zentrale" Objekt eine Zugriffsmethode +anbieten, die die anderen Objekte regelmäßig abrufen ("pollen"). + +Mit dem Observer-Pattern kann man das aktive Polling vermeiden. Die interessierten +Objekte "registrieren" sich beim "zentralen" Objekt. Sobald dieses eine Änderung +erfährt oder Informationen bereitstehen o.ä., wird das "zentrale" Objekt alle +registrierten Objekte über den Aufruf einer Methode benachrichtigen. Dazu müssen +diese eine gemeinsame Schnittstelle implementieren. + +Das "zentrale" Objekt, welches abgefragt wird, nennt man "*Observable*" oder +"*Subject*". Die Objekte, die die Information abfragen möchten, nennt man +"*Observer*". +::: + +::: youtube +- [VL Observer-Pattern](https://youtu.be/833lHcoxeog) +- [Demo Observer-Pattern](https://youtu.be/0mgB8RfcNuM) +::: # Verteilung der Prüfungsergebnisse @@ -96,10 +33,9 @@ Die Studierenden möchten nach einer Prüfung wissen, ob für einen bestimmten K die/ihre Prüfungsergebnisse im LSF bereit stehen. Dazu modelliert man eine Klasse `LSF` und implementiert eine Abfragemethode, die -dann alle Objekte regelmäßig aufrufen können. Dies sieht dann praktisch etwa so -aus: +dann alle Objekte regelmäßig aufrufen können. Dies sieht dann praktisch etwa so aus: -```java +``` java final Person[] persons = { new Lecturer("Frau Holle"), new Student("Heinz"), new Student("Karla"), @@ -113,7 +49,6 @@ for (Person p : persons) { ``` ::: - # Elegantere Lösung: Observer-Entwurfsmuster ![](images/observerexample.png){width="80%"} @@ -122,55 +57,135 @@ for (Person p : persons) { Sie erstellen im `LSF` eine Methode `register()`, mit der sich interessierte Objekte beim `LSF` registrieren können. -Zur Benachrichtigung der registrierten Objekte brauchen diese eine geeignete Methode, -die traditionell `update()` genannt wird. +Zur Benachrichtigung der registrierten Objekte brauchen diese eine geeignete +Methode, die traditionell `update()` genannt wird. ::: -[Demo: observer]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/pattern/src/observer/"} - +[Demo: observer]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/pattern/src/observer/"} # Observer-Pattern verallgemeinert ![](images/observer.png){width="80%"} ::: notes -Im vorigen Beispiel wurde die Methode `update()` einfach der gemeinsamen Basisklasse `Person` -hinzugefügt. Normalerweise möchte man die Aspekte `Person` und `Observer` aber sauber trennen -und definiert sich dazu ein _separates_ Interface `Observer` mit der Methode `update()`, die -dann alle "interessierten" Klassen (zusätzlich zur bestehenden Vererbungshierarchie) implementieren. - -Die Klasse für das zu beobachtende Objekt benötigt dann eine Methode `register()`, mit der sich -Observer registrieren können. Die Objektreferenzen werden dabei einfach einer internen Sammlung -hinzugefügt. - -Häufig findet sich dann noch eine Methode `unregister()`, mit der sich bereits registrierte -Beobachter wieder abmelden können. Weiterhin findet man häufig eine Methode `notifyObservers()`, -die man von außen auf dem beobachteten Objekt aufrufen kann und die dann auf allen registrierten -Beobachtern deren Methoden `update()` aufruft. (Dieser Vorgang kann aber auch durch eine sonstige -Zustandsänderung im beobachteten Objekt durchgeführt werden.) - -In der Standarddefinition des Observer-Patterns nach [@Gamma2011] werden beim Aufruf der Methode -`update()` keine Werte an die Beobachter mitgegeben. Der Beobachter muss sich entsprechend eine -eigene Referenz auf das beobachtete Objekt halten, um dort dann weitere Informationen erhalten -zu können. Dies kann vereinfacht werden, indem das beobachtete Objekt beim Aufruf der -`update()`-Methode die Informationen als Parameter mitgibt, beispielsweise eine Referenz auf sich -selbst o.ä. ... Dies muss dann natürlich im `Observer`-Interface nachgezogen werden. - -**Hinweis**: Es gibt in Swing bereits die Interfaces `Observer` und `Observable`, die aber als -"deprecated" gekennzeichnet sind. Sinnvollerweise nutzen Sie nicht diese Interfaces aus Swing, -sondern implementieren Ihre eigenen Interfaces, wenn Sie das Observer-Pattern einsetzen wollen! +Im vorigen Beispiel wurde die Methode `update()` einfach der gemeinsamen Basisklasse +`Person` hinzugefügt. Normalerweise möchte man die Aspekte `Person` und `Observer` +aber sauber trennen und definiert sich dazu ein *separates* Interface `Observer` mit +der Methode `update()`, die dann alle "interessierten" Klassen (zusätzlich zur +bestehenden Vererbungshierarchie) implementieren. + +Die Klasse für das zu beobachtende Objekt benötigt dann eine Methode `register()`, +mit der sich Observer registrieren können. Die Objektreferenzen werden dabei einfach +einer internen Sammlung hinzugefügt. + +Häufig findet sich dann noch eine Methode `unregister()`, mit der sich bereits +registrierte Beobachter wieder abmelden können. Weiterhin findet man häufig eine +Methode `notifyObservers()`, die man von außen auf dem beobachteten Objekt aufrufen +kann und die dann auf allen registrierten Beobachtern deren Methoden `update()` +aufruft. (Dieser Vorgang kann aber auch durch eine sonstige Zustandsänderung im +beobachteten Objekt durchgeführt werden.) + +In der Standarddefinition des Observer-Patterns nach [@Gamma2011] werden beim Aufruf +der Methode `update()` keine Werte an die Beobachter mitgegeben. Der Beobachter muss +sich entsprechend eine eigene Referenz auf das beobachtete Objekt halten, um dort +dann weitere Informationen erhalten zu können. Dies kann vereinfacht werden, indem +das beobachtete Objekt beim Aufruf der `update()`-Methode die Informationen als +Parameter mitgibt, beispielsweise eine Referenz auf sich selbst o.ä. ... Dies muss +dann natürlich im `Observer`-Interface nachgezogen werden. + +**Hinweis**: Es gibt in Swing bereits die Interfaces `Observer` und `Observable`, +die aber als "deprecated" gekennzeichnet sind. Sinnvollerweise nutzen Sie nicht +diese Interfaces aus Swing, sondern implementieren Ihre eigenen Interfaces, wenn Sie +das Observer-Pattern einsetzen wollen! ::: - # Wrap-Up Observer-Pattern: Benachrichtige registrierte Objekte über Statusänderungen \bigskip -* Interface `Observer` mit Methode `update()` -* Interessierte Objekte +- Interface `Observer` mit Methode `update()` +- Interessierte Objekte 1. implementieren das Interface `Observer` 2. registrieren sich beim zu beobachtenden Objekt (`Observable`) -* Beobachtetes Objekt ruft auf allen registrierten Objekten `update()` auf -* `update()` kann auch Parameter haben +- Beobachtetes Objekt ruft auf allen registrierten Objekten `update()` auf +- `update()` kann auch Parameter haben + +::: readings +- @Nystrom2014 [Kap. 4] +- @Gamma2011 +::: + +::: outcomes +- k2: Ich kenne den Aufbau des Observer-Patterns und kann dies an einem Beispiel + erklären +- k3: Ich kann das Observer-Pattern auf konkrete Beispiele (etwa den PM-Dungeon) + anwenden +::: + +::: challenges +**Observer: Restaurant** + +Stellen Sie sich ein Restaurant vor, in welchem man nicht eine komplette Mahlzeit +bestellt, sondern aus einzelnen Komponenten auswählen kann. Die Kunden bestellen +also die gewünschten Komponenten, suchen sich einen Tisch und warten auf die +Fertigstellung ihrer Bestellung. Da die Küche leider nur sehr klein ist, werden +immer alle Bestellungen einer bestimmten Komponente zusammen bearbeitet - also +beispielsweise werden alle bestellten Salate angerichtet oder die alle bestellten +Pommes-Portionen zubereitet. Sobald eine solche Komponente fertig ist, werden alle +Kunden aufgerufen, die diese Komponente bestellt haben ... + +Modellieren Sie dies in Java. Nutzen Sie dazu das Observer-Pattern, welches Sie ggf. +leicht anpassen müssen. + + + +**Observer: Einzel- und Großhandel** + +In den +[Vorgaben](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/pattern/src/challenges/observer) +finden Sie ein Modell für eine Lieferkette zwischen Großhandel und Einzelhandel. + +Wenn beim Einzelhändler eine Bestellung von einem Kunden eingeht +(`Einzelhandel#bestellen`), speichert dieser den `Auftrag` zunächst in einer Liste +ab. In regelmäßigen Abständen (`Einzelhandel#loop`) sendet der Einzelhändler die +offenen Bestellungen an seinen Großhändler (`Grosshandel#bestellen`). Hat der +Großhändler die benötigte Ware vorrätig, sendet er diese an den Einzelhändler +(`Einzelhandel#empfangen`). Dieser kann dann den Auftrag gegenüber seinem Kunden +erfüllen (keine Methode vorgesehen). + +Anders als der Einzelhandel speichert der Großhandel keine Aufträge ab. Ist die +benötigte Ware bei einer Bestellung also nicht oder nicht in ausreichender Zahl auf +Lager, wird diese nicht geliefert und der Einzelhandel muss (später) eine neue +Bestellung aufgeben. + +Der Großhandel bekommt regelmäßig (`Grosshandel#loop`) neue Ware für die am +wenigsten vorrätigen Positionen. + +Im aktuellen Modell wird der Einzelhandel nicht über den neuen Lagerbestand des +Großhändlers informiert und kann daher nur "zufällig" neue Bestellanfragen an den +Großhändler senden. + +Verbessern Sie das Modell, indem Sie das Observer-Pattern integrieren. Wer ist +Observer? Wer ist Observable? Welche Informationen werden bei einem `update` +mitgeliefert? + +Bauen Sie in alle Aktionen vom Einzelhändler und vom Großhändler passendes Logging +ein. + +*Anmerkung*: Sie dürfen nur die Vorgaben-Klassen `Einzelhandel` und `Grosshandel` +verändern, die anderen Vorgaben-Klassen dürfen Sie nicht bearbeiten. Sie können +zusätzlich benötigte eigene Klassen/Interfaces implementieren. +::: diff --git a/lecture/pattern/readme.md b/lecture/pattern/readme.md index d022d93d1..9bda8aabc 100644 --- a/lecture/pattern/readme.md +++ b/lecture/pattern/readme.md @@ -1,5 +1,6 @@ --- -title: "Entwurfsmuster" -no_pdf: true no_beamer: true +no_pdf: true +title: Entwurfsmuster --- + diff --git a/lecture/pattern/singleton.md b/lecture/pattern/singleton.md index 129cb7c6f..46a679fc4 100644 --- a/lecture/pattern/singleton.md +++ b/lecture/pattern/singleton.md @@ -1,74 +1,65 @@ --- -title: "Singleton-Pattern" -author: "Carsten Gips (HSBI)" -readings: - - "@Nystrom2014 [Kap. 6]" -tldr: | - Wenn von einer Klasse nur genau ein Objekt angelegt werden kann, nennt man dies auch das - "Singleton-Pattern". - - Dazu muss verhindert werden, dass der Konstruktor aufgerufen werden kann. Üblicherweise - "versteckt" man diesen einfach (Sichtbarkeit auf `private` setzen). Für den Zugriff auf - die Instanz bietet man eine statische Methode an. - - Im Prinzip kann man die Instanz direkt beim Laden der Klasse anlegen ("Eager") oder - abwarten, bis die Instanz über die statische Methode angefordert wird, und das Objekt - erst dann anlegen ("Lazy"). -outcomes: - - k2: "Was ist ein _Singleton_? Was ist der Unterschied zw. einem _Lazy_ und einem _Eager_ Singleton?" - - k3: "Anwendung des Singleton-Patterns" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106536&client_id=FH-Bielefeld" -# name: "Quiz Singleton-Pattern (ILIAS)" -youtube: - - link: "https://youtu.be/ZT3rl1t85aY" - name: "VL Singleton-Pattern" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/22cc77330d6a5fd8784bdf83dc050be4173622cf4ea3c1ef7ddbefa1350d12ea144e228fdded23a4b96aa6948c3b4613f51f99de8c3cd0d3b858577e67851bb5" - name: "VL Singleton-Pattern" +author: Carsten Gips (HSBI) +title: Singleton-Pattern --- +::: tldr +Wenn von einer Klasse nur genau ein Objekt angelegt werden kann, nennt man dies auch +das "Singleton-Pattern". + +Dazu muss verhindert werden, dass der Konstruktor aufgerufen werden kann. +Üblicherweise "versteckt" man diesen einfach (Sichtbarkeit auf `private` setzen). +Für den Zugriff auf die Instanz bietet man eine statische Methode an. + +Im Prinzip kann man die Instanz direkt beim Laden der Klasse anlegen ("Eager") oder +abwarten, bis die Instanz über die statische Methode angefordert wird, und das +Objekt erst dann anlegen ("Lazy"). +::: + +::: youtube +- [VL Singleton-Pattern](https://youtu.be/ZT3rl1t85aY) +::: # Motivation -```java +``` java public enum Fach { IFM, ELM, ARC } ``` \bigskip -```java +``` java Logger l = Logger.getLogger(MyClass.class.getName()); ``` ::: notes -Von den Enum-Konstanten soll es nur genau eine Instantiierung, also jeweils nur genau ein Objekt -geben. Ähnlich war es beim Logging: Für jeden Namen soll/darf es nur einen tatsächlichen Logger -(== Objekt) geben. +Von den Enum-Konstanten soll es nur genau eine Instantiierung, also jeweils nur +genau ein Objekt geben. Ähnlich war es beim Logging: Für jeden Namen soll/darf es +nur einen tatsächlichen Logger (== Objekt) geben. Dies nennt man "**Singleton Pattern**". -_Anmerkung_: Im Logger-Fall handelt es sich streng genommen nicht um ein Singleton, da es vom -Logger mehrere Instanzen geben kann (wenn der Name sich unterscheidet). Aber jeden Logger mit -einem bestimmten Namen gibt es nur einmal im ganzen Programm, insofern ist es doch wieder ein -Beispiel für das Singleton-Pattern ... +*Anmerkung*: Im Logger-Fall handelt es sich streng genommen nicht um ein Singleton, +da es vom Logger mehrere Instanzen geben kann (wenn der Name sich unterscheidet). +Aber jeden Logger mit einem bestimmten Namen gibt es nur einmal im ganzen Programm, +insofern ist es doch wieder ein Beispiel für das Singleton-Pattern ... ::: - # Umsetzung: "Eager" Singleton Pattern ::: notes -Damit man von "außen" keine Instanzen einer Klasse anlegen kann, versteckt man den Konstruktor, -d.h. man setzt die Sichtbarkeit auf `private`. Zusätzlich benötigt man eine Methode, die das -Objekt zurückliefern kann. Beim Logger war dies beispielsweise der Aufruf `Logger.getLogger("name")`. - -Man kann verschiedene Ausprägungen bei der Umsetzung des Singleton Patterns beobachten. Die -beiden wichtigsten sind das "Eager Singleton Pattern" und das "Lazy Singleton Pattern". Der -Unterschied liegt darin, wann genau das Objekt erzeugt wird: Beim "Eager Singleton Pattern" -wird es direkt beim Laden der Klasse erzeugt. +Damit man von "außen" keine Instanzen einer Klasse anlegen kann, versteckt man den +Konstruktor, d.h. man setzt die Sichtbarkeit auf `private`. Zusätzlich benötigt man +eine Methode, die das Objekt zurückliefern kann. Beim Logger war dies beispielsweise +der Aufruf `Logger.getLogger("name")`. + +Man kann verschiedene Ausprägungen bei der Umsetzung des Singleton Patterns +beobachten. Die beiden wichtigsten sind das "Eager Singleton Pattern" und das "Lazy +Singleton Pattern". Der Unterschied liegt darin, wann genau das Objekt erzeugt wird: +Beim "Eager Singleton Pattern" wird es direkt beim Laden der Klasse erzeugt. ::: -```java +``` java public class SingletonEager { private static final SingletonEager inst = new SingletonEager(); @@ -81,17 +72,17 @@ public class SingletonEager { } ``` -[Beispiel: singleton.SingletonEager]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/pattern/src/singleton/SingletonEager.java"} - +[Beispiel: singleton.SingletonEager]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/pattern/src/singleton/SingletonEager.java"} # Umsetzung: "Lazy" Singleton Pattern ::: notes -Beim "Lazy Singleton Pattern" wird das Objekt erst erzeugt, wenn die Instanz tatsächlich benötigt -wird (also erst beim Aufruf der `get`-Methode). +Beim "Lazy Singleton Pattern" wird das Objekt erst erzeugt, wenn die Instanz +tatsächlich benötigt wird (also erst beim Aufruf der `get`-Methode). ::: -```java +``` java public class SingletonLazy { private static SingletonLazy inst = null; @@ -110,8 +101,8 @@ public class SingletonLazy { } ``` -[Beispiel: singleton.SingletonLazy]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/pattern/src/singleton/SingletonLazy.java"} - +[Beispiel: singleton.SingletonLazy]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/pattern/src/singleton/SingletonLazy.java"} # Vorsicht! @@ -120,15 +111,15 @@ Sie schaffen damit eine globale Variable! ::: ::: notes -Da es von der Klasse nur eine Instanz gibt, und Sie sich diese dank der statischen Methode an jeder -Stelle im Programm "geben" lassen können, haben Sie in der Praxis eine globale Variable geschaffen. -Das kann direkt zu schlechter Programmierung (ver-) führen. Zudem wird der Code schwerer lesbar/navigierbar, -da diese Singletons nicht über die Schnittstellen von Methoden übergeben werden müssen. +Da es von der Klasse nur eine Instanz gibt, und Sie sich diese dank der statischen +Methode an jeder Stelle im Programm "geben" lassen können, haben Sie in der Praxis +eine globale Variable geschaffen. Das kann direkt zu schlechter Programmierung +(ver-) führen. Zudem wird der Code schwerer lesbar/navigierbar, da diese Singletons +nicht über die Schnittstellen von Methoden übergeben werden müssen. Nutzen Sie das Pattern **sparsam**. ::: - # Wrap-Up Singleton-Pattern: Klasse, von der nur genau ein Objekt instantiiert werden kann @@ -137,5 +128,15 @@ Singleton-Pattern: Klasse, von der nur genau ein Objekt instantiiert werden kann 1. Konstruktor "verstecken" (Sichtbarkeit auf `private` setzen) 2. Methode zum Zugriff auf die eine Instanz -3. Anlegen der Instanz beispielsweise beim Laden der Klasse ("Eager") oder - beim Aufruf der Zugriffsmethode ("Lazy") +3. Anlegen der Instanz beispielsweise beim Laden der Klasse ("Eager") oder beim + Aufruf der Zugriffsmethode ("Lazy") + +::: readings +- @Nystrom2014 [Kap. 6] +::: + +::: outcomes +- k2: Was ist ein Singleton? Was ist der Unterschied zw. einem Lazy und einem + Eager Singleton? +- k3: Anwendung des Singleton-Patterns +::: diff --git a/lecture/pattern/strategy.md b/lecture/pattern/strategy.md index 4731f0bcb..919d2ccc8 100644 --- a/lecture/pattern/strategy.md +++ b/lecture/pattern/strategy.md @@ -1,64 +1,33 @@ --- -title: "Strategy-Pattern" -author: "Carsten Gips (HSBI)" -readings: - - "@Eilebrecht2013" - - "@Gamma2011" - - "@Kleuker2018" -tldr: | - Das Verhalten von Klassen kann über Vererbungshierarchien weitergegeben und durch - Überschreiben in den erbenden Klassen verändert werden. Dies führt häufig schnell - zu breiten und tiefen Vererbungsstrukturen. - - Das Strategy-Pattern ist ein Entwurfsmuster, in dem Verhalten stattdessen an - passende Klassen/Objekte ausgelagert (delegiert) wird. - - Es wird eine Schnittstelle benötigt (Interface oder abstrakte Klasse), in dem - Methoden zum Abrufen des gewünschten Verhaltens definiert werden. Konkrete Klassen - leiten davon ab und implementieren das gewünschte konkrete Verhalten. - - In den nutzenden Klassen wird zur Laufzeit eine passende Instanz der (Strategie-) - Klassen übergeben (Konstruktor, Setter, ...) und beispielsweise über ein Attribut - referenziert. Das gewünschte Verhalten muss nun nicht mehr in der nutzenden Klasse - selbst implementiert werden, stattdessen wird einfach auf dem übergebenen Objekt - die Methode aus der Schnittstelle aufgerufen. Dies nennt man auch "Delegation", - weil die Aufgabe (das Verhalten) an ein anderes Objekt (hier das Strategie-Objekt) - weiter gereicht (delegiert) wurde. -outcomes: - - k3: "Strategie-Entwurfsmuster praktisch anwenden" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106537&client_id=FH-Bielefeld" -# name: "Quiz Strategy-Pattern (ILIAS)" -youtube: - - link: "https://youtu.be/WI2riW7yOSE" - name: "VL Strategy-Pattern" - - link: "https://youtu.be/IgjlFr2ZcW4" - name: "Demo Strategy-Pattern" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/5ffcaeeee30c133dad50fcb19b5277f4c38f24ced7541531ac8212c56db3114fdb1b435f4eb8e76c8fef3df0e1a49fa510ffa97dee4a7b568ecbc56d1e483692" - name: "VL Strategy-Pattern" -challenges: | - Implementieren Sie das Spiel "Schere,Stein,Papier" (Spielregeln vergleiche - [wikipedia.org/wiki/Schere,Stein,Papier](https://de.wikipedia.org/wiki/Schere,_Stein,_Papier)) in Java. - - Nutzen Sie das Strategy-Pattern, um den Spielerinstanzen zur Laufzeit eine konkrete Spielstrategie mitzugeben, nach denen die Spieler ihre Züge _berechnen_. - Implementieren Sie mindestens drei unterschiedliche konkrete Strategien. - - _Hinweis_: Eine mögliche Strategie könnte sein, den Nutzer via Tastatureingabe nach dem nächsten Zug zu fragen. - - **Gehen Sie bei der Lösung der Aufgabe methodisch vor**: - - 1. Stellen Sie sich eine Liste mit relevanten Anforderungen zusammen. - 2. Erstellen Sie (von Hand) ein Modell (UML-Klassendiagramm): - * Welche Klassen und Interfaces werden benötigt? - * Welche Aufgaben sollen die Klassen haben? - * Welche Attribute und Methoden sind nötig? - * Wie sollen die Klassen interagieren, wer hängt von wem ab? - 3. Implementieren Sie Ihr Modell in Java. Schreiben Sie ein Hauptprogramm, welches das Spiel startet, - die Spieler ziehen lässt und dann das Ergebnis ausgibt. - 4. Überlegen Sie, wie Sie Ihr Programm sinnvoll manuell testen können und tun Sie das. +author: Carsten Gips (HSBI) +title: Strategy-Pattern --- +::: tldr +Das Verhalten von Klassen kann über Vererbungshierarchien weitergegeben und durch +Überschreiben in den erbenden Klassen verändert werden. Dies führt häufig schnell zu +breiten und tiefen Vererbungsstrukturen. + +Das Strategy-Pattern ist ein Entwurfsmuster, in dem Verhalten stattdessen an +passende Klassen/Objekte ausgelagert (delegiert) wird. + +Es wird eine Schnittstelle benötigt (Interface oder abstrakte Klasse), in dem +Methoden zum Abrufen des gewünschten Verhaltens definiert werden. Konkrete Klassen +leiten davon ab und implementieren das gewünschte konkrete Verhalten. + +In den nutzenden Klassen wird zur Laufzeit eine passende Instanz der (Strategie-) +Klassen übergeben (Konstruktor, Setter, ...) und beispielsweise über ein Attribut +referenziert. Das gewünschte Verhalten muss nun nicht mehr in der nutzenden Klasse +selbst implementiert werden, stattdessen wird einfach auf dem übergebenen Objekt die +Methode aus der Schnittstelle aufgerufen. Dies nennt man auch "Delegation", weil die +Aufgabe (das Verhalten) an ein anderes Objekt (hier das Strategie-Objekt) weiter +gereicht (delegiert) wurde. +::: + +::: youtube +- [VL Strategy-Pattern](https://youtu.be/WI2riW7yOSE) +- [Demo Strategy-Pattern](https://youtu.be/IgjlFr2ZcW4) +::: # Wie kann man das Verhalten einer Klasse dynamisch ändern? @@ -67,82 +36,73 @@ challenges: | ::: notes Modellierung unterschiedlicher Hunderassen: Jede Art bellt anders. -Es bietet sich an, die Hunderassen von einer gemeinsamen Basisklasse -`Hund` abzuleiten, um die Hundeartigkeit allgemein sicherzustellen. +Es bietet sich an, die Hunderassen von einer gemeinsamen Basisklasse `Hund` +abzuleiten, um die Hundeartigkeit allgemein sicherzustellen. -Da jede Rasse anders bellen soll, muss jedes Mal die Methode `bellen` -überschrieben werden. Das ist relativ aufwändig und fehleranfällig. -Außerdem kann man damit nicht modellieren, dass es beispielsweise -auch konkrete Bulldoggen geben mag, die nur leise fiepen ... +Da jede Rasse anders bellen soll, muss jedes Mal die Methode `bellen` überschrieben +werden. Das ist relativ aufwändig und fehleranfällig. Außerdem kann man damit nicht +modellieren, dass es beispielsweise auch konkrete Bulldoggen geben mag, die nur +leise fiepen ... ::: - # Lösung: Delegation der Aufgabe an geeignetes Objekt ![](images/hunde_strat.png){width="80%"} ::: notes -Der `Hund` delegiert das Verhalten beim Bellen an ein Objekt, -welches beispielsweise bei der Instantiierung der Klasse übergeben -wurde (oder später über einen Setter). D.h. die Methode `Hund#bellen` -bellt nicht mehr selbst, sondern ruft auf einem passenden Objekt -eine vereinbarte Methode auf. - -Dieses passende Objekt ist hier im Beispiel vom Typ `Bellen` und -hat eine Methode `bellen` (Interface). Die verschiedenen Bell-Arten -kann man über eigene Klassen implementieren, die das Interface -einhalten. - -Damit braucht man in den Klassen für die Hunderassen die Methode -`bellen` nicht jeweils neu überschreiben, sondern muss nur bei -der Instantiierung eines Hundes ein passendes `Bellen`-Objekt -mitgeben. - -Als netten Nebeneffekt kann man so auch leicht eine konkrete -Bulldogge realisieren, die eben nicht fies knurrt, sondern -leise fiept ... +Der `Hund` delegiert das Verhalten beim Bellen an ein Objekt, welches beispielsweise +bei der Instantiierung der Klasse übergeben wurde (oder später über einen Setter). +D.h. die Methode `Hund#bellen` bellt nicht mehr selbst, sondern ruft auf einem +passenden Objekt eine vereinbarte Methode auf. + +Dieses passende Objekt ist hier im Beispiel vom Typ `Bellen` und hat eine Methode +`bellen` (Interface). Die verschiedenen Bell-Arten kann man über eigene Klassen +implementieren, die das Interface einhalten. + +Damit braucht man in den Klassen für die Hunderassen die Methode `bellen` nicht +jeweils neu überschreiben, sondern muss nur bei der Instantiierung eines Hundes ein +passendes `Bellen`-Objekt mitgeben. + +Als netten Nebeneffekt kann man so auch leicht eine konkrete Bulldogge realisieren, +die eben nicht fies knurrt, sondern leise fiept ... ::: \bigskip Entwurfsmuster: **Strategy Pattern** - ::: notes -# Exkurs UML: Assoziation vs. Aggregation vs. Komposition +# Exkurs UML: Assoziation vs. Aggregation vs. Komposition Eine **Assoziation** beschreibt eine Beziehung zwischen zwei (oder mehr) UML-Elementen (etwa Klassen oder Interfaces). Eine **Aggregation** (leere Raute) ist eine Assoziation, die eine -_Teil-Ganzes-Beziehung_ hervorhebt. Teile können dabei ohne das Ganze -existieren (Beispiel: Personen als Partner in einer Ehe-Beziehung). -D.h. auf der einbindenden Seite (mit der leeren Raute) hat man implizit -`0..*` stehen. +*Teil-Ganzes-Beziehung* hervorhebt. Teile können dabei ohne das Ganze existieren +(Beispiel: Personen als Partner in einer Ehe-Beziehung). D.h. auf der einbindenden +Seite (mit der leeren Raute) hat man implizit `0..*` stehen. Eine **Komposition** (volle Raute) ist eine Assoziation, die eine -Teil-Ganzes-Beziehung hervorhebt. Teile können aber nicht ohne das Ganze -existieren (Beispiel: Gebäude und Stockwerke: Ein Gebäude besteht aus -Stockwerken, die ohne das Gebäude aber nicht existieren.). D.h. auf der -einbindenden Seite (mit der vollen Raute) steht implizit eine `1` (ein -Stockwerk gehört genau zu einem Gebäude, ein Gebäude besteht aber aus -mehreren Stockwerken). +Teil-Ganzes-Beziehung hervorhebt. Teile können aber nicht ohne das Ganze existieren +(Beispiel: Gebäude und Stockwerke: Ein Gebäude besteht aus Stockwerken, die ohne das +Gebäude aber nicht existieren.). D.h. auf der einbindenden Seite (mit der vollen +Raute) steht implizit eine `1` (ein Stockwerk gehört genau zu einem Gebäude, ein +Gebäude besteht aber aus mehreren Stockwerken). Siehe auch [Aggregation](https://de.wikipedia.org/wiki/Aggregation_(Informatik)), [Assoziation](https://de.wikipedia.org/wiki/Assoziation_(UML)#Aggregation_und_Komposition) und [Klassendiagramm](https://de.wikipedia.org/wiki/Klassendiagramm). ::: - ::: notes # Zweites Beispiel: Sortieren einer Liste von Studis -Sortieren einer Liste von Studis: `Collections.sort` kann eine Liste -nach einem Default-Kriterium sortieren oder aber über einen extra -`Comparator` nach benutzerdefinierten Kriterien ... Das Verhalten der -Sortiermethode wird also quasi an dieses Comparator-Objekt delegiert ... +Sortieren einer Liste von Studis: `Collections.sort` kann eine Liste nach einem +Default-Kriterium sortieren oder aber über einen extra `Comparator` nach +benutzerdefinierten Kriterien ... Das Verhalten der Sortiermethode wird also quasi +an dieses Comparator-Objekt delegiert ... -```java +``` java public class Studi { private String name; public Studi(String name) { this.name = name; } @@ -159,26 +119,24 @@ public class Studi { } ``` -[Konsole strategy.SortDefault, strategy.SortOwnCrit]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/pattern/src/strategy/"} +[Konsole strategy.SortDefault, strategy.SortOwnCrit]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/pattern/src/strategy/"} -_Anmerkung_: -Die Interfaces `Comparable` und `Comparator` und deren Nutzung wurde(n) in -OOP besprochen. Anonyme Klassen wurden ebenfalls in OOP besprochen. Bitte -lesen Sie dies noch einmal in der Semesterliteratur nach, wenn Sie hier -unsicher sind! +*Anmerkung*: Die Interfaces `Comparable` und `Comparator` und deren Nutzung wurde(n) +in OOP besprochen. Anonyme Klassen wurden ebenfalls in OOP besprochen. Bitte lesen +Sie dies noch einmal in der Semesterliteratur nach, wenn Sie hier unsicher sind! ::: - # Hands-On: Strategie-Muster Implementieren Sie das Strategie-Muster für eine Übersetzungsfunktion: \smallskip -* Eine Klasse liefert eine Nachricht (`String`) mit `getMessage()` zurück. -* Diese Nachricht ist in der Klasse in Englisch implementiert. -* Ein passendes Übersetzerobjekt soll die Nachricht beim Aufruf der Methode - `getMessage()` \newline in die Ziel-Sprache übersetzen. +- Eine Klasse liefert eine Nachricht (`String`) mit `getMessage()` zurück. +- Diese Nachricht ist in der Klasse in Englisch implementiert. +- Ein passendes Übersetzerobjekt soll die Nachricht beim Aufruf der Methode + `getMessage()` `\newline`{=tex} in die Ziel-Sprache übersetzen. \bigskip \bigskip @@ -188,15 +146,14 @@ Implementieren Sie das Strategie-Muster für eine Übersetzungsfunktion: 1. Wie muss das Pattern angepasst werden? 2. Wie sieht die Implementierung aus? - ::: notes # Auflösung ![](images/translator.png){width="80%"} ::: -[Konsole strategy.TranslatorExample]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/pattern/src/strategy/TranslatorExample.java"} - +[Konsole strategy.TranslatorExample]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/pattern/src/strategy/TranslatorExample.java"} # Wrap-Up @@ -204,7 +161,43 @@ Strategy-Pattern: Verhaltensänderung durch Delegation an passendes Objekt \smallskip -* Interface oder abstrakte Klasse als Schnittstelle -* Konkrete Klassen implementieren Schnittstelle => konkrete Strategien -* Zur Laufzeit Instanz dieser Klassen übergeben (Aggregation) ... -* ... und nutzen (Delegation) +- Interface oder abstrakte Klasse als Schnittstelle +- Konkrete Klassen implementieren Schnittstelle =\> konkrete Strategien +- Zur Laufzeit Instanz dieser Klassen übergeben (Aggregation) ... +- ... und nutzen (Delegation) + +::: readings +- @Eilebrecht2013 +- @Gamma2011 +- @Kleuker2018 +::: + +::: outcomes +- k3: Strategie-Entwurfsmuster praktisch anwenden +::: + +::: challenges +Implementieren Sie das Spiel "Schere,Stein,Papier" (Spielregeln vergleiche +[wikipedia.org/wiki/Schere,Stein,Papier](https://de.wikipedia.org/wiki/Schere,_Stein,_Papier)) +in Java. + +Nutzen Sie das Strategy-Pattern, um den Spielerinstanzen zur Laufzeit eine konkrete +Spielstrategie mitzugeben, nach denen die Spieler ihre Züge *berechnen*. +Implementieren Sie mindestens drei unterschiedliche konkrete Strategien. + +*Hinweis*: Eine mögliche Strategie könnte sein, den Nutzer via Tastatureingabe nach +dem nächsten Zug zu fragen. + +**Gehen Sie bei der Lösung der Aufgabe methodisch vor**: + +1. Stellen Sie sich eine Liste mit relevanten Anforderungen zusammen. +2. Erstellen Sie (von Hand) ein Modell (UML-Klassendiagramm): + - Welche Klassen und Interfaces werden benötigt? + - Welche Aufgaben sollen die Klassen haben? + - Welche Attribute und Methoden sind nötig? + - Wie sollen die Klassen interagieren, wer hängt von wem ab? +3. Implementieren Sie Ihr Modell in Java. Schreiben Sie ein Hauptprogramm, welches + das Spiel startet, die Spieler ziehen lässt und dann das Ergebnis ausgibt. +4. Überlegen Sie, wie Sie Ihr Programm sinnvoll manuell testen können und tun Sie + das. +::: diff --git a/lecture/pattern/template-method.md b/lecture/pattern/template-method.md index ab2f6d45d..91ff42edc 100644 --- a/lecture/pattern/template-method.md +++ b/lecture/pattern/template-method.md @@ -1,58 +1,30 @@ --- -title: "Template-Method-Pattern" -author: "Carsten Gips (HSBI)" -readings: - - "@Eilebrecht2013" - - "@Gamma2011" -tldr: | - Das Template-Method-Pattern ist ein Entwurfsmuster, bei dem ein gewisses Verhalten - in einer Methode implementiert wird, die wie eine Schablone agiert, der sogenannten - "Template-Methode". Darin werden dann u.a. Hilfsmethoden aufgerufen, die in der - Basisklasse entweder als `abstract` markiert sind oder mit einem leeren Body - implementiert sind ("Hook-Methoden"). Über diese Template-Methode legt also die - Basisklasse ein gewisses Verhaltensschema fest ("Template") - daher auch der Name. - - In den ableitenden Klassen werden dann die abstrakten Methoden und/oder die Hook-Methoden - implementiert bzw. überschrieben und damit das Verhalten verfeinert. - - Zur Laufzeit ruft man auf den Objekten die Template-Methode auf. Dabei wird von der - Laufzeitumgebung der konkrete Typ der Objekte bestimmt (auch wenn man sie unter dem - Typ der Oberklasse führt) und die am tiefsten in der Vererbungshierarchie implementierten - Methoden aufgerufen. D.h. die Aufrufe der Hilfsmethoden in der Template-Methode führen - zu den in der jeweiligen ableitenden Klasse implementierten Varianten. -outcomes: - - k3: "Ich kann das Template-Method-Entwurfsmuster praktisch anwenden" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106538&client_id=FH-Bielefeld" -# name: "Quiz Template-Method-Pattern (ILIAS)" -youtube: - - link: "https://youtu.be/EE-n2T6AO-g" - name: "VL Template-Method-Pattern" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/a368cbd0990f4f58e5ee776bb4d6a1443c0a7cd443177eaa35ba35558e5c01307f689be5c9c239c6ffb41c74d2726397f2ba6be086df30737eec1df17ac4827a" - name: "VL Template-Method-Pattern" -challenges: | - Schreiben Sie eine abstrakte Klasse Drucker. Implementieren Sie die Funktion - `kopieren`, bei der zuerst die Funktion `scannen` und dann die Funktion `drucken` - aufgerufen wird. Der Kopiervorgang ist für alle Druckertypen identisch, - das Scannen und Drucken ist abhängig vom Druckertyp. - - Implementieren Sie zusätzlich zwei unterschiedliche Druckertypen: - - - `Tintendrucker extends Drucker` - - `Tintendrucker#drucken` loggt den Text "Drucke das Dokument auf dem Tintendrucker." - - `Tintendrucker#scannen` loggt den Text "Scanne das Dokument mit dem Tintendrucker." - - `Laserdrucker extends Drucker` - - `Laserdrucker#drucken` loggt den Text "Drucke das Dokument auf dem Laserdrucker." - - `Laserdrucker#scannen` loggt den Text "Scanne das Dokument mit dem Laserdrucker." - - Nutzen Sie das Template-Method-Pattern. - - +author: Carsten Gips (HSBI) +title: Template-Method-Pattern --- +::: tldr +Das Template-Method-Pattern ist ein Entwurfsmuster, bei dem ein gewisses Verhalten +in einer Methode implementiert wird, die wie eine Schablone agiert, der sogenannten +"Template-Methode". Darin werden dann u.a. Hilfsmethoden aufgerufen, die in der +Basisklasse entweder als `abstract` markiert sind oder mit einem leeren Body +implementiert sind ("Hook-Methoden"). Über diese Template-Methode legt also die +Basisklasse ein gewisses Verhaltensschema fest ("Template") - daher auch der Name. + +In den ableitenden Klassen werden dann die abstrakten Methoden und/oder die +Hook-Methoden implementiert bzw. überschrieben und damit das Verhalten verfeinert. + +Zur Laufzeit ruft man auf den Objekten die Template-Methode auf. Dabei wird von der +Laufzeitumgebung der konkrete Typ der Objekte bestimmt (auch wenn man sie unter dem +Typ der Oberklasse führt) und die am tiefsten in der Vererbungshierarchie +implementierten Methoden aufgerufen. D.h. die Aufrufe der Hilfsmethoden in der +Template-Methode führen zu den in der jeweiligen ableitenden Klasse implementierten +Varianten. +::: + +::: youtube +- [VL Template-Method-Pattern](https://youtu.be/EE-n2T6AO-g) +::: # Motivation: Syntax-Highlighting im Tokenizer @@ -64,7 +36,7 @@ eingelesenen Programmcode) wird ein Strom von Token, mit dem die nächste Stufe Compiler dann weiter arbeiten kann. ::: -```java +``` java public class Lexer { private final List allToken; // alle verfügbaren Token-Klassen @@ -90,29 +62,29 @@ public class Lexer { ::: notes Dazu prüft man jedes Token, ob es auf den aktuellen Anfang des Eingabestroms passt. -Wenn ein Token passt, erzeugt man eine Instanz dieser Token-Klasse und speichert darin -den gematchten Eingabeteil, den man dann vom Eingabestrom entfernt. Danach geht man -in die Schleife und prüft wieder alle Token ... bis irgendwann der Eingabestrom leer -ist und man den gesamten eingelesenen Programmcode in eine dazu passende Folge von -Token umgewandelt hat. - -_Anmerkung_: Abgesehen von fehlenden Javadoc etc. hat das obige Code-Beispiel mehrere -Probleme: Man würde im realen Leben nicht mit `String`, sondern mit einem Zeichenstrom -arbeiten. Außerdem fehlt noch eine Fehlerbehandlung, wenn nämlich keines der Token in -der Liste `allToken` auf den aktuellen Anfang des Eingabestroms passt. +Wenn ein Token passt, erzeugt man eine Instanz dieser Token-Klasse und speichert +darin den gematchten Eingabeteil, den man dann vom Eingabestrom entfernt. Danach +geht man in die Schleife und prüft wieder alle Token ... bis irgendwann der +Eingabestrom leer ist und man den gesamten eingelesenen Programmcode in eine dazu +passende Folge von Token umgewandelt hat. + +*Anmerkung*: Abgesehen von fehlenden Javadoc etc. hat das obige Code-Beispiel +mehrere Probleme: Man würde im realen Leben nicht mit `String`, sondern mit einem +Zeichenstrom arbeiten. Außerdem fehlt noch eine Fehlerbehandlung, wenn nämlich +keines der Token in der Liste `allToken` auf den aktuellen Anfang des Eingabestroms +passt. ::: - # Token-Klassen mit formatiertem Inhalt ::: notes -Um den eigenen Tokenizer besser testen zu können, wurde beschlossen, dass jedes Token -seinen Inhalt als formatiertes HTML-Schnipsel zurückliefern soll. Damit kann man dann -alle erkannten Token formatiert ausgeben und erhält eine Art Syntax-Highlighting für -den eingelesenen Programmcode. +Um den eigenen Tokenizer besser testen zu können, wurde beschlossen, dass jedes +Token seinen Inhalt als formatiertes HTML-Schnipsel zurückliefern soll. Damit kann +man dann alle erkannten Token formatiert ausgeben und erhält eine Art +Syntax-Highlighting für den eingelesenen Programmcode. ::: -```java +``` java public abstract class Token { protected String content; @@ -141,18 +113,17 @@ In der ersten Umsetzung erhält die Basisklasse `Token` eine weitere abstrakte Methode, die jede Token-Klasse implementieren muss und in der die Token-Klassen einen String mit dem Token-Inhalt und einer Formatierung für HTML zurückgeben. -Dabei fällt auf, dass der Aufbau immer gleich ist: Es werden ein oder mehrere -Tags zum Start der Format-Sequenz mit dem Token-Inhalt verbunden, gefolgt mit -einem zum verwendeten startenden HTML-Format-Tag passenden End-Tag. +Dabei fällt auf, dass der Aufbau immer gleich ist: Es werden ein oder mehrere Tags +zum Start der Format-Sequenz mit dem Token-Inhalt verbunden, gefolgt mit einem zum +verwendeten startenden HTML-Format-Tag passenden End-Tag. Auch wenn die Inhalte unterschiedlich sind, sieht das stark nach einer Verletzung von [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) aus ... ::: - # Don't call us, we'll call you -```java +``` java public abstract class Token { protected String content; @@ -178,30 +149,31 @@ LOG.info(t.getHtml()); ``` ::: notes -Wir können den Spaß einfach umdrehen (["inversion of control"](https://en.wikipedia.org/wiki/Inversion_of_control)) -und die Methode zum Zusammenbasteln des HTML-Strings bereits in der Basisklasse -implementieren. Dazu "rufen" wir dort drei Hilfsmethoden auf, die die jeweiligen -Bestandteile des Strings (Format-Start, Inhalt, Format-Ende) erzeugen und deren -konkrete Implementierung wir in der Basisklasse nicht kennen. Dies ist dann Sache -der ableitenden konkreten Token-Klassen. +Wir können den Spaß einfach umdrehen (["inversion of +control"](https://en.wikipedia.org/wiki/Inversion_of_control)) und die Methode zum +Zusammenbasteln des HTML-Strings bereits in der Basisklasse implementieren. Dazu +"rufen" wir dort drei Hilfsmethoden auf, die die jeweiligen Bestandteile des Strings +(Format-Start, Inhalt, Format-Ende) erzeugen und deren konkrete Implementierung wir +in der Basisklasse nicht kennen. Dies ist dann Sache der ableitenden konkreten +Token-Klassen. Objekte vom Typ `KeyWord` sind dank der Vererbungsbeziehung auch `Token` (Vererbung: -_is-a-Beziehung_). Wenn man nun auf einem `Token t` die Methode `getHtml()` aufruft, +*is-a-Beziehung*). Wenn man nun auf einem `Token t` die Methode `getHtml()` aufruft, wird zur Laufzeit geprüft, welchen Typ `t` tatsächlich hat (im Beispiel `KeyWord`). -Methodenaufrufe werden dann mit den am tiefsten in der vorliegenden Vererbungshierarchie -implementierten Methoden durchgeführt: Hier wird also die von `Token` geerbte Methode -`getHtml()` in `KeyWord` aufgerufen, die ihrerseits die Methoden `htmlStart()` und -`htmlEnd()` aufruft. Diese sind in `KeyWord` implementiert und liefern nun die passenden -Ergebnisse. - -Die Methode `getHtml()` wird auch als "_Template-Methode_" bezeichnet. Die beiden darin -aufgerufenen Methoden `htmlStart()` und `htmlEnd()` in `Token` werden auch als "Hilfsmethoden" -(oder "_Helper Methods_") bezeichnet. - -Dies ist ein Beispiel für das **[Template-Method-Pattern](https://en.wikipedia.org/wiki/Template_method_pattern)**. +Methodenaufrufe werden dann mit den am tiefsten in der vorliegenden +Vererbungshierarchie implementierten Methoden durchgeführt: Hier wird also die von +`Token` geerbte Methode `getHtml()` in `KeyWord` aufgerufen, die ihrerseits die +Methoden `htmlStart()` und `htmlEnd()` aufruft. Diese sind in `KeyWord` +implementiert und liefern nun die passenden Ergebnisse. + +Die Methode `getHtml()` wird auch als "*Template-Methode*" bezeichnet. Die beiden +darin aufgerufenen Methoden `htmlStart()` und `htmlEnd()` in `Token` werden auch als +"Hilfsmethoden" (oder "*Helper Methods*") bezeichnet. + +Dies ist ein Beispiel für das +**[Template-Method-Pattern](https://en.wikipedia.org/wiki/Template_method_pattern)**. ::: - # Template-Method-Pattern ![](images/template-method.png){width="80%" web_width="50%"} @@ -209,14 +181,15 @@ Dies ist ein Beispiel für das **[Template-Method-Pattern](https://en.wikipedia. ::: notes ## Aufbau Template-Method-Pattern -In der Basisklasse implementiert man eine Template-Methode (in der Skizze `templateMethod`), -die sich auf anderen in der Basisklasse deklarierten (Hilfs-) Methoden "abstützt" (diese also -aufruft; in der Skizze `method1`, `method2`, `method3`). Diese Hilfsmethoden können als -`abstract` markiert werden und _müssen_ dann von den ableitenden Klassen implementiert werden -(in der Skizze `method1` und `method2`). Man kann aber auch einige/alle dieser aufgerufenen -Hilfsmethoden in der Basisklasse implementieren (beispielsweise mit einem leeren Body - sogenannte -"Hook"-Methoden) und die ableitenden Klassen _können_ dann diese Methoden überschreiben und das -Verhalten so neu formulieren (in der Skizze `method3`). +In der Basisklasse implementiert man eine Template-Methode (in der Skizze +`templateMethod`), die sich auf anderen in der Basisklasse deklarierten (Hilfs-) +Methoden "abstützt" (diese also aufruft; in der Skizze `method1`, `method2`, +`method3`). Diese Hilfsmethoden können als `abstract` markiert werden und *müssen* +dann von den ableitenden Klassen implementiert werden (in der Skizze `method1` und +`method2`). Man kann aber auch einige/alle dieser aufgerufenen Hilfsmethoden in der +Basisklasse implementieren (beispielsweise mit einem leeren Body - sogenannte +"Hook"-Methoden) und die ableitenden Klassen *können* dann diese Methoden +überschreiben und das Verhalten so neu formulieren (in der Skizze `method3`). Damit werden Teile des Verhaltens an die ableitenden Klassen ausgelagert. @@ -224,28 +197,65 @@ Damit werden Teile des Verhaltens an die ableitenden Klassen ausgelagert. Das Template-Method-Pattern hat eine starke Verwandtschaft zum Strategy-Pattern. -Im Strategy-Pattern haben wir Verhalten komplett an andere Objekte _delegiert_, indem wir in -einer Methode einfach die passende Methode auf dem übergebenen Strategie-Objekt aufgerufen haben. - -Im Template-Method-Pattern nutzen wir statt Delegation die Mechanismen Vererbung und dynamische -Polymorphie und definieren in der Basis-Klasse abstrakte oder Hook-Methoden, die wir bereits in -der Template-Methode der Basis-Klasse aufrufen. Damit ist das grobe Verhalten in der Basis-Klasse -festgelegt, wird aber in den ableitenden Klassen durch das dortige Definieren oder Überschreiben -der Hilfsmethoden verfeinert. Zur Laufzeit werden dann durch die dynamische Polymorphie die -tatsächlich implementierten Hilfsmethoden in den ableitenden Klassen aufgerufen. Damit lagert man -im Template-Method-Pattern gewissermaßen nur Teile des Verhaltens an die ableitenden Klassen aus. +Im Strategy-Pattern haben wir Verhalten komplett an andere Objekte *delegiert*, +indem wir in einer Methode einfach die passende Methode auf dem übergebenen +Strategie-Objekt aufgerufen haben. + +Im Template-Method-Pattern nutzen wir statt Delegation die Mechanismen Vererbung und +dynamische Polymorphie und definieren in der Basis-Klasse abstrakte oder +Hook-Methoden, die wir bereits in der Template-Methode der Basis-Klasse aufrufen. +Damit ist das grobe Verhalten in der Basis-Klasse festgelegt, wird aber in den +ableitenden Klassen durch das dortige Definieren oder Überschreiben der +Hilfsmethoden verfeinert. Zur Laufzeit werden dann durch die dynamische Polymorphie +die tatsächlich implementierten Hilfsmethoden in den ableitenden Klassen aufgerufen. +Damit lagert man im Template-Method-Pattern gewissermaßen nur Teile des Verhaltens +an die ableitenden Klassen aus. ::: - # Wrap-Up Template-Method-Pattern: Verhaltensänderung durch Vererbungsbeziehungen \smallskip -* Basis-Klasse: - * Template-Methode, die Verhalten definiert und Hilfsmethoden aufruft - * Hilfsmethoden: Abstrakte Methoden (oder "Hook": Basis-Implementierung) -* Ableitende Klassen: Verfeinern Verhalten durch Implementieren der Hilfsmethoden -* Zur Laufzeit: Dynamische Polymorphie: Aufruf der Template-Methode nutzt - die im tatsächlichen Typ des Objekts implementierten Hilfsmethoden +- Basis-Klasse: + - Template-Methode, die Verhalten definiert und Hilfsmethoden aufruft + - Hilfsmethoden: Abstrakte Methoden (oder "Hook": Basis-Implementierung) +- Ableitende Klassen: Verfeinern Verhalten durch Implementieren der Hilfsmethoden +- Zur Laufzeit: Dynamische Polymorphie: Aufruf der Template-Methode nutzt die im + tatsächlichen Typ des Objekts implementierten Hilfsmethoden + +::: readings +- @Eilebrecht2013 +- @Gamma2011 +::: + +::: outcomes +- k3: Ich kann das Template-Method-Entwurfsmuster praktisch anwenden +::: + +::: challenges +Schreiben Sie eine abstrakte Klasse Drucker. Implementieren Sie die Funktion +`kopieren`, bei der zuerst die Funktion `scannen` und dann die Funktion `drucken` +aufgerufen wird. Der Kopiervorgang ist für alle Druckertypen identisch, das Scannen +und Drucken ist abhängig vom Druckertyp. + +Implementieren Sie zusätzlich zwei unterschiedliche Druckertypen: + +- `Tintendrucker extends Drucker` + - `Tintendrucker#drucken` loggt den Text "Drucke das Dokument auf dem + Tintendrucker." + - `Tintendrucker#scannen` loggt den Text "Scanne das Dokument mit dem + Tintendrucker." +- `Laserdrucker extends Drucker` + - `Laserdrucker#drucken` loggt den Text "Drucke das Dokument auf dem + Laserdrucker." + - `Laserdrucker#scannen` loggt den Text "Scanne das Dokument mit dem + Laserdrucker." + +Nutzen Sie das Template-Method-Pattern. + + +::: diff --git a/lecture/pattern/type-object.md b/lecture/pattern/type-object.md index 20b0b798c..8ffbce8cb 100644 --- a/lecture/pattern/type-object.md +++ b/lecture/pattern/type-object.md @@ -1,74 +1,39 @@ --- -title: "Type-Object-Pattern" -author: "Carsten Gips (HSBI)" -readings: - - "@Nystrom2014 [Kap. 13]" -tldr: | - Das Type-Object-Pattern dient dazu, die Anzahl der Klassen auf Code-Ebene zu reduzieren und - durch eine Konfiguration zu ersetzen und damit eine höhere Flexibilität zu erreichen. - - Dazu werden sogenannte Type-Objects definiert: Sie enthalten genau die Eigenschaften, die - in verschiedenen (Unter-) Klassen gemeinsam vorkommen. Damit können diese Eigenschaften - aus den ursprünglichen Klassen entfernt und durch eine Referenz auf ein solches Type-Object - ersetzt werden. In den Klassen muss man dann nur noch die für die einzelnen Typen - individuellen Eigenschaften implementieren. Zusätzlich kann man nun verschiedene (Unter-) - Klassen zusammenlegen, da der Typ über das geteilte Type-Object definiert wird (zur Laufzeit) - und nicht mehr durch eine separate Klasse auf Code-Ebene repräsentiert werden muss. - - Die Type-Objects werden zur Laufzeit mit den entsprechenden Ausprägungen der früheren (Unter-) - Klassen angelegt und dann über den Konstruktor in die nutzenden Objekte übergeben. Dadurch - teilen sich alle Objekte einer früheren (Unter-) Klasse das selbe Type-Objekt und zeigen nach - außen das selbe Verhalten. Die Type-Objects werden häufig über eine entsprechende - Konfiguration erzeugt, so dass man beispielsweise unterschiedliche Monsterklassen und - -eigenschaften ausprobieren kann, ohne den Code neu kompilieren zu müssen. Man kann - sogar eine Art "Vererbung" unter den Type-Objects implementieren. -outcomes: - - k2: "Verschieben des Typ-definierenden Teils der Eigenschaften in ein Type-Object" - - k2: "Erklären der Ähnlichkeit zum Flyweight-Pattern" - - k3: "Praktischer Einsatz des Type-Object-Patterns" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106539&client_id=FH-Bielefeld" -# name: "Quiz Type-Object-Pattern (ILIAS)" -youtube: - - link: "https://youtu.be/No-xduTVlt0" - name: "VL Type-Object-Pattern" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/cbd631e6a505ed773555ab6ebf3b3618496dbca65b07027db5a8655e018336f234a562e1648f312964d796b449ba9fea15db9e07ef6ec13c9d0928ab50ba6d78" - name: "VL Type-Object-Pattern" -challenges: | - Betrachten Sie das folgende `IMonster`-Interface: - - ```java - public interface IMonster { - String getVariety(); - int getXp(); - int getMagic(); - String makeNoise(); - } - ``` - - Leiten Sie von diesem Interface eine Klasse `Monster` ab. Nutzen Sie das Type-Object-Pattern - und erzeugen Sie verschiedene "Klassen" von Monstern, die sich in den Eigenschaften `variety`, - `xp` und `magic` unterscheiden und in der Methode `makeNoise()` entsprechend unterschiedlich - verhalten. Die Eigenschaft `xp` wird dabei von jedem Monster während seiner Lebensdauer selbst - verwaltet, die anderen Eigenschaften bleiben während der Lebensdauer eines Monsters konstant - (ebenso wie die Methode `makeNoise()`). - - 1. Was wird Bestandteil des Type-Objects? Begründen Sie Ihre Antwort. - 2. Implementieren Sie das Type-Object und integrieren Sie es in die Klasse `Monster`. - 3. Implementieren Sie eine Factory-Methode in der Klasse für die Type-Objects, um ein neues - Monster mit diesem Type-Objekt erzeugen zu können. - 4. Implementieren Sie einen "Vererbungs"-Mechanismus für die Type-Objects (nicht Vererbung - im Java-/OO-Sinn!). Dabei soll eine Eigenschaft überschrieben werden können. - 5. Erzeugen Sie einige Monstertypen und jeweils einige Monster und lassen Sie diese ein - Geräusch machen (`makeNoise()`). - 6. Ersetzen Sie das Type-Object durch ein selbst definiertes (komplexes) Enum. +author: Carsten Gips (HSBI) +title: Type-Object-Pattern --- +::: tldr +Das Type-Object-Pattern dient dazu, die Anzahl der Klassen auf Code-Ebene zu +reduzieren und durch eine Konfiguration zu ersetzen und damit eine höhere +Flexibilität zu erreichen. + +Dazu werden sogenannte Type-Objects definiert: Sie enthalten genau die +Eigenschaften, die in verschiedenen (Unter-) Klassen gemeinsam vorkommen. Damit +können diese Eigenschaften aus den ursprünglichen Klassen entfernt und durch eine +Referenz auf ein solches Type-Object ersetzt werden. In den Klassen muss man dann +nur noch die für die einzelnen Typen individuellen Eigenschaften implementieren. +Zusätzlich kann man nun verschiedene (Unter-) Klassen zusammenlegen, da der Typ über +das geteilte Type-Object definiert wird (zur Laufzeit) und nicht mehr durch eine +separate Klasse auf Code-Ebene repräsentiert werden muss. + +Die Type-Objects werden zur Laufzeit mit den entsprechenden Ausprägungen der +früheren (Unter-) Klassen angelegt und dann über den Konstruktor in die nutzenden +Objekte übergeben. Dadurch teilen sich alle Objekte einer früheren (Unter-) Klasse +das selbe Type-Objekt und zeigen nach außen das selbe Verhalten. Die Type-Objects +werden häufig über eine entsprechende Konfiguration erzeugt, so dass man +beispielsweise unterschiedliche Monsterklassen und -eigenschaften ausprobieren kann, +ohne den Code neu kompilieren zu müssen. Man kann sogar eine Art "Vererbung" unter +den Type-Objects implementieren. +::: + +::: youtube +- [VL Type-Object-Pattern](https://youtu.be/No-xduTVlt0) +::: # Motivation: Monster und spezialisierte Monster -```java +``` java public abstract class Monster { protected int attackDamage; protected int movementSpeed; @@ -98,26 +63,25 @@ Eigenschaften eines Monsters: Es kann sich mit einer bestimmten Geschwindigkeit bewegen und es kann anderen Monstern bei einem Angriff einen bestimmten Schaden zufügen. -Um nun andere Monstertypen zu erzeugen, greifen Sie zur Vererbung und leiten von -der Basisklasse Ihre spezialisierten Monster ab und überschreiben die Defaultwerte -und bei Bedarf auch das Verhalten (die Methoden). +Um nun andere Monstertypen zu erzeugen, greifen Sie zur Vererbung und leiten von der +Basisklasse Ihre spezialisierten Monster ab und überschreiben die Defaultwerte und +bei Bedarf auch das Verhalten (die Methoden). Damit entsteht aber recht schnell eine tiefe und verzweigte Vererbungshierarchie, -Sie müssen ja für jede Variation eine neue Unterklasse anlegen. Außerdem müssen -für jede (noch so kleine) Änderung an den Monster-Eigenschaften viele Klassen -editiert und das gesamte Projekt neu kompiliert werden. - -Es würde auch nicht wirklich helfen, die Eigenschaften der Unterklassen über -deren Konstruktor einstellbar zu machen (die `Rat` könnte in ihrem Konstruktor -beispielsweise noch die Werte für Damage und Speed übergeben bekommen). Dann -würden die Eigenschaften an allen Stellen im Programm verstreut, wo Sie den -Konstruktor aufrufen. +Sie müssen ja für jede Variation eine neue Unterklasse anlegen. Außerdem müssen für +jede (noch so kleine) Änderung an den Monster-Eigenschaften viele Klassen editiert +und das gesamte Projekt neu kompiliert werden. + +Es würde auch nicht wirklich helfen, die Eigenschaften der Unterklassen über deren +Konstruktor einstellbar zu machen (die `Rat` könnte in ihrem Konstruktor +beispielsweise noch die Werte für Damage und Speed übergeben bekommen). Dann würden +die Eigenschaften an allen Stellen im Programm verstreut, wo Sie den Konstruktor +aufrufen. ::: - # Vereinfachen der Vererbungshierarchie (mit Enums als Type-Object) -```java +``` java public enum Species { RAT, GNOLL, ... } public final class Monster { @@ -143,26 +107,28 @@ public static void main(String[] args) { ``` ::: notes -Die Lösung für die Vermeidung der Vererbungshierarchie: Die Monster-Basisklasse bekommt ein -Attribut, welches den Typ des Monsters bestimmt (das sogenannte "Type-Object"). Das könnte -wie im Beispiel ein einfaches Enum sein, das in den Methoden des Monsters abgefragt wird. -So kann zur Laufzeit bei der Erzeugung der Monster-Objekte durch Übergabe des Enums bestimmt -werden, was genau dieses konkrete Monster genau ist bzw. wie es sich verhält. - -Im obigen Beispiel wird eine Variante gezeigt, wo das Enum im Konstruktor ausgewertet -wird und die Attribute entsprechend gesetzt werden. Man könnte das auch so implementieren, -dass man auf die Attribute verzichtet und stattdessen stets das Enum auswertet. - -Allerdings ist das Hantieren mit den Enums etwas umständlich: Man muss an allen Stellen, -wo das Verhalten der Monster unterschiedlich ist, ein `switch/case` einbauen und den Wert -des Type-Objects abfragen. Das bedeutet einerseits viel duplizierten Code und andererseits -muss man bei Erweiterungen des Enums auch _alle_ `switch/case`-Blöcke anpassen. +Die Lösung für die Vermeidung der Vererbungshierarchie: Die Monster-Basisklasse +bekommt ein Attribut, welches den Typ des Monsters bestimmt (das sogenannte +"Type-Object"). Das könnte wie im Beispiel ein einfaches Enum sein, das in den +Methoden des Monsters abgefragt wird. So kann zur Laufzeit bei der Erzeugung der +Monster-Objekte durch Übergabe des Enums bestimmt werden, was genau dieses konkrete +Monster genau ist bzw. wie es sich verhält. + +Im obigen Beispiel wird eine Variante gezeigt, wo das Enum im Konstruktor +ausgewertet wird und die Attribute entsprechend gesetzt werden. Man könnte das auch +so implementieren, dass man auf die Attribute verzichtet und stattdessen stets das +Enum auswertet. + +Allerdings ist das Hantieren mit den Enums etwas umständlich: Man muss an allen +Stellen, wo das Verhalten der Monster unterschiedlich ist, ein `switch/case` +einbauen und den Wert des Type-Objects abfragen. Das bedeutet einerseits viel +duplizierten Code und andererseits muss man bei Erweiterungen des Enums auch *alle* +`switch/case`-Blöcke anpassen. ::: - # Monster mit Strategie -```java +``` java public final class Species { private final int attackDamage; private final int movementSpeed; @@ -196,19 +162,19 @@ Statt des Enums nimmt man eine "echte" Klasse mit Methoden für die Type-Objects Davon legt man zur Laufzeit Objekte an (das sind dann die möglichen Monster-Typen) und bestückt damit die zu erzeugenden Monster. -Im Monster selbst rufen die Monster-Methoden dann einfach nur die Methoden des Type-Objects -auf (Delegation => [Strategie-Pattern](strategy.md)). Man -kann aber auch Attribute im Monster selbst pflegen und durch das Type-Object nur passend +Im Monster selbst rufen die Monster-Methoden dann einfach nur die Methoden des +Type-Objects auf (Delegation =\> [Strategie-Pattern](strategy.md)). Man kann aber +auch Attribute im Monster selbst pflegen und durch das Type-Object nur passend initialisieren. -Vorteil: Änderungen erfolgen bei der Parametrisierung der Objekte (an **einer** Stelle im -Code, vermutlich `main()` oder beispielsweise durch Einlesen einer Konfig-Datei). +Vorteil: Änderungen erfolgen bei der Parametrisierung der Objekte (an **einer** +Stelle im Code, vermutlich `main()` oder beispielsweise durch Einlesen einer +Konfig-Datei). ::: - # Fabrikmethode für die Type-Objects -```java +``` java public final class Species { ... @@ -229,15 +195,14 @@ public static void main(String[] args) { ::: notes Das Hantieren mit den Type-Objects und den Monstern ist nicht so schön. Deshalb kann -man in der Klasse für die Type-Objects noch eine Fabrikmethode (=> -[Factory-Method-Pattern](factory-method.md)) mit -einbauen, über die dann die Monster erzeugt werden. +man in der Klasse für die Type-Objects noch eine Fabrikmethode (=\> +[Factory-Method-Pattern](factory-method.md)) mit einbauen, über die dann die Monster +erzeugt werden. ::: - # Vererbung unter den Type-Objects -```java +``` java public final class Species { ... @@ -262,20 +227,20 @@ public static void main(String[] args) { ``` ::: notes -Es wäre hilfreich, wenn die Type-Objects Eigenschaften untereinander teilen/weitergeben -könnten. Damit man aber jetzt nicht hier eine tiefe Vererbungshierarchie aufbaut und -damit wieder am Anfang des Problems wäre, baut man die Vererbung quasi selbst ein über -eine Referenz auf ein Eltern-Type-Object. Damit kann man zur Laufzeit einem Type-Object -sagen, dass es bestimmte Eigenschaften von einem anderen Type-Object übernehmen soll. - -Im Beispiel werden die Eigenschaften `movementSpeed` und `xp` "vererbt" und entsprechend -aus dem Eltern-Type-Object übernommen (sofern dieses übergeben wird). +Es wäre hilfreich, wenn die Type-Objects Eigenschaften untereinander +teilen/weitergeben könnten. Damit man aber jetzt nicht hier eine tiefe +Vererbungshierarchie aufbaut und damit wieder am Anfang des Problems wäre, baut man +die Vererbung quasi selbst ein über eine Referenz auf ein Eltern-Type-Object. Damit +kann man zur Laufzeit einem Type-Object sagen, dass es bestimmte Eigenschaften von +einem anderen Type-Object übernehmen soll. + +Im Beispiel werden die Eigenschaften `movementSpeed` und `xp` "vererbt" und +entsprechend aus dem Eltern-Type-Object übernommen (sofern dieses übergeben wird). ::: - # Erzeugen der Type-Objects dynamisch über eine Konfiguration -```json +``` json { "Rat": { "attackDamage": 10, @@ -295,61 +260,106 @@ aus dem Eltern-Type-Object übernommen (sofern dieses übergeben wird). ``` ::: notes -Jetzt kann man die Konfiguration der Type-Objects in einer Konfig-Datei ablegen und einfach -an einer passenden Stelle im Programm einlesen. Dort werden dann damit die Type-Objects -angelegt und mit Hilfe dieser dann die passend konfigurierten Monster (und deren Unterarten) -erzeugt. +Jetzt kann man die Konfiguration der Type-Objects in einer Konfig-Datei ablegen und +einfach an einer passenden Stelle im Programm einlesen. Dort werden dann damit die +Type-Objects angelegt und mit Hilfe dieser dann die passend konfigurierten Monster +(und deren Unterarten) erzeugt. ::: - ::: notes # Vor- und Nachteile des Type-Object-Pattern ## Vorteil -Es gibt nur noch wenige Klassen auf Code-Ebene (im Beispiel: 2), und man kann über die -Konfiguration beliebig viele Monster-Typen erzeugen. +Es gibt nur noch wenige Klassen auf Code-Ebene (im Beispiel: 2), und man kann über +die Konfiguration beliebig viele Monster-Typen erzeugen. ## Nachteil -Es werden zunächst nur Daten "überschrieben", d.h. man kann nur für die einzelnen Typen -spezifische Werte mitgeben/definieren. +Es werden zunächst nur Daten "überschrieben", d.h. man kann nur für die einzelnen +Typen spezifische Werte mitgeben/definieren. -Bei Vererbung kann man in den Unterklassen nahezu beliebig das Verhalten durch einfaches -Überschreiben der Methoden ändern. Das könnte man in diesem Entwurfsmuster erreichen, in -dem man beispielsweise eine Reihe von vordefinierten Verhaltensarten implementiert, die -dann anhand von Werten ausgewählt und anhand anderer Werte weiter parametrisiert werden. +Bei Vererbung kann man in den Unterklassen nahezu beliebig das Verhalten durch +einfaches Überschreiben der Methoden ändern. Das könnte man in diesem Entwurfsmuster +erreichen, in dem man beispielsweise eine Reihe von vordefinierten Verhaltensarten +implementiert, die dann anhand von Werten ausgewählt und anhand anderer Werte weiter +parametrisiert werden. ## Verwandtschaft zum Flyweight-Pattern -Das [Type-Object-Pattern](https://gameprogrammingpatterns.com/type-object.html) ist keines -der ["klassischen" Design-Pattern](https://en.wikipedia.org/wiki/Design_Patterns) der "Gang -of Four" [@Gamma2011]. Dennoch ist es gerade in der Spiele-Entwicklung häufig anzutreffen. - -Das Type-Object-Pattern ist sehr ähnlich zum [Flyweight-Pattern](flyweight.md). -In beiden Pattern teilen sich mehrere Objekte gemeinsame Daten, die über Referenzen auf -gemeinsame Hilfsobjekte eingebunden werden. Die Zielrichtung unterscheidet sich aber deutlich: - -* Beim Flyweight-Pattern ist das Ziel vor allem die Erhöhung der Speichereffizienz, und die - dort geteilten Daten müssen nicht unbedingt den "Typ" des nutzenden Objekts definieren. -* Beim Type-Objekt-Pattern ist das Ziel die Flexibilität auf Code-Ebene, indem man die Anzahl - der Klassen minimiert und die Typen in ein eigenes Objekt-Modell verschiebt. Das Teilen von - Speicher ist hier nur ein Nebeneffekt. +Das [Type-Object-Pattern](https://gameprogrammingpatterns.com/type-object.html) ist +keines der ["klassischen" +Design-Pattern](https://en.wikipedia.org/wiki/Design_Patterns) der "Gang of Four" +[@Gamma2011]. Dennoch ist es gerade in der Spiele-Entwicklung häufig anzutreffen. + +Das Type-Object-Pattern ist sehr ähnlich zum [Flyweight-Pattern](flyweight.md). In +beiden Pattern teilen sich mehrere Objekte gemeinsame Daten, die über Referenzen auf +gemeinsame Hilfsobjekte eingebunden werden. Die Zielrichtung unterscheidet sich aber +deutlich: + +- Beim Flyweight-Pattern ist das Ziel vor allem die Erhöhung der + Speichereffizienz, und die dort geteilten Daten müssen nicht unbedingt den "Typ" + des nutzenden Objekts definieren. +- Beim Type-Objekt-Pattern ist das Ziel die Flexibilität auf Code-Ebene, indem man + die Anzahl der Klassen minimiert und die Typen in ein eigenes Objekt-Modell + verschiebt. Das Teilen von Speicher ist hier nur ein Nebeneffekt. ::: - # Wrap-Up Type-Object-Pattern: Implementierung eines eigenen Objekt-Modells \bigskip -* Ziel: Minimierung der Anzahl der Klassen -* Ziel: Erhöhung der Flexibilität +- Ziel: Minimierung der Anzahl der Klassen +- Ziel: Erhöhung der Flexibilität \smallskip -* Schiebe "Typen" in ein eigenes Objekt-Modell -* Type-Objects lassen sich dynamisch über eine Konfiguration anlegen -* Objekte erhalten eine Referenz auf "ihr" Type-Object -* "Vererbung" unter den Type-Objects möglich +- Schiebe "Typen" in ein eigenes Objekt-Modell +- Type-Objects lassen sich dynamisch über eine Konfiguration anlegen +- Objekte erhalten eine Referenz auf "ihr" Type-Object +- "Vererbung" unter den Type-Objects möglich + +::: readings +- @Nystrom2014 [Kap. 13] +::: + +::: outcomes +- k2: Verschieben des Typ-definierenden Teils der Eigenschaften in ein Type-Object +- k2: Erklären der Ähnlichkeit zum Flyweight-Pattern +- k3: Praktischer Einsatz des Type-Object-Patterns +::: + +::: challenges +Betrachten Sie das folgende `IMonster`-Interface: + +``` java +public interface IMonster { + String getVariety(); + int getXp(); + int getMagic(); + String makeNoise(); +} +``` + +Leiten Sie von diesem Interface eine Klasse `Monster` ab. Nutzen Sie das +Type-Object-Pattern und erzeugen Sie verschiedene "Klassen" von Monstern, die sich +in den Eigenschaften `variety`, `xp` und `magic` unterscheiden und in der Methode +`makeNoise()` entsprechend unterschiedlich verhalten. Die Eigenschaft `xp` wird +dabei von jedem Monster während seiner Lebensdauer selbst verwaltet, die anderen +Eigenschaften bleiben während der Lebensdauer eines Monsters konstant (ebenso wie +die Methode `makeNoise()`). + +1. Was wird Bestandteil des Type-Objects? Begründen Sie Ihre Antwort. +2. Implementieren Sie das Type-Object und integrieren Sie es in die Klasse + `Monster`. +3. Implementieren Sie eine Factory-Methode in der Klasse für die Type-Objects, um + ein neues Monster mit diesem Type-Objekt erzeugen zu können. +4. Implementieren Sie einen "Vererbungs"-Mechanismus für die Type-Objects (nicht + Vererbung im Java-/OO-Sinn!). Dabei soll eine Eigenschaft überschrieben werden + können. +5. Erzeugen Sie einige Monstertypen und jeweils einige Monster und lassen Sie diese + ein Geräusch machen (`makeNoise()`). +6. Ersetzen Sie das Type-Object durch ein selbst definiertes (komplexes) Enum. +::: diff --git a/lecture/pattern/visitor.md b/lecture/pattern/visitor.md index e825144c3..5cb521d89 100644 --- a/lecture/pattern/visitor.md +++ b/lecture/pattern/visitor.md @@ -1,192 +1,104 @@ --- -title: "Visitor-Pattern" -author: "Carsten Gips (HSBI)" -readings: - - "@Eilebrecht2013" - - "@Gamma2011" -tldr: | - Häufig bietet es sich bei Datenstrukturen an, die Traversierung nicht direkt in den Klassen - der Datenstrukturen zu implementieren, sondern in Hilfsklassen zu verlagern. Dies gilt vor - allem dann, wenn die Datenstruktur aus mehreren Klassen besteht (etwa ein Baum mit verschiedenen - Knotentypen) und/oder wenn man nicht nur eine Traversierungsart ermöglichen will oder/und wenn - man immer wieder neue Arten der Traversierung ergänzen will. Das würde nämlich bedeuten, dass - man für jede weitere Form der Traversierung in _allen_ Klassen eine entsprechende neue Methode - implementieren müsste. - - Das Visitor-Pattern lagert die Traversierung in eigene Klassenstruktur aus. - - Die Klassen der Datenstruktur bekommen nur noch eine `accept()`-Methode, in der ein Visitor - übergeben wird und rufen auf diesem Visitor einfach dessen `visit()`-Methode auf (mit einer - Referenz auf sich selbst als Argument). - - Der Visitor hat für jede Klasse der Datenstruktur eine Überladung der `visit()`-Methode. In - diesen kann er je nach Klasse die gewünschte Verarbeitung vornehmen. Üblicherweise gibt es - ein Interface oder eine abstrakte Klasse für die Visitoren, von denen dann konkrete Visitoren - ableiten. - - Bei Elementen mit "Kindern" muss man sich entscheiden, wie die Traversierung implementiert - werden soll. Man könnte in der `accept()`-Methode den Visitor an die Kinder weiter reichen - (also auf den Kindern `accept()` mit dem Visitor aufrufen), bevor man die `visit()`-Methode - des Visitors mit sich selbst als Referenz aufruft. Damit ist die Form der Traversierung in - den Klassen der Datenstruktur fest verankert und über den Visitor findet "nur" noch eine - unterschiedliche Form der Verarbeitung statt. Alternativ überlässt man es dem Visitor, die - Traversierung durchzuführen: Hier muss in den `visit()`-Methoden für die einzelnen Elemente - entsprechend auf mögliche Kinder reagiert werden. - - In diesem Pattern findet ein sogenannter "Double-Dispatch" statt: Zur Laufzeit wird ein konkreter - Visitor instantiiert und über `accept()` an ein Element der Datenstruktur übergeben. Dort ist - zur Compile-Zeit aber nur der Obertyp der Visitoren bekannt, d.h. zur Laufzeit wird hier der - konkrete Typ bestimmt und entsprechend die richtige `visit()`-Methode auf der "echten" Klasse - des Visitors aufgerufen (erster Dispatch). Da im Visitor die `visit()`-Methoden für jeden Typ - der Datenstrukur überladen sind, findet nun zur Laufzeit die Auflösung der korrekten Überladung - statt (zweiter Dispatch). - - Das Pattern wird traditionell gern für die Traversierung von Datenstrukturen eingesetzt. - Es hilft aber auch, wenn man einer gewissen Anzahl von Klassen je eine neue Hilfsmethode - hinzufügen möchte - normalerweise müsste man jetzt jede Klasse einzeln ergänzen. Mit dem - Visitor-Pattern muss lediglich ein neuer Visitor mit den Hilfsmethoden implementiert werden. -outcomes: - - k2: "Ich verstehe den Aufbau des Visitor-Patterns und kann den Double-Dispatch erklären" - - k3: "Ich kann das Visitor-Pattern auf konkrete Beispiele anwenden" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106543&client_id=FH-Bielefeld" -# name: "Quiz Visitor-Pattern (ILIAS)" -youtube: - - link: "https://youtu.be/zW_2oQmjp8M" - name: "VL Visitor-Pattern" - - link: "https://youtu.be/9dvcufpyQdw" - name: "Demo Visitor-Pattern (Part I: Traversierung ohne Visitor)" - - link: "https://youtu.be/4rBRkXKhuN4" - name: "Demo Visitor-Pattern (Part II: Traversierung mit Visitor)" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/8a307719da2fd87b9cba54d34c05715a2fdaf115e80feb8ef29e53dcfe45f02e587ae0f76c7700e8d82fe102a234a2922af549aeaa261034dba59cbacfaaa8c3" - name: "VL Visitor-Pattern" -challenges: | - **Visitor-Pattern praktisch (und einfach)** - - Betrachten Sie den folgenden Code und erklären Sie das Ergebnis: - - ```java - interface Fruit { } - class Apple implements Fruit { } - class Orange implements Fruit { } - class Banana implements Fruit { } - class Foo extends Apple { } - - public class FruitBasketDirect { - public static void main(String... args) { - List basket = List.of(new Apple(), new Apple(), new Banana(), new Foo()); - - int oranges = 0; int apples = 0; int bananas = 0; int foo = 0; - - for (Fruit f : basket) { - if (f instanceof Apple) apples++; - if (f instanceof Orange) oranges++; - if (f instanceof Banana) bananas++; - if (f instanceof Foo) foo++; - } - } - } - ``` - - - - Das Verwenden von `instanceof` ist unschön und fehleranfällig. Schreiben Sie den - Code unter Einsatz des Visitor-Patterns um. - - - - - - Diskutieren Sie Vor- und Nachteile des Visitor-Patterns. +author: Carsten Gips (HSBI) +title: Visitor-Pattern --- +::: tldr +Häufig bietet es sich bei Datenstrukturen an, die Traversierung nicht direkt in den +Klassen der Datenstrukturen zu implementieren, sondern in Hilfsklassen zu verlagern. +Dies gilt vor allem dann, wenn die Datenstruktur aus mehreren Klassen besteht (etwa +ein Baum mit verschiedenen Knotentypen) und/oder wenn man nicht nur eine +Traversierungsart ermöglichen will oder/und wenn man immer wieder neue Arten der +Traversierung ergänzen will. Das würde nämlich bedeuten, dass man für jede weitere +Form der Traversierung in *allen* Klassen eine entsprechende neue Methode +implementieren müsste. + +Das Visitor-Pattern lagert die Traversierung in eigene Klassenstruktur aus. + +Die Klassen der Datenstruktur bekommen nur noch eine `accept()`-Methode, in der ein +Visitor übergeben wird und rufen auf diesem Visitor einfach dessen `visit()`-Methode +auf (mit einer Referenz auf sich selbst als Argument). + +Der Visitor hat für jede Klasse der Datenstruktur eine Überladung der +`visit()`-Methode. In diesen kann er je nach Klasse die gewünschte Verarbeitung +vornehmen. Üblicherweise gibt es ein Interface oder eine abstrakte Klasse für die +Visitoren, von denen dann konkrete Visitoren ableiten. + +Bei Elementen mit "Kindern" muss man sich entscheiden, wie die Traversierung +implementiert werden soll. Man könnte in der `accept()`-Methode den Visitor an die +Kinder weiter reichen (also auf den Kindern `accept()` mit dem Visitor aufrufen), +bevor man die `visit()`-Methode des Visitors mit sich selbst als Referenz aufruft. +Damit ist die Form der Traversierung in den Klassen der Datenstruktur fest verankert +und über den Visitor findet "nur" noch eine unterschiedliche Form der Verarbeitung +statt. Alternativ überlässt man es dem Visitor, die Traversierung durchzuführen: +Hier muss in den `visit()`-Methoden für die einzelnen Elemente entsprechend auf +mögliche Kinder reagiert werden. + +In diesem Pattern findet ein sogenannter "Double-Dispatch" statt: Zur Laufzeit wird +ein konkreter Visitor instantiiert und über `accept()` an ein Element der +Datenstruktur übergeben. Dort ist zur Compile-Zeit aber nur der Obertyp der +Visitoren bekannt, d.h. zur Laufzeit wird hier der konkrete Typ bestimmt und +entsprechend die richtige `visit()`-Methode auf der "echten" Klasse des Visitors +aufgerufen (erster Dispatch). Da im Visitor die `visit()`-Methoden für jeden Typ der +Datenstrukur überladen sind, findet nun zur Laufzeit die Auflösung der korrekten +Überladung statt (zweiter Dispatch). + +Das Pattern wird traditionell gern für die Traversierung von Datenstrukturen +eingesetzt. Es hilft aber auch, wenn man einer gewissen Anzahl von Klassen je eine +neue Hilfsmethode hinzufügen möchte - normalerweise müsste man jetzt jede Klasse +einzeln ergänzen. Mit dem Visitor-Pattern muss lediglich ein neuer Visitor mit den +Hilfsmethoden implementiert werden. +::: -# Motivation: Parsen von "5*4+3" +::: youtube +- [VL Visitor-Pattern](https://youtu.be/zW_2oQmjp8M) +- [Demo Visitor-Pattern (Part I: Traversierung ohne + Visitor)](https://youtu.be/9dvcufpyQdw) +- [Demo Visitor-Pattern (Part II: Traversierung mit + Visitor)](https://youtu.be/4rBRkXKhuN4) +::: -::::::::: {.columns} -:::::: {.column width="50%"} +# Motivation: Parsen von "5\*4+3" +::::::: columns +:::: {.column width="50%"} ::: notes -Zum Parsen von Ausdrücken (_Expressions_) könnte man diese einfache Grammatik -einsetzen. Ein Ausdruck ist dabei entweder ein einfacher Integer oder eine -Addition oder Multiplikation zweier Ausdrücke. +Zum Parsen von Ausdrücken (*Expressions*) könnte man diese einfache Grammatik +einsetzen. Ein Ausdruck ist dabei entweder ein einfacher Integer oder eine Addition +oder Multiplikation zweier Ausdrücke. ::: -```yacc +``` yacc expr : e1=expr '*' e2=expr # MUL | e1=expr '+' e2=expr # ADD | INT # NUM ; ``` +:::: -:::::: -:::::: {.column width="40%"} - +:::: {.column width="40%"} ::: notes -Beim Parsen von "5*4+3" würde dabei der folgende Parsetree entstehen: +Beim Parsen von "5\*4+3" würde dabei der folgende Parsetree entstehen: ::: ![](images/parsetree.png){width="50%" web_width="20%"} - -:::::: -::::::::: - +:::: +::::::: # Strukturen für den Parsetree ![](images/parsetree_classes_uml.png){width="70%"} ::: notes -Der Parsetree für diese einfache Grammatik ist ein Binärbaum. Die Regeln -werden auf Knoten im Baum zurückgeführt. Es gibt Knoten mit zwei Kindknoten, -und es gibt Knoten ohne Kindknoten ("Blätter"). +Der Parsetree für diese einfache Grammatik ist ein Binärbaum. Die Regeln werden auf +Knoten im Baum zurückgeführt. Es gibt Knoten mit zwei Kindknoten, und es gibt Knoten +ohne Kindknoten ("Blätter"). -Entsprechend kann man sich einfache Klassen definieren, die die verschiedenen -Knoten in diesem Parsetree repräsentieren. Als Obertyp könnte es ein (noch -leeres) Interface `Expr` geben. +Entsprechend kann man sich einfache Klassen definieren, die die verschiedenen Knoten +in diesem Parsetree repräsentieren. Als Obertyp könnte es ein (noch leeres) +Interface `Expr` geben. -```java +``` java public interface Expr {} public class NumExpr implements Expr { @@ -223,7 +135,6 @@ public class DemoExpr { ``` ::: - # Ergänzung I: Ausrechnen des Ausdrucks ::: notes @@ -235,12 +146,12 @@ möchte man den Ausdruck ausrechnen? ::: notes Zum Ausrechnen des Ausdrucks könnte man dem Interface eine `eval()`-Methode -spendieren. Jeder Knoten kann für sich entscheiden, wie die entsprechende -Operation ausgewertet werden soll: Bei einer `NumExpr` ist dies einfach der -gespeicherte Wert, bei Addition oder Multiplikation entsprechend die Addition -oder Multiplikation der Auswertungsergebnisse der beiden Kindknoten. +spendieren. Jeder Knoten kann für sich entscheiden, wie die entsprechende Operation +ausgewertet werden soll: Bei einer `NumExpr` ist dies einfach der gespeicherte Wert, +bei Addition oder Multiplikation entsprechend die Addition oder Multiplikation der +Auswertungsergebnisse der beiden Kindknoten. -```java +``` java public interface Expr { int eval(); } @@ -284,48 +195,47 @@ public class DemoExpr { ``` ::: - # Ergänzung II: Pretty-Print des Ausdrucks ::: notes -Nachdem das Ausrechnen so gut geklappt hat, will der Chef nun noch flink eine Funktion, -mit der man den Ausdruck hübsch ausgeben kann: +Nachdem das Ausrechnen so gut geklappt hat, will der Chef nun noch flink eine +Funktion, mit der man den Ausdruck hübsch ausgeben kann: ::: ![](images/parsetree_eval_print_uml.png){width="70%"} ::: notes -Das fängt an, sich zu wiederholen. Wir implementieren immer wieder ähnliche Strukturen, -mit denen wir diesen Parsetree traversieren ... Und wir müssen für _jede_ Erweiterung -immer _alle_ Expression-Klassen anpassen! +Das fängt an, sich zu wiederholen. Wir implementieren immer wieder ähnliche +Strukturen, mit denen wir diesen Parsetree traversieren ... Und wir müssen für +*jede* Erweiterung immer *alle* Expression-Klassen anpassen! -[Beispiel: direct.DemoExpr]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/pattern/src/visitor/direct/DemoExpr.java"} +[Beispiel: direct.DemoExpr]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/pattern/src/visitor/direct/DemoExpr.java"} ::: \vfill **Das geht besser.** - # Visitor-Pattern (Besucher-Entwurfsmuster) ![](images/visitor.png){web_width="80%"} [[Hinweis: Implementierungsdetail Traversierung]{.ex}]{.slides} -::: notes -Das Entwurfsmuster "Besucher" (_Visitor Pattern_) lagert die Aktion beim Besuchen eines -Knotens in eine separate Klasse aus. +:::: notes +Das Entwurfsmuster "Besucher" (*Visitor Pattern*) lagert die Aktion beim Besuchen +eines Knotens in eine separate Klasse aus. Dazu bekommt jeder Knoten im Baum eine neue Methode, die einen Besucher akzeptiert. -Dieser Besucher kümmert sich dann um die entsprechende Verarbeitung des Knotens, also -um das Auswerten oder Ausgeben im obigen Beispiel. +Dieser Besucher kümmert sich dann um die entsprechende Verarbeitung des Knotens, +also um das Auswerten oder Ausgeben im obigen Beispiel. -Die Besucher haben eine Methode, die für jeden zu bearbeitenden Knoten überladen wird. -In dieser Methode findet dann die eigentliche Verarbeitung statt: Auswerten des Knotens -oder Ausgeben des Knotens ... +Die Besucher haben eine Methode, die für jeden zu bearbeitenden Knoten überladen +wird. In dieser Methode findet dann die eigentliche Verarbeitung statt: Auswerten +des Knotens oder Ausgeben des Knotens ... -```java +``` java public interface Expr { void accept(ExprVisitor v); } @@ -421,51 +331,57 @@ public class DemoExpr { ## Implementierungsdetail -In den beiden Klasse `AddExpr` und `MulExpr` müssen auch die beiden Kindknoten besucht -werden, d.h. hier muss der Baum weiter traversiert werden. +In den beiden Klasse `AddExpr` und `MulExpr` müssen auch die beiden Kindknoten +besucht werden, d.h. hier muss der Baum weiter traversiert werden. Man kann sich überlegen, diese Traversierung in den Klassen `AddExpr` und `MulExpr` selbst anzustoßen. -Alternativ könnte auch der Visitor die Traversierung vornehmen. Gerade bei der Traversierung -von Datenstrukturen ist diese Variante oft von Vorteil, da man hier unterschiedliche -Traversierungsarten haben möchte (Breitensuche vs. Tiefensuche, Pre-Order vs. Inorder vs. -Post-Order, ...) und diese elegant in den Visitor verlagern kann. +Alternativ könnte auch der Visitor die Traversierung vornehmen. Gerade bei der +Traversierung von Datenstrukturen ist diese Variante oft von Vorteil, da man hier +unterschiedliche Traversierungsarten haben möchte (Breitensuche vs. Tiefensuche, +Pre-Order vs. Inorder vs. Post-Order, ...) und diese elegant in den Visitor +verlagern kann. -[Beispiel Traversierung intern (in den Knotenklassen): visitor.visit.intrav.DemoExpr]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/pattern/src/visitor/visit/intrav/DemoExpr.java"} +[Beispiel Traversierung intern (in den Knotenklassen): +visitor.visit.intrav.DemoExpr]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/pattern/src/visitor/visit/intrav/DemoExpr.java"} -[Beispiel Traversierung extern (im Visitor): visitor.visit.extrav.DemoExpr]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/pattern/src/visitor/visit/extrav/DemoExpr.java"} +[Beispiel Traversierung extern (im Visitor): visitor.visit.extrav.DemoExpr]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/pattern/src/visitor/visit/extrav/DemoExpr.java"} ## (Double-) Dispatch ::: tip -Zur Laufzeit wird in `accept()` mit dem Aufruf von `visit()` der konkrete Typ des Visitors -aufgelöst und dann für `visit(this)` durch den Typ der besuchten Klasse (`this`) die korrekte -Überladung ausgewählt. Dies nennt man auch "**Double-Dispatch**". +Zur Laufzeit wird in `accept()` mit dem Aufruf von `visit()` der konkrete Typ des +Visitors aufgelöst und dann für `visit(this)` durch den Typ der besuchten Klasse +(`this`) die korrekte Überladung ausgewählt. Dies nennt man auch +"**Double-Dispatch**". ::: -In den `accept()`-Methoden der besuchten Klassen ist nur der gemeinsame Obertyp der Visitoren -bekannt. Dies ist wichtig, weil man sonst ja für jeden neuen Visitor neue passende -`accept()`-Methoden in allen zu besuchenden Klassen implementieren müsste! +In den `accept()`-Methoden der besuchten Klassen ist nur der gemeinsame Obertyp der +Visitoren bekannt. Dies ist wichtig, weil man sonst ja für jeden neuen Visitor neue +passende `accept()`-Methoden in allen zu besuchenden Klassen implementieren müsste! Zur Laufzeit wird hier ein konkreter Visitor (also ein Objekt von einem Untertyp der Visitoren-Oberklasse) als Parameter übergeben. -Beim Aufruf von `visit(this)` in der `accept()`-Methode des besuchten Objekts wird durch die -Laufzeitumgebung der tatsächliche konkrete Typ des Visitors bestimmt und die in der -Typhierarchie in Bezug auf den Typ des Visitors "tiefste" Implementierung der `visit`-Methode -(also die Implementierung in der Visitorklasse selbst oder, falls dort nicht vorhanden, in der -jeweils nächsthöheren Elternklasse). Über das Argument `this` wird die tatsächliche konkrete -Klasse des besuchten Objekts ermittelt, so dass die passende Überladung der `visit`-Methode im -konkreten Visitor ausgewählt und aufgerufen werden kann. +Beim Aufruf von `visit(this)` in der `accept()`-Methode des besuchten Objekts wird +durch die Laufzeitumgebung der tatsächliche konkrete Typ des Visitors bestimmt und +die in der Typhierarchie in Bezug auf den Typ des Visitors "tiefste" Implementierung +der `visit`-Methode (also die Implementierung in der Visitorklasse selbst oder, +falls dort nicht vorhanden, in der jeweils nächsthöheren Elternklasse). Über das +Argument `this` wird die tatsächliche konkrete Klasse des besuchten Objekts +ermittelt, so dass die passende Überladung der `visit`-Methode im konkreten Visitor +ausgewählt und aufgerufen werden kann. ## Hinweis I -Man könnte nun versucht sein, eine dieser zwei Stufen zu überspringen - man könnte ja die -`visit`-Methode des `EvalVisitors` direkt aufrufen und dabei die Wurzel des Baums (das Objekt -`e`) übergeben. +Man könnte nun versucht sein, eine dieser zwei Stufen zu überspringen - man könnte +ja die `visit`-Methode des `EvalVisitors` direkt aufrufen und dabei die Wurzel des +Baums (das Objekt `e`) übergeben. -```java +``` java // Beispiel von oben (Ausschnitt) Expr e = new AddExpr(new MulExpr(new NumExpr(5), new NumExpr(4)), new NumExpr(3)); EvalVisitor v = new EvalVisitor(); @@ -479,19 +395,19 @@ Fragen Sie sich selbst: Kann das funktionieren? Was ist die Begründung? ## Hinweis II -Man könnte versucht sein, die `accept()`-Methode aus den Knotenklassen in die gemeinsame -Basisklasse zu verlagern: Statt +Man könnte versucht sein, die `accept()`-Methode aus den Knotenklassen in die +gemeinsame Basisklasse zu verlagern: Statt -```java +``` java public void accept(ExprVisitor v) { v.visit(this); } ``` -in _jeder_ Knotenklasse einzeln zu definieren, könnte man das doch _einmalig_ in der +in *jeder* Knotenklasse einzeln zu definieren, könnte man das doch *einmalig* in der Basisklasse definieren: -```java +``` java public abstract class Expr { /** Akzeptiere einen Visitor für die Verarbeitung */ public void accept(ExprVisitor v) { @@ -505,48 +421,50 @@ funktioniert in Java leider nicht. (Warum?) ## Hinweis III -Während die `accept()`-Methode nicht in die Basisklasse der besuchten Typen (im Bild oben -die Klasse `Elem` bzw. im Beispiel oben die Klasse `Expr`) verlagert werden kann, kann man -die `visit()`-Methoden im Interface `Visitor` durchaus als Default-Methoden im Interface -implementieren. -::: - +Während die `accept()`-Methode nicht in die Basisklasse der besuchten Typen (im Bild +oben die Klasse `Elem` bzw. im Beispiel oben die Klasse `Expr`) verlagert werden +kann, kann man die `visit()`-Methoden im Interface `Visitor` durchaus als +Default-Methoden im Interface implementieren. +:::: # Ausrechnen des Ausdrucks mit einem Visitor ![](images/parsetree_visitor_uml.png) -[Demo: visitor.visit.extrav.DemoExpr]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/pattern/src/visitor/visit/extrav/DemoExpr.java"} - +[Demo: visitor.visit.extrav.DemoExpr]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/pattern/src/visitor/visit/extrav/DemoExpr.java"} ::: notes # Diskussion -In der typischen OO-Denkweise geht man davon aus, dass man eher neue Klassen über Vererbung -hinzufügt als dass man in einer bestehenden Vererbungshierarchie in jeder der beteiligten -Klassen neue Methoden einbaut. Man leitet einfach von der gewünschten Klasse ab und definiert -mittels Überschreiben von Methoden o.ä. das geänderte Verhalten und erbt den Rest - es wird -also nur eine neue Klasse hinzugefügt samt den überschriebenen Teilen. - -Wenn man allerdings in einer solchen Hierarchie in allen Klassen eine neue Methode einbauen -muss, die dann auch noch in den einzelnen Klassen individuell implementiert werden muss, dann -kommt das Visitor-Pattern zur Hilfe und erspart Arbeit. Es muss nämlich in der Klassenhierarchie -nur einmal die Schnittstelle für den Visitor einbaut werden (pro Klasse eine `accept`-Methode). -Danach kann man von außen sehr einfach neue Methoden (also neue Visitoren) erstellen und nutzen, -ohne die Klassenhierarchie noch einmal ändern zu müssen. - -Siehe auch [When should I use the Visitor Design Pattern?](https://stackoverflow.com/a/478672). - -Ein anderer Blick ist auf die Rolle der jeweiligen Klassen: Es gibt Objekte für/in Datenstrukturen, -und es gibt Algorithmen, die auf diesen Objekten bzw. Datenstrukturen arbeiten. Im Sinne des -sauberen OO-Designs würde man diese Strukturen trennen: "Trenne Algorithmen von den Objekten, -auf denen die Algorithmen arbeiten." - -Vergleiche auch die Darstellung des Visitor-Patterns in -[Visitor (Refactoring Guru)](https://refactoring.guru/design-patterns/visitor). +In der typischen OO-Denkweise geht man davon aus, dass man eher neue Klassen über +Vererbung hinzufügt als dass man in einer bestehenden Vererbungshierarchie in jeder +der beteiligten Klassen neue Methoden einbaut. Man leitet einfach von der +gewünschten Klasse ab und definiert mittels Überschreiben von Methoden o.ä. das +geänderte Verhalten und erbt den Rest - es wird also nur eine neue Klasse +hinzugefügt samt den überschriebenen Teilen. + +Wenn man allerdings in einer solchen Hierarchie in allen Klassen eine neue Methode +einbauen muss, die dann auch noch in den einzelnen Klassen individuell implementiert +werden muss, dann kommt das Visitor-Pattern zur Hilfe und erspart Arbeit. Es muss +nämlich in der Klassenhierarchie nur einmal die Schnittstelle für den Visitor +einbaut werden (pro Klasse eine `accept`-Methode). Danach kann man von außen sehr +einfach neue Methoden (also neue Visitoren) erstellen und nutzen, ohne die +Klassenhierarchie noch einmal ändern zu müssen. + +Siehe auch [When should I use the Visitor Design +Pattern?](https://stackoverflow.com/a/478672). + +Ein anderer Blick ist auf die Rolle der jeweiligen Klassen: Es gibt Objekte für/in +Datenstrukturen, und es gibt Algorithmen, die auf diesen Objekten bzw. +Datenstrukturen arbeiten. Im Sinne des sauberen OO-Designs würde man diese +Strukturen trennen: "Trenne Algorithmen von den Objekten, auf denen die Algorithmen +arbeiten." + +Vergleiche auch die Darstellung des Visitor-Patterns in [Visitor (Refactoring +Guru)](https://refactoring.guru/design-patterns/visitor). ::: - # Wrap-Up **Visitor-Pattern**: Auslagern der Traversierung in eigene Klassenstruktur @@ -554,18 +472,109 @@ Vergleiche auch die Darstellung des Visitor-Patterns in \bigskip \smallskip -* Klassen der Datenstruktur - * bekommen eine `accept()`-Methode für einen Visitor - * rufen den Visitor mit sich selbst als Argument auf +- Klassen der Datenstruktur + - bekommen eine `accept()`-Methode für einen Visitor + - rufen den Visitor mit sich selbst als Argument auf \smallskip -* Visitor - * hat für jede Klasse eine Überladung der `visit()`-Methode - * Rückgabewerte schwierig: Intern halten oder per `return` - [(dann aber unterschiedliche `visit()`-Methoden für die verschiedenen Rückgabetypen!)]{.notes} +- Visitor + - hat für jede Klasse eine Überladung der `visit()`-Methode + - Rückgabewerte schwierig: Intern halten oder per `return` [(dann aber + unterschiedliche `visit()`-Methoden für die verschiedenen + Rückgabetypen!)]{.notes} \smallskip -* (Double-) Dispatch: Zur Laufzeit wird in `accept()` der Typ des Visitors - und in `visit()` der Typ der zu besuchenden Klasse aufgelöst +- (Double-) Dispatch: Zur Laufzeit wird in `accept()` der Typ des Visitors und in + `visit()` der Typ der zu besuchenden Klasse aufgelöst + +::: readings +- @Eilebrecht2013 +- @Gamma2011 +::: + +::: outcomes +- k2: Ich verstehe den Aufbau des Visitor-Patterns und kann den Double-Dispatch + erklären +- k3: Ich kann das Visitor-Pattern auf konkrete Beispiele anwenden +::: + +::: challenges +**Visitor-Pattern praktisch (und einfach)** + +Betrachten Sie den folgenden Code und erklären Sie das Ergebnis: + +``` java +interface Fruit { } +class Apple implements Fruit { } +class Orange implements Fruit { } +class Banana implements Fruit { } +class Foo extends Apple { } + +public class FruitBasketDirect { + public static void main(String... args) { + List basket = List.of(new Apple(), new Apple(), new Banana(), new Foo()); + + int oranges = 0; int apples = 0; int bananas = 0; int foo = 0; + + for (Fruit f : basket) { + if (f instanceof Apple) apples++; + if (f instanceof Orange) oranges++; + if (f instanceof Banana) bananas++; + if (f instanceof Foo) foo++; + } + } +} +``` + + + +Das Verwenden von `instanceof` ist unschön und fehleranfällig. Schreiben Sie den +Code unter Einsatz des Visitor-Patterns um. + + + + +Diskutieren Sie Vor- und Nachteile des Visitor-Patterns. +::: diff --git a/lecture/quality/codingrules.md b/lecture/quality/codingrules.md index 45a8cbc6f..afba78d2e 100644 --- a/lecture/quality/codingrules.md +++ b/lecture/quality/codingrules.md @@ -1,96 +1,86 @@ --- -title: "Coding Conventions und Metriken" -author: "Carsten Gips (HSBI)" -readings: - - "@Martin2009" - - "@Inden2013 [Kap. 13]" - - "@googlestyleguide" -tldr: | - Code entsteht nicht zum Selbstzweck, er muss von anderen Menschen leicht verstanden und - gewartet werden können: Entwickler verbringen einen wesentlichen Teil ihrer Zeit mit dem - **Lesen** von (fremdem) Code. - - Dabei helfen "Coding Conventions", die eine gewisse einheitliche äußerliche Erscheinung - des Codes vorgeben (Namen, Einrückungen, ...). Im Java-Umfeld ist der "Google Java Style" - bzw. der recht ähnliche "AOSP Java Code Style for Contributors" häufig anzutreffen. - Coding Conventions beinhalten typischerweise Regeln zu - * Schreibweisen und Layout - * Leerzeichen, Einrückung, Klammern - * Zeilenlänge, Umbrüche - * Kommentare - - Die Beachtung von grundlegenden Programmierprinzipien hilft ebenso, die Lesbarkeit und - Verständlichkeit zu verbessern. - - Metriken sind Kennzahlen, die aus dem Code berechnet werden, und können zur Überwachung - der Einhaltung von Coding Conventions und anderen Regeln genutzt werden. Nützliche - Metriken sind dabei NCSS (_Non Commenting Source Statements_), McCabe (_Cyclomatic Complexity_), - BEC (_Boolean Expression Complexity_) und DAC (_Class Data Abstraction Coupling_). - - Für die Formatierung des Codes kann man die IDE nutzen, muss dort dann aber die Regeln - detailliert manuell einstellen. Das Tool **Spotless** lässt sich dagegen in den Build-Prozess - einbinden und kann die Konfiguration über ein vordefiniertes Regelset passend zum Google - Java Style/AOSP automatisiert vornehmen. - - Die Prüfung der Coding Conventions und Metriken kann durch das Tool **Checkstyle** erfolgen. - Dieses kann beispielsweise als Plugin in der IDE oder direkt in den Build-Prozess eingebunden - werden und wird mit Hilfe einer XML-Datei konfiguriert. - - Um typische Anti-Pattern zu vermeiden, kann man den Code mit sogenannten _Lintern_ prüfen. - Ein Beispiel für die Java-Entwicklung ist **SpotBugs**, welches sich in den Build-Prozess - einbinden lässt und über 400 typische problematische Muster im Code erkennt. - - Für die Praktika in der Veranstaltung Programmiermethoden wird der Google Java Style oder - AOSP genutzt. Für die passende Checkstyle-Konfiguration wird eine minimale - [checkstyle.xml](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/quality/src/checkstyle.xml) - bereitgestellt (vgl. Folie "Konfiguration für das PM-Praktikum"). -outcomes: - - k2: "Ich kann verschiedene Coding Conventions erklären" - - k2: "Ich kann die Metriken NCSS, McCabe, BEC, DAC erklären" - - k3: "Ich kann das Tool Spotless zur Formatierung des Codes nutzen" - - k3: "Ich kann das Tool Checkstyle zum Überprüfen von Coding Conventions und Metriken nutzen" - - k2: "Ich kenne das Tool SpotBugs zum Vermeiden von Anti-Pattern" -youtube: - - link: "https://youtu.be/nLAEak6Fwfk" - name: "VL Coding Conventions" - - link: "https://youtu.be/oCMwyDrPkFI" - name: "Demo Formatter und Spotless" - - link: "https://youtu.be/NR070ZimbH4" - name: "Demo Checkstyle" - - link: "https://youtu.be/0ny6e6CNTF8" - name: "Demo Checkstyle: Konfiguration mit Eclipse-CS" - - link: "https://youtu.be/tSczcf_EOwI" - name: "Demo SpotBugs" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/31a425902a7d01e5fd4b3b082259d9d71bcff55030eb4564c98bb044c554162088d7ab02edbf50aff0503232dfd4c3bed43341ec058e272a0cd6f9a388584d84" - name: "VL Coding Conventions" +author: Carsten Gips (HSBI) +title: Coding Conventions und Metriken --- +::: tldr +Code entsteht nicht zum Selbstzweck, er muss von anderen Menschen leicht verstanden +und gewartet werden können: Entwickler verbringen einen wesentlichen Teil ihrer Zeit +mit dem **Lesen** von (fremdem) Code. + +Dabei helfen "Coding Conventions", die eine gewisse einheitliche äußerliche +Erscheinung des Codes vorgeben (Namen, Einrückungen, ...). Im Java-Umfeld ist der +"Google Java Style" bzw. der recht ähnliche "AOSP Java Code Style for Contributors" +häufig anzutreffen. Coding Conventions beinhalten typischerweise Regeln zu + +- Schreibweisen und Layout +- Leerzeichen, Einrückung, Klammern +- Zeilenlänge, Umbrüche +- Kommentare + +Die Beachtung von grundlegenden Programmierprinzipien hilft ebenso, die Lesbarkeit +und Verständlichkeit zu verbessern. + +Metriken sind Kennzahlen, die aus dem Code berechnet werden, und können zur +Überwachung der Einhaltung von Coding Conventions und anderen Regeln genutzt werden. +Nützliche Metriken sind dabei NCSS (*Non Commenting Source Statements*), McCabe +(*Cyclomatic Complexity*), BEC (*Boolean Expression Complexity*) und DAC (*Class +Data Abstraction Coupling*). + +Für die Formatierung des Codes kann man die IDE nutzen, muss dort dann aber die +Regeln detailliert manuell einstellen. Das Tool **Spotless** lässt sich dagegen in +den Build-Prozess einbinden und kann die Konfiguration über ein vordefiniertes +Regelset passend zum Google Java Style/AOSP automatisiert vornehmen. + +Die Prüfung der Coding Conventions und Metriken kann durch das Tool **Checkstyle** +erfolgen. Dieses kann beispielsweise als Plugin in der IDE oder direkt in den +Build-Prozess eingebunden werden und wird mit Hilfe einer XML-Datei konfiguriert. + +Um typische Anti-Pattern zu vermeiden, kann man den Code mit sogenannten *Lintern* +prüfen. Ein Beispiel für die Java-Entwicklung ist **SpotBugs**, welches sich in den +Build-Prozess einbinden lässt und über 400 typische problematische Muster im Code +erkennt. + +Für die Praktika in der Veranstaltung Programmiermethoden wird der Google Java Style +oder AOSP genutzt. Für die passende Checkstyle-Konfiguration wird eine minimale +[checkstyle.xml](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/quality/src/checkstyle.xml) +bereitgestellt (vgl. Folie "Konfiguration für das PM-Praktikum"). +::: + +::: youtube +- [VL Coding Conventions](https://youtu.be/nLAEak6Fwfk) +- [Demo Formatter und Spotless](https://youtu.be/oCMwyDrPkFI) +- [Demo Checkstyle](https://youtu.be/NR070ZimbH4) +- [Demo Checkstyle: Konfiguration mit Eclipse-CS](https://youtu.be/0ny6e6CNTF8) +- [Demo SpotBugs](https://youtu.be/tSczcf_EOwI) +::: # Coding Conventions: Richtlinien für einheitliches Aussehen von Code -=> Ziel: Andere Programmierer sollen Code schnell lesen können +=\> Ziel: Andere Programmierer sollen Code schnell lesen können \bigskip -* **Namen, Schreibweisen**: UpperCamelCase vs. lowerCamelCase vs. UPPER_SNAKE_CASE -* **Kommentare** (Ort, Form, Inhalt): Javadoc an allen `public` und `protected` Elementen -* **Einrückungen und Spaces vs. Tabs**: 4 Spaces -* **Zeilenlängen**: 100 Zeichen -* **Leerzeilen**: Leerzeilen für Gliederung -* **Klammern**: Auf selber Zeile wie Code +- **Namen, Schreibweisen**: UpperCamelCase vs. lowerCamelCase vs. UPPER_SNAKE_CASE +- **Kommentare** (Ort, Form, Inhalt): Javadoc an allen `public` und `protected` + Elementen +- **Einrückungen und Spaces vs. Tabs**: 4 Spaces +- **Zeilenlängen**: 100 Zeichen +- **Leerzeilen**: Leerzeilen für Gliederung +- **Klammern**: Auf selber Zeile wie Code \bigskip \smallskip -Beispiele: [Sun Code Conventions](https://www.oracle.com/technetwork/java/codeconventions-150003.pdf), -[Google Java Style](https://google.github.io/styleguide/javaguide.html), -[AOSP Java Code Style for Contributors](https://source.android.com/docs/setup/contribute/code-style) - +Beispiele: [Sun Code +Conventions](https://www.oracle.com/technetwork/java/codeconventions-150003.pdf), +[Google Java Style](https://google.github.io/styleguide/javaguide.html), [AOSP Java +Code Style for +Contributors](https://source.android.com/docs/setup/contribute/code-style) # Beispiel nach Google Java Style/AOSP formatiert -```java +``` java package wuppie.deeplearning.strategy; /** @@ -122,31 +112,36 @@ public class MyWuppieStudi implements Comparable { ::: notes Dieses Beispiel wurde nach Google Java Style/AOSP formatiert. -Die Zeilenlänge beträgt max. 100 Zeichen. Pro Methode werden max. 40 Zeilen genutzt. Zwischen Attributen, -Methoden und Importen wird jeweils eine Leerzeile eingesetzt (zwischen den einzelnen Attributen _muss_ -aber keine Leerzeile genutzt werden). Zur logischen Gliederung können innerhalb von Methoden weitere -Leerzeilen eingesetzt werden, aber immer nur eine. +Die Zeilenlänge beträgt max. 100 Zeichen. Pro Methode werden max. 40 Zeilen genutzt. +Zwischen Attributen, Methoden und Importen wird jeweils eine Leerzeile eingesetzt +(zwischen den einzelnen Attributen *muss* aber keine Leerzeile genutzt werden). Zur +logischen Gliederung können innerhalb von Methoden weitere Leerzeilen eingesetzt +werden, aber immer nur eine. -Klassennamen sind UpperCamelCase, Attribute und Methoden und Parameter lowerCamelCase, Konstanten (im -Beispiel nicht vorhanden) UPPER_SNAKE_CASE. Klassen sind Substantive, Methoden Verben. +Klassennamen sind UpperCamelCase, Attribute und Methoden und Parameter +lowerCamelCase, Konstanten (im Beispiel nicht vorhanden) UPPER_SNAKE_CASE. Klassen +sind Substantive, Methoden Verben. -Alle `public` und `protected` Elemente werden mit einem Javadoc-Kommentar versehen. Überschriebene Methoden -müssen nicht mit Javadoc kommentiert werden, müssen aber mit `@Override` markiert werden. +Alle `public` und `protected` Elemente werden mit einem Javadoc-Kommentar versehen. +Überschriebene Methoden müssen nicht mit Javadoc kommentiert werden, müssen aber mit +`@Override` markiert werden. -Geschweifte Klammern starten immer auf der selben Codezeile. Wenn bei einem `if` nur ein Statement vorhanden -ist und dieses auf die selbe Zeile passt, kann auf die umschließenden geschweiften Klammern ausnahmsweise -verzichtet werden. +Geschweifte Klammern starten immer auf der selben Codezeile. Wenn bei einem `if` nur +ein Statement vorhanden ist und dieses auf die selbe Zeile passt, kann auf die +umschließenden geschweiften Klammern ausnahmsweise verzichtet werden. -Es wird mit Leerzeichen eingerückt. [Google Java Style](https://google.github.io/styleguide/javaguide.html#s4.2-block-indentation) -arbeitet mit 2 Leerzeichen, während [AOSP](https://source.android.com/docs/setup/contribute/code-style#use-spaces-for-indentation) +Es wird mit Leerzeichen eingerückt. [Google Java +Style](https://google.github.io/styleguide/javaguide.html#s4.2-block-indentation) +arbeitet mit 2 Leerzeichen, während +[AOSP](https://source.android.com/docs/setup/contribute/code-style#use-spaces-for-indentation) hier 4 Leerzeichen vorschreibt. Im Beispiel wurde nach AOSP eingerückt. -Darüber hinaus gibt es vielfältige weitere Regeln für das Aussehen des Codes. Lesen Sie dazu entsprechend -auf [Google Java Style](https://google.github.io/styleguide/javaguide.html) und auch auf +Darüber hinaus gibt es vielfältige weitere Regeln für das Aussehen des Codes. Lesen +Sie dazu entsprechend auf [Google Java +Style](https://google.github.io/styleguide/javaguide.html) und auch auf [AOSP](https://source.android.com/docs/setup/contribute/code-style) nach. ::: - # Formatieren Sie Ihren Code (mit der IDE) ::: notes @@ -154,15 +149,15 @@ Sie können den Code manuell formatieren, oder aber (sinnvollerweise) über Tool formatieren lassen. Hier einige Möglichkeiten: ::: -* IDE: Code-Style einstellen und zum Formatieren nutzen +- IDE: Code-Style einstellen und zum Formatieren nutzen -* [google-java-format](https://github.com/google/google-java-format): - `java -jar google-java-format.jar --replace *.java` - [(auch als IDE-Plugin)]{.notes} +- [google-java-format](https://github.com/google/google-java-format): + `java -jar google-java-format.jar --replace *.java` [(auch als + IDE-Plugin)]{.notes} -* [**Spotless**](https://github.com/diffplug/spotless) in Gradle: +- [**Spotless**](https://github.com/diffplug/spotless) in Gradle: - ```groovy + ``` groovy plugins { id "java" id "com.diffplug.spotless" version "7.0.3" @@ -176,99 +171,108 @@ formatieren lassen. Hier einige Möglichkeiten: } ``` - [Prüfen mit]{.notes} `./gradlew spotlessCheck` (Teil von `./gradlew check`) - und [Formatieren mit]{.notes} `./gradlew spotlessApply` + [Prüfen mit]{.notes} `./gradlew spotlessCheck` (Teil von `./gradlew check`) und + [Formatieren mit]{.notes} `./gradlew spotlessApply` - -::::::::: notes +::: notes ## Einstellungen der IDE's -* Eclipse: - * `Project > Properties > Java Code Style > Formatter`: Coding-Style einstellen/einrichten - * Code markieren, `Source > Format` - * Komplettes Aufräumen: `Source > Clean Up` (Formatierung, Importe, Annotationen, ...) - Kann auch so eingestellt werden, dass ein "Clean Up" immer beim Speichern ausgeführt wird! -* IntelliJ verfügt über ähnliche Fähigkeiten: - * Einstellen über `Preferences > Editor > Code Style > Java` - * Formatieren mit `Code > Reformat Code` oder `Code > Reformat File` - -Die Details kann/muss man einzeln einstellen. Für die "bekannten" Styles (Google Java Style) -bringen die IDE's oft aber schon eine Gesamtkonfiguration mit. - -**Achtung**: Zumindest in Eclipse gibt es mehrere Stellen, wo ein Code-Style eingestellt werden -kann ("Clean Up", "Formatter", ...). Diese sollten dann jeweils auf den selben Style eingestellt -werden, sonst gibt es unter Umständen lustige Effekte, da beim Speichern ein anderer Style -angewendet wird als beim "Clean Up" oder beim "Format Source" ... - -Analog sollte man bei der Verwendung von Checkstyle auch in der IDE im Formatter die entsprechenden -Checkstyle-Regeln (s.u.) passend einstellen, sonst bekommt man durch Checkstyle Warnungen angezeigt, -die man durch ein automatisches Formatieren _nicht_ beheben kann. +- Eclipse: + - `Project > Properties > Java Code Style > Formatter`: Coding-Style + einstellen/einrichten + - Code markieren, `Source > Format` + - Komplettes Aufräumen: `Source > Clean Up` (Formatierung, Importe, + Annotationen, ...) Kann auch so eingestellt werden, dass ein "Clean Up" + immer beim Speichern ausgeführt wird! +- IntelliJ verfügt über ähnliche Fähigkeiten: + - Einstellen über `Preferences > Editor > Code Style > Java` + - Formatieren mit `Code > Reformat Code` oder `Code > Reformat File` + +Die Details kann/muss man einzeln einstellen. Für die "bekannten" Styles (Google +Java Style) bringen die IDE's oft aber schon eine Gesamtkonfiguration mit. + +**Achtung**: Zumindest in Eclipse gibt es mehrere Stellen, wo ein Code-Style +eingestellt werden kann ("Clean Up", "Formatter", ...). Diese sollten dann jeweils +auf den selben Style eingestellt werden, sonst gibt es unter Umständen lustige +Effekte, da beim Speichern ein anderer Style angewendet wird als beim "Clean Up" +oder beim "Format Source" ... + +Analog sollte man bei der Verwendung von Checkstyle auch in der IDE im Formatter die +entsprechenden Checkstyle-Regeln (s.u.) passend einstellen, sonst bekommt man durch +Checkstyle Warnungen angezeigt, die man durch ein automatisches Formatieren *nicht* +beheben kann. ## Google Java Style und google-java-format -Wer direkt den [Google Java Style](https://google.github.io/styleguide/javaguide.html) nutzt, -kann auch den dazu passenden Formatter von Google einsetzen: -[google-java-format](https://github.com/google/google-java-format). -Diesen kann man entweder als Plugin für IntelliJ/Eclipse einsetzen oder als Stand-alone-Tool +Wer direkt den [Google Java +Style](https://google.github.io/styleguide/javaguide.html) nutzt, kann auch den dazu +passenden Formatter von Google einsetzen: +[google-java-format](https://github.com/google/google-java-format). Diesen kann man +entweder als Plugin für IntelliJ/Eclipse einsetzen oder als Stand-alone-Tool (Kommandozeile oder Build-Skripte) aufrufen. Wenn man sich noch einen entsprechenden -Git-Hook definiert, wird vor jedem Commit der Code entsprechend den Richtlinien formatiert :) +Git-Hook definiert, wird vor jedem Commit der Code entsprechend den Richtlinien +formatiert :) ## Spotless und google-java-format in Gradle -_Hinweis_: Bei Spotless in Gradle müssen je nach den Versionen von Spotless/google-java-format -bzw. des JDK noch Optionen in der Datei `gradle.properties` eingestellt werden (siehe -[Demo](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/quality/src/formatter/) und -[Spotless > google-java-format (Web)](https://github.com/diffplug/spotless/tree/main/plugin-gradle#google-java-format)). - -**Tipp**: Die Formatierung über die IDE ist angenehm, aber in der Praxis leider oft etwas -hakelig: Man muss alle Regeln selbst einstellen (und es gibt _einige_ dieser Einstellungen), -und gerade IntelliJ "greift" manchmal nicht alle Code-Stellen beim Formatieren. Nutzen Sie -Spotless und bauen Sie die Konfiguration in Ihr Build-Skript ein und konfigurieren Sie über -den Build-Prozess. -::::::::: +*Hinweis*: Bei Spotless in Gradle müssen je nach den Versionen von +Spotless/google-java-format bzw. des JDK noch Optionen in der Datei +`gradle.properties` eingestellt werden (siehe +[Demo](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/quality/src/formatter/) +und [Spotless \> google-java-format +(Web)](https://github.com/diffplug/spotless/tree/main/plugin-gradle#google-java-format)). + +**Tipp**: Die Formatierung über die IDE ist angenehm, aber in der Praxis leider oft +etwas hakelig: Man muss alle Regeln selbst einstellen (und es gibt *einige* dieser +Einstellungen), und gerade IntelliJ "greift" manchmal nicht alle Code-Stellen beim +Formatieren. Nutzen Sie Spotless und bauen Sie die Konfiguration in Ihr Build-Skript +ein und konfigurieren Sie über den Build-Prozess. +::: -[Demo: Konfiguration Formatter (IDE), Spotless/Gradle]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/quality/src/formatter/"} - +[Demo: Konfiguration Formatter (IDE), Spotless/Gradle]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/quality/src/formatter/"} # Metriken: Kennzahlen für verschiedene Aspekte zum Code -::::::::: notes -Metriken messen verschiedene Aspekte zum Code und liefern eine Zahl zurück. Mit Metriken kann -man beispielsweise die Einhaltung der Coding Rules (Formate, ...) prüfen, aber auch die Einhaltung -verschiedener Regeln des objektorientierten Programmierens. +::: notes +Metriken messen verschiedene Aspekte zum Code und liefern eine Zahl zurück. Mit +Metriken kann man beispielsweise die Einhaltung der Coding Rules (Formate, ...) +prüfen, aber auch die Einhaltung verschiedener Regeln des objektorientierten +Programmierens. ## Beispiele für wichtige Metriken (jeweils Max-Werte für PM) -Die folgenden Metriken und deren Maximal-Werte sind gute Erfahrungswerte aus der Praxis und helfen, -den Code Smell "Langer Code" (vgl. ["Code Smells"](smells.md)) zu -erkennen und damit zu vermeiden. Über die Metriken _BEC_, _McCabe_ und _DAC_ wird auch die Einhaltung -elementarer Programmierregeln gemessen. -::::::::: - -* **NCSS** (_Non Commenting Source Statements_) - * Zeilen pro Methode: 40; pro Klasse: 250; pro Datei: 300 \newline - _Annahme_: Eine Anweisung je Zeile ... -* **Anzahl der Methoden** pro Klasse: 10 -* **Parameter** pro Methode: 3 -* **BEC** (_Boolean Expression Complexity_) \newline - Anzahl boolescher Ausdrücke in `if` etc.: 3 -* **McCabe** (_Cyclomatic Complexity_) - * Anzahl der möglichen Verzweigungen (Pfade) pro Methode + 1 - * 1-4 gut, 5-7 noch OK -* **DAC** (_Class Data Abstraction Coupling_) - * Anzahl der genutzten (instantiierten) "Fremdklassen" - * Werte kleiner 7 werden i.A. als normal betrachtet - -::::::::: notes -Die obigen Grenzwerte sind typische Standardwerte, die sich in der Praxis allgemein bewährt haben -(vergleiche u.a. [@Martin2009] oder auch in -[AOSP: Write short methods](https://source.android.com/docs/setup/contribute/code-style#write-short-methods) -und [AOSP: Limit line length](https://source.android.com/docs/setup/contribute/code-style#limit-line-length)). +Die folgenden Metriken und deren Maximal-Werte sind gute Erfahrungswerte aus der +Praxis und helfen, den Code Smell "Langer Code" (vgl. ["Code Smells"](smells.md)) zu +erkennen und damit zu vermeiden. Über die Metriken *BEC*, *McCabe* und *DAC* wird +auch die Einhaltung elementarer Programmierregeln gemessen. +::: + +- **NCSS** (*Non Commenting Source Statements*) + - Zeilen pro Methode: 40; pro Klasse: 250; pro Datei: 300 `\newline`{=tex} + *Annahme*: Eine Anweisung je Zeile ... +- **Anzahl der Methoden** pro Klasse: 10 +- **Parameter** pro Methode: 3 +- **BEC** (*Boolean Expression Complexity*) `\newline`{=tex} Anzahl boolescher + Ausdrücke in `if` etc.: 3 +- **McCabe** (*Cyclomatic Complexity*) + - Anzahl der möglichen Verzweigungen (Pfade) pro Methode + 1 + - 1-4 gut, 5-7 noch OK +- **DAC** (*Class Data Abstraction Coupling*) + - Anzahl der genutzten (instantiierten) "Fremdklassen" + - Werte kleiner 7 werden i.A. als normal betrachtet + +::: notes +Die obigen Grenzwerte sind typische Standardwerte, die sich in der Praxis allgemein +bewährt haben (vergleiche u.a. [@Martin2009] oder auch in [AOSP: Write short +methods](https://source.android.com/docs/setup/contribute/code-style#write-short-methods) +und [AOSP: Limit line +length](https://source.android.com/docs/setup/contribute/code-style#limit-line-length)). Dennoch sind das keine absoluten Werte an sich. Ein Übertreten der Grenzen ist ein **Hinweis** darauf, dass **höchstwahrscheinlich** etwas nicht stimmt, muss aber im @@ -276,7 +280,7 @@ konkreten Fall hinterfragt und diskutiert und begründet werden! ## Metriken im Beispiel von oben -```java +``` java private static String lastName; private static MyWuppieStudi studi; @@ -290,45 +294,48 @@ konkreten Fall hinterfragt und diskutiert und begründet werden! } ``` -* BEC: 1 (nur ein boolescher Ausdruck im `if`) -* McCabe: 3 (es gibt zwei mögliche Verzweigungen in der Methode plus die Methode selbst) -* DAC: 1 (eine "Fremdklasse": `String`) +- BEC: 1 (nur ein boolescher Ausdruck im `if`) +- McCabe: 3 (es gibt zwei mögliche Verzweigungen in der Methode plus die Methode + selbst) +- DAC: 1 (eine "Fremdklasse": `String`) -_Anmerkung_: In Checkstyle werden für einige häufig verwendete Standard-Klassen Ausnahmen definiert, -d.h. `String` würde im obigen Beispiel _nicht_ bei DAC mitgezählt/angezeigt. -::::::::: +*Anmerkung*: In Checkstyle werden für einige häufig verwendete Standard-Klassen +Ausnahmen definiert, d.h. `String` würde im obigen Beispiel *nicht* bei DAC +mitgezählt/angezeigt. +::: [[Beispiel: Metriken an MyWuppieStudi#getMyWuppieStudi]{.ex}]{.slides} \bigskip -=> Verweis auf LV Softwareengineering - +=\> Verweis auf LV Softwareengineering # Tool-Support: Checkstyle -::::::::: notes -Metriken und die Einhaltung von Coding-Conventions werden sinnvollerweise nicht manuell, -sondern durch diverse Tools erfasst, etwa im Java-Bereich mit Hilfe von +::: notes +Metriken und die Einhaltung von Coding-Conventions werden sinnvollerweise nicht +manuell, sondern durch diverse Tools erfasst, etwa im Java-Bereich mit Hilfe von [**Checkstyle**](https://github.com/checkstyle). -Das Tool lässt sich [Standalone über CLI](https://checkstyle.org/cmdline.html) nutzen -oder als Plugin für IDE's ([Eclipse](https://checkstyle.org/eclipse-cs) oder +Das Tool lässt sich [Standalone über CLI](https://checkstyle.org/cmdline.html) +nutzen oder als Plugin für IDE's ([Eclipse](https://checkstyle.org/eclipse-cs) oder [IntelliJ](https://github.com/jshiell/checkstyle-idea)) einsetzen. Gradle bringt ein -eigenes [Plugin](https://docs.gradle.org/current/userguide/checkstyle_plugin.html) mit. -::::::::: +eigenes [Plugin](https://docs.gradle.org/current/userguide/checkstyle_plugin.html) +mit. +::: -* IDE: diverse [Plugins](https://checkstyle.org/index.html#Related_Tools): +- IDE: diverse [Plugins](https://checkstyle.org/index.html#Related_Tools): [Eclipse-CS](https://checkstyle.org/eclipse-cs), [CheckStyle-IDEA](https://github.com/jshiell/checkstyle-idea) -* [CLI](https://checkstyle.org/cmdline.html): +- [CLI](https://checkstyle.org/cmdline.html): `java -jar checkstyle-10.2-all.jar -c google_checks.xml *.java` -* [Plugin "**checkstyle**"](https://docs.gradle.org/current/userguide/checkstyle_plugin.html) +- [Plugin + "**checkstyle**"](https://docs.gradle.org/current/userguide/checkstyle_plugin.html) in Gradle: - ```groovy + ``` groovy plugins { id "java" id "checkstyle" @@ -340,51 +347,55 @@ eigenes [Plugin](https://docs.gradle.org/current/userguide/checkstyle_plugin.htm } ``` - * Aufruf: [Prüfen mit]{.notes} `./gradlew checkstyleMain` (Teil von `./gradlew check`) - * Konfiguration: `/config/checkstyle/checkstyle.xml` (Default) + - Aufruf: [Prüfen mit]{.notes} `./gradlew checkstyleMain` (Teil von + `./gradlew check`) + - Konfiguration: `/config/checkstyle/checkstyle.xml` (Default) [bzw. mit der obigen Konfiguration direkt im Projektordner]{.notes} - * Report: `/build/reports/checkstyle/main.html` + - Report: `/build/reports/checkstyle/main.html` -[Demo: IntelliJ, Checkstyle/Gradle]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/quality/src/checkstyle/"} - +[Demo: IntelliJ, Checkstyle/Gradle]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/quality/src/checkstyle/"} # Checkstyle: Konfiguration -::::::::: notes -Die auszuführenden Checks lassen sich über eine [XML-Datei](https://checkstyle.org/config.html) -konfigurieren. In [Eclipse-CS](https://checkstyle.org/eclipse-cs) kann man die Konfiguration -auch in einer GUI bearbeiten. - -Das Checkstyle-Projekt stellt eine passende Konfiguration für den -[Google Java Style](https://github.com/checkstyle/checkstyle/blob/master/src/main/resources/google_checks.xml) -bereit. Diese ist auch in den entsprechenden Plugins oft bereits enthalten und kann direkt -ausgewählt oder als Startpunkt für eigene Konfigurationen genutzt werden. - -Der Startpunkt für die Konfigurationsdatei ist immer das Modul "Checker". Darin können sich -"FileSetChecks" (Module, die auf einer Menge von Dateien Checks ausführen), "Filters" (Module, -die Events bei der Prüfung von Regeln filtern) und "AuditListeners" (Module, die akzeptierte -Events in einen Report überführen) befinden. Der "TreeWalker" ist mit der wichtigste Vertreter -der FileSetChecks-Module und transformiert die zu prüfenden Java-Sourcen in einen -_Abstract Syntax Tree_, also eine Baumstruktur, die dem jeweiligen Code unter der Java-Grammatik -entspricht. Darauf können dann wiederum die meisten Low-Level-Module arbeiten. - -Eine Reihe von [Standard-Checks](https://checkstyle.org/checks.html) sind bereits in Checkstyle -implementiert und benötigen keine weitere externe Abhängigkeiten. Man kann aber zusätzliche Regeln -aus anderen Projekten beziehen (etwa via Gradle/Maven) oder sich eigene zusätzliche Regeln in Java -schreiben. Die einzelnen Checks werden in der Regel als "Modul" dem "TreeWalker" hinzugefügt und -über die jeweiligen Properties näher konfiguriert. - -Sie finden in der [Doku](https://checkstyle.org/checks.html) zu jedem Check das entsprechende Modul, -das Eltern-Modul (also wo müssen Sie das Modul im XML-Baum einfügen) und auch die möglichen -Properties und deren Default-Einstellungen. -::::::::: - -```xml +::: notes +Die auszuführenden Checks lassen sich über eine +[XML-Datei](https://checkstyle.org/config.html) konfigurieren. In +[Eclipse-CS](https://checkstyle.org/eclipse-cs) kann man die Konfiguration auch in +einer GUI bearbeiten. + +Das Checkstyle-Projekt stellt eine passende Konfiguration für den [Google Java +Style](https://github.com/checkstyle/checkstyle/blob/master/src/main/resources/google_checks.xml) +bereit. Diese ist auch in den entsprechenden Plugins oft bereits enthalten und kann +direkt ausgewählt oder als Startpunkt für eigene Konfigurationen genutzt werden. + +Der Startpunkt für die Konfigurationsdatei ist immer das Modul "Checker". Darin +können sich "FileSetChecks" (Module, die auf einer Menge von Dateien Checks +ausführen), "Filters" (Module, die Events bei der Prüfung von Regeln filtern) und +"AuditListeners" (Module, die akzeptierte Events in einen Report überführen) +befinden. Der "TreeWalker" ist mit der wichtigste Vertreter der FileSetChecks-Module +und transformiert die zu prüfenden Java-Sourcen in einen *Abstract Syntax Tree*, +also eine Baumstruktur, die dem jeweiligen Code unter der Java-Grammatik entspricht. +Darauf können dann wiederum die meisten Low-Level-Module arbeiten. + +Eine Reihe von [Standard-Checks](https://checkstyle.org/checks.html) sind bereits in +Checkstyle implementiert und benötigen keine weitere externe Abhängigkeiten. Man +kann aber zusätzliche Regeln aus anderen Projekten beziehen (etwa via Gradle/Maven) +oder sich eigene zusätzliche Regeln in Java schreiben. Die einzelnen Checks werden +in der Regel als "Modul" dem "TreeWalker" hinzugefügt und über die jeweiligen +Properties näher konfiguriert. + +Sie finden in der [Doku](https://checkstyle.org/checks.html) zu jedem Check das +entsprechende Modul, das Eltern-Modul (also wo müssen Sie das Modul im XML-Baum +einfügen) und auch die möglichen Properties und deren Default-Einstellungen. +::: + +``` xml @@ -401,27 +412,33 @@ Properties und deren Default-Einstellungen. ``` ::: notes -Alternativen/Ergänzungen: beispielsweise [MetricsReloaded](https://github.com/BasLeijdekkers/MetricsReloaded). +Alternativen/Ergänzungen: beispielsweise +[MetricsReloaded](https://github.com/BasLeijdekkers/MetricsReloaded). ::: -[Demo: Konfiguration mit Eclipse-CS, Hinweis auf Formatter]{.ex href="https://youtu.be/0ny6e6CNTF8"} - +[Demo: Konfiguration mit Eclipse-CS, Hinweis auf Formatter]{.ex +href="https://youtu.be/0ny6e6CNTF8"} # SpotBugs: Finde Anti-Pattern und potentielle Bugs (Linter) -* [**SpotBugs**](https://github.com/spotbugs/spotbugs) sucht nach über 400 potentiellen Bugs im Code - * Anti-Pattern (schlechte Praxis, "dodgy" Code) - * Sicherheitsprobleme - * Korrektheit +- [**SpotBugs**](https://github.com/spotbugs/spotbugs) sucht nach über 400 + potentiellen Bugs im Code + - Anti-Pattern (schlechte Praxis, "dodgy" Code) + - Sicherheitsprobleme + - Korrektheit \smallskip -* CLI: `java -jar spotbugs.jar options ...` -* IDE: [IntelliJ SpotBugs plugin](https://github.com/JetBrains/spotbugs-intellij-plugin), - [SpotBugs Eclipse plugin](https://spotbugs.readthedocs.io/en/latest/eclipse.html) -* Gradle: [SpotBugs Gradle Plugin](https://github.com/spotbugs/spotbugs-gradle-plugin) +- CLI: `java -jar spotbugs.jar options ...` - ```groovy +- IDE: [IntelliJ SpotBugs + plugin](https://github.com/JetBrains/spotbugs-intellij-plugin), [SpotBugs + Eclipse plugin](https://spotbugs.readthedocs.io/en/latest/eclipse.html) + +- Gradle: [SpotBugs Gradle + Plugin](https://github.com/spotbugs/spotbugs-gradle-plugin) + + ``` groovy plugins { id "java" id "com.github.spotbugs" version "5.0.6" @@ -439,45 +456,46 @@ docker pull gradle docker run --rm -it -v "$PWD":/data -w /data --entrypoint "bash" gradle --> -[Demo: SpotBugs/Gradle]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/quality/src/spotbugs/"} - +[Demo: SpotBugs/Gradle]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/quality/src/spotbugs/"} # Konfiguration für das PM-Praktikum (Format, Metriken, Checkstyle, SpotBugs) ::: notes -Im PM-Praktikum beachten wir die obigen Coding Conventions und Metriken mit den dort definierten -Grenzwerten. Diese sind bereits in der bereit gestellten Minimal-Konfiguration für Checkstyle -(s.u.) konfiguriert. +Im PM-Praktikum beachten wir die obigen Coding Conventions und Metriken mit den dort +definierten Grenzwerten. Diese sind bereits in der bereit gestellten +Minimal-Konfiguration für Checkstyle (s.u.) konfiguriert. ::: ## Formatierung -* Google Java Style/AOSP: **Spotless** +- Google Java Style/AOSP: **Spotless** ::: notes -Zusätzlich wenden wir den [Google Java Style](https://google.github.io/styleguide/javaguide.html) -an. Statt der dort vorgeschriebenen Einrückung mit 2 Leerzeichen (und 4+ Leerzeichen bei Zeilenumbruch -in einem Statement) können Sie auch mit 4 Leerzeichen einrücken (8 Leerzeichen bei Zeilenumbruch) -([AOSP](https://source.android.com/docs/setup/contribute/code-style)). Halten Sie sich in Ihrem -Team an eine einheitliche Einrückung (Google Java Style _oder_ AOSP). - -Formatieren Sie Ihren Code vor den Commits mit **Spotless** (über Gradle) oder stellen Sie den -Formatter Ihrer IDE entsprechend ein. +Zusätzlich wenden wir den [Google Java +Style](https://google.github.io/styleguide/javaguide.html) an. Statt der dort +vorgeschriebenen Einrückung mit 2 Leerzeichen (und 4+ Leerzeichen bei Zeilenumbruch +in einem Statement) können Sie auch mit 4 Leerzeichen einrücken (8 Leerzeichen bei +Zeilenumbruch) +([AOSP](https://source.android.com/docs/setup/contribute/code-style)). Halten Sie +sich in Ihrem Team an eine einheitliche Einrückung (Google Java Style *oder* AOSP). + +Formatieren Sie Ihren Code vor den Commits mit **Spotless** (über Gradle) oder +stellen Sie den Formatter Ihrer IDE entsprechend ein. ::: \bigskip ## Checkstyle -* Minimal-Konfiguration für **Checkstyle** (Coding Conventions, Metriken) +- Minimal-Konfiguration für **Checkstyle** (Coding Conventions, Metriken) ::: notes Nutzen Sie die folgende **Minimal-Konfiguration** für **Checkstyle** für Ihre -Praktikumsaufgaben. Diese beinhaltet die Prüfung der wichtigsten Formate nach -Google Java Style/AOSP sowie der obigen Metriken. Halten Sie diese Regeln -ein. +Praktikumsaufgaben. Diese beinhaltet die Prüfung der wichtigsten Formate nach Google +Java Style/AOSP sowie der obigen Metriken. Halten Sie diese Regeln ein. -```xml +``` xml @@ -539,55 +557,73 @@ ein. ``` -Sie können diese Basis-Einstellungen auch aus dem Programmiermethoden-CampusMinden/Prog2-Lecture-Repo direkt herunterladen: +Sie können diese Basis-Einstellungen auch aus dem +Programmiermethoden-CampusMinden/Prog2-Lecture-Repo direkt herunterladen: [checkstyle.xml](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/quality/src/checkstyle.xml). -Sie können zusätzlich gern noch die weiteren (und strengeren) Regeln aus der vom Checkstyle-Projekt -bereitgestellten Konfigurationsdatei für den -[Google Java Style](https://github.com/checkstyle/checkstyle/blob/master/src/main/resources/google_checks.xml) -nutzen. _Hinweis_: Einige der dort konfigurierten Checkstyle-Regeln gehen allerdings über den -Google Java Style hinaus. +Sie können zusätzlich gern noch die weiteren (und strengeren) Regeln aus der vom +Checkstyle-Projekt bereitgestellten Konfigurationsdatei für den [Google Java +Style](https://github.com/checkstyle/checkstyle/blob/master/src/main/resources/google_checks.xml) +nutzen. *Hinweis*: Einige der dort konfigurierten Checkstyle-Regeln gehen allerdings +über den Google Java Style hinaus. ::: \bigskip ## Linter: SpotBugs -* Vermeiden von Anti-Pattern mit **SpotBugs** +- Vermeiden von Anti-Pattern mit **SpotBugs** ::: notes -Setzen Sie zusätzlich **SpotBugs** mit ein. Ihre Lösungen dürfen keine Warnungen oder -Fehler beinhalten, die SpotBugs melden würde. +Setzen Sie zusätzlich **SpotBugs** mit ein. Ihre Lösungen dürfen keine Warnungen +oder Fehler beinhalten, die SpotBugs melden würde. ::: - # Wrap-Up -* Code entsteht nicht zum Selbstzweck => Regeln nötig! - * Coding Conventions +- Code entsteht nicht zum Selbstzweck =\> Regeln nötig! + - Coding Conventions ::: notes - * Regeln zu Schreibweisen und Layout - * Leerzeichen, Einrückung, Klammern - * Zeilenlänge, Umbrüche - * Kommentare + - Regeln zu Schreibweisen und Layout + - Leerzeichen, Einrückung, Klammern + - Zeilenlänge, Umbrüche + - Kommentare ::: - * Formatieren mit **Spotless** + - Formatieren mit **Spotless** - * Prinzipien des objektorientierten Programmierens [(vgl. ["Code Smells"](smells.md))]{.notes} + - Prinzipien des objektorientierten Programmierens [(vgl. ["Code + Smells"](smells.md))]{.notes} ::: notes - * Jede Klasse ist für genau **einen** Aspekt des Systems verantwortlich. - (_Single Responsibility_) - * Keine Code-Duplizierung! (_DRY_ - Don't repeat yourself) - * Klassen und Methoden sollten sich erwartungsgemäß verhalten - * Kapselung: Möglichst wenig öffentlich zugänglich machen + - Jede Klasse ist für genau **einen** Aspekt des Systems verantwortlich. + (*Single Responsibility*) + - Keine Code-Duplizierung! (*DRY* - Don't repeat yourself) + - Klassen und Methoden sollten sich erwartungsgemäß verhalten + - Kapselung: Möglichst wenig öffentlich zugänglich machen ::: \bigskip -* Metriken: Einhaltung von Regeln in Zahlen ausdrücken -* Prüfung manuell durch Code Reviews oder durch Tools wie **Checkstyle** oder **SpotBugs** -* Definition des ["PM-Styles"](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/quality/src/checkstyle.xml) +- Metriken: Einhaltung von Regeln in Zahlen ausdrücken +- Prüfung manuell durch Code Reviews oder durch Tools wie **Checkstyle** oder + **SpotBugs** +- Definition des + ["PM-Styles"](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/quality/src/checkstyle.xml) [(siehe Folie "Konfiguration für das PM-Praktikum")]{.notes} + +::: readings +- @Martin2009 +- @Inden2013 [Kap. 13] +- @googlestyleguide +::: + +::: outcomes +- k2: Ich kann verschiedene Coding Conventions erklären +- k2: Ich kann die Metriken NCSS, McCabe, BEC, DAC erklären +- k3: Ich kann das Tool Spotless zur Formatierung des Codes nutzen +- k3: Ich kann das Tool Checkstyle zum Überprüfen von Coding Conventions und + Metriken nutzen +- k2: Ich kenne das Tool SpotBugs zum Vermeiden von Anti-Pattern +::: diff --git a/lecture/quality/javadoc.md b/lecture/quality/javadoc.md index d636cc551..a42c6160f 100644 --- a/lecture/quality/javadoc.md +++ b/lecture/quality/javadoc.md @@ -1,60 +1,35 @@ --- -title: "Javadoc" -author: "Carsten Gips (HSBI)" -readings: - - "@Ullenboom2021 [Kap. 23.4]" - - "@googlestyleguide [Kap. 7]" - - "@oraclejavadocguide" -tldr: | - Mit Javadoc kann aus speziell markierten Block-Kommentaren eine externe Dokumentation im HTML-Format - erzeugt werden. Die Block-Kommentare, auf die das im JDK enthaltene Programm `javadoc` reagiert, - beginnen mit `/**` (also einem zusätzlichen Stern, der für den Java-Compiler nur das erste Kommentarzeichen - ist). - - Die erste Zeile eines Javadoc-Kommentars ist eine "Zusammenfassung" und an fast allen Stellen der - generierten Doku sichtbar. Diese Summary sollte kurz gehalten werden und eine Idee vermitteln, was - die Klasse oder die Methode oder das Attribut macht. - - Für die Dokumentation von Parametern, Rückgabetypen, Exceptions und veralteten Elementen existieren - spezielle Annotationen: `@param`, `@return`, `@throws` und `@deprecated`. - - Als Faustregel gilt: Es werden **alle** `public` und `protected` Elemente (Klassen, Methoden, Attribute) - mit Javadoc kommentiert. Alle nicht-öffentlichen Elemente bekommen normale Java-Kommentare (Zeilen- oder - Blockkommentare). -outcomes: - - k2: "Ich verstehe den Sinn von Javadoc-Kommentaren" - - k2: "Ich kenne den typischen Aufbau von Javadoc-Kommentaren" - - k3: "Ich kann sämtliche öffentlich sichtbaren Elemente mit Javadoc dokumentieren" - - k3: "Ich kann eine sinnvolle Summary schreiben" - - k3: "Ich kann verschiedene Annotationen zur Dokumentation von Parametern, Rückgabetypen, Exceptions, veralteten Elementen einsetzen" - - k3: "Ich kann die Dokumentation generieren" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106229&client_id=FH-Bielefeld" -# name: "Quiz Javadoc (ILIAS)" -youtube: - - link: "https://youtu.be/Qo2TTD593eQ" - name: "VL Javadoc" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/15984eacd03de989ab2bce322ace5d74da962a911ae45afbb60958714ed6b16c72c962aec4b60acda9419ef15d26c5a5265129245f26beb0f905af9a7176b9fa" - name: "VL Javadoc" -challenges: | - Betrachten Sie die Javadoc einiger Klassen im Dungeon-Projekt: - [dojo.rooms.LevelRoom](https://github.com/Dungeon-CampusMinden/Dungeon/blob/31c0e3aaf25eb412a33751c897df43eb21bf2744/dojo-dungeon/src/dojo/rooms/LevelRoom.java), - [dojo.rooms.MonsterRoom](https://github.com/Dungeon-CampusMinden/Dungeon/blob/31c0e3aaf25eb412a33751c897df43eb21bf2744/dojo-dungeon/src/dojo/rooms/MonsterRoom.java), und - [contrib.components.HealthComponent](https://github.com/Dungeon-CampusMinden/Dungeon/blob/master/dungeon/src/contrib/components/HealthComponent.java). - - Stellen Sie sich vor, Sie müssten diese Klassen in einer Übungsaufgabe nutzen (das könnte tatsächlich passieren!) ... - - Können Sie anhand der Javadoc verstehen, wozu die drei Klassen dienen und wie Sie diese Klassen benutzen sollten? - Vergleichen Sie die Qualität der Dokumentation. - Was würden Sie gern in der Dokumentation finden? - Was würden Sie ändern? +author: Carsten Gips (HSBI) +title: Javadoc --- +::: tldr +Mit Javadoc kann aus speziell markierten Block-Kommentaren eine externe +Dokumentation im HTML-Format erzeugt werden. Die Block-Kommentare, auf die das im +JDK enthaltene Programm `javadoc` reagiert, beginnen mit `/**` (also einem +zusätzlichen Stern, der für den Java-Compiler nur das erste Kommentarzeichen ist). + +Die erste Zeile eines Javadoc-Kommentars ist eine "Zusammenfassung" und an fast +allen Stellen der generierten Doku sichtbar. Diese Summary sollte kurz gehalten +werden und eine Idee vermitteln, was die Klasse oder die Methode oder das Attribut +macht. + +Für die Dokumentation von Parametern, Rückgabetypen, Exceptions und veralteten +Elementen existieren spezielle Annotationen: `@param`, `@return`, `@throws` und +`@deprecated`. + +Als Faustregel gilt: Es werden **alle** `public` und `protected` Elemente (Klassen, +Methoden, Attribute) mit Javadoc kommentiert. Alle nicht-öffentlichen Elemente +bekommen normale Java-Kommentare (Zeilen- oder Blockkommentare). +::: + +::: youtube +- [VL Javadoc](https://youtu.be/Qo2TTD593eQ) +::: # Dokumentation mit Javadoc -```java +``` java /** * Beschreibung Beschreibung (Summary). * @@ -65,26 +40,25 @@ public void wuppie() {} ``` ::: notes -Javadoc-Kommentare sind (aus Java-Sicht) normale Block-Kommentare, wobei der Beginn mit -`/**` eingeleitet wird. Dieser Beginn ist für das Tool `javadoc` (Bestandteil des JDK, -genau wie `java` und `javac`) das Signal, dass hier ein Kommentar anfängt, den das -Tool in eine HTML-Dokumentation übersetzen soll. +Javadoc-Kommentare sind (aus Java-Sicht) normale Block-Kommentare, wobei der Beginn +mit `/**` eingeleitet wird. Dieser Beginn ist für das Tool `javadoc` (Bestandteil +des JDK, genau wie `java` und `javac`) das Signal, dass hier ein Kommentar anfängt, +den das Tool in eine HTML-Dokumentation übersetzen soll. -Typischerweise wird am Anfang jeder Kommentarzeile ein `*` eingefügt; dieser wird von -Javadoc ignoriert. +Typischerweise wird am Anfang jeder Kommentarzeile ein `*` eingefügt; dieser wird +von Javadoc ignoriert. -Sie können neben normalem Text und speziellen Annotationen auch HTML-Elemente wie `

` -und `` oder `

    ` nutzen. +Sie können neben normalem Text und speziellen Annotationen auch HTML-Elemente wie +`

    ` und `` oder `

      ` nutzen. -Mit `javadoc *.java` können Sie in der Konsole aus den Java-Dateien die Dokumentation -generieren lassen. Oder Sie geben das in Ihrer IDE in Auftrag ... (die dann diesen -Aufruf gern für Sie tätigt). +Mit `javadoc *.java` können Sie in der Konsole aus den Java-Dateien die +Dokumentation generieren lassen. Oder Sie geben das in Ihrer IDE in Auftrag ... (die +dann diesen Aufruf gern für Sie tätigt). ::: - # Standard-Aufbau -```java +``` java /** * Beschreibung Beschreibung (Summary). * @@ -102,40 +76,42 @@ public int setDate(int date) { ``` ::: notes -* Erste Zeile bei Methoden/Attributen geht in die generierte "Summary" in der Übersicht, - der Rest in die "Details" - * Die "Summary" sollte kein kompletter Satz sein, wird aber wie ein Satz geschrieben - (Groß beginnen, mit Punkt beenden). Es sollte nicht beginnen mit "Diese Methode - macht ..." oder "Diese Klasse ist ...". Ein gutes Beispiel wäre "Berechnet die - Steuerrückerstattung." - * Danach kommen die Details, die in der generierten Dokumentation erst durch - Aufklappen der Elemente sichtbar sind. Erklären Sie, wieso der Code was machen - soll und welche Designentscheidungen getroffen wurden (und warum). -* Leerzeilen gliedern den Text in Absätze. Neue Absätze werden mit einem `

      ` eingeleitet. - (Ausnahmen: Wenn der Text mit `

        ` o.ä. beginnt oder der Absatz mit den Block-Tags.) -* Die "Block-Tags" `@param`, `@return`, `@throws`, `@deprecated` werden durch einen - Absatz von der restlichen Beschreibung getrennt und tauchen in exakt dieser Reihenfolge - auf. Die Beschreibung dieser Tags ist nicht leer - anderenfalls lässt man das Tag weg. - Falls die Zeile für die Beschreibung nicht reicht, wird umgebrochen und die Folgezeile - mit vier Leerzeichen (beginnend mit dem `@`) eingerückt. - * Mit `@param` erklären Sie die Bedeutung eines Parameters (von links nach rechts) einer - Methode. Beispiel: `@param date Tag, Wert zw. 1 .. 31`. Wiederholen Sie dies für - jeden Parameter. - * Mit `@return` beschreiben Sie den Rückgabetyp/-wert. Beispiel: - `@return Anzahl der Sekunden seit 1.1.1970`. - Bei Rückgabe von `void` wird diese Beschreibung weggelassen (die Beschreibung wäre - dann ja leer). - * Mit `@throws` geben Sie an, welche "checked" Exceptions die Methode wirft. - * Mit `@deprecated` können Sie im Kommentar sagen, dass ein Element veraltet ist und - möglicherweise mit der nächsten Version o.ä. entfernt wird. (siehe nächste Folie) - -=> Dies sind die Basis-Regeln aus dem populären Google-Java-Style [@googlestyleguide]. +- Erste Zeile bei Methoden/Attributen geht in die generierte "Summary" in der + Übersicht, der Rest in die "Details" + - Die "Summary" sollte kein kompletter Satz sein, wird aber wie ein Satz + geschrieben (Groß beginnen, mit Punkt beenden). Es sollte nicht beginnen mit + "Diese Methode macht ..." oder "Diese Klasse ist ...". Ein gutes Beispiel + wäre "Berechnet die Steuerrückerstattung." + - Danach kommen die Details, die in der generierten Dokumentation erst durch + Aufklappen der Elemente sichtbar sind. Erklären Sie, wieso der Code was + machen soll und welche Designentscheidungen getroffen wurden (und warum). +- Leerzeilen gliedern den Text in Absätze. Neue Absätze werden mit einem `

        ` + eingeleitet. (Ausnahmen: Wenn der Text mit `

          ` o.ä. beginnt oder der Absatz + mit den Block-Tags.) +- Die "Block-Tags" `@param`, `@return`, `@throws`, `@deprecated` werden durch + einen Absatz von der restlichen Beschreibung getrennt und tauchen in exakt + dieser Reihenfolge auf. Die Beschreibung dieser Tags ist nicht leer - + anderenfalls lässt man das Tag weg. Falls die Zeile für die Beschreibung nicht + reicht, wird umgebrochen und die Folgezeile mit vier Leerzeichen (beginnend mit + dem `@`) eingerückt. + - Mit `@param` erklären Sie die Bedeutung eines Parameters (von links nach + rechts) einer Methode. Beispiel: `@param date Tag, Wert zw. 1 .. 31`. + Wiederholen Sie dies für jeden Parameter. + - Mit `@return` beschreiben Sie den Rückgabetyp/-wert. Beispiel: + `@return Anzahl der Sekunden seit 1.1.1970`. Bei Rückgabe von `void` wird + diese Beschreibung weggelassen (die Beschreibung wäre dann ja leer). + - Mit `@throws` geben Sie an, welche "checked" Exceptions die Methode wirft. + - Mit `@deprecated` können Sie im Kommentar sagen, dass ein Element veraltet + ist und möglicherweise mit der nächsten Version o.ä. entfernt wird. (siehe + nächste Folie) + +=\> Dies sind die Basis-Regeln aus dem populären Google-Java-Style +[@googlestyleguide]. ::: - # Veraltete Elemente -```java +``` java /** * Beschreibung Beschreibung Beschreibung. * @@ -146,18 +122,19 @@ public void wuppie() {} ``` ::: notes -* Annotation zum Markieren als "veraltet" (in der generierten Dokumentation): `@deprecated` -* Für Sichtbarkeit zur Laufzeit bzw. im Tooling/IDE: normale Code-Annotation `@Deprecated` +- Annotation zum Markieren als "veraltet" (in der generierten Dokumentation): + `@deprecated` +- Für Sichtbarkeit zur Laufzeit bzw. im Tooling/IDE: normale Code-Annotation + `@Deprecated` Dies ist ein guter Weg, um Elemente einer öffentlichen API als "veraltet" zu kennzeichnen. Üblicherweise wird diese Kennzeichnung für einige wenige Releases beibehalten und danach das veraltete Element aus der API entfernt. ::: - # Autoren, Versionen, ... -```java +``` java /** * Beschreibung Beschreibung Beschreibung. * @@ -168,54 +145,94 @@ beibehalten und danach das veraltete Element aus der API entfernt. ``` ::: notes -* Annotationen für Autoren und Version: `@author`, `@version`, `@since` +- Annotationen für Autoren und Version: `@author`, `@version`, `@since` Diese Annotationen finden Sie vor allem in Kommentaren zu Packages oder Klassen. ::: - # Was muss kommentiert werden? -* Alle `public` Klassen -* Alle `public` und `protected` Elemente der Klassen +- Alle `public` Klassen +- Alle `public` und `protected` Elemente der Klassen \bigskip -* Ausnahme: `@Override` [(An diesen Methoden _kann_, aber _muss_ nicht kommentiert werden.)]{.notes} +- Ausnahme: `@Override` [(An diesen Methoden *kann*, aber *muss* nicht kommentiert + werden.)]{.notes} \bigskip \bigskip -Alle anderen Elemente bei Bedarf mit _normalen_ Kommentaren versehen. +Alle anderen Elemente bei Bedarf mit *normalen* Kommentaren versehen. ::: notes ## Beispiel aus dem JDK: ArrayList -Schauen Sie sich gern mal Klassen aus der Java-API an, beispielsweise eine `java.util.ArrayList`: -* Generierte Dokumentation: - [zu "ArrayList" runterscrollen](https://docs.oracle.com/javase/8/docs/api/index.html?java/util/package-summary.html) - bzw. [direkt](https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html) -* Quellcode: [ArrayList.java](https://hg.openjdk.java.net/jdk8/jdk8/jdk/file/tip/src/share/classes/java/util/ArrayList.java) +Schauen Sie sich gern mal Klassen aus der Java-API an, beispielsweise eine +`java.util.ArrayList`: + +- Generierte Dokumentation: [zu "ArrayList" + runterscrollen](https://docs.oracle.com/javase/8/docs/api/index.html?java/util/package-summary.html) + bzw. + [direkt](https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html) +- Quellcode: + [ArrayList.java](https://hg.openjdk.java.net/jdk8/jdk8/jdk/file/tip/src/share/classes/java/util/ArrayList.java) ## Best Practices: Was beschreibe ich eigentlich? -Unter [Documentation Best Practices](https://github.com/google/styleguide/blob/gh-pages/docguide/best_practices.md#documentation-is-the-story-of-your-code) -finden Sie eine sehr gute Beschreibung, was das Ziel der Dokumentation sein sollte. Versuchen Sie, dieses zu erreichen! +Unter [Documentation Best +Practices](https://github.com/google/styleguide/blob/gh-pages/docguide/best_practices.md#documentation-is-the-story-of-your-code) +finden Sie eine sehr gute Beschreibung, was das Ziel der Dokumentation sein sollte. +Versuchen Sie, dieses zu erreichen! -Etwas technisch, aber ebenfalls sehr lesenswert ist der Style-Guide für Java-Software -[How to Write Doc Comments for the Javadoc Tool](https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html) -von @oraclejavadocguide. -::: +Etwas technisch, aber ebenfalls sehr lesenswert ist der Style-Guide für +Java-Software [How to Write Doc Comments for the Javadoc +Tool](https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html) +von +1. +::: # Wrap-Up -* Javadoc-Kommentare sind normale Block-Kommentare beginnend mit `/**` -* Generierung der HTML-Dokumentation mit `javadoc *.java` -* Erste Zeile ist eine Zusammenfassung (fast immer sichtbar) -* Längerer Text danach als "Description" einer Methode/Klasse -* Annotationen für besondere Elemente: `@param`, `@return`, `@throws`, `@deprecated` +- Javadoc-Kommentare sind normale Block-Kommentare beginnend mit `/**` +- Generierung der HTML-Dokumentation mit `javadoc *.java` +- Erste Zeile ist eine Zusammenfassung (fast immer sichtbar) +- Längerer Text danach als "Description" einer Methode/Klasse +- Annotationen für besondere Elemente: `@param`, `@return`, `@throws`, + `@deprecated` \bigskip -* Faustregel: Alle `public` und `protected` Elemente mit Javadoc kommentieren! +- Faustregel: Alle `public` und `protected` Elemente mit Javadoc kommentieren! + +::: readings +- @Ullenboom2021 [Kap. 23.4] +- @googlestyleguide [Kap. 7] +- 1 +::: + +::: outcomes +- k2: Ich verstehe den Sinn von Javadoc-Kommentaren +- k2: Ich kenne den typischen Aufbau von Javadoc-Kommentaren +- k3: Ich kann sämtliche öffentlich sichtbaren Elemente mit Javadoc dokumentieren +- k3: Ich kann eine sinnvolle Summary schreiben +- k3: Ich kann verschiedene Annotationen zur Dokumentation von Parametern, + Rückgabetypen, Exceptions, veralteten Elementen einsetzen +- k3: Ich kann die Dokumentation generieren +::: + +::: challenges +Betrachten Sie die Javadoc einiger Klassen im Dungeon-Projekt: +[dojo.rooms.LevelRoom](https://github.com/Dungeon-CampusMinden/Dungeon/blob/31c0e3aaf25eb412a33751c897df43eb21bf2744/dojo-dungeon/src/dojo/rooms/LevelRoom.java), +[dojo.rooms.MonsterRoom](https://github.com/Dungeon-CampusMinden/Dungeon/blob/31c0e3aaf25eb412a33751c897df43eb21bf2744/dojo-dungeon/src/dojo/rooms/MonsterRoom.java), +und +[contrib.components.HealthComponent](https://github.com/Dungeon-CampusMinden/Dungeon/blob/master/dungeon/src/contrib/components/HealthComponent.java). + +Stellen Sie sich vor, Sie müssten diese Klassen in einer Übungsaufgabe nutzen (das +könnte tatsächlich passieren!) ... + +Können Sie anhand der Javadoc verstehen, wozu die drei Klassen dienen und wie Sie +diese Klassen benutzen sollten? Vergleichen Sie die Qualität der Dokumentation. Was +würden Sie gern in der Dokumentation finden? Was würden Sie ändern? +::: diff --git a/lecture/quality/junit-basics.md b/lecture/quality/junit-basics.md index cb1941156..a87d70fd8 100644 --- a/lecture/quality/junit-basics.md +++ b/lecture/quality/junit-basics.md @@ -1,153 +1,32 @@ --- -title: "Testen mit JUnit (JUnit-Basics)" -author: "Carsten Gips (HSBI)" -readings: - - "@vogellaJUnit" - - "@junit4" - - "@Kleuker2019" - - "@Osherove2014" - - "@Spillner2012" - - "@fernunihagenJunit" -tldr: | - In JUnit 4 und 5 werden Testmethoden mit Hilfe der Annotation `@Test` ausgezeichnet. Über die - verschiedenen `assert*()`-Methoden kann das Testergebnis mit dem erwarteten Ergebnis verglichen - werden und entsprechend ist der Test "grün" oder "rot". Mit den verschiedenen `assume*()`-Methoden - kann dagegen geprüft werden, ob eventuelle Vorbedingungen für das Ausführen eines Testfalls - erfüllt sind - anderenfalls wird der Testfall dann übersprungen. - - Mit Hilfe von `@Before` und `@After` können Methoden gekennzeichnet werden, die jeweils vor jeder - Testmethode und nach jeder Testmethode aufgerufen werden. Damit kann man seine Testumgebung auf- - und auch wieder abbauen (JUnit 4). - - Erwartete Exceptions lassen sich in JUnit 4 mit einem Parameter `expected` in der Annotation `@Test` - automatisch prüfen: `@Test(expected=package.Exception.class)`. In JUnit 4 besteht die Möglichkeit, - Testklassen zu Testsuiten zusammenzufassen und gemeinsam laufen zu lassen. -outcomes: - - k3: "Ich kann Testergebnisse prüfen" - - k2: "Ich kenne den Unterschied zwischen `assert` und `assume`" - - k3: "Ich kann vor/nach jedem Test bestimmten Code ausführen" - - k2: "Ich habe verstanden, warum `@Before` und `@After` sparsam einzusetzen sind" - - k3: "Ich kann die Ausführung von Tests steuern, beispielsweise Tests ignorieren oder mit zeitlicher Begrenzung ausführen" - - k3: "Ich kann das Auftreten von Exceptions prüfen" - - k3: "Ich kann Tests zu Testsuiten zusammenfassen" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106545&client_id=FH-Bielefeld" -# name: "Quiz JUnit-Basics (ILIAS)" -youtube: - - link: "https://youtu.be/2SC40rO0ZOE" - name: "VL JUnit Basics" - - link: "https://youtu.be/j3FK9iTHuDk" - name: "Demo assume() vs. assert()" - - link: "https://youtu.be/KsFydUSBDTc" - name: "Demo Parametrisierte Tests mit JUnit4" - - link: "https://youtu.be/0H-OCICktS0" - name: "Demo Parametrisierte Tests mit JUnit5" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/e10dd378f9b18ba4a42ffbb2c13bfb83685b60fd39a221dba8658b8edcb0df032c2dcf9a1dcd44cf59aa6b483a00a19195cb5a8d117a6fbda52cfcfcf9efe5da" - name: "VL JUnit Basics" -challenges: | - **Setup und Teardown** - - Sie haben in den Challenges in "Intro SW-Test" erste JUnit-Tests für - die Klasse `MyList` implementiert. - - Wie müssten Sie Ihre JUnit-Tests anpassen, wenn Sie im obigen Szenario - Setup- und Teardown-Methoden einsetzen würden? - - - - - **Testmethoden** - - Betrachten Sie den folgenden Code. Was fällt Ihnen auf? - - ```java - public class Studi { - public int getCredits(); - public void addToCredits(int credits); - - @Test - public void testStudi() { - Studi s = new Studi(); - s.addToCredits(2); - assertEquals(2, s.getCredits()); - } - } - ``` - - - **Parametrisierte Tests** - - Betrachten Sie die folgende einfache Klasse `MyMath`: - - ```java - public class MyMath { - public static String add(String s, int c) { - return s.repeat(c); - } - } - ``` - - Beim Testen der Methode `MyMath#add` fällt auf, dass man hier immer - wieder den selben Testfall mit lediglich anderen Werten ausführt - ein - Fall für parametrisierte Tests. - - Schreiben Sie mit Hilfe von JUnit (4.x oder 5.x) einige parametrisierte - Unit-Tests für die Methode `MyMath#add`. - - +author: Carsten Gips (HSBI) +title: Testen mit JUnit (JUnit-Basics) --- +::: tldr +In JUnit 4 und 5 werden Testmethoden mit Hilfe der Annotation `@Test` ausgezeichnet. +Über die verschiedenen `assert*()`-Methoden kann das Testergebnis mit dem erwarteten +Ergebnis verglichen werden und entsprechend ist der Test "grün" oder "rot". Mit den +verschiedenen `assume*()`-Methoden kann dagegen geprüft werden, ob eventuelle +Vorbedingungen für das Ausführen eines Testfalls erfüllt sind - anderenfalls wird +der Testfall dann übersprungen. + +Mit Hilfe von `@Before` und `@After` können Methoden gekennzeichnet werden, die +jeweils vor jeder Testmethode und nach jeder Testmethode aufgerufen werden. Damit +kann man seine Testumgebung auf- und auch wieder abbauen (JUnit 4). + +Erwartete Exceptions lassen sich in JUnit 4 mit einem Parameter `expected` in der +Annotation `@Test` automatisch prüfen: `@Test(expected=package.Exception.class)`. In +JUnit 4 besteht die Möglichkeit, Testklassen zu Testsuiten zusammenzufassen und +gemeinsam laufen zu lassen. +::: + +::: youtube +- [VL JUnit Basics](https://youtu.be/2SC40rO0ZOE) +- [Demo assume() vs. assert()](https://youtu.be/j3FK9iTHuDk) +- [Demo Parametrisierte Tests mit JUnit4](https://youtu.be/KsFydUSBDTc) +- [Demo Parametrisierte Tests mit JUnit5](https://youtu.be/0H-OCICktS0) +::: # JUnit: Ergebnis prüfen @@ -155,7 +34,7 @@ Klasse **`org.junit.Assert`** enthält diverse **statische** Methoden zum Prüfe \bigskip -```java +``` java // Argument muss true bzw. false sein void assertTrue(boolean); void assertFalse(boolean); @@ -169,39 +48,38 @@ void fail(); ... ``` - # To "assert" or to "assume"? -* Mit `assert*` werden Testergebnisse geprüft - * Test wird ausgeführt - * Ergebnis: OK, Failure, Error +- Mit `assert*` werden Testergebnisse geprüft + - Test wird ausgeführt + - Ergebnis: OK, Failure, Error \bigskip -* Mit `assume*` werden Annahmen über den Zustand geprüft - * Test wird abgebrochen, wenn Annahme nicht erfüllt (Ergebnis: "Ignored") - * Prüfen von Vorbedingungen: Ist der Test hier ausführbar/anwendbar? +- Mit `assume*` werden Annahmen über den Zustand geprüft + - Test wird abgebrochen, wenn Annahme nicht erfüllt (Ergebnis: "Ignored") + - Prüfen von Vorbedingungen: Ist der Test hier ausführbar/anwendbar? -[Beispiel: junit4.TestAssume]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/quality/src/junit4/TestAssume.java"} +[Beispiel: junit4.TestAssume]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/quality/src/junit4/TestAssume.java"} ::: notes -Im JUnit-Kontext nutzen wir `assume*` für das **Überprüfen von *Annahmen*** (im Sinne -von **Vorbedingungen**): Wenn ein `assume*` fehlschlägt, wird der Testfall abgebrochen -bzw. als "ignoriert" gewertet. +Im JUnit-Kontext nutzen wir `assume*` für das **Überprüfen von *Annahmen*** (im +Sinne von **Vorbedingungen**): Wenn ein `assume*` fehlschlägt, wird der Testfall +abgebrochen bzw. als "ignoriert" gewertet. -Dagegen setzen wir `assert*` für das **Überprüfen der *Testergebnisse*** ein, d.h. ein -fehlschlagendes `assert*` lässt den Testfall "rot" werden. +Dagegen setzen wir `assert*` für das **Überprüfen der *Testergebnisse*** ein, d.h. +ein fehlschlagendes `assert*` lässt den Testfall "rot" werden. ::: - -::: notes +:::: notes # Java: "assert"-Keyword -Neben den wohlbekannten `assert*`-Methoden aus JUnit gibt es auch direkt von -Java ein etwas verstecktes `assert`-Keyword, mit dem man Annahmen über Zustände -und Werte explizit ausdrücken kann: +Neben den wohlbekannten `assert*`-Methoden aus JUnit gibt es auch direkt von Java +ein etwas verstecktes `assert`-Keyword, mit dem man Annahmen über Zustände und Werte +explizit ausdrücken kann: -```java +``` java public void foo() { String bar = wuppie(); assert bar != null : "result of wuppie() must not be null"; @@ -216,35 +94,35 @@ Allerdings sind diese JVM-Assertions per Default **deaktiviert**. Man muss sie b Aufruf manuell über die Option `-enableassertions` bzw. `-ea` (Kurzschreibweise) aktivieren (`java -ea main`)! Dies gilt auch beim Start über die IDE oder Gradle ... -:::::: warning -Wichtig: Die Assertions sind per Default deaktiviert und müssen erst manuell aktiviert -werden. Außerdem wird bei Verletzung der Bedingung eine *unchecked exception* (ein Error) -geworfen, der auf einen nicht korrigierbaren Programmzustand hindeutet. +::: warning +Wichtig: Die Assertions sind per Default deaktiviert und müssen erst manuell +aktiviert werden. Außerdem wird bei Verletzung der Bedingung eine *unchecked +exception* (ein Error) geworfen, der auf einen nicht korrigierbaren Programmzustand +hindeutet. 1. Nutzen Sie das Java-`assert` deshalb nicht als Ersatz für das normale Prüfen von - Parametern von `public` Methoden (also Methoden der Schnittstelle, die Ihre Kunden - aufrufen). + Parametern von `public` Methoden (also Methoden der Schnittstelle, die Ihre + Kunden aufrufen). -2. Während der Entwicklungszeit kann das Java-`assert` aber ganz nützlich sein, weil - Sie so interne Annahmen sichtbar und prüfbar machen (vorausgesetzt, Sie haben `-ea` - aktiviert). +2. Während der Entwicklungszeit kann das Java-`assert` aber ganz nützlich sein, + weil Sie so interne Annahmen sichtbar und prüfbar machen (vorausgesetzt, Sie + haben `-ea` aktiviert). - Analog könnte ein Java-`assert` an Stellen eingebaut werden, die eigentlich nicht - erreichbar sein sollten (etwa nach einer Dauerschleife oder in einem nicht erreichbaren - `default`-Zweig in einem `switch`). + Analog könnte ein Java-`assert` an Stellen eingebaut werden, die eigentlich + nicht erreichbar sein sollten (etwa nach einer Dauerschleife oder in einem nicht + erreichbaren `default`-Zweig in einem `switch`). 3. Bitte das Java-`assert` **nie** in einer JUnit-Testmethode statt der "richtigen" JUnit-`assert*` verwenden! 4. Das Java-`assert` ist in einer JUnit-Testmethode **kein** Ersatz für die JUnit-`assume*`-Methoden! -:::::: ::: - +:::: # Setup und Teardown: Testübergreifende Konfiguration -```java +``` java private Studi x; @Before @@ -271,7 +149,6 @@ public void testToString() { **`@AfterClass`** : wird **einmalig** nach allen Tests aufgerufen (`static`!) - ::: notes In JUnit 5 wurden die Namen dieser Annotationen leicht geändert: @@ -283,13 +160,12 @@ In JUnit 5 wurden die Namen dieser Annotationen leicht geändert: | `@AfterClass` | `@AfterAll` | ::: - ::: notes # Beispiel für den Einsatz von `@Before` Annahme: **alle/viele** Testmethoden brauchen **neues** Objekt `x` vom Typ `Studi` -```java +``` java private Studi x; @Before @@ -311,19 +187,17 @@ public void testGetName() { ``` ::: - -::: notes +:::::: notes # Ignorieren von Tests -* Hinzufügen der Annotation `@Ignore` -* Alternativ mit Kommentar: `@Ignore("Erst im nächsten Release")` +- Hinzufügen der Annotation `@Ignore` +- Alternativ mit Kommentar: `@Ignore("Erst im nächsten Release")` \bigskip -:::::: columns +::::: columns ::: {.column width="52%"} - -```java +``` java @Ignore("Warum ignoriert") @Test public void testBsp() { @@ -331,56 +205,51 @@ public void testBsp() { assertTrue(x.isTrue()); } ``` - ::: -::: {.column width="48%"} - -![](images/junitIgnore.png){width=80% web_width=40%} - -::: -:::::: -In JUnit 5 wird statt der Annotation `@Ignore` die Annotation `@Disabled` mit -der selben Bedeutung verwendet. Auch hier lässt sich als Parameter ein String -mit dem Grund für das Ignorieren des Tests hinterlegen. +::: {.column width="48%"} +![](images/junitIgnore.png){width="80%" web_width="40%"} ::: +::::: +In JUnit 5 wird statt der Annotation `@Ignore` die Annotation `@Disabled` mit der +selben Bedeutung verwendet. Auch hier lässt sich als Parameter ein String mit dem +Grund für das Ignorieren des Tests hinterlegen. +:::::: # Vermeidung von Endlosschleifen: Timeout ::: notes -* Testfälle werden nacheinander ausgeführt -* Test mit Endlosschleife würde restliche Tests blockieren -* Erweitern der `@Test`-Annotation mit Parameter "`timeout`": \newline - => `@Test(timeout=2000)` (Zeitangabe in Millisekunden) +- Testfälle werden nacheinander ausgeführt +- Test mit Endlosschleife würde restliche Tests blockieren +- Erweitern der `@Test`-Annotation mit Parameter "`timeout`": `\newline`{=tex} =\> + `@Test(timeout=2000)` (Zeitangabe in Millisekunden) ::: -:::::: columns +::::: columns ::: {.column width="52%"} - \vspace{6mm} -```java +``` java @Test(timeout = 2000) void testTestDauerlaeufer() { while (true) { ; } } ``` - ::: -::: {.column width="44%"} - -![](images/junitIgnore.png){width=80% web_width=40%} +::: {.column width="44%"} +![](images/junitIgnore.png){width="80%" web_width="40%"} ::: -:::::: +::::: ::: notes -In JUnit 5 hat die Annotation `@Test` keinen `timeout`-Parameter mehr. -Als Alternative bietet sich der Einsatz von `org.junit.jupiter.api.Assertions.assertTimeout` -an. Dabei benötigt man allerdings *Lambda-Ausdrücke* (Verweis auf spätere VL): +In JUnit 5 hat die Annotation `@Test` keinen `timeout`-Parameter mehr. Als +Alternative bietet sich der Einsatz von +`org.junit.jupiter.api.Assertions.assertTimeout` an. Dabei benötigt man allerdings +*Lambda-Ausdrücke* (Verweis auf spätere VL): -```java +``` java @Test void testTestDauerlaeufer() { assertTimeout(ofMillis(2000), () -> { @@ -392,14 +261,13 @@ void testTestDauerlaeufer() { (Beispiel von oben mit Hilfe von JUnit 5 formuliert) ::: - # Test von Exceptions: Expected ::: notes Traditionelles Testen von Exceptions mit `try` und `catch`: ::: -```java +``` java @Test public void testExceptTradit() { try { @@ -417,12 +285,12 @@ public void testExceptTradit() { \bigskip ::: notes -Der `expected`-Parameter für die `@Test`-Annotation in JUnit 4 macht -dies deutlich einfacher: `@Test(expected = MyException.class)` -=> Test scheitert, wenn diese Exception **nicht** geworfen wird +Der `expected`-Parameter für die `@Test`-Annotation in JUnit 4 macht dies deutlich +einfacher: `@Test(expected = MyException.class)` =\> Test scheitert, wenn diese +Exception **nicht** geworfen wird ::: -```java +``` java @Test(expected = java.lang.ArithmeticException.class) public void testExceptAnnot() { int i = 0 / 0; @@ -430,11 +298,12 @@ public void testExceptAnnot() { ``` ::: notes -In JUnit 5 hat die Annotation `@Test` keinen `expected`-Parameter mehr. -Als Alternative bietet sich der Einsatz von `org.junit.jupiter.api.Assertions.assertThrows` -an. Dabei benötigt man allerdings *Lambda-Ausdrücke* (Verweis auf spätere VL): +In JUnit 5 hat die Annotation `@Test` keinen `expected`-Parameter mehr. Als +Alternative bietet sich der Einsatz von +`org.junit.jupiter.api.Assertions.assertThrows` an. Dabei benötigt man allerdings +*Lambda-Ausdrücke* (Verweis auf spätere VL): -```java +``` java @Test public void testExceptAnnot() { assertThrows(java.lang.ArithmeticException.class, () -> { @@ -446,17 +315,17 @@ public void testExceptAnnot() { (Beispiel von oben mit Hilfe von JUnit 5 formuliert) ::: - ::: notes # "Given - When - Then"-Mantra -Aus dem [Behavior-driven development](https://en.wikipedia.org/wiki/Behavior-driven_development) -stammt die Strukturierung nach den Punkten "given - when - then" (oft auch als -*"given - when - then"-Mantra* bezeichnet). +Aus dem [Behavior-driven +development](https://en.wikipedia.org/wiki/Behavior-driven_development) stammt die +Strukturierung nach den Punkten "given - when - then" (oft auch als *"given - when - +then"-Mantra* bezeichnet). Betrachten Sie noch einmal die Schnittstelle der Studi-Klasse: -```java +``` java public class Studi { public String getName(); public void setName(String name); @@ -467,7 +336,7 @@ public class Studi { Dafür wurde ein Test geschrieben: -```java +``` java class StudiTest { @Test public void testStudi() { @@ -479,18 +348,17 @@ class StudiTest { } ``` -Dieser Code ist in seiner Absicht nicht sofort verständlich. Es fällt -auf, dass +Dieser Code ist in seiner Absicht nicht sofort verständlich. Es fällt auf, dass 1. am Anfang eine Art Setup des Tests vorgenommen wird und das Testobjekt initialisiert wird ("**given**"). 2. Dann wird die zu untersuchende Aktion ausgeführt ("**when**"), gefolgt von 3. einem Vergleich des tatsächlichen mit dem erwarteten Ergebnis ("**then**"). -Diese gedachte Struktur kann (und sollte!) man mit entsprechenden Kommentaren -auch sichtbar machen: +Diese gedachte Struktur kann (und sollte!) man mit entsprechenden Kommentaren auch +sichtbar machen: -```java +``` java class StudiTest { @Test public void testStudi() { @@ -508,14 +376,14 @@ class StudiTest { ``` In Testframeworks wie [Spock](https://spockframework.org/) oder -[Cucumber](https://cucumber.io/) ist dies sogar bereits in die Sprache (eine -DSL zum Testen) eingebaut! Einen schönen Blog zum Thema finden Sie hier: -[Spock testing framework versus JUnit](https://blog.codepipes.com/testing/spock-vs-junit.html). +[Cucumber](https://cucumber.io/) ist dies sogar bereits in die Sprache (eine DSL zum +Testen) eingebaut! Einen schönen Blog zum Thema finden Sie hier: [Spock testing +framework versus JUnit](https://blog.codepipes.com/testing/spock-vs-junit.html). -Weiterhin könnte (und sollte) man die implizit getroffenen Annahmen über das SUT -für alle sichtbar machen: +Weiterhin könnte (und sollte) man die implizit getroffenen Annahmen über das SUT für +alle sichtbar machen: -```java +``` java class StudiTest { @Test public void testStudi() { @@ -535,14 +403,13 @@ class StudiTest { Der Test würde ohnehin fehlschlagen, wenn ein neues `Studi`-Objekt mit einem anderen Wert für die Credits initialisiert würde. Aber so zeigt das `assume` direkt unsere -(bisher) implizite Annahme sichtbar an, und bei einer Verletzung dieser Annahme würde -der Testfall mit einer entsprechenden Mitteilung nicht ausgeführt. +(bisher) implizite Annahme sichtbar an, und bei einer Verletzung dieser Annahme +würde der Testfall mit einer entsprechenden Mitteilung nicht ausgeführt. -Oft wird noch das "given - when - then"-Mantra auch auf die Methodennamen der Testmethoden -übertragen:^[Naja, ein kläglicher Versuch. Namen sind eines der schwierigen Probleme in -der Informatik.] +Oft wird noch das "given - when - then"-Mantra auch auf die Methodennamen der +Testmethoden übertragen:[^1] -```java +``` java class StudiTest { @Test public void GivenNewStudi_WhenAddingCredits_ThenCreditsCountChangesAccordingly() { @@ -560,15 +427,14 @@ class StudiTest { } ``` -Eine schöne Erklärung finden Sie im Blog -[The subtle Art of Java Test Method Naming](https://jonasg.io/posts/subtle-art-of-java-test-method-naming/). +Eine schöne Erklärung finden Sie im Blog [The subtle Art of Java Test Method +Naming](https://jonasg.io/posts/subtle-art-of-java-test-method-naming/). -... Und nun könnte man sich fragen, warum man das Erhöhen von Credits nur für ein *neues* -`Studi`-Objekt testet und nicht auch für andere Zustände des `Studi`-Objekts? ... -=> Parametrisierte Tests! +... Und nun könnte man sich fragen, warum man das Erhöhen von Credits nur für ein +*neues* `Studi`-Objekt testet und nicht auch für andere Zustände des +`Studi`-Objekts? ... =\> Parametrisierte Tests! ::: - # Parametrisierte Tests ::: notes @@ -576,7 +442,7 @@ Manchmal möchte man den selben Testfall mehrfach mit anderen Werten (Parametern durchführen. ::: -```java +``` java class Sum { public static int sum(int i, int j) { return i + j; @@ -594,44 +460,42 @@ class SumTest { ``` ::: notes -Prinzipiell könnte man dafür entweder in einem Testfall eine Schleife schreiben, -die über die verschiedenen Parameter iteriert. In der Schleife würde dann -jeweils der Aufruf der zu testenden Methode und das gewünschte Assert passieren. -Alternativ könnte man den Testfall entsprechend oft duplizieren mit jeweils den -gewünschten Werten. - -Beide Vorgehensweisen haben Probleme: Im ersten Fall würde die Schleife bei -einem Fehler oder unerwarteten Ergebnis abbrechen, ohne dass die restlichen -Tests (Werte) noch durchgeführt würden. Im zweiten Fall bekommt man eine -unnötig große Anzahl an Testmethoden, die bis auf die jeweiligen Werte identisch -sind (Code-Duplizierung). +Prinzipiell könnte man dafür entweder in einem Testfall eine Schleife schreiben, die +über die verschiedenen Parameter iteriert. In der Schleife würde dann jeweils der +Aufruf der zu testenden Methode und das gewünschte Assert passieren. Alternativ +könnte man den Testfall entsprechend oft duplizieren mit jeweils den gewünschten +Werten. + +Beide Vorgehensweisen haben Probleme: Im ersten Fall würde die Schleife bei einem +Fehler oder unerwarteten Ergebnis abbrechen, ohne dass die restlichen Tests (Werte) +noch durchgeführt würden. Im zweiten Fall bekommt man eine unnötig große Anzahl an +Testmethoden, die bis auf die jeweiligen Werte identisch sind (Code-Duplizierung). ::: - ::: notes ## Parametrisierte Tests mit JUnit 4 -JUnit 4 bietet für dieses Problem sogenannte "parametrisierte Tests" an. Dafür -muss eine Testklasse in JUnit 4 folgende Bedingungen erfüllen: +JUnit 4 bietet für dieses Problem sogenannte "parametrisierte Tests" an. Dafür muss +eine Testklasse in JUnit 4 folgende Bedingungen erfüllen: 1. Die Testklasse wird mit der Annotation `@RunWith(Parameterized.class)` ausgezeichnet. 2. Es muss eine öffentliche statische Methode geben mit der Annotation - `@Parameters`. Diese Methode liefert eine Collection zurück, wobei jedes - Element dieser Collection ein Array mit den Parametern für einen - Durchlauf der Testmethoden ist. + `@Parameters`. Diese Methode liefert eine Collection zurück, wobei jedes Element + dieser Collection ein Array mit den Parametern für einen Durchlauf der + Testmethoden ist. 3. Die Parameter müssen gesetzt werden. Dafür gibt es zwei Varianten: - * (A) Für jeden Parameter gibt es ein öffentliches Attribut. Diese Attribute - müssen mit der Annotation `@Parameter` markiert sein und können in den - Testmethoden normal genutzt werden. JUnit sorgt dafür, dass für jeden - Eintrag in der Collection aus der statischen `@Parameters`-Methode - diese Felder gesetzt werden und die Testmethoden aufgerufen werden. - * (B) Alternativ gibt es einen Konstruktor, der diese Werte setzt. Die Anzahl - der Parameter im Konstruktor muss dabei exakt der Anzahl (und - Reihenfolge) der Werte in jedem Array in der von der statischen - `@Parameters`-Methode gelieferten Collection entsprechen. Der - Konstruktor wird für jeden Parametersatz einmal aufgerufen und die - Testmethoden einmal durchgeführt. + - (A) Für jeden Parameter gibt es ein öffentliches Attribut. Diese Attribute + müssen mit der Annotation `@Parameter` markiert sein und können in den + Testmethoden normal genutzt werden. JUnit sorgt dafür, dass für jeden + Eintrag in der Collection aus der statischen `@Parameters`-Methode diese + Felder gesetzt werden und die Testmethoden aufgerufen werden. + - (B) Alternativ gibt es einen Konstruktor, der diese Werte setzt. Die Anzahl + der Parameter im Konstruktor muss dabei exakt der Anzahl (und + Reihenfolge) der Werte in jedem Array in der von der statischen + `@Parameters`-Methode gelieferten Collection entsprechen. Der + Konstruktor wird für jeden Parametersatz einmal aufgerufen und die + Testmethoden einmal durchgeführt. Letztlich wird damit das Kreuzprodukt aus Testmethoden und Testdaten durchgeführt. ::: @@ -644,7 +508,7 @@ Letztlich wird damit das Kreuzprodukt aus Testmethoden und Testdaten durchgefüh ### (A) Parametrisierte Tests: Konstruktor (JUnit 4) ::: -```java +``` java @RunWith(Parameterized.class) public class SumTestConstructor { private final int s1; @@ -673,7 +537,7 @@ public class SumTestConstructor { ### (B) Parametrisierte Tests: Parameter (JUnit 4) ::: -```java +``` java @RunWith(Parameterized.class) public class SumTestParameters { @@ -693,17 +557,22 @@ public class SumTestParameters { } ``` -[Beispiel: junit4.SumTestConstructor, junit4.SumTestParameters]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/quality/src/junit4/"} - +[Beispiel: junit4.SumTestConstructor, junit4.SumTestParameters]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/quality/src/junit4/"} ::: notes ## Parametrisierte Tests mit JUnit 5 -In JUnit 5 werden [parametrisierte Tests] mit der Annotation [`@ParameterizedTest`] +In JUnit 5 werden [parametrisierte +Tests](https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests) +mit der Annotation +[`@ParameterizedTest`](https://junit.org/junit5/docs/current/api/org.junit.jupiter.params/org/junit/jupiter/params/ParameterizedTest.html) gekennzeichnet (statt mit `@Test`). -Mit Hilfe von [`@ValueSource`] kann man ein einfaches Array von Werten (Strings oder primitive -Datentypen) angeben, mit denen der Test ausgeführt wird. Dazu bekommt die Testmethode einen +Mit Hilfe von +[`@ValueSource`](https://junit.org/junit5/docs/current/api/org.junit.jupiter.params/org/junit/jupiter/params/provider/ValueSource.html) +kann man ein einfaches Array von Werten (Strings oder primitive Datentypen) angeben, +mit denen der Test ausgeführt wird. Dazu bekommt die Testmethode einen entsprechenden passenden Parameter: ``` java @@ -714,11 +583,16 @@ void testWuppie(String candidate) { } ``` -Alternativ lassen sich als Parameterquelle u.a. Aufzählungen ([`@EnumSource`]) oder Methoden -([`@MethodSource`]) oder auch Komma-separierte Daten ([`@CsvSource`]) angeben. +Alternativ lassen sich als Parameterquelle u.a. Aufzählungen +([`@EnumSource`](https://junit.org/junit5/docs/current/api/org.junit.jupiter.params/org/junit/jupiter/params/provider/EnumSource.html)) +oder Methoden +([`@MethodSource`](https://junit.org/junit5/docs/current/api/org.junit.jupiter.params/org/junit/jupiter/params/provider/MethodSource.html)) +oder auch Komma-separierte Daten +([`@CsvSource`](https://junit.org/junit5/docs/current/api/org.junit.jupiter.params/org/junit/jupiter/params/provider/CsvSource.html)) +angeben. -Das obige Beispiel aus JUnit 4.x könnte mit Hilfe von `@CsvSource` so in JUnit 5.x umgesetzt -werden: +Das obige Beispiel aus JUnit 4.x könnte mit Hilfe von `@CsvSource` so in JUnit 5.x +umgesetzt werden: ``` java public class SumTest { @@ -736,25 +610,17 @@ public class SumTest { } ``` -[Beispiel: junit5.TestValueSource, junit5.TestMethodSource]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/quality/src/junit5/"} - - [parametrisierte Tests]: https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests - [`@ParameterizedTest`]: https://junit.org/junit5/docs/current/api/org.junit.jupiter.params/org/junit/jupiter/params/ParameterizedTest.html - [`@ValueSource`]: https://junit.org/junit5/docs/current/api/org.junit.jupiter.params/org/junit/jupiter/params/provider/ValueSource.html - [`@EnumSource`]: https://junit.org/junit5/docs/current/api/org.junit.jupiter.params/org/junit/jupiter/params/provider/EnumSource.html - [`@MethodSource`]: https://junit.org/junit5/docs/current/api/org.junit.jupiter.params/org/junit/jupiter/params/provider/MethodSource.html - [`@CsvSource`]: https://junit.org/junit5/docs/current/api/org.junit.jupiter.params/org/junit/jupiter/params/provider/CsvSource.html - +[Beispiel: junit5.TestValueSource, junit5.TestMethodSource]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/quality/src/junit5/"} ::: - # Testsuiten: Tests gemeinsam ausführen (JUnit 4) ::: notes Eclipse: `New > Other > Java > JUnit > JUnit Test Suite` ::: -```java +``` java import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; @@ -776,10 +642,12 @@ public class MyTestSuite { In JUnit 5 gibt es zwei Möglichkeiten, Testsuiten zu erstellen: -* `@SelectPackages`: Angabe der Packages, die für die Testsuite zusammengefasst werden sollen -* `@SelectClasses`: Angabe der Klassen, die für die Testsuite zusammengefasst werden sollen +- `@SelectPackages`: Angabe der Packages, die für die Testsuite zusammengefasst + werden sollen +- `@SelectClasses`: Angabe der Klassen, die für die Testsuite zusammengefasst + werden sollen -```java +``` java @RunWith(JUnitPlatform.class) @SelectClasses({StudiTest5.class, WuppieTest5.class}) public class MyTestSuite5 { @@ -787,103 +655,235 @@ public class MyTestSuite5 { } ``` -Zusätzlich kann man beispielsweise mit `@IncludeTags` oder `@ExcludeTags` Testmethoden mit bestimmten Tags -einbinden oder ausschließen. Beispiel: Schließe alle Tests mit Tag "develop" aus: `@ExcludeTags("develop")`. -Dabei wird an den Testmethoden zusätzlich das Tag `@Tag` verwendet, etwas `@Tag("develop")`. - -**Achtung**: Laut der offiziellen Dokumentation -[(Abschnitt "4.4.4. Test Suite")](https://junit.org/junit5/docs/current/user-guide/#running-tests-junit-platform-runner-test-suite) -gilt zumindest bei der Selection über `@SelectPackages` der Zwang zu einer Namenskonvention: -Es werden dabei nur Klassen gefunden, deren Name mit `Test` beginnt oder endet! -Weiterhin werden Testsuites mit der Annotation `@RunWith(JUnitPlatform.class)` **nicht** -auf der "JUnit 5"-Plattform ausgeführt, sondern mit der JUnit 4-Infrastuktur! +Zusätzlich kann man beispielsweise mit `@IncludeTags` oder `@ExcludeTags` +Testmethoden mit bestimmten Tags einbinden oder ausschließen. Beispiel: Schließe +alle Tests mit Tag "develop" aus: `@ExcludeTags("develop")`. Dabei wird an den +Testmethoden zusätzlich das Tag `@Tag` verwendet, etwas `@Tag("develop")`. + +**Achtung**: Laut der offiziellen Dokumentation [(Abschnitt "4.4.4. Test +Suite")](https://junit.org/junit5/docs/current/user-guide/#running-tests-junit-platform-runner-test-suite) +gilt zumindest bei der Selection über `@SelectPackages` der Zwang zu einer +Namenskonvention: Es werden dabei nur Klassen gefunden, deren Name mit `Test` +beginnt oder endet! Weiterhin werden Testsuites mit der Annotation +`@RunWith(JUnitPlatform.class)` **nicht** auf der "JUnit 5"-Plattform ausgeführt, +sondern mit der JUnit 4-Infrastuktur! ::: - ::: notes # Best Practices -1. Eine Testmethode behandelt exakt eine Idee/ein Szenario (einen Testfall). Das bedeutet - auch, dass man in der Regel nur ein bis wenige `assert*` pro Testmethode benutzt. +1. Eine Testmethode behandelt exakt eine Idee/ein Szenario (einen Testfall). Das + bedeutet auch, dass man in der Regel nur ein bis wenige `assert*` pro + Testmethode benutzt. (Wenn man verschiedene Ideen in eine Testmethode kombiniert, wird der Testfall unübersichtlicher und auch auch schwerer zu warten. - Außerdem können so leichter versteckte Fehler auftreten: Das erste oder zweite oder dritte - `assert*` schlägt fehl - und alle dahinter kommenden `assert*` werden nicht mehr - ausgewertet!) + Außerdem können so leichter versteckte Fehler auftreten: Das erste oder zweite + oder dritte `assert*` schlägt fehl - und alle dahinter kommenden `assert*` + werden nicht mehr ausgewertet!) -2. Wenn die selbe Testidee mehrfach wiederholt wird, sollte man diese Tests zu einem - parametrisierten Test zusammenfassen. +2. Wenn die selbe Testidee mehrfach wiederholt wird, sollte man diese Tests zu + einem parametrisierten Test zusammenfassen. - (Das erhöht die Lesbarkeit drastisch - und man läuft auch nicht in das Problem der - Benennung der Testmethoden.) + (Das erhöht die Lesbarkeit drastisch - und man läuft auch nicht in das Problem + der Benennung der Testmethoden.) -3. Es wird nur das Verhalten der öffentlichen Schnittstelle getestet, nicht die inneren - Strukturen einer Klasse oder Methode. +3. Es wird nur das Verhalten der öffentlichen Schnittstelle getestet, nicht die + inneren Strukturen einer Klasse oder Methode. (Es ist verlockend, auch private Methoden zu testen und in den Tests auch die Datenstrukturen o.ä. im Blick zu behalten und zu testen. Das führt aber zu sehr - "zerbrechlichen" (*brittle*) Tests: Sobald sich etwas an der inneren Struktur ändert, ohne - dass sich das von außen beobachtbare Verhalten ändert und also die Klasse/Methode immer - noch ordnungsgemäß funktioniert, gehen all diese "internen" Tests kaputt. Nicht ohne - Grund wird in der objektorientierten Programmierung mit Kapselung (Klassen, Methoden, ...) - gearbeitet.) + "zerbrechlichen" (*brittle*) Tests: Sobald sich etwas an der inneren Struktur + ändert, ohne dass sich das von außen beobachtbare Verhalten ändert und also die + Klasse/Methode immer noch ordnungsgemäß funktioniert, gehen all diese "internen" + Tests kaputt. Nicht ohne Grund wird in der objektorientierten Programmierung mit + Kapselung (Klassen, Methoden, ...) gearbeitet.) 4. Von Setup- und Teardown-Methoden sollte eher sparsam Gebrauch gemacht werden. - (Normalerweise folgen wir in der objektorientierten Programmierung dem DRY-Prinzip ([Don't - repeat yourself](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself)). Entsprechend - liegt es nahe, häufig benötigte Elemente in einer Setup-Methode zentral zu initialisieren - und ggf. in einer Teardown-Methode wieder freizugeben. - - Das führt aber speziell bei Unit-Tests dazu, dass die einzelnen Testmethoden schwerer - lesbar werden: Sie hängen von einer gemeinsamen, zentralen Konfiguration ab, die man - üblicherweise nicht gleichzeitig mit dem Code der Testmethode sehen kann (begrenzter Platz - auf der Bildschirmseite). - - Wenn nun in einem oder vielleicht mehreren Testfällen der Wunsch nach einer leicht anderen - Konfiguration auftaucht, muss man die gemeinsame Konfiguration entsprechend anpassen bzw. - erweitern. Dabei muss man dann aber *alle* anderen Testmethoden mit bedenken, die ja - ebenfalls von dieser Konfiguration abhängen! Das führt in der Praxis dann häufig dazu, - dass die gemeinsame Konfiguration sehr schnell sehr groß und verschachtelt und - entsprechend unübersichtlich wird. - - Jede Änderung an dieser Konfiguration kann leicht einen oder mehrere Testfälle kaputt - machen (man hat ja i.d.R. nie alle Testfälle gleichzeitig im Blick), weshalb man hier - unbedingt mit passenden `assume*` arbeiten muss - aber dann kann man eigentlich auch - stattdessen die Konfiguration direkt passend für den jeweiligen Testfall in der jeweiligen - Testmethode erledigen!) - -5. Wie immer sollten auch die Namen der Testmethoden klar über ihren Zweck Auskunft geben. - - (Da Tests oft auch als "ausführbare Dokumentation" betrachtet werden, ist eine sinnvolle Benamung - besonders wichtig. Oft werden hier deshalb Ausnahmen von den üblichen Java-Konventionen erlaubt. - Man findet häufig das aus dem [Behavior-driven development](https://en.wikipedia.org/wiki/Behavior-driven_development) - bekannte "given - when - then"-Mantra. Siehe auch - [The subtle Art of Java Test Method Naming](https://jonasg.io/posts/subtle-art-of-java-test-method-naming/) - und auch [Spock testing framework versus JUnit](https://blog.codepipes.com/testing/spock-vs-junit.html). - - Der Präfix "test" für Testmethoden wird seit JUnit 4.x nicht mehr benötigt, aber dennoch - ist es in vielen Projekten Praxis, diesen Präfix beizubehalten - damit kann man in der - Package-Ansicht in der IDE leichter zwischen den "normalen" und den Testmethoden unterscheiden. - Das ist analog zum Suffix "Test" für die Klassennamen der Testklassen ...) + (Normalerweise folgen wir in der objektorientierten Programmierung dem + DRY-Prinzip ([Don't repeat + yourself](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself)). Entsprechend + liegt es nahe, häufig benötigte Elemente in einer Setup-Methode zentral zu + initialisieren und ggf. in einer Teardown-Methode wieder freizugeben. + + Das führt aber speziell bei Unit-Tests dazu, dass die einzelnen Testmethoden + schwerer lesbar werden: Sie hängen von einer gemeinsamen, zentralen + Konfiguration ab, die man üblicherweise nicht gleichzeitig mit dem Code der + Testmethode sehen kann (begrenzter Platz auf der Bildschirmseite). + + Wenn nun in einem oder vielleicht mehreren Testfällen der Wunsch nach einer + leicht anderen Konfiguration auftaucht, muss man die gemeinsame Konfiguration + entsprechend anpassen bzw. erweitern. Dabei muss man dann aber *alle* anderen + Testmethoden mit bedenken, die ja ebenfalls von dieser Konfiguration abhängen! + Das führt in der Praxis dann häufig dazu, dass die gemeinsame Konfiguration sehr + schnell sehr groß und verschachtelt und entsprechend unübersichtlich wird. + + Jede Änderung an dieser Konfiguration kann leicht einen oder mehrere Testfälle + kaputt machen (man hat ja i.d.R. nie alle Testfälle gleichzeitig im Blick), + weshalb man hier unbedingt mit passenden `assume*` arbeiten muss - aber dann + kann man eigentlich auch stattdessen die Konfiguration direkt passend für den + jeweiligen Testfall in der jeweiligen Testmethode erledigen!) + +5. Wie immer sollten auch die Namen der Testmethoden klar über ihren Zweck Auskunft + geben. + + (Da Tests oft auch als "ausführbare Dokumentation" betrachtet werden, ist eine + sinnvolle Benamung besonders wichtig. Oft werden hier deshalb Ausnahmen von den + üblichen Java-Konventionen erlaubt. Man findet häufig das aus dem + [Behavior-driven + development](https://en.wikipedia.org/wiki/Behavior-driven_development) bekannte + "given - when - then"-Mantra. Siehe auch [The subtle Art of Java Test Method + Naming](https://jonasg.io/posts/subtle-art-of-java-test-method-naming/) und auch + [Spock testing framework versus + JUnit](https://blog.codepipes.com/testing/spock-vs-junit.html). + + Der Präfix "test" für Testmethoden wird seit JUnit 4.x nicht mehr benötigt, aber + dennoch ist es in vielen Projekten Praxis, diesen Präfix beizubehalten - damit + kann man in der Package-Ansicht in der IDE leichter zwischen den "normalen" und + den Testmethoden unterscheiden. Das ist analog zum Suffix "Test" für die + Klassennamen der Testklassen ...) Diese Erfahrungen werden ausführlich in [@SWEGoogle, pp. 231-256] diskutiert. Eine lesenswerte Diskussion von "Anti-Pattern" beim Testen finden Sie im Blog -[Software Testing Anti-patterns](https://blog.codepipes.com/testing/software-testing-antipatterns.html). +[Software Testing +Anti-patterns](https://blog.codepipes.com/testing/software-testing-antipatterns.html). ::: - # Wrap-Up JUnit als Framework für (Unit-) Tests; hier JUnit 4 (mit Ausblick auf JUnit 5) -* Testmethoden mit Annotation `@Test` -* `assert` (Testergebnis) vs. `assume` (Testvorbedingung) -* Aufbau der Testumgebung `@Before` -* Abbau der Testumgebung `@After` -* Steuern von Tests mit `@Ignore` oder `@Test(timout=XXX)` -* Exceptions einfordern mit `@Test(expected=package.Exception.class)` -* Tests zusammenfassen zu Testsuiten +- Testmethoden mit Annotation `@Test` +- `assert` (Testergebnis) vs. `assume` (Testvorbedingung) +- Aufbau der Testumgebung `@Before` +- Abbau der Testumgebung `@After` +- Steuern von Tests mit `@Ignore` oder `@Test(timout=XXX)` +- Exceptions einfordern mit `@Test(expected=package.Exception.class)` +- Tests zusammenfassen zu Testsuiten + +::: readings +- @vogellaJUnit +- @junit4 +- @Kleuker2019 +- @Osherove2014 +- @Spillner2012 +- @fernunihagenJunit +::: + +::: outcomes +- k3: Ich kann Testergebnisse prüfen +- k2: Ich kenne den Unterschied zwischen `assert` und `assume` +- k3: Ich kann vor/nach jedem Test bestimmten Code ausführen +- k2: Ich habe verstanden, warum `@Before` und `@After` sparsam einzusetzen sind +- k3: Ich kann die Ausführung von Tests steuern, beispielsweise Tests ignorieren + oder mit zeitlicher Begrenzung ausführen +- k3: Ich kann das Auftreten von Exceptions prüfen +- k3: Ich kann Tests zu Testsuiten zusammenfassen +::: + +::: challenges +**Setup und Teardown** + +Sie haben in den Challenges in "Intro SW-Test" erste JUnit-Tests für die Klasse +`MyList` implementiert. + +Wie müssten Sie Ihre JUnit-Tests anpassen, wenn Sie im obigen Szenario Setup- und +Teardown-Methoden einsetzen würden? + + + +**Testmethoden** + +Betrachten Sie den folgenden Code. Was fällt Ihnen auf? + +``` java +public class Studi { + public int getCredits(); + public void addToCredits(int credits); + + @Test + public void testStudi() { + Studi s = new Studi(); + s.addToCredits(2); + assertEquals(2, s.getCredits()); + } +} +``` + +**Parametrisierte Tests** + +Betrachten Sie die folgende einfache Klasse `MyMath`: + +``` java +public class MyMath { + public static String add(String s, int c) { + return s.repeat(c); + } +} +``` + +Beim Testen der Methode `MyMath#add` fällt auf, dass man hier immer wieder den +selben Testfall mit lediglich anderen Werten ausführt - ein Fall für parametrisierte +Tests. + +Schreiben Sie mit Hilfe von JUnit (4.x oder 5.x) einige parametrisierte Unit-Tests +für die Methode `MyMath#add`. + + +::: + +[^1]: Naja, ein kläglicher Versuch. Namen sind eines der schwierigen Probleme in der + Informatik. diff --git a/lecture/quality/logging.md b/lecture/quality/logging.md index 049031586..403303df5 100644 --- a/lecture/quality/logging.md +++ b/lecture/quality/logging.md @@ -1,143 +1,61 @@ --- -title: "Logging" -author: "Carsten Gips (HSBI)" -readings: - - "@JDK-Doc [Kap. 8]" -tldr: | - Im Paket `java.util.logging` findet sich eine einfache Logging-API. - - Über die Methode `getLogger()` der Klasse `Logger` (_Factory-Method-Pattern_) - kann ein (neuer) Logger erzeugt werden, dabei wird über den String-Parameter - eine Logger-Hierarchie aufgebaut analog zu den Java-Package-Strukturen. Der - oberste Logger (der "Root-Logger") hat den leeren Namen. - - Jeder Logger kann mit einem Log-Level (Klasse `Level`) eingestellt werden; - Log-Meldungen unterhalb des eingestellten Levels werden verworfen. - - Vom Logger nicht verworfene Log-Meldungen werden an den bzw. die Handler des - Loggers und (per Default) an den Eltern-Logger weiter gereicht. Die Handler - haben ebenfalls ein einstellbares Log-Level und verwerfen alle Nachrichten - unterhalb der eingestellten Schwelle. Zur tatsächlichen Ausgabe gibt man einem - Handler noch einen Formatter mit. Defaultmäßig hat nur der Root-Logger einen - Handler. - - Der Root-Logger (leerer String als Name) hat als Default-Level (wie auch sein - Console-Handler) "`Info`" eingestellt. - - Nachrichten, die durch Weiterleitung nach oben empfangen wurden, werden nicht - am Log-Level des empfangenden Loggers gemessen, sondern akzeptiert und an die - Handler des Loggers und (sofern nicht deaktiviert) an den Elternlogger weitergereicht. -outcomes: - - k3: "Ich kann die Java Logging API im Paket `java.util.logging` aktiv einsetzen" - - k3: "Ich kann eigene Handler und Formatter schreiben" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106228&client_id=FH-Bielefeld" -# name: "Quiz Logging (ILIAS)" -youtube: - - link: "https://youtu.be/_jYWJzr1rkA" - name: "VL Logging" - - link: "https://youtu.be/fWSc5A_CPL8" - name: "Demo Logging (Überblick)" - - link: "https://youtu.be/0UUVQCVYNHo" - name: "Demo Log-Level" - - link: "https://youtu.be/dYOYA99EfrY" - name: "Demo Logging: Handler und Formatter" - - link: "https://youtu.be/19Bki4IglWQ" - name: "Demo Weiterleitung an den Elternlogger" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/a91451640b7833daed3f6fb212fff9490ef6b8885783cc0297603a418055f1a8c2ff7b51f3cb9fb2c4344132eb95bef5af55201f8958f24d767dbd075120bce2" - name: "VL Logging" -challenges: | - **Logger-Konfiguration** - - Betrachten Sie den folgenden Java-Code: - - ```java - import java.util.logging.*; - - public class Logging { - public static void main(String... args) { - Logger l = Logger.getLogger("Logging"); - l.setLevel(Level.FINE); - - ConsoleHandler myHandler = new ConsoleHandler(); - myHandler.setFormatter(new SimpleFormatter() { - public String format(LogRecord record) { - return "WUPPIE\n"; - } - }); - l.addHandler(myHandler); - - l.info("A"); - l.fine("B"); - l.finer("C"); - l.finest("D"); - l.severe("E"); - } - } - ``` - - Welche Ausgaben entstehen durch den obigen Code? Erklären Sie, welche der Logger-Aufrufe zu einer Ausgabe - führen und wieso und wie diese Ausgaben zustande kommen bzw. es keine Ausgabe bei einem Logger-Aufruf gibt. - Gehen Sie dabei auf jeden der fünf Aufrufe ein. - - +author: Carsten Gips (HSBI) +title: Logging +--- +::: tldr +Im Paket `java.util.logging` findet sich eine einfache Logging-API. - **Analyse eines Live-Beispiels aus dem Dungeon** +Über die Methode `getLogger()` der Klasse `Logger` (*Factory-Method-Pattern*) kann +ein (neuer) Logger erzeugt werden, dabei wird über den String-Parameter eine +Logger-Hierarchie aufgebaut analog zu den Java-Package-Strukturen. Der oberste +Logger (der "Root-Logger") hat den leeren Namen. - Analysieren Sie die Konfiguration des Loggers im Dungeon-Projekt: - [Dungeon-CampusMinden/Dungeon: core/utils/logging/LoggerConfig.java](https://github.com/Dungeon-CampusMinden/Dungeon/blob/master/game/src/core/utils/logging/LoggerConfig.java). +Jeder Logger kann mit einem Log-Level (Klasse `Level`) eingestellt werden; +Log-Meldungen unterhalb des eingestellten Levels werden verworfen. - ---- +Nachrichten, die durch Weiterleitung nach oben empfangen wurden, werden nicht am +Log-Level des empfangenden Loggers gemessen, sondern akzeptiert und an die Handler +des Loggers und (sofern nicht deaktiviert) an den Elternlogger weitergereicht. +::: +::: youtube +- [VL Logging](https://youtu.be/_jYWJzr1rkA) +- [Demo Logging (Überblick)](https://youtu.be/fWSc5A_CPL8) +- [Demo Log-Level](https://youtu.be/0UUVQCVYNHo) +- [Demo Logging: Handler und Formatter](https://youtu.be/dYOYA99EfrY) +- [Demo Weiterleitung an den Elternlogger](https://youtu.be/19Bki4IglWQ) +::: # Wie prüfen Sie die Werte von Variablen/Objekten? 1. Debugging - * Beeinflusst Code nicht - * Kann schnell komplex und umständlich werden - * Sitzung transient - nicht wiederholbar + - Beeinflusst Code nicht + - Kann schnell komplex und umständlich werden + - Sitzung transient - nicht wiederholbar \bigskip 2. "Poor-man's-debugging" (Ausgaben mit `System.out.println`) - * Müssen irgendwann entfernt werden - * Ausgabe nur auf einem Kanal (Konsole) - * Keine Filterung nach Problemgrad - keine Unterscheidung - zwischen Warnungen, einfachen Informationen, ... + - Müssen irgendwann entfernt werden + - Ausgabe nur auf einem Kanal (Konsole) + - Keine Filterung nach Problemgrad - keine Unterscheidung zwischen Warnungen, + einfachen Informationen, ... \bigskip 3. **Logging** - * Verschiedene (Java-) Frameworks: \newline - `java.util.logging` (JDK), *log4j* (Apache), *SLF4J*, *Logback*, ... - + - Verschiedene (Java-) Frameworks: `\newline`{=tex} `java.util.logging` (JDK), + *log4j* (Apache), *SLF4J*, *Logback*, ... # Java Logging API - Überblick @@ -148,58 +66,59 @@ Paket `java.util.logging` ![](images/logging.png){width="80%" web_width="65%"} ::: notes -Eine Applikation kann verschiedene Logger instanziieren. Die Logger bauen -per Namenskonvention hierarchisch aufeinander auf. Jeder Logger kann selbst -mehrere Handler haben, die eine Log-Nachricht letztlich auf eine bestimmte -Art und Weise an die Außenwelt weitergeben. +Eine Applikation kann verschiedene Logger instanziieren. Die Logger bauen per +Namenskonvention hierarchisch aufeinander auf. Jeder Logger kann selbst mehrere +Handler haben, die eine Log-Nachricht letztlich auf eine bestimmte Art und Weise an +die Außenwelt weitergeben. -Log-Meldungen werden einem Level zugeordnet. Jeder Logger und Handler hat -ein Mindest-Level eingestellt, d.h. Nachrichten mit einem kleineren Level -werden verworfen. +Log-Meldungen werden einem Level zugeordnet. Jeder Logger und Handler hat ein +Mindest-Level eingestellt, d.h. Nachrichten mit einem kleineren Level werden +verworfen. -Zusätzlich gibt es noch Filter, mit denen man Nachrichten (zusätzlich zum -Log-Level) nach weiteren Kriterien filtern kann. +Zusätzlich gibt es noch Filter, mit denen man Nachrichten (zusätzlich zum Log-Level) +nach weiteren Kriterien filtern kann. ::: -[Konsole: logging.LoggingDemo]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/quality/src/logging/LoggingDemo.java"} - +[Konsole: logging.LoggingDemo]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/quality/src/logging/LoggingDemo.java"} # Erzeugen neuer Logger -```java +``` java import java.util.logging.Logger; Logger l = Logger.getLogger(MyClass.class.getName()); ``` -* **Factory-Methode** der Klasse `java.util.logging.Logger` +- **Factory-Methode** der Klasse `java.util.logging.Logger` - ```java + ``` java public static Logger getLogger(String name); ``` - => Methode liefert bereits **vorhandenen Logger** mit diesem Namen [(sonst neuen Logger)]{.notes} + =\> Methode liefert bereits **vorhandenen Logger** mit diesem Namen [(sonst + neuen Logger)]{.notes} -* **Best Practice**: \newline - Nutzung des voll-qualifizierten Klassennamen: `MyClass.class.getName()` - * Leicht zu implementieren - * Leicht zu erklären - * Spiegelt modulares Design - * Ausgaben enthalten automatisch Hinweis auf Herkunft (Lokalität) der Meldung - * **Alternativen**: Funktionale Namen wie "XML", "DB", "Security" +- **Best Practice**: `\newline`{=tex} Nutzung des voll-qualifizierten + Klassennamen: `MyClass.class.getName()` + - Leicht zu implementieren + - Leicht zu erklären + - Spiegelt modulares Design + - Ausgaben enthalten automatisch Hinweis auf Herkunft (Lokalität) der Meldung + - **Alternativen**: Funktionale Namen wie "XML", "DB", "Security" # Ausgabe von Logmeldungen -```java +``` java public void log(Level level, String msg); ``` \bigskip \bigskip -* Diverse Convenience-Methoden (Auswahl): +- Diverse Convenience-Methoden (Auswahl): - ```java + ``` java public void warning(String msg) public void info(String msg) public void entering(String srcClass, String srcMethod) @@ -208,37 +127,36 @@ public void log(Level level, String msg); \bigskip -* Beispiel +- Beispiel - ```java + ``` java import java.util.logging.Logger; Logger l = Logger.getLogger(MyClass.class.getName()); l.info("Hello World :-)"); ``` - # Wichtigkeit von Logmeldungen: Stufen -* `java.util.logger.Level` definiert 7 Stufen: - * `SEVERE`, `WARNING`, `INFO`, `CONFIG`, `FINE`, `FINER`, `FINEST` \newline - (von höchster zu niedrigster Prio) - * Zusätzlich `ALL` und `OFF` +- `java.util.logger.Level` definiert 7 Stufen: + - `SEVERE`, `WARNING`, `INFO`, `CONFIG`, `FINE`, `FINER`, `FINEST` + `\newline`{=tex} (von höchster zu niedrigster Prio) + - Zusätzlich `ALL` und `OFF` \bigskip -* Nutzung der Log-Level: - * Logger hat Log-Level: Meldungen mit kleinerem Level werden verworfen - * Prüfung mit `public boolean isLoggable(Level)` - * Setzen mit `public void setLevel(Level)` +- Nutzung der Log-Level: + - Logger hat Log-Level: Meldungen mit kleinerem Level werden verworfen + - Prüfung mit `public boolean isLoggable(Level)` + - Setzen mit `public void setLevel(Level)` -[Konsole: logging.LoggingLevel]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/quality/src/logging/LoggingLevel.java"} +[Konsole: logging.LoggingLevel]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/quality/src/logging/LoggingLevel.java"} ::: notes -=> Warum wird im Beispiel nach `log.setLevel(Level.ALL);` trotzdem nur -ab `INFO` geloggt? Wer erzeugt eigentlich die Ausgaben?! +=\> Warum wird im Beispiel nach `log.setLevel(Level.ALL);` trotzdem nur ab `INFO` +geloggt? Wer erzeugt eigentlich die Ausgaben?! ::: - # Jemand muss die Arbeit machen ... \bigskip @@ -249,59 +167,141 @@ ab `INFO` geloggt? Wer erzeugt eigentlich die Ausgaben?! \bigskip -* Pro Logger **mehrere** Handler möglich - * Logger übergibt nicht verworfene Nachrichten an Handler - * Handler haben selbst ein Log-Level (analog zum Logger) - * Handler verarbeiten die Nachrichten, wenn Level ausreichend +- Pro Logger **mehrere** Handler möglich + - Logger übergibt nicht verworfene Nachrichten an Handler + - Handler haben selbst ein Log-Level (analog zum Logger) + - Handler verarbeiten die Nachrichten, wenn Level ausreichend \smallskip -* Standard-Handler: `StreamHandler`, `ConsoleHandler`, `FileHandler` +- Standard-Handler: `StreamHandler`, `ConsoleHandler`, `FileHandler` \smallskip -* Handler nutzen zur Formatierung der Ausgabe einen `Formatter` -* Standard-Formatter: `SimpleFormatter` und `XMLFormatter` +- Handler nutzen zur Formatierung der Ausgabe einen `Formatter` +- Standard-Formatter: `SimpleFormatter` und `XMLFormatter` -[Konsole: logging.LoggingHandler]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/quality/src/logging/LoggingHandler.java"} +[Konsole: logging.LoggingHandler]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/quality/src/logging/LoggingHandler.java"} ::: notes -=> Warum wird im Beispiel nach dem Auskommentieren von -`log.setUseParentHandlers(false);` immer noch eine zusätzliche Ausgabe -angezeigt (ab `INFO` aufwärts)?! +=\> Warum wird im Beispiel nach dem Auskommentieren von +`log.setUseParentHandlers(false);` immer noch eine zusätzliche Ausgabe angezeigt (ab +`INFO` aufwärts)?! ::: - # Ich ... bin ... Dein ... Vater ... -* Logger bilden **Hierarchie** über Namen - * Trenner für Namenshierarchie: "`.`" (analog zu Packages) [=> mit jedem "`.`" wird eine weitere Ebene der Hierarchie aufgemacht ...]{.notes} - * Jeder Logger kennt seinen Eltern-Logger: `Logger#getParent()` - * Basis-Logger: leerer Name (`""`) - * Voreingestelltes Level des Basis-Loggers: `Level.INFO` (!) +- Logger bilden **Hierarchie** über Namen + - Trenner für Namenshierarchie: "`.`" (analog zu Packages) [=\> mit jedem + "`.`" wird eine weitere Ebene der Hierarchie aufgemacht ...]{.notes} + - Jeder Logger kennt seinen Eltern-Logger: `Logger#getParent()` + - Basis-Logger: leerer Name (`""`) + - Voreingestelltes Level des Basis-Loggers: `Level.INFO` (!) \bigskip -* Weiterleiten von Nachrichten - * Nicht verworfene Log-Aufrufe werden an Eltern-Logger weitergeleitet [(Default)]{.notes} - * Abschalten mit `Logger#setUseParentHandlers(false);` - * Diese leiten [an ihre Handler sowie]{.notes} an ihren Eltern-Logger weiter (unabhängig von Log-Level!) - -[Konsole: logging.LoggingParent; Tafel: Skizze Logger-Baum]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/quality/src/logging/LoggingParent.java"} +- Weiterleiten von Nachrichten + - Nicht verworfene Log-Aufrufe werden an Eltern-Logger weitergeleitet + [(Default)]{.notes} + - Abschalten mit `Logger#setUseParentHandlers(false);` + - Diese leiten [an ihre Handler sowie]{.notes} an ihren Eltern-Logger weiter + (unabhängig von Log-Level!) +[Konsole: logging.LoggingParent; Tafel: Skizze Logger-Baum]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/quality/src/logging/LoggingParent.java"} # Wrap-Up -* Java Logging API im Paket `java.util.logging` +- Java Logging API im Paket `java.util.logging` \smallskip -* Neuer Logger über **Factory-Methode** der Klasse `Logger` - * Einstellbares Log-Level (Klasse `Level`) - * Handler kümmern sich um die Ausgabe, nutzen dazu Formatter - * Mehrere Handler je Logger registrierbar - * Log-Level auch für Handler einstellbar (!) - * Logger (und Handler) "interessieren" sich nur für Meldungen ab - bestimmter Wichtigkeit - * Logger reichen nicht verworfene Meldungen defaultmäßig an - Eltern-Logger weiter (rekursiv) +- Neuer Logger über **Factory-Methode** der Klasse `Logger` + - Einstellbares Log-Level (Klasse `Level`) + - Handler kümmern sich um die Ausgabe, nutzen dazu Formatter + - Mehrere Handler je Logger registrierbar + - Log-Level auch für Handler einstellbar (!) + - Logger (und Handler) "interessieren" sich nur für Meldungen ab bestimmter + Wichtigkeit + - Logger reichen nicht verworfene Meldungen defaultmäßig an Eltern-Logger + weiter (rekursiv) + +::: readings +- @JDK-Doc [Kap. 8] +::: + +::: outcomes +- k3: Ich kann die Java Logging API im Paket java.util.logging aktiv einsetzen +- k3: Ich kann eigene Handler und Formatter schreiben +::: + +::: challenges +**Logger-Konfiguration** + +Betrachten Sie den folgenden Java-Code: + +``` java +import java.util.logging.*; + +public class Logging { + public static void main(String... args) { + Logger l = Logger.getLogger("Logging"); + l.setLevel(Level.FINE); + + ConsoleHandler myHandler = new ConsoleHandler(); + myHandler.setFormatter(new SimpleFormatter() { + public String format(LogRecord record) { + return "WUPPIE\n"; + } + }); + l.addHandler(myHandler); + + l.info("A"); + l.fine("B"); + l.finer("C"); + l.finest("D"); + l.severe("E"); + } +} +``` + +Welche Ausgaben entstehen durch den obigen Code? Erklären Sie, welche der +Logger-Aufrufe zu einer Ausgabe führen und wieso und wie diese Ausgaben zustande +kommen bzw. es keine Ausgabe bei einem Logger-Aufruf gibt. Gehen Sie dabei auf jeden +der fünf Aufrufe ein. + + + +**Analyse eines Live-Beispiels aus dem Dungeon** + +Analysieren Sie die Konfiguration des Loggers im Dungeon-Projekt: +[Dungeon-CampusMinden/Dungeon: +core/utils/logging/LoggerConfig.java](https://github.com/Dungeon-CampusMinden/Dungeon/blob/master/game/src/core/utils/logging/LoggerConfig.java). + + +::: diff --git a/lecture/quality/mockito.md b/lecture/quality/mockito.md index 02bace2b5..f1512f31a 100644 --- a/lecture/quality/mockito.md +++ b/lecture/quality/mockito.md @@ -1,184 +1,47 @@ --- -title: "Mocking mit Mockito" -author: "Carsten Gips (HSBI)" -readingsX: - - "@Mockito" -tldr: | - Häufig hat man es in Softwaretests mit dem Problem zu tun, dass die zu testenden Klassen von - anderen, noch nicht implementierten Klassen oder von zufälligen oder langsamen Operationen - abhängen. - - In solchen Situationen kann man auf "Platzhalter" für diese Abhängigkeiten zurückgreifen. Dies - können einfache Stubs sein, also Objekte, die einfach einen festen Wert bei einem Methodenaufruf - zurückliefern oder Mocks, wo man auf die Argumente eines Methodenaufrufs reagieren kann und - passende unterschiedliche Rückgabewerte zurückgeben kann. - - Mockito ist eine Java-Bibliothek, die zusammen mit JUnit das Mocking von Klassen in Java - erlaubt. Man kann hier zusätzlich auch die Interaktion mit dem gemockten Objekt überprüfen und - testen, ob eine bestimmte Methode mit bestimmten Argumenten aufgerufen wurde und wie oft. -outcomes: - - k2: "Ich kann die Begriffe: Mocking, Mock, Stub, Spy unterscheiden und erklären" - - k3: "Ich kann Mocks in Mockito anlegen und nutzen" - - k3: "Ich kann Spies in Mockito anlegen und nutzen" - - k3: "Ich kann die Interaktion mit Mocks/Spies über _verify()_ prüfen" - - k3: "Ich kann den _ArgumentMatcher_ praktisch einsetzen" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106547&client_id=FH-Bielefeld" -# name: "Quiz Mocking (ILIAS)" -youtube: - - link: "https://youtu.be/8deFZKtjXSk" - name: "VL Mocking" - - link: "https://youtu.be/WNEedC7PrVQ" - name: "Demo Mocking: Stubs" - - link: "https://youtu.be/avUyYVePFCI" - name: "Demo Mocking: Mocks" - - link: "https://youtu.be/dj3pmOZfS_A" - name: "Demo Mocking: Spy" - - link: "https://youtu.be/CPGqhyC8BjU" - name: "Demo Mocking: verify()" - - link: "https://youtu.be/JNouzOd0s-w" - name: "Demo Mocking: Matcher" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/9531e04dd11458ea45915269aca452ba6b9978ef7b1b3a777a424d673573f75724a3fbedd0a2bec5e4d14de4025ea92ae4a966f95ee312e9f6ecca4fde4c98ef" - name: "VL Mocking" -challenges: | - Betrachten Sie die drei Klassen `Utility.java`, `Evil.java` - und `UtilityTest.java`: - - ```java - public class Utility { - private int intResult = 0; - private Evil evilClass; - - public Utility(Evil evilClass) { - this.evilClass = evilClass; - } - - public void evilMethod() { - int i = 2 / 0; - } - - public int nonEvilAdd(int a, int b) { - return a + b; - } - - public int evilAdd(int a, int b) { - evilClass.evilMethod(); - return a + b; - } - - public void veryEvilAdd(int a, int b) { - evilMethod(); - evilClass.evilMethod(); - intResult = a + b; - } - - public int getIntResult() { - return intResult; - } - } - - public class Evil { - public void evilMethod() { - int i = 3 / 0; - } - } - - public class UtilityTest { - private Utility utilityClass; - // Initialisieren Sie die Attribute entsprechend vor jedem Test. - - @Test - void test_nonEvilAdd() { - Assertions.assertEquals(10, utilityClass.nonEvilAdd(9, 1)); - } - - @Test - void test_evilAdd() { - Assertions.assertEquals(10, utilityClass.evilAdd(9, 1)); - } - - @Test - void test_veryEvilAdd() { - utilityClass.veryEvilAdd(9, 1); - Assertions.assertEquals(10, utilityClass.getIntResult()); - } - } - ``` - - Testen Sie die Methoden `nonEvilAdd`, `evilAdd` und `veryEvilAdd` der - Klasse `Utility.java` mit dem [JUnit-](https://junit.org/) und dem - [Mockito-Framework](https://github.com/mockito/mockito). - - Vervollständigen Sie dazu die Klasse `UtilityTest.java` und nutzen Sie - Mocking mit [Mockito](https://github.com/mockito/mockito), um die Tests - zum Laufen zu bringen. Die Tests dürfen Sie entsprechend verändern, aber - die Aufrufe aus der Vorgabe müssen erhalten bleiben. Die Klassen `Evil.java` - und `Utility.java` dürfen Sie nicht ändern. - - _Hinweis:_ Die Klasse `Evil.java` und die Methode `evilMethod()` aus - `Utility.java` lösen eine ungewollte bzw. "zufällige" Exception aus, - auf deren Auftreten jedoch _nicht_ getestet werden soll. Stattdessen - sollen diese Klassen bzw. Methoden mit Mockito "weggemockt" werden, so - dass die vorgegebenen Testmethoden (wieder) funktionieren. - - +author: Carsten Gips (HSBI) +title: Mocking mit Mockito --- +::: tldr +Häufig hat man es in Softwaretests mit dem Problem zu tun, dass die zu testenden +Klassen von anderen, noch nicht implementierten Klassen oder von zufälligen oder +langsamen Operationen abhängen. + +In solchen Situationen kann man auf "Platzhalter" für diese Abhängigkeiten +zurückgreifen. Dies können einfache Stubs sein, also Objekte, die einfach einen +festen Wert bei einem Methodenaufruf zurückliefern oder Mocks, wo man auf die +Argumente eines Methodenaufrufs reagieren kann und passende unterschiedliche +Rückgabewerte zurückgeben kann. + +Mockito ist eine Java-Bibliothek, die zusammen mit JUnit das Mocking von Klassen in +Java erlaubt. Man kann hier zusätzlich auch die Interaktion mit dem gemockten Objekt +überprüfen und testen, ob eine bestimmte Methode mit bestimmten Argumenten +aufgerufen wurde und wie oft. +::: + +::: youtube +- [VL Mocking](https://youtu.be/8deFZKtjXSk) +- [Demo Mocking: Stubs](https://youtu.be/WNEedC7PrVQ) +- [Demo Mocking: Mocks](https://youtu.be/avUyYVePFCI) +- [Demo Mocking: Spy](https://youtu.be/dj3pmOZfS_A) +- [Demo Mocking: verify()](https://youtu.be/CPGqhyC8BjU) +- [Demo Mocking: Matcher](https://youtu.be/JNouzOd0s-w) +::: # Motivation: Entwicklung einer Studi-/Prüfungsverwaltung ::: notes ## Szenario -Zwei Teams entwickeln eine neue Studi-/Prüfungsverwaltung für die Hochschule. Ein Team modelliert dabei -die Studierenden, ein anderes Team modelliert die Prüfungsverwaltung LSF. +Zwei Teams entwickeln eine neue Studi-/Prüfungsverwaltung für die Hochschule. Ein +Team modelliert dabei die Studierenden, ein anderes Team modelliert die +Prüfungsverwaltung LSF. ::: -* Team A: +- Team A: - ```java + ``` java public class Studi { String name; LSF lsf; @@ -191,9 +54,9 @@ die Studierenden, ein anderes Team modelliert die Prüfungsverwaltung LSF. } ``` -* Team B: +- Team B: - ```java + ``` java public class LSF { public boolean anmelden(String name, String modul) { throw new UnsupportedOperationException(); } public int ergebnis(String name, String modul) { throw new UnsupportedOperationException(); } @@ -211,94 +74,97 @@ Wie kann Team A seinen Code testen? ::: notes **Optionen**: -* Gar nicht testen?! -* Das LSF selbst implementieren? Wer pflegt das dann? => manuell implementierte Stubs -* Das LSF durch einen Mock ersetzen => Einsatz der Bibliothek "mockito" +- Gar nicht testen?! +- Das LSF selbst implementieren? Wer pflegt das dann? =\> manuell implementierte + Stubs +- Das LSF durch einen Mock ersetzen =\> Einsatz der Bibliothek "mockito" ::: -::::::::: notes +::: notes ## Motivation Mocking und Mockito -[Mockito](https://github.com/mockito/mockito) ist ein Mocking-Framework für JUnit. Es -simuliert das Verhalten eines realen Objektes oder einer realen Methode. +[Mockito](https://github.com/mockito/mockito) ist ein Mocking-Framework für JUnit. +Es simuliert das Verhalten eines realen Objektes oder einer realen Methode. Wofür brauchen wir denn jetzt so ein Mocking-Framework überhaupt? -Wir wollen die Funktionalität einer Klasse isoliert vom Rest testen können. -Dabei stören uns aber bisher so ein paar Dinge: +Wir wollen die Funktionalität einer Klasse isoliert vom Rest testen können. Dabei +stören uns aber bisher so ein paar Dinge: -* Arbeiten mit den echten Objekten ist langsam (zum Beispiel aufgrund von +- Arbeiten mit den echten Objekten ist langsam (zum Beispiel aufgrund von Datenbankenzugriffen) -* Objekte beinhalten oft komplexe Abhängigkeiten, die in Tests schwer abzudecken +- Objekte beinhalten oft komplexe Abhängigkeiten, die in Tests schwer abzudecken sind -* Manchmal existiert der zu testende Teil einer Applikation auch noch gar nicht, +- Manchmal existiert der zu testende Teil einer Applikation auch noch gar nicht, sondern es gibt nur die Interfaces. -* Oder es gibt unschöne Seiteneffekte beim Arbeiten mit den realen Objekten. Zum +- Oder es gibt unschöne Seiteneffekte beim Arbeiten mit den realen Objekten. Zum Beispiel könnte es sein, das immer eine E-Mail versendet wird, wenn wir mit einem Objekt interagieren. -In solchen Situationen wollen wir eine Möglichkeit haben, das Verhalten eines -realen Objektes bzw. der Methoden zu simulieren, ohne dabei die originalen -Methoden aufrufen zu müssen. (Manchmal möchte man das dennoch, aber dazu später -mehr...) +In solchen Situationen wollen wir eine Möglichkeit haben, das Verhalten eines realen +Objektes bzw. der Methoden zu simulieren, ohne dabei die originalen Methoden +aufrufen zu müssen. (Manchmal möchte man das dennoch, aber dazu später mehr...) Und genau hier kommt Mockito ins Spiel. Mockito hilft uns dabei, uns von den externen Abhängigkeiten zu lösen, indem es sogenannte Mocks, Stubs oder Spies -anbietet, mit denen sich das Verhalten der realen Objekte simulieren/überwachen -und testen lässt. +anbietet, mit denen sich das Verhalten der realen Objekte simulieren/überwachen und +testen lässt. ## Aber was genau ist denn jetzt eigentlich Mocking? -Ein Mock-Objekt ("etwas vortäuschen") ist im Software-Test ein Objekt, das als Platzhalter -(Attrappe) für das echte Objekt verwendet wird. +Ein Mock-Objekt ("etwas vortäuschen") ist im Software-Test ein Objekt, das als +Platzhalter (Attrappe) für das echte Objekt verwendet wird. -Mocks sind in JUnit-Tests immer dann nützlich, wenn man externe Abhängigkeiten -hat, auf die der eigene Code zugreift. Das können zum Beispiel externe APIs sein -oder Datenbanken etc. ... Mocks helfen einem beim Testen nun dabei, sich von diesen -externen Abhängigkeiten zu lösen und seine Softwarefunktionalität dennoch -schnell und effizient testen zu können ohne evtl. auftretende Verbindungsfehler -oder andere mögliche Seiteneffekte der externen Abhängigkeiten auszulösen. +Mocks sind in JUnit-Tests immer dann nützlich, wenn man externe Abhängigkeiten hat, +auf die der eigene Code zugreift. Das können zum Beispiel externe APIs sein oder +Datenbanken etc. ... Mocks helfen einem beim Testen nun dabei, sich von diesen +externen Abhängigkeiten zu lösen und seine Softwarefunktionalität dennoch schnell +und effizient testen zu können ohne evtl. auftretende Verbindungsfehler oder andere +mögliche Seiteneffekte der externen Abhängigkeiten auszulösen. Dabei simulieren Mocks die Funktionalität der externen APIs oder Datenbankzugriffe. -Auf diese Weise ist es möglich Softwaretests zu schreiben, die scheinbar die gleichen -Methoden aufrufen, die sie auch im regulären Softwarebetrieb nutzen würden, allerdings -werden diese wie oben erwähnt allerdings für die Tests nur simuliert. +Auf diese Weise ist es möglich Softwaretests zu schreiben, die scheinbar die +gleichen Methoden aufrufen, die sie auch im regulären Softwarebetrieb nutzen würden, +allerdings werden diese wie oben erwähnt allerdings für die Tests nur simuliert. Mocking ist also eine Technik, die in Softwaretests verwendet wird, in denen die gemockten Objekte anstatt der realen Objekte zu Testzwecken genutzt werden. Die -gemockten Objekte liefern dabei bei einem vom Programmierer bestimmten (Dummy-) Input, -einen dazu passenden gelieferten (Dummy-) Output, der durch seine vorhersagbare -Funktionalität dann in den eigentlichen Testobjekten gut für den Test nutzbar ist. +gemockten Objekte liefern dabei bei einem vom Programmierer bestimmten (Dummy-) +Input, einen dazu passenden gelieferten (Dummy-) Output, der durch seine +vorhersagbare Funktionalität dann in den eigentlichen Testobjekten gut für den Test +nutzbar ist. -Dabei ist es von Vorteil die drei Grundbegriffe "Mock", "Stub" oder "Spy", auf die wir -in der Vorlesung noch häufiger treffen werden, voneinander abgrenzen und +Dabei ist es von Vorteil die drei Grundbegriffe "Mock", "Stub" oder "Spy", auf die +wir in der Vorlesung noch häufiger treffen werden, voneinander abgrenzen und unterscheiden zu können. ## Dabei bezeichnet ein -* **Stub**: Ein Stub ist ein Objekt, dessen Methoden nur mit einer minimalen Logik +- **Stub**: Ein Stub ist ein Objekt, dessen Methoden nur mit einer minimalen Logik für den Test implementiert wurden. Häufig werden dabei einfach feste (konstante) - Werte zurückgeliefert, d.h. beim Aufruf einer Methode wird unabhängig von der konkreten - Eingabe immer die selbe Ausgabe zurückgeliefert. -* **Mock**: Ein Mock ist ein Objekt, welches im Gegensatz zum Stub bei vorher definierten - Funktionsaufrufen mit vorher definierten Argumente eine definierte Rückgabe liefert. -* **Spy**: Ein Spy ist ein Objekt, welches Aufrufe und übergebene Werte protokolliert und - abfragbar macht. Es ist also eine Art Wrapper um einen Stub oder einen Mock. + Werte zurückgeliefert, d.h. beim Aufruf einer Methode wird unabhängig von der + konkreten Eingabe immer die selbe Ausgabe zurückgeliefert. +- **Mock**: Ein Mock ist ein Objekt, welches im Gegensatz zum Stub bei vorher + definierten Funktionsaufrufen mit vorher definierten Argumente eine definierte + Rückgabe liefert. +- **Spy**: Ein Spy ist ein Objekt, welches Aufrufe und übergebene Werte + protokolliert und abfragbar macht. Es ist also eine Art Wrapper um einen Stub + oder einen Mock. ## Mockito Setup -* Gradle: `build.gradle` +- Gradle: `build.gradle` - ```groovy + ``` groovy dependencies { implementation 'junit:junit:4.13.2' implementation 'org.mockito:mockito-core:4.5.1' } ``` -* Maven: `pom.xml` +- Maven: `pom.xml` - ```xml + ``` xml junit @@ -311,17 +177,16 @@ unterscheiden zu können. ``` -::::::::: - +::: # Manuell Stubs implementieren ::: notes -Team A könnte manuell das LSF rudimentär implementieren (nur für die Tests, einfach mit -festen Rückgabewerten): **Stubs** +Team A könnte manuell das LSF rudimentär implementieren (nur für die Tests, einfach +mit festen Rückgabewerten): **Stubs** ::: -```java +``` java public class StudiStubTest { Studi studi; LSF lsf; @@ -343,15 +208,17 @@ public class StudiStubTest { ``` ::: notes -**Problem**: Wartung der Tests (wenn das richtige LSF fertig ist) und Wartung der Stubs (wenn sich die Schnittstelle -des LSF ändert, muss auch der Stub nachgezogen werden). +**Problem**: Wartung der Tests (wenn das richtige LSF fertig ist) und Wartung der +Stubs (wenn sich die Schnittstelle des LSF ändert, muss auch der Stub nachgezogen +werden). -**Problem**: Der Stub hat nur eine Art minimale Default-Logik (sonst könnte man ja das LSF gleich selbst implementieren). -Wenn man im Test andere Antworten braucht, müsste man einen weiteren Stub anlegen ... +**Problem**: Der Stub hat nur eine Art minimale Default-Logik (sonst könnte man ja +das LSF gleich selbst implementieren). Wenn man im Test andere Antworten braucht, +müsste man einen weiteren Stub anlegen ... ::: -[Demo hsbi.StudiStubTest]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/quality/src/mockito/src/test/java/hsbi/StudiStubTest.java"} - +[Demo hsbi.StudiStubTest]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/quality/src/mockito/src/test/java/hsbi/StudiStubTest.java"} # Mockito: Mocking von ganzen Klassen @@ -359,7 +226,7 @@ Wenn man im Test andere Antworten braucht, müsste man einen weiteren Stub anleg **Lösung**: Mocking der Klasse `LSF` mit Mockito für den Test von `Studi`: `mock()`. ::: -```java +``` java public class StudiMockTest { Studi studi; LSF lsf; @@ -386,14 +253,17 @@ public class StudiMockTest { ``` ::: notes -Der Aufruf `mock(LSF.class)` erzeugt einen Mock der Klasse (oder des Interfaces) `LSF`. Dabei wird ein Objekt -vom Typ `LSF` erzeugt, mit dem man dann wie mit einem normalen Objekt weiter arbeiten kann. Die Methoden sind -allerdings nicht implementiert ... - -Mit Hilfe von `when().thenReturn()` kann man definieren, was genau beim Aufruf einer bestimmten Methode auf dem -Mock passieren soll, d.h. welcher Rückgabewert entsprechend zurückgegeben werden soll. Hier kann man dann für -bestimmte Argumentwerte andere Rückgabewerte definieren. `when(lsf.ergebnis("Harald", "PM-Dungeon")).thenReturn(80)` -gibt also für den Aufruf von `ergebnis` mit den Argumenten `"Harald"` und `"PM-Dungeon"` auf dem Mock `lsf` +Der Aufruf `mock(LSF.class)` erzeugt einen Mock der Klasse (oder des Interfaces) +`LSF`. Dabei wird ein Objekt vom Typ `LSF` erzeugt, mit dem man dann wie mit einem +normalen Objekt weiter arbeiten kann. Die Methoden sind allerdings nicht +implementiert ... + +Mit Hilfe von `when().thenReturn()` kann man definieren, was genau beim Aufruf einer +bestimmten Methode auf dem Mock passieren soll, d.h. welcher Rückgabewert +entsprechend zurückgegeben werden soll. Hier kann man dann für bestimmte +Argumentwerte andere Rückgabewerte definieren. +`when(lsf.ergebnis("Harald", "PM-Dungeon")).thenReturn(80)` gibt also für den Aufruf +von `ergebnis` mit den Argumenten `"Harald"` und `"PM-Dungeon"` auf dem Mock `lsf` den Wert 80 zurück. Dies kann man in weiten Grenzen flexibel anpassen. @@ -401,20 +271,21 @@ Dies kann man in weiten Grenzen flexibel anpassen. Mit Hilfe der Argument-Matcher `anyString()` wird jedes String-Argument akzeptiert. ::: -[Demo hsbi.StudiMockTest]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/quality/src/mockito/src/test/java/hsbi/StudiMockTest.java"} - +[Demo hsbi.StudiMockTest]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/quality/src/mockito/src/test/java/hsbi/StudiMockTest.java"} # Mockito: Spy = Wrapper um ein Objekt ::: notes -Team B hat das `LSF` nun implementiert und Team A kann es endlich für die Tests benutzen. Aber -das `LSF` hat eine Zufallskomponente (`ergebnis()`). Wie kann man nun die Reaktion des Studis -testen (`einsicht()`)? +Team B hat das `LSF` nun implementiert und Team A kann es endlich für die Tests +benutzen. Aber das `LSF` hat eine Zufallskomponente (`ergebnis()`). Wie kann man nun +die Reaktion des Studis testen (`einsicht()`)? -**Lösung**: Mockito-Spy als partieller Mock einer Klasse (Wrapper um ein Objekt): `spy()`. +**Lösung**: Mockito-Spy als partieller Mock einer Klasse (Wrapper um ein Objekt): +`spy()`. ::: -```java +``` java public class StudiSpyTest { Studi studi; LSF lsf; @@ -438,28 +309,33 @@ public class StudiSpyTest { ``` ::: notes -Der Aufruf `spy(LSF.class)` erzeugt einen Spy um ein Objekt der Klasse `LSF`. Dabei bleiben zunächst die Methoden -in `LSF` erhalten und können aufgerufen werden, sie können aber auch mit einem (partiellen) Mock überlagert werden. -Der Spy zeichnet wie der Mock die Interaktion mit dem Objekt auf. - -Mit Hilfe von `doReturn().when()` kann man definieren, was genau beim Aufruf einer bestimmten Methode auf dem -Spy passieren soll, d.h. welcher Rückgabewert entsprechend zurückgegeben werden soll. Hier kann man analog zum Mock -für bestimmte Argumentwerte andere Rückgabewerte definieren. `doReturn(40).when(lsf).ergebnis("Harald", "PM-Dungeon")` -gibt also für den Aufruf von `ergebnis` mit den Argumenten `"Harald"` und `"PM-Dungeon"` auf dem Spy `lsf` den Wert -40 zurück. - -Wenn man die Methoden nicht mit einem partiellen Mock überschreibt, dann wird einfach die originale Methode aufgerufen -(Beispiel: In `studi.anmelden("PM-Dungeon")` wird `lsf.anmelden("Harald", "PM-Dungeon")` aufgerufen.). +Der Aufruf `spy(LSF.class)` erzeugt einen Spy um ein Objekt der Klasse `LSF`. Dabei +bleiben zunächst die Methoden in `LSF` erhalten und können aufgerufen werden, sie +können aber auch mit einem (partiellen) Mock überlagert werden. Der Spy zeichnet wie +der Mock die Interaktion mit dem Objekt auf. + +Mit Hilfe von `doReturn().when()` kann man definieren, was genau beim Aufruf einer +bestimmten Methode auf dem Spy passieren soll, d.h. welcher Rückgabewert +entsprechend zurückgegeben werden soll. Hier kann man analog zum Mock für bestimmte +Argumentwerte andere Rückgabewerte definieren. +`doReturn(40).when(lsf).ergebnis("Harald", "PM-Dungeon")` gibt also für den Aufruf +von `ergebnis` mit den Argumenten `"Harald"` und `"PM-Dungeon"` auf dem Spy `lsf` +den Wert 40 zurück. + +Wenn man die Methoden nicht mit einem partiellen Mock überschreibt, dann wird +einfach die originale Methode aufgerufen (Beispiel: In +`studi.anmelden("PM-Dungeon")` wird `lsf.anmelden("Harald", "PM-Dungeon")` +aufgerufen.). Auch hier können Argument-Matcher wie `anyString()` eingesetzt werden. ::: -[Demo hsbi.StudiSpyTest]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/quality/src/mockito/src/test/java/hsbi/StudiSpyTest.java"} - +[Demo hsbi.StudiSpyTest]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/quality/src/mockito/src/test/java/hsbi/StudiSpyTest.java"} # Wurde eine Methode aufgerufen? -```java +``` java public class VerifyTest { @Test public void testAnmelden() { @@ -484,19 +360,22 @@ public class VerifyTest { ``` ::: notes -Mit der Methode `verify()` kann auf einem Mock oder Spy überprüft werden, ob und wie oft und in welcher Reihenfolge -Methoden aufgerufen wurden und mit welchen Argumenten. Auch hier lassen sich wieder Argument-Matcher wie `anyString()` +Mit der Methode `verify()` kann auf einem Mock oder Spy überprüft werden, ob und wie +oft und in welcher Reihenfolge Methoden aufgerufen wurden und mit welchen +Argumenten. Auch hier lassen sich wieder Argument-Matcher wie `anyString()` einsetzen. -Ein einfaches `verify(mock)` prüft dabei, ob die entsprechende Methode exakt einmal vorher aufgerufen wurde. Dies -ist äquivalent zu `verify(mock, times(1))`. Analog kann man mit den Parametern `atLeast()` oder `atMost` bestimmte -Unter- oder Obergrenzen für die Aufrufe angeben und mit `never()` prüfen, ob es gar keinen Aufruf vorher gab. +Ein einfaches `verify(mock)` prüft dabei, ob die entsprechende Methode exakt einmal +vorher aufgerufen wurde. Dies ist äquivalent zu `verify(mock, times(1))`. Analog +kann man mit den Parametern `atLeast()` oder `atMost` bestimmte Unter- oder +Obergrenzen für die Aufrufe angeben und mit `never()` prüfen, ob es gar keinen +Aufruf vorher gab. -`verifyNoMoreInteractions(lsf)` ist interessant: Es ist genau dann `true`, wenn es außer den vorher abgefragten -Interaktionen keinerlei sonstigen Interaktionen mit dem Mock oder Spy gab. +`verifyNoMoreInteractions(lsf)` ist interessant: Es ist genau dann `true`, wenn es +außer den vorher abgefragten Interaktionen keinerlei sonstigen Interaktionen mit dem +Mock oder Spy gab. - -```java +``` java LSF lsf = mock(LSF.class); Studi studi = new Studi("Harald", lsf); @@ -511,16 +390,16 @@ inOrder.verify(lsf).anmelden("Harald", "Wuppie"); inOrder.verify(lsf).anmelden("Harald", "PM-Dungeon"); ``` -Mit `InOrder` lassen sich Aufrufe auf einem Mock/Spy oder auch auf verschiedenen Mocks/Spies in eine zeitliche -Reihenfolge bringen und so überprüfen. +Mit `InOrder` lassen sich Aufrufe auf einem Mock/Spy oder auch auf verschiedenen +Mocks/Spies in eine zeitliche Reihenfolge bringen und so überprüfen. ::: -[Demo hsbi.VerifyTest]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/quality/src/mockito/src/test/java/hsbi/VerifyTest.java"} - +[Demo hsbi.VerifyTest]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/quality/src/mockito/src/test/java/hsbi/VerifyTest.java"} # Fangen von Argumenten -```java +``` java public class MatcherTest { @Test public void testAnmelden() { @@ -548,64 +427,64 @@ public class MatcherTest { ``` ::: notes -Sie können die konkreten Argumente angeben, für die der Aufruf gelten soll. Alternativ -können Sie mit vordefinierten `ArgumentMatchers` wie `anyString()` beispielsweise auf -beliebige Strings reagieren oder selbst einen eigenen `ArgumentMatcher` für Ihren -Typ `T` erstellen und nutzen. - -**Wichtig**: Wenn Sie für einen Parameter einen `ArgumentMatcher` einsetzen, müssen Sie -für die restlichen Parameter der Methode dies ebenfalls tun. Sie können keine konkreten -Argumente mit `ArgumentMatcher` mischen. - -Sie finden viele weitere vordefinierte Matcher in der Klasse `ArgumentMatchers`. -Mit der Klasse `ArgumentCaptor` finden Sie eine alternative Möglichkeit, auf -Argumente in gemockten Methoden zu reagieren. Schauen Sie sich dazu die Javadoc -von [Mockito](https://javadoc.io/doc/org.mockito/mockito-core/) an. +Sie können die konkreten Argumente angeben, für die der Aufruf gelten soll. +Alternativ können Sie mit vordefinierten `ArgumentMatchers` wie `anyString()` +beispielsweise auf beliebige Strings reagieren oder selbst einen eigenen +`ArgumentMatcher` für Ihren Typ `T` erstellen und nutzen. + +**Wichtig**: Wenn Sie für einen Parameter einen `ArgumentMatcher` einsetzen, müssen +Sie für die restlichen Parameter der Methode dies ebenfalls tun. Sie können keine +konkreten Argumente mit `ArgumentMatcher` mischen. + +Sie finden viele weitere vordefinierte Matcher in der Klasse `ArgumentMatchers`. Mit +der Klasse `ArgumentCaptor` finden Sie eine alternative Möglichkeit, auf +Argumente in gemockten Methoden zu reagieren. Schauen Sie sich dazu die Javadoc von +[Mockito](https://javadoc.io/doc/org.mockito/mockito-core/) an. ::: -[Demo hsbi.MatcherTest]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/quality/src/mockito/src/test/java/hsbi/MatcherTest.java"} - +[Demo hsbi.MatcherTest]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/quality/src/mockito/src/test/java/hsbi/MatcherTest.java"} # Ausblick: PowerMock Mockito sehr mächtig, aber unterstützt (u.a.) keine -* Konstruktoren -* private Methoden -* final Methoden -* static Methoden - [(ab Version 3.4.0 scheint auch [Mockito statische Methoden](https://www.baeldung.com/mockito-mock-static-methods) zu unterstützen)]{.notes} +- Konstruktoren +- private Methoden +- final Methoden +- static Methoden [(ab Version 3.4.0 scheint auch [Mockito statische + Methoden](https://www.baeldung.com/mockito-mock-static-methods) zu + unterstützen)]{.notes} \bigskip \bigskip -=> Lösung: [PowerMock](https://github.com/powermock/powermock) - +=\> Lösung: [PowerMock](https://github.com/powermock/powermock) -::::::::: notes +::: notes # Ausführlicheres Beispiel: WuppiWarenlager -**Credits**: Der Dank für die Erstellung des nachfolgenden Beispiels und Textes geht an -[\@jedi101](https://github.com/jedi101). - +**Credits**: Der Dank für die Erstellung des nachfolgenden Beispiels und Textes geht +an [\@jedi101](https://github.com/jedi101). -[Demo: WuppiWarenlager (wuppie.stub)]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/quality/src/mockito/src/test/java/wuppie/stub/"} +[Demo: WuppiWarenlager (wuppie.stub)]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/quality/src/mockito/src/test/java/wuppie/stub/"} Bei dem gezeigten Beispiel unseres `WuppiStores` sieht man, dass dieser -normalerweise von einem fertigen Warenlager die Wuppis beziehen möchte. Da -dieses Lager aber noch nicht existiert, haben wir uns kurzerhand einfach einen -Stub von unserem `IWuppiWarenlager`-Interface erstellt, in dem wir zu -Testzwecken händisch ein Paar Wuppis ins Lager geräumt haben. - -Das funktioniert in diesem Mini-Testbeispiel ganz gut aber, wenn unsere Stores -erst einmal so richtig Fahrt aufnehmen und wir irgendwann weltweit Wuppis -verkaufen, wird der Code des `IWuppiWarenlagers` wahrscheinlich sehr schnell viel -komplexer werden, was unweigerlich dann zu Maintenance-Problemen unserer -händisch angelegten Tests führt. Wenn wir zum Beispiel einmal eine Methode -hinzufügen wollen, die es uns ermöglicht, nicht immer alle Wuppis aus dem Lager -zu ordern oder vielleicht noch andere Methoden, die Fluppis orderbar machen, -hinzufügen, müssen wir immer dafür sorgen, dass wir die getätigten Änderungen -händisch in den Stub des Warenlagers einpflegen. +normalerweise von einem fertigen Warenlager die Wuppis beziehen möchte. Da dieses +Lager aber noch nicht existiert, haben wir uns kurzerhand einfach einen Stub von +unserem `IWuppiWarenlager`-Interface erstellt, in dem wir zu Testzwecken händisch +ein Paar Wuppis ins Lager geräumt haben. + +Das funktioniert in diesem Mini-Testbeispiel ganz gut aber, wenn unsere Stores erst +einmal so richtig Fahrt aufnehmen und wir irgendwann weltweit Wuppis verkaufen, wird +der Code des `IWuppiWarenlagers` wahrscheinlich sehr schnell viel komplexer werden, +was unweigerlich dann zu Maintenance-Problemen unserer händisch angelegten Tests +führt. Wenn wir zum Beispiel einmal eine Methode hinzufügen wollen, die es uns +ermöglicht, nicht immer alle Wuppis aus dem Lager zu ordern oder vielleicht noch +andere Methoden, die Fluppis orderbar machen, hinzufügen, müssen wir immer dafür +sorgen, dass wir die getätigten Änderungen händisch in den Stub des Warenlagers +einpflegen. Das will eigentlich niemand... @@ -615,29 +494,30 @@ Aber es gibt da einen Ausweg. Wenn es komplexer wird, verwenden wir Mocks. Bislang haben wir noch keinen Gebrauch von Mockito gemacht. Das ändern wir nun. -[Demo: WuppiWarenlager (wuppie.mock)]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/quality/src/mockito/src/test/java/wuppie/mock/"} +[Demo: WuppiWarenlager (wuppie.mock)]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/quality/src/mockito/src/test/java/wuppie/mock/"} -Wie in diesem Beispiel gezeigt, müssen wir nun keinen Stub mehr von Hand -erstellen, sondern überlassen dies Mockito. +Wie in diesem Beispiel gezeigt, müssen wir nun keinen Stub mehr von Hand erstellen, +sondern überlassen dies Mockito. -```java +``` java IWuppiWarenlager lager = mock(IWuppiWarenlager.class); ``` Anschließend können wir, ohne die Methode `getAllWuppis()` implementiert zu haben, dennoch so tun als, ob die Methode eine Funktionalität hätte. -```java +``` java // Erstellen eines imaginären Lagerbestands. List wuppisImLager = Arrays.asList("GruenerWuppi","RoterWuppi"); when(lager.getAlleWuppis()).thenReturn(wuppisImLager); ``` -Wann immer nun die Methode `getAlleWuppis()` des gemockten Lagers aufgerufen -wird, wird dieser Aufruf von Mockito abgefangen und wie oben definiert -verändert. Das Ergebnis können wir abschließend einfach in unserem Test testen: +Wann immer nun die Methode `getAlleWuppis()` des gemockten Lagers aufgerufen wird, +wird dieser Aufruf von Mockito abgefangen und wie oben definiert verändert. Das +Ergebnis können wir abschließend einfach in unserem Test testen: -```java +``` java // Erzeugen des WuppiStores. WuppiStore wuppiStore = new WuppiStore(lager); @@ -650,21 +530,23 @@ assertEquals(2,bestellteWuppis.size()); ## Mockito Spies -Manchmal möchten wir allerdings nicht immer gleich ein ganzes Objekt mocken, -aber dennoch Einfluss auf die aufgerufenen Methoden eines Objekts haben, um -diese testen zu können. Vielleicht gibt es dabei ja sogar eine Möglichkeit unsere -JUnit-Tests, mit denen wir normalerweise nur Rückgabewerte von Methoden testen -können, zusätzlich auch das Verhalten also die Interaktionen mit einem Objekt -beobachtbar zu machen. Somit wären diese Interaktionen auch testbar. +Manchmal möchten wir allerdings nicht immer gleich ein ganzes Objekt mocken, aber +dennoch Einfluss auf die aufgerufenen Methoden eines Objekts haben, um diese testen +zu können. Vielleicht gibt es dabei ja sogar eine Möglichkeit unsere JUnit-Tests, +mit denen wir normalerweise nur Rückgabewerte von Methoden testen können, zusätzlich +auch das Verhalten also die Interaktionen mit einem Objekt beobachtbar zu machen. +Somit wären diese Interaktionen auch testbar. Und genau dafür bietet Mockito eine Funktion: der sogenannte "Spy". -Dieser Spion erlaubt es uns nun zusätzlich das Verhalten zu testen. Das geht in -die Richtung von [BDD - Behavior Driven Development](https://de.wikipedia.org/wiki/Behavior_Driven_Development). +Dieser Spion erlaubt es uns nun zusätzlich das Verhalten zu testen. Das geht in die +Richtung von [BDD - Behavior Driven +Development](https://de.wikipedia.org/wiki/Behavior_Driven_Development). -[Demo: WuppiWarenlager (wuppie.spy)]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/quality/src/mockito/src/test/java/wuppie/spy/"} +[Demo: WuppiWarenlager (wuppie.spy)]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/quality/src/mockito/src/test/java/wuppie/spy/"} -```java +``` java // Spion erstellen, der unser wuppiWarenlager überwacht. this.wuppiWarenlager = spy(WuppiWarenlager.class); ``` @@ -672,91 +554,90 @@ this.wuppiWarenlager = spy(WuppiWarenlager.class); Hier hatten wir uns einen Spion erzeugt, mit dem sich anschließend das Verhalten verändern lässt: -```java +``` java when(wuppiWarenlager.getAlleWuppis()).thenReturn(Arrays.asList(new Wuppi("Wuppi007"))); ``` Aber auch der Zugriff lässt sich kontrollieren/testen: -```java +``` java verify(wuppiWarenlager).addWuppi(normalerWuppi); verifyNoMoreInteractions(wuppiWarenlager); ``` Die normalen Testmöglichkeiten von JUnit runden unseren Test zudem ab. -```java +``` java assertEquals(1,wuppiWarenlager.lager.size()); ``` # Mockito und Annotationen -In Mockito können Sie wie oben gezeigt mit `mock()` und `spy()` neue -Mocks bzw. Spies erzeugen und mit `verify()` die Interaktion überprüfen -und mit `ArgumentMatcher` bzw. den vordefinierten `ArgumentMatchers` -auf Argumente zuzugreifen bzw. darauf zu reagieren. +In Mockito können Sie wie oben gezeigt mit `mock()` und `spy()` neue Mocks bzw. +Spies erzeugen und mit `verify()` die Interaktion überprüfen und mit +`ArgumentMatcher` bzw. den vordefinierten `ArgumentMatchers` auf Argumente +zuzugreifen bzw. darauf zu reagieren. Zusätzlich/alternativ gibt es in Mockito zahlreiche Annotationen, die -**ersatzweise** statt der genannten Methoden genutzt werden können. Hier -ein kleiner Überblick über die wichtigsten in Mockito verwendeten Annotation: +**ersatzweise** statt der genannten Methoden genutzt werden können. Hier ein kleiner +Überblick über die wichtigsten in Mockito verwendeten Annotation: -* `@Mock` wird zum Markieren des zu mockenden Objekts verwendet. +- `@Mock` wird zum Markieren des zu mockenden Objekts verwendet. - ```java + ``` java @Mock WuppiWarenlager lager; ``` -* `@RunWith(MockitoJUnitRunner.class)` ist der entsprechende JUnit-Runner, - wenn Sie Mocks mit `@Mock` anlegen. +- `@RunWith(MockitoJUnitRunner.class)` ist der entsprechende JUnit-Runner, wenn + Sie Mocks mit `@Mock` anlegen. - ```java + ``` java @RunWith(MockitoJUnitRunner.class) public class ToDoBusinessMock {...} ``` -* `@Spy` erlaubt das Erstellen von partiell gemockten Objekten. Dabei wird eine +- `@Spy` erlaubt das Erstellen von partiell gemockten Objekten. Dabei wird eine Art Wrapper um das zu mockende Objekt gewickelt, der dafür sorgt, dass alle Methodenaufrufe des Objekts an den Spy delegiert werden. Diese können über den Spion dann abgefangen/verändert oder ausgewertet werden. - ```java + ``` java @Spy ArrayList arrayListenSpion; ``` -* `@InjectMocks` erlaubt es, Parameter zu markieren, in denen Mocks und/oder - Spies injiziert werden. Mockito versucht dann (in dieser Reihenfolge) per +- `@InjectMocks` erlaubt es, Parameter zu markieren, in denen Mocks und/oder Spies + injiziert werden. Mockito versucht dann (in dieser Reihenfolge) per Konstruktorinjektion, Setterinjektion oder Propertyinjektion die Mocks zu - injizieren. Weitere Informationen darüber findet man hier: - [Mockito Dokumentation](https://javadoc.io/static/org.mockito/mockito-core/4.5.1/org/mockito/InjectMocks.html) + injizieren. Weitere Informationen darüber findet man hier: [Mockito + Dokumentation](https://javadoc.io/static/org.mockito/mockito-core/4.5.1/org/mockito/InjectMocks.html) **Anmerkung**: Es ist aber nicht ratsam "Field- oder Setterinjection" zu nutzen, da man nur bei der Verwendung von "Constructorinjection" sicherstellen kann, das eine Klasse nicht ohne die eigentlich notwendigen Parameter instanziiert wurde. - ```java + ``` java @InjectMocks Wuppi fluppi; ``` -* `@Captor` erlaubt es, die Argumente einer Methode abzufangen/auszuwerten. Im +- `@Captor` erlaubt es, die Argumente einer Methode abzufangen/auszuwerten. Im Zusammenspiel mit Mockitos `verify()`-Methode kann man somit auch die einer Methode übergebenen Argumente verifizieren. - ```java + ``` java @Captor ArgumentCaptor argumentCaptor; ``` -* `@ExtendWith(MockitoExtension.class)` wird in JUnit5 verwendet, um die - Initialisierung von Mocks zu vereinfachen. Damit entfällt zum Beispiel die - noch unter JUnit4 nötige Initialisierung der Mocks durch einen Aufruf der - Methode `MockitoAnnotations.openMocks()` im Setup des Tests (`@Before` bzw. +- `@ExtendWith(MockitoExtension.class)` wird in JUnit5 verwendet, um die + Initialisierung von Mocks zu vereinfachen. Damit entfällt zum Beispiel die noch + unter JUnit4 nötige Initialisierung der Mocks durch einen Aufruf der Methode + `MockitoAnnotations.openMocks()` im Setup des Tests (`@Before` bzw. `@BeforeEach`). - -## Prüfen der Interaktion mit _verify()_ +## Prüfen der Interaktion mit *verify()* Mit Hilfe der umfangreichen `verify()`-Methoden, die uns Mockito mitliefert, können wir unseren Code unter anderem auf unerwünschte Seiteneffekte testen. So ist es mit @@ -767,7 +648,7 @@ in welcher Reihenfolge die Interaktionen damit erfolgt sind. Hier nur eine kurze Übersicht über das Testen des Codes mit Hilfe von Mockitos `verify()`-Methoden. -```java +``` java @Test public void testVerify_DasKeineInteraktionMitDerListeStattgefundenHat() { // Testet, ob die spezifizierte Interaktion mit der Liste nie stattgefunden hat. @@ -775,7 +656,7 @@ public void testVerify_DasKeineInteraktionMitDerListeStattgefundenHat() { } ``` -```java +``` java @Test public void testVerify_ReihenfolgeDerInteraktionenMitDerFluppisListe() { // Testet, ob die Reihenfolge der spezifizierten Interaktionen mit der Liste eingehalten wurde. @@ -786,7 +667,7 @@ public void testVerify_ReihenfolgeDerInteraktionenMitDerFluppisListe() { } ``` -```java +``` java @Test public void testVerify_FlexibleArgumenteBeimZugriffAufFluppisListe() { // Testet, ob schon jemals etwas zu der Liste hinzugefügt wurde. @@ -795,7 +676,7 @@ public void testVerify_FlexibleArgumenteBeimZugriffAufFluppisListe() { } ``` -```java +``` java @Test public void testVerify_InteraktionenMitHilfeDesArgumentCaptor() { // Testet, welches Argument beim Methodenaufruf übergeben wurde. @@ -807,18 +688,150 @@ public void testVerify_InteraktionenMitHilfeDesArgumentCaptor() { } ``` -[Demo: WuppiWarenlager (wuppie.verify)]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/quality/src/mockito/src/test/java/wuppie/verify/"} -::::::::: - +[Demo: WuppiWarenlager (wuppie.verify)]{.ex +href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/quality/src/mockito/src/test/java/wuppie/verify/"} +::: # Wrap-Up -* Gründliches Testen ist ebenso viel Aufwand wie Coden! +- Gründliches Testen ist ebenso viel Aufwand wie Coden! \bigskip -* Mockito ergänzt JUnit: - * Mocken ganzer Klassen (`mock()`, `when().thenReturn()`) - * Wrappen von Objekten (`spy()`, `doReturn().when()`) - * Auswerten, wie häufig Methoden aufgerufen wurden (`verify()`) - * Auswerten, mit welchen Argumenten Methoden aufgerufen wurden (`anyString`) +- Mockito ergänzt JUnit: + - Mocken ganzer Klassen (`mock()`, `when().thenReturn()`) + - Wrappen von Objekten (`spy()`, `doReturn().when()`) + - Auswerten, wie häufig Methoden aufgerufen wurden (`verify()`) + - Auswerten, mit welchen Argumenten Methoden aufgerufen wurden (`anyString`) + +::: outcomes +- k2: Ich kann die Begriffe: Mocking, Mock, Stub, Spy unterscheiden und erklären +- k3: Ich kann Mocks in Mockito anlegen und nutzen +- k3: Ich kann Spies in Mockito anlegen und nutzen +- k3: Ich kann die Interaktion mit Mocks/Spies über verify() prüfen +- k3: Ich kann den ArgumentMatcher praktisch einsetzen +::: + +::: challenges +Betrachten Sie die drei Klassen `Utility.java`, `Evil.java` und `UtilityTest.java`: + +``` java +public class Utility { + private int intResult = 0; + private Evil evilClass; + + public Utility(Evil evilClass) { + this.evilClass = evilClass; + } + + public void evilMethod() { + int i = 2 / 0; + } + + public int nonEvilAdd(int a, int b) { + return a + b; + } + + public int evilAdd(int a, int b) { + evilClass.evilMethod(); + return a + b; + } + + public void veryEvilAdd(int a, int b) { + evilMethod(); + evilClass.evilMethod(); + intResult = a + b; + } + + public int getIntResult() { + return intResult; + } +} + +public class Evil { + public void evilMethod() { + int i = 3 / 0; + } +} + +public class UtilityTest { + private Utility utilityClass; + // Initialisieren Sie die Attribute entsprechend vor jedem Test. + + @Test + void test_nonEvilAdd() { + Assertions.assertEquals(10, utilityClass.nonEvilAdd(9, 1)); + } + + @Test + void test_evilAdd() { + Assertions.assertEquals(10, utilityClass.evilAdd(9, 1)); + } + + @Test + void test_veryEvilAdd() { + utilityClass.veryEvilAdd(9, 1); + Assertions.assertEquals(10, utilityClass.getIntResult()); + } +} +``` + +Testen Sie die Methoden `nonEvilAdd`, `evilAdd` und `veryEvilAdd` der Klasse +`Utility.java` mit dem [JUnit-](https://junit.org/) und dem +[Mockito-Framework](https://github.com/mockito/mockito). + +Vervollständigen Sie dazu die Klasse `UtilityTest.java` und nutzen Sie Mocking mit +[Mockito](https://github.com/mockito/mockito), um die Tests zum Laufen zu bringen. +Die Tests dürfen Sie entsprechend verändern, aber die Aufrufe aus der Vorgabe müssen +erhalten bleiben. Die Klassen `Evil.java` und `Utility.java` dürfen Sie nicht +ändern. + +*Hinweis:* Die Klasse `Evil.java` und die Methode `evilMethod()` aus `Utility.java` +lösen eine ungewollte bzw. "zufällige" Exception aus, auf deren Auftreten jedoch +*nicht* getestet werden soll. Stattdessen sollen diese Klassen bzw. Methoden mit +Mockito "weggemockt" werden, so dass die vorgegebenen Testmethoden (wieder) +funktionieren. + + +::: diff --git a/lecture/quality/readme.md b/lecture/quality/readme.md index 2af8399c5..4058439e9 100644 --- a/lecture/quality/readme.md +++ b/lecture/quality/readme.md @@ -1,5 +1,6 @@ --- -title: "Softwarequalität und Testen mit JUnit und Mockito" -no_pdf: true no_beamer: true +no_pdf: true +title: Softwarequalität und Testen mit JUnit und Mockito --- + diff --git a/lecture/quality/refactoring.md b/lecture/quality/refactoring.md index 8f05aa745..d21cf6228 100644 --- a/lecture/quality/refactoring.md +++ b/lecture/quality/refactoring.md @@ -1,217 +1,101 @@ --- -title: "Refactoring" -author: "Carsten Gips (HSBI)" -readings: - - "@Fowler2011" - - "@Inden2013 [Kap. 11]" -tldr: | - Refactoring bedeutet Änderung der inneren Struktur des Codes ohne Beeinflussung äußeren Verhaltens. - - Mit Hilfe von Refactoring kann man Code Smells beheben, und Lesbarkeit, Verständlichkeit und Wartbarkeit - von Software verbessern. - - Es ist wichtig, immer nur einzelne Schritte zu machen und anschließend die Testsuite laufen zu lassen, - damit nicht versehentlich Fehler oder Verhaltensänderungen beim Refactoring eingebaut werden. - - Prinzipiell kann man Refactoring manuell mit Search&Replace durchführen, aber es bietet sich an, hier - die IDE-Unterstützung zu nutzen. Es stehen verschiedene Methoden zur Verfügung, die nicht unbedingt - einheitlich benannt sein müssen oder in jeder IDE vorkommen. Zu den häufig genutzten Methoden zählen - _Rename_, _Extract_, _Move_ und _Push Up/Pull Down_. -outcomes: - - k2: "Ich kann den Begriff sowie die Notwendigkeit und das Vorgehen des/beim Refactoring erklären" - - k2: "Ich kann die Bedeutung kleiner Schritte beim Refactoring erklären" - - k2: "Ich kann die Bedeutung einer sinnvollen Testsuite beim Refactoring erklären" - - k2: "Ich habe verstanden, dass 'Refactoring' bedeutet: Nur die innere Struktur ändern, nicht das von außen sichtbare Verhalten!" - - k3: "Ich kann die wichtigsten Refactoring-Methoden anwenden: _Rename_, _Extract_, _Move_, _Push Up/Pull Down_" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106231&client_id=FH-Bielefeld" -# name: "Quiz Refactoring (ILIAS)" -youtube: - - link: "https://youtu.be/n0RaQ_Qve0Y" - name: "VL Refactoring" - - link: "https://youtu.be/zZ2RGKRBVz4" - name: "Demo Refactoring: Rename" - - link: "https://youtu.be/PR4mEjBl_No" - name: "Demo Refactoring: Encapsulate" - - link: "https://youtu.be/4VbxgqZ68ng" - name: "Demo Refactoring: Extract Method" - - link: "https://youtu.be/Wr92Oboh05E" - name: "Demo Refactoring: Move Method" - - link: "https://youtu.be/t24c88RshL8" - name: "Demo Refactoring: Pull up" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/36389f8fe4befc6370c28cda4475690224942c00c854e6dfc953b60c26acdf62093345ae1ee0698f71dc0a7f02739253d4ba29b7c05b69036cbb09fb1e361549" - name: "VL Refactoring" -challenges: | - Betrachten Sie das [Theatrical Players Refactoring Kata](https://github.com/emilybache/Theatrical-Players-Refactoring-Kata). - Dort finden Sie im Unterordner [java/](https://github.com/emilybache/Theatrical-Players-Refactoring-Kata/tree/main/java) - einige Klassen mit unübersichtlichem und schlecht strukturierten Code. - - Welche _Bad Smells_ können Sie hier identifizieren? - - Beheben Sie die Smells durch die _schrittweise Anwendung_ von den aus der Vorlesung - bekannten Refactoring-Methoden. Denken Sie auch daran, dass Refactoring immer durch - eine entsprechende Testsuite abgesichert sein muss - ergänzen Sie ggf. die Testfälle. - - - **Real-Life Refactoring** - - Betrachten Sie das folgende Code-Beispiel, welches aus dem [Dungeon-Projekt](https://github.com/Dungeon-CampusMinden/Dungeon) - stammt: - - ```java - public void shootFireBall() { - AmmunitionComponent heroAC = - hero.fetch(AmmunitionComponent.class) - .orElseThrow(() -> MissingComponentException.build(hero, AmmunitionComponent.class)); - if (!heroAC.checkAmmunition()) return; - utils.Direction viewDirection = - convertPosCompDirectionToUtilsDirection(EntityUtils.getViewDirection(hero)); - Skill fireball = - new Skill( - new FireballSkill( - () -> { - CollideComponent collider = - hero.fetch(CollideComponent.class) - .orElseThrow( - () -> MissingComponentException.build(hero, CollideComponent.class)); - Point start = collider.center(hero); - return start.add(new Point(viewDirection.x(), viewDirection.y())); - }), - 1); - fireball.execute(hero); - heroAC.spendAmmo(); - waitDelta(); - } - ``` - - 1. Analysieren Sie den Code und versuchen Sie zu verstehen, was hier passiert. Welche Typen müssen - die unterschiedlichen Operationen haben (Parametertypen, Rückgabetypen)? - 2. Welche Funktionseinheiten können Sie identifizieren? Entwickeln Sie Ideen, wie dieser Code zu - mittels Refactoring lesbarer und verständlicher gemacht werden kann. Nutzen Sie dabei möglichst - geschickt u.a. die Java-Stream-API und Optionals und überlegen Sie, welche der Exceptions wirklich - relevant sind ... - 3. Kopieren Sie den Code in einen Editor (mit Syntaxunterstützung - die IDE geht auch, wird aber - wegen der fehlenden Klassen und Variablen keine wirkliche Hilfe sein). Führen Sie das Refactoring - "zu Fuß" durch. Vergleichen Sie Ihr Ergebnis und den ursprünglichen Code. - - (*Sie brauchen im Dungeon-Projekt nicht zu suchen, diese Code-Stelle ist längst bereinigt ...*) +author: Carsten Gips (HSBI) +title: Refactoring +--- - ---- +Prinzipiell kann man Refactoring manuell mit Search&Replace durchführen, aber es +bietet sich an, hier die IDE-Unterstützung zu nutzen. Es stehen verschiedene +Methoden zur Verfügung, die nicht unbedingt einheitlich benannt sein müssen oder in +jeder IDE vorkommen. Zu den häufig genutzten Methoden zählen *Rename*, *Extract*, +*Move* und *Push Up/Pull Down*. +::: +::: youtube +- [VL Refactoring](https://youtu.be/n0RaQ_Qve0Y) +- [Demo Refactoring: Rename](https://youtu.be/zZ2RGKRBVz4) +- [Demo Refactoring: Encapsulate](https://youtu.be/PR4mEjBl_No) +- [Demo Refactoring: Extract Method](https://youtu.be/4VbxgqZ68ng) +- [Demo Refactoring: Move Method](https://youtu.be/Wr92Oboh05E) +- [Demo Refactoring: Pull up](https://youtu.be/t24c88RshL8) +::: # Was ist Refactoring? -> Refactoring ist, wenn einem auffällt, daß der Funktionsname `foobar` -> ziemlich bescheuert ist, und man die Funktion in `sinus` umbenennt. +> Refactoring ist, wenn einem auffällt, daß der Funktionsname `foobar` ziemlich +> bescheuert ist, und man die Funktion in `sinus` umbenennt. > -> \hfill\ [["356: Refactoring"](http://altlasten.lutz.donnerhacke.de/mitarb/lutz/usenet/Fachbegriffe.der.Informatik.html#356) by [Andreas Bogk](mailto:andreas@andreas.org) on Lutz Donnerhacke: "Fachbegriffe der Informatik"]{.origin} +> `\hfill`{=tex} [["356: +> Refactoring"](http://altlasten.lutz.donnerhacke.de/mitarb/lutz/usenet/Fachbegriffe.der.Informatik.html#356) +> by [Andreas Bogk](mailto:andreas@andreas.org) on Lutz Donnerhacke: "Fachbegriffe +> der Informatik"]{.origin} \pause \bigskip \vfill -> Refactoring (noun): a change made to the internal structure of software to make -> it easier to understand and cheaper to modify without changing its observable +> Refactoring (noun): a change made to the internal structure of software to make it +> easier to understand and cheaper to modify without changing its observable > behaviour. > -> \hfill\ [@Fowler2011, p. 53] +> `\hfill`{=tex} [@Fowler2011, p. 53] ::: notes **Refactoring:** Änderungen an der **inneren Struktur** einer Software -* Beobachtbares (äußeres) Verhalten ändert sich dabei **nicht** - * Keine neuen Features einführen - * Keine Bugs fixen - * Keine öffentliche Schnittstelle ändern (_Anmerkung_: Bis auf Umbenennungen +- Beobachtbares (äußeres) Verhalten ändert sich dabei **nicht** + - Keine neuen Features einführen + - Keine Bugs fixen + - Keine öffentliche Schnittstelle ändern (*Anmerkung*: Bis auf Umbenennungen oder Verschiebungen von Elementen innerhalb der Software) -* Ziel: Verbesserung von Verständlichkeit und Änderbarkeit +- Ziel: Verbesserung von Verständlichkeit und Änderbarkeit ::: - # Anzeichen, dass Refactoring jetzt eine gute Idee wäre -* Code "stinkt" (zeigt/enthält _Code Smells_) +- Code "stinkt" (zeigt/enthält *Code Smells*) ::: notes - Code Smells sind strukturelle Probleme, die im Laufe der Zeit zu - Problemen führen können. Refactoring ändert die innere Struktur - des Codes und kann entsprechend genutzt werden, um die Smells zu - beheben. + Code Smells sind strukturelle Probleme, die im Laufe der Zeit zu Problemen + führen können. Refactoring ändert die innere Struktur des Codes und kann + entsprechend genutzt werden, um die Smells zu beheben. ::: \bigskip -* Schwer erklärbarer Code +- Schwer erklärbarer Code ::: notes - Könnten Sie Ihren Code ohne Vorbereitung in der Abgabe erklären? - In einer Minute? In fünf Minuten? In zehn? Gar nicht? + Könnten Sie Ihren Code ohne Vorbereitung in der Abgabe erklären? In einer + Minute? In fünf Minuten? In zehn? Gar nicht? - In den letzten beiden Fällen sollten Sie definitiv über eine - Vereinfachung der Strukturen nachdenken. + In den letzten beiden Fällen sollten Sie definitiv über eine Vereinfachung der + Strukturen nachdenken. ::: -* Verständnisprobleme, Erweiterungen +- Verständnisprobleme, Erweiterungen ::: notes Sie grübeln in der Abgabe, was Ihr Code machen sollte? - Sie überlegen, was Ihr Code bedeutet, um herauszufinden, wo Sie - die neue Funktionalität anbauen können? + Sie überlegen, was Ihr Code bedeutet, um herauszufinden, wo Sie die neue + Funktionalität anbauen können? - Sie suchen nach Codeteilen, finden diese aber nicht, da die sich - in anderen (falschen?) Stellen/Klassen befinden? + Sie suchen nach Codeteilen, finden diese aber nicht, da die sich in anderen + (falschen?) Stellen/Klassen befinden? - Nutzen Sie die (neuen) Erkenntnisse, um den Code leichter - verständlich zu gestalten. + Nutzen Sie die (neuen) Erkenntnisse, um den Code leichter verständlich zu + gestalten. ::: \bigskip @@ -220,74 +104,70 @@ challenges: | ::: center > "Three strikes and you refactor." > -> \hfill\ [@Fowler2011, p. 58]: "The Rule of Three" +> `\hfill`{=tex} [@Fowler2011, p. 58]: "The Rule of Three" ::: ::: notes -Wenn Sie sich zum dritten Mal über eine suboptimale Lösung ärgern, dann -werden Sie sich vermutlich noch öfter darüber ärgern. Jetzt ist der -Zeitpunkt für eine Verbesserung. +Wenn Sie sich zum dritten Mal über eine suboptimale Lösung ärgern, dann werden Sie +sich vermutlich noch öfter darüber ärgern. Jetzt ist der Zeitpunkt für eine +Verbesserung. -Schauen Sie sich die entsprechenden Kapitel in [@Passig2013] und [@Fowler2011] -an, dort finden Sie noch viele weitere Anhaltspunkte, ob und wann Refactoring -sinnvoll ist. +Schauen Sie sich die entsprechenden Kapitel in [@Passig2013] und [@Fowler2011] an, +dort finden Sie noch viele weitere Anhaltspunkte, ob und wann Refactoring sinnvoll +ist. ::: - # Bevor Sie loslegen ... 1. **Unit Tests** schreiben - * Normale und ungültige Eingaben - * Rand- und Spezialfälle + - Normale und ungültige Eingaben + - Rand- und Spezialfälle \smallskip 2. **Coding Conventions** einhalten - * Sourcecode formatieren (lassen) + - Sourcecode formatieren (lassen) \bigskip \smallskip 3. Haben Sie die fragliche Codestelle auch wirklich verstanden?! - # Vorgehen beim Refactoring ::: notes ## Überblick über die Methoden des Refactorings Die Refactoring-Methoden sind nicht einheitlich definiert, es existiert ein großer -und uneinheitlicher "Katalog" an möglichen Schritten. Teilweise benennt jede IDE -die Schritte etwas anders, teilweise werden unterschiedliche Möglichkeiten angeboten. +und uneinheitlicher "Katalog" an möglichen Schritten. Teilweise benennt jede IDE die +Schritte etwas anders, teilweise werden unterschiedliche Möglichkeiten angeboten. Zu den am häufigsten genutzten Methoden zählen -* Rename Method/Class/Field -* Encapsulate Field -* Extract Method/Class -* Move Method -* Pull Up, Push Down (Field, Method) +- Rename Method/Class/Field +- Encapsulate Field +- Extract Method/Class +- Move Method +- Pull Up, Push Down (Field, Method) ## Best Practice -Eine Best Practice (oder nennen Sie es einfach eine wichtige Erfahrung) ist, -beim Refactoring langsam und gründlich vorzugehen. Sie ändern die Struktur -der Software und können dabei leicht Fehler oder echte Probleme einbauen. -Gehen Sie also langsam und sorgsam vor, machen Sie einen Schritt nach dem -anderen und sichern Sie sich durch eine gute Testsuite ab, die Sie nach jedem -Schritt erneut ausführen: Das Verhalten der Software soll sich ja nicht -ändern, d.h. die Tests müssen nach jedem einzelnen Refactoring-Schritt immer -grün sein (oder Sie haben einen Fehler gemacht). +Eine Best Practice (oder nennen Sie es einfach eine wichtige Erfahrung) ist, beim +Refactoring langsam und gründlich vorzugehen. Sie ändern die Struktur der Software +und können dabei leicht Fehler oder echte Probleme einbauen. Gehen Sie also langsam +und sorgsam vor, machen Sie einen Schritt nach dem anderen und sichern Sie sich +durch eine gute Testsuite ab, die Sie nach jedem Schritt erneut ausführen: Das +Verhalten der Software soll sich ja nicht ändern, d.h. die Tests müssen nach jedem +einzelnen Refactoring-Schritt immer grün sein (oder Sie haben einen Fehler gemacht). ::: -* Kleine Schritte: immer nur **eine** Änderung zu einer Zeit +- Kleine Schritte: immer nur **eine** Änderung zu einer Zeit -* Nach **jedem** Refactoring-Schritt **Testsuite** laufen lassen +- Nach **jedem** Refactoring-Schritt **Testsuite** laufen lassen - => Nächster Refactoring-Schritt erst, wenn alle Tests wieder "grün" - -* Versionskontrolle nutzen: **Jeden** Schritt **einzeln** committen + =\> Nächster Refactoring-Schritt erst, wenn alle Tests wieder "grün" +- Versionskontrolle nutzen: **Jeden** Schritt **einzeln** committen # Refactoring-Methode: Rename Method/Class/Field @@ -309,18 +189,16 @@ Aufrufer? Superklassen? **Vorher** -```java +``` java public String getTeN() {} ``` **Nachher** -```java +``` java public String getTelefonNummer() {} ``` - - # Refactoring-Methode: Encapsulate Field ::: notes @@ -341,7 +219,7 @@ Superklassen? Referenzen? (Neue) JUnit-Tests? **Vorher** -```java +``` java int cps; public void printDetails() { @@ -351,7 +229,7 @@ public void printDetails() { **Nachher** -```java +``` java private int cps; int getCps() { return cps; } @@ -362,37 +240,37 @@ public void printDetails() { } ``` - # Refactoring-Methode: Extract Method/Class ::: notes ## Motivation -* Codefragment stellt eigenständige Methode dar -* "Überschriften-Code" -* Code-Duplizierung -* Code ist zu "groß" -* Klasse oder Methode erfüllt unterschiedliche Aufgaben +- Codefragment stellt eigenständige Methode dar +- "Überschriften-Code" +- Code-Duplizierung +- Code ist zu "groß" +- Klasse oder Methode erfüllt unterschiedliche Aufgaben ## Durchführung -Codefragment selektieren, "`Refactor > Extract Method`" bzw. "`Refactor > Extract Class`" +Codefragment selektieren, "`Refactor > Extract Method`" bzw. +"`Refactor > Extract Class`" ## Anschließend ggf. prüfen -* Aufruf der neuen Methode? Nutzung der neuen Klasse? -* Neue JUnit-Tests nötig? Veränderung bestehender Tests nötig? -* Speziell bei Methoden: - * Nutzung lokaler Variablen: Übergabe als Parameter! - * Veränderung lokaler Variablen: Rückgabewert in neuer Methode - und Zuweisung bei Aufruf; evtl. neue Typen nötig! +- Aufruf der neuen Methode? Nutzung der neuen Klasse? +- Neue JUnit-Tests nötig? Veränderung bestehender Tests nötig? +- Speziell bei Methoden: + - Nutzung lokaler Variablen: Übergabe als Parameter! + - Veränderung lokaler Variablen: Rückgabewert in neuer Methode und Zuweisung + bei Aufruf; evtl. neue Typen nötig! ## Beispiel ::: **Vorher** -```java +``` java public void printInfos() { printHeader(); // Details ausgeben @@ -403,7 +281,7 @@ public void printInfos() { **Nachher** -```java +``` java public void printInfos() { printHeader(); printDetails(); @@ -414,34 +292,33 @@ private void printDetails() { } ``` - # Refactoring-Methode: Move Method ::: notes ## Motivation -Methode nutzt (oder wird genutzt von) mehr Eigenschaften einer -fremden Klasse als der eigenen Klasse. +Methode nutzt (oder wird genutzt von) mehr Eigenschaften einer fremden Klasse als +der eigenen Klasse. ## Durchführung -Methode selektieren, "`Refactor > Move`" -(ggf. "Keep original method as delegate to moved method" aktivieren) +Methode selektieren, "`Refactor > Move`" (ggf. "Keep original method as delegate to +moved method" aktivieren) ## Anschließend ggf. prüfen -* Aufruf der neuen Methode (Delegation)? -* Neue JUnit-Tests nötig? Veränderung bestehender Tests nötig? -* Nutzung lokaler Variablen: Übergabe als Parameter! -* Veränderung lokaler Variablen: Rückgabewert in neuer Methode - und Zuweisung bei Aufruf; evtl. neue Typen nötig! +- Aufruf der neuen Methode (Delegation)? +- Neue JUnit-Tests nötig? Veränderung bestehender Tests nötig? +- Nutzung lokaler Variablen: Übergabe als Parameter! +- Veränderung lokaler Variablen: Rückgabewert in neuer Methode und Zuweisung bei + Aufruf; evtl. neue Typen nötig! ## Beispiel ::: **Vorher** -```java +``` java public class Kurs { int cps; String descr; @@ -465,7 +342,7 @@ public class Studi extends Person { **Nachher** -```java +``` java public class Kurs { int cps; String descr; @@ -485,15 +362,14 @@ public class Studi extends Person { } ``` - # Refactoring-Methode: Pull Up, Push Down (Field, Method) ::: notes ## Motivation -* Attribut/Methode nur für die Oberklasse relevant: **Pull Up** -* Subklassen haben identische Attribute/Methoden: **Pull Up** -* Attribut/Methode nur für eine Subklasse relevant: **Push Down** +- Attribut/Methode nur für die Oberklasse relevant: **Pull Up** +- Subklassen haben identische Attribute/Methoden: **Pull Up** +- Attribut/Methode nur für eine Subklasse relevant: **Push Down** ## Durchführung @@ -508,7 +384,7 @@ Referenzen/Aufrufer? JUnit-Tests? **Vorher** -```java +``` java public class Person { } public class Studi extends Person { @@ -519,7 +395,7 @@ public class Studi extends Person { **Nachher** -```java +``` java public class Person { protected String name; } public class Studi extends Person { @@ -527,19 +403,144 @@ public class Studi extends Person { } ``` - # Wrap-Up Behebung von **Bad Smells** durch **Refactoring** \smallskip -=> Änderung der inneren Struktur ohne Beeinflussung des äußeren Verhaltens +=\> Änderung der inneren Struktur ohne Beeinflussung des äußeren Verhaltens \bigskip -* Verbessert Lesbarkeit, Verständlichkeit, Wartbarkeit -* Immer nur kleine Schritte machen -* Nach jedem Schritt Testsuite laufen lassen -* Katalog von Maßnahmen, beispielsweise _Rename_, _Extract_, _Move_, _Push Up/Pull Down_, ... -* Unterstützung durch IDEs wie Eclipse, Idea, ... +- Verbessert Lesbarkeit, Verständlichkeit, Wartbarkeit +- Immer nur kleine Schritte machen +- Nach jedem Schritt Testsuite laufen lassen +- Katalog von Maßnahmen, beispielsweise *Rename*, *Extract*, *Move*, *Push Up/Pull + Down*, ... +- Unterstützung durch IDEs wie Eclipse, Idea, ... + +::: readings +- @Fowler2011 +- @Inden2013 [Kap. 11] +::: + +::: outcomes +- k2: Ich kann den Begriff sowie die Notwendigkeit und das Vorgehen des/beim + Refactoring erklären +- k2: Ich kann die Bedeutung kleiner Schritte beim Refactoring erklären +- k2: Ich kann die Bedeutung einer sinnvollen Testsuite beim Refactoring erklären +- k2: Ich habe verstanden, dass 'Refactoring' bedeutet: Nur die innere Struktur + ändern, nicht das von außen sichtbare Verhalten! +- k3: Ich kann die wichtigsten Refactoring-Methoden anwenden: Rename, Extract, + Move, Push Up/Pull Down +::: + +::: challenges +Betrachten Sie das [Theatrical Players Refactoring +Kata](https://github.com/emilybache/Theatrical-Players-Refactoring-Kata). Dort +finden Sie im Unterordner +[java/](https://github.com/emilybache/Theatrical-Players-Refactoring-Kata/tree/main/java) +einige Klassen mit unübersichtlichem und schlecht strukturierten Code. + +Welche *Bad Smells* können Sie hier identifizieren? + +Beheben Sie die Smells durch die *schrittweise Anwendung* von den aus der Vorlesung +bekannten Refactoring-Methoden. Denken Sie auch daran, dass Refactoring immer durch +eine entsprechende Testsuite abgesichert sein muss - ergänzen Sie ggf. die +Testfälle. + +**Real-Life Refactoring** + +Betrachten Sie das folgende Code-Beispiel, welches aus dem +[Dungeon-Projekt](https://github.com/Dungeon-CampusMinden/Dungeon) stammt: + +``` java +public void shootFireBall() { + AmmunitionComponent heroAC = + hero.fetch(AmmunitionComponent.class) + .orElseThrow(() -> MissingComponentException.build(hero, AmmunitionComponent.class)); + if (!heroAC.checkAmmunition()) return; + utils.Direction viewDirection = + convertPosCompDirectionToUtilsDirection(EntityUtils.getViewDirection(hero)); + Skill fireball = + new Skill( + new FireballSkill( + () -> { + CollideComponent collider = + hero.fetch(CollideComponent.class) + .orElseThrow( + () -> MissingComponentException.build(hero, CollideComponent.class)); + Point start = collider.center(hero); + return start.add(new Point(viewDirection.x(), viewDirection.y())); + }), + 1); + fireball.execute(hero); + heroAC.spendAmmo(); + waitDelta(); +} +``` + +1. Analysieren Sie den Code und versuchen Sie zu verstehen, was hier passiert. + Welche Typen müssen die unterschiedlichen Operationen haben (Parametertypen, + Rückgabetypen)? +2. Welche Funktionseinheiten können Sie identifizieren? Entwickeln Sie Ideen, wie + dieser Code zu mittels Refactoring lesbarer und verständlicher gemacht werden + kann. Nutzen Sie dabei möglichst geschickt u.a. die Java-Stream-API und + Optionals und überlegen Sie, welche der Exceptions wirklich relevant sind ... +3. Kopieren Sie den Code in einen Editor (mit Syntaxunterstützung - die IDE geht + auch, wird aber wegen der fehlenden Klassen und Variablen keine wirkliche Hilfe + sein). Führen Sie das Refactoring "zu Fuß" durch. Vergleichen Sie Ihr Ergebnis + und den ursprünglichen Code. + +(*Sie brauchen im Dungeon-Projekt nicht zu suchen, diese Code-Stelle ist längst +bereinigt ...*) + + +::: diff --git a/lecture/quality/smells.md b/lecture/quality/smells.md index 43605dec9..b540ee201 100644 --- a/lecture/quality/smells.md +++ b/lecture/quality/smells.md @@ -1,42 +1,28 @@ --- -title: "Code Smells" -author: "Carsten Gips (HSBI)" -readings: - - "@Martin2009" - - "@Passig2013" - - "@Inden2013 [Kap. 10]" -tldr: | - Code entsteht nicht zum Selbstzweck, er muss von anderen Menschen leicht verstanden und - gewartet werden können: Entwickler verbringen einen wesentlichen Teil ihrer Zeit mit dem - **Lesen** von (fremdem) Code. Dabei helfen "Coding Conventions", die eine gewisse einheitliche - äußerliche Erscheinung des Codes vorgeben (Namen, Einrückungen, ...). Die Beachtung von - grundlegenden Programmierprinzipien hilft ebenso, die Lesbarkeit und Verständlichkeit zu - verbessern. - - Code, der diese Konventionen und Regeln verletzt, zeigt sogenannte "**Code Smells**" oder - "Bad Smells". Das sind Probleme im Code, die noch nicht direkt zu einem Fehler führen, die - aber im Laufe der Zeit die Chance für echte Probleme deutlich erhöht. -outcomes: - - k2: "Ich kann typische Programmierprinzipien wie 'Information Hiding', 'DRY', 'Single Responsibility' erklären" - - k3: "Ich kann typische Code Smells erkennen und vermeiden" - - k3: "Ich kann leicht lesbaren von schwer lesbarem Code unterscheiden" - - k3: "Ich kann Programmierprinzipien anwenden, um den Code sauberer zu gestalten" - - k3: "Ich kann sinnvolle Kommentare schreiben" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106230&client_id=FH-Bielefeld" -# name: "Quiz Code Smells (ILIAS)" -youtube: - - link: "https://youtu.be/ALDuLxm71tg" - name: "VL Code Smells" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/9d53c3536ada817eee76c9f6e8f7838bfb2308a79d220b8dab0a3cf339f90c52e28ae25e85a652d6da31c8f38c581463bc1679fa5f9cc376115558f7fee41217" - name: "VL Code Smells" +author: Carsten Gips (HSBI) +title: Code Smells --- +::: tldr +Code entsteht nicht zum Selbstzweck, er muss von anderen Menschen leicht verstanden +und gewartet werden können: Entwickler verbringen einen wesentlichen Teil ihrer Zeit +mit dem **Lesen** von (fremdem) Code. Dabei helfen "Coding Conventions", die eine +gewisse einheitliche äußerliche Erscheinung des Codes vorgeben (Namen, Einrückungen, +...). Die Beachtung von grundlegenden Programmierprinzipien hilft ebenso, die +Lesbarkeit und Verständlichkeit zu verbessern. + +Code, der diese Konventionen und Regeln verletzt, zeigt sogenannte "**Code Smells**" +oder "Bad Smells". Das sind Probleme im Code, die noch nicht direkt zu einem Fehler +führen, die aber im Laufe der Zeit die Chance für echte Probleme deutlich erhöht. +::: + +::: youtube +- [VL Code Smells](https://youtu.be/ALDuLxm71tg) +::: # Code Smells: Ist das Code oder kann das weg? -```java +``` java class checker { static public void CheckANDDO(DATA1 inp, int c, FH.Studi CustD, int x, int y, int in, int out,int c1, int c2, int c3 = 4) @@ -66,18 +52,18 @@ möglicherweise tut er sogar das, was er tun soll. Dennoch: **Der Code "stinkt"** (zeigt **Code Smells**): -* Nichtbeachtung üblicher Konventionen (Coding Rules) -* Schlechte Kommentare -* Auskommentierter Code -* Fehlende Datenkapselung -* Zweifelhafte Namen -* Duplizierter Code -* "Langer" Code: Lange Methoden, Klassen, Parameterlisten, tief - verschachtelte `if/then`-Bedingungen, ... -* Feature Neid -* `switch/case` oder `if/else` statt Polymorphie -* Globale Variablen, lokale Variablen als Attribut -* Magic Numbers +- Nichtbeachtung üblicher Konventionen (Coding Rules) +- Schlechte Kommentare +- Auskommentierter Code +- Fehlende Datenkapselung +- Zweifelhafte Namen +- Duplizierter Code +- "Langer" Code: Lange Methoden, Klassen, Parameterlisten, tief verschachtelte + `if/then`-Bedingungen, ... +- Feature Neid +- `switch/case` oder `if/else` statt Polymorphie +- Globale Variablen, lokale Variablen als Attribut +- Magic Numbers Diese Liste enthält die häufigsten "Smells" und ließe sich noch beliebig fortsetzen. Schauen Sie mal in die unten angegebene Literatur :-) @@ -85,58 +71,58 @@ Schauen Sie mal in die unten angegebene Literatur :-) **Stinkender Code führt zu möglichen (späteren) Problemen.** ::: - # Was ist guter ("sauberer") Code ("Clean Code")? ::: notes -Im Grunde bezeichnet "sauberer Code" ("Clean Code") die Abwesenheit von Smells. D.h. man -könnte Code als "sauberen" Code bezeichnen, wenn die folgenden Eigenschaften erfüllt sind -(keine vollständige Aufzählung!): +Im Grunde bezeichnet "sauberer Code" ("Clean Code") die Abwesenheit von Smells. D.h. +man könnte Code als "sauberen" Code bezeichnen, wenn die folgenden Eigenschaften +erfüllt sind (keine vollständige Aufzählung!): ::: -* Gut ("angenehm") lesbar -* Schnell verständlich: Geeignete Abstraktionen -* Konzentriert sich auf **eine** Aufgabe -* So einfach und direkt wie möglich -* Ist gut getestet +- Gut ("angenehm") lesbar +- Schnell verständlich: Geeignete Abstraktionen +- Konzentriert sich auf **eine** Aufgabe +- So einfach und direkt wie möglich +- Ist gut getestet ::: notes -In [@Martin2009] lässt der Autor Robert Martin verschiedene Ikonen der SW-Entwicklung -zu diesem Thema zu Wort kommen - eine sehr lesenswerte Lektüre! +In [@Martin2009] lässt der Autor Robert Martin verschiedene Ikonen der +SW-Entwicklung zu diesem Thema zu Wort kommen - eine sehr lesenswerte Lektüre! ::: \bigskip \bigskip -=> Jemand kümmert sich um den Code; solides Handwerk +=\> Jemand kümmert sich um den Code; solides Handwerk # Warum ist guter ("sauberer") Code so wichtig? -> Any fool can write code that a computer can understand. -> Good programmers write code that humans can understand. +> Any fool can write code that a computer can understand. Good programmers write +> code that humans can understand. > -> \hfill\ [[@Fowler2011, p. 15]]{.origin} +> `\hfill`{=tex} [[@Fowler2011, p. 15]]{.origin} -::::::::: notes +::: notes Auch wenn das zunächst seltsam klingt, aber Code muss auch von Menschen gelesen und -verstanden werden können. Klar, der Code muss inhaltlich korrekt sein und die jeweilige -Aufgabe erfüllen, er muss kompilieren etc. ... aber er muss auch von anderen Personen -weiter entwickelt werden und dazu gelesen und verstanden werden. Guter Code ist nicht -einfach nur inhaltlich korrekt, sondern kann auch einfach verstanden werden. +verstanden werden können. Klar, der Code muss inhaltlich korrekt sein und die +jeweilige Aufgabe erfüllen, er muss kompilieren etc. ... aber er muss auch von +anderen Personen weiter entwickelt werden und dazu gelesen und verstanden werden. +Guter Code ist nicht einfach nur inhaltlich korrekt, sondern kann auch einfach +verstanden werden. Code, der nicht einfach lesbar ist oder nur schwer verständlich ist, wird oft in der -Praxis später nicht gut gepflegt: Andere Entwickler haben (die berechtigte) Angst, etwas -kaputt zu machen und arbeiten "um den Code herum". Nur leider wird das Konstrukt dann -nur noch schwerer verständlich ... +Praxis später nicht gut gepflegt: Andere Entwickler haben (die berechtigte) Angst, +etwas kaputt zu machen und arbeiten "um den Code herum". Nur leider wird das +Konstrukt dann nur noch schwerer verständlich ... ## Code Smells -Verstöße gegen die Prinzipien von _Clean Code_ nennt man auch _Code Smells_: Der +Verstöße gegen die Prinzipien von *Clean Code* nennt man auch *Code Smells*: Der Code "stinkt" gewissermaßen. Dies bedeutet nicht unbedingt, dass der Code nicht -funktioniert (d.h. er kann dennoch compilieren und die Anforderungen erfüllen). -Er ist nur nicht sauber formuliert, schwer verständlich, enthält Doppelungen etc., -was im Laufe der Zeit die Chance für tatsächliche Probleme deutlich erhöht. -::::::::: +funktioniert (d.h. er kann dennoch compilieren und die Anforderungen erfüllen). Er +ist nur nicht sauber formuliert, schwer verständlich, enthält Doppelungen etc., was +im Laufe der Zeit die Chance für tatsächliche Probleme deutlich erhöht. +::: \pause \bigskip @@ -148,142 +134,140 @@ was im Laufe der Zeit die Chance für tatsächliche Probleme deutlich erhöht. **Stinkender Code führt zu möglichen (späteren) Problemen.** ::: -::::::::: notes +::: notes ## "Broken Windows" Phänomen Wenn ein Gebäude leer steht, wird es eine gewisse Zeit lang nur relativ langsam verfallen: Die Fenster werden nicht mehr geputzt, es sammelt sich Graffiti, Gras wächst in der Dachrinne, Putz blättert ab ... -Irgendwann wird dann eine Scheibe eingeworfen. Wenn dieser Punkt überschritten -ist, beschleunigt sich der Verfall rasant: Über Nacht werden alle erreichbaren -Scheiben eingeworfen, Türen werden zerstört, es werden sogar Brände gelegt ... - -Das passiert auch bei Software! Wenn man als Entwickler das Gefühl bekommt, -die Software ist nicht gepflegt, wird man selbst auch nur relativ schlechte -Arbeit abliefern. Sei es, weil man nicht versteht, was der Code macht und sich -nicht an die Überarbeitung der richtigen Stellen traut und stattdessen die -Änderungen als weiteren "Erker" einfach dran pappt. Seit es, weil man keine Lust -hat, Zeit in ordentliche Arbeit zu investieren, weil der Code ja eh schon -schlecht ist ... Das wird mit der Zeit nicht besser ... -::::::::: +Irgendwann wird dann eine Scheibe eingeworfen. Wenn dieser Punkt überschritten ist, +beschleunigt sich der Verfall rasant: Über Nacht werden alle erreichbaren Scheiben +eingeworfen, Türen werden zerstört, es werden sogar Brände gelegt ... + +Das passiert auch bei Software! Wenn man als Entwickler das Gefühl bekommt, die +Software ist nicht gepflegt, wird man selbst auch nur relativ schlechte Arbeit +abliefern. Sei es, weil man nicht versteht, was der Code macht und sich nicht an die +Überarbeitung der richtigen Stellen traut und stattdessen die Änderungen als +weiteren "Erker" einfach dran pappt. Seit es, weil man keine Lust hat, Zeit in +ordentliche Arbeit zu investieren, weil der Code ja eh schon schlecht ist ... Das +wird mit der Zeit nicht besser ... +::: -["Broken Windows" Phänomen]{.ex href="https://en.wikipedia.org/wiki/Broken_windows_theory"} +["Broken Windows" Phänomen]{.ex +href="https://en.wikipedia.org/wiki/Broken_windows_theory"} -::::::::: notes +::: notes ## Maßeinheit für Code-Qualität ;-) -Es gibt eine "praxisnahe" (und nicht ganz ernst gemeinte) Maßeinheit für Code-Qualität: -Die "WTF/m" (_What the Fuck per minute_): -[Thom Holwerda: www.osnews.com/story/19266/WTFs_](https://www.osnews.com/story/19266/wtfsm/). +Es gibt eine "praxisnahe" (und nicht ganz ernst gemeinte) Maßeinheit für +Code-Qualität: Die "WTF/m" (*What the Fuck per minute*): [Thom Holwerda: +www.osnews.com/story/19266/WTFs](https://www.osnews.com/story/19266/wtfsm/). Wenn beim Code-Review durch Kollegen viele "WTF" kommen, ist der Code offenbar nicht in Ordnung ... -::::::::: - +::: # Code Smells: Nichtbeachtung von Coding Conventions -* Richtlinien für einheitliches Aussehen - => Andere Programmierer sollen Code schnell lesen können - * Namen, Schreibweisen - * Kommentare (Ort, Form, Inhalt) - * Einrückungen und Spaces vs. Tabs - * Zeilenlängen, Leerzeilen - * Klammern +- Richtlinien für einheitliches Aussehen =\> Andere Programmierer sollen Code + schnell lesen können + - Namen, Schreibweisen + - Kommentare (Ort, Form, Inhalt) + - Einrückungen und Spaces vs. Tabs + - Zeilenlängen, Leerzeilen + - Klammern \smallskip -* Beispiele: [Sun Code Conventions](https://www.oracle.com/technetwork/java/codeconventions-150003.pdf), +- Beispiele: [Sun Code + Conventions](https://www.oracle.com/technetwork/java/codeconventions-150003.pdf), [Google Java Style](https://google.github.io/styleguide/javaguide.html) \bigskip -* _Hinweis_: Betrifft vor allem die (äußere) Form! +- *Hinweis*: Betrifft vor allem die (äußere) Form! [[Hinweis: Genauere Betrachtung in "Coding Rules"]{.ex}]{.slides} - # Code Smells: Schlechte Kommentare I -* Ratlose Kommentare +- Ratlose Kommentare - ```java + ``` java /* k.A. was das bedeutet, aber wenn man es raus nimmt, geht's nicht mehr */ /* TODO: was passiert hier, und warum? */ ``` ::: notes - Der Programmierer hat selbst nicht verstanden (und macht sich auch nicht - die Mühe zu verstehen), was er da tut! Fehler sind vorprogrammiert! + Der Programmierer hat selbst nicht verstanden (und macht sich auch nicht die + Mühe zu verstehen), was er da tut! Fehler sind vorprogrammiert! ::: \bigskip -* Redundante Kommentare: +- Redundante Kommentare: - ```java + ``` java public int i; // neues i for(i=0;i<10;i++) // fuer alle i ``` ::: notes - Was würden Sie Ihrem Kollegen erklären (müssen), wenn Sie ihm/ihr den - Code vorstellen? + Was würden Sie Ihrem Kollegen erklären (müssen), wenn Sie ihm/ihr den Code + vorstellen? - Wiederholen Sie nicht, was der Code tut (das kann ich ja selbst lesen), - sondern **beschreiben Sie, was der Code tun _sollte_ und _warum_**. + Wiederholen Sie nicht, was der Code tut (das kann ich ja selbst lesen), sondern + **beschreiben Sie, was der Code tun *sollte* und *warum***. - Beschreiben Sie dabei auch das Konzept hinter einem Codebaustein und gerne - auch die typische Anwendung der Methode oder Klasse. + Beschreiben Sie dabei auch das Konzept hinter einem Codebaustein und gerne auch + die typische Anwendung der Methode oder Klasse. ::: - # Code Smells: Schlechte Kommentare II -* Veraltete Kommentare +- Veraltete Kommentare ::: notes Hinweis auf unsauberes Arbeiten: Oft wird im Zuge der Überarbeitung von - Code-Stellen vergessen, auch den Kommentar anzupassen! Sollte beim Lesen - extrem misstrauisch machen. + Code-Stellen vergessen, auch den Kommentar anzupassen! Sollte beim Lesen extrem + misstrauisch machen. ::: -* Auskommentierter Code +- Auskommentierter Code ::: notes Da ist jemand seiner Sache unsicher bzw. hat eine Überarbeitung nicht - abgeschlossen. Die Chance, dass sich der restliche Code im Laufe der Zeit - so verändert, dass der auskommentierte Code nicht mehr (richtig) läuft, ist - groß! Auskommentierter Code ist gefährlich und dank Versionskontrolle - absolut überflüssig! + abgeschlossen. Die Chance, dass sich der restliche Code im Laufe der Zeit so + verändert, dass der auskommentierte Code nicht mehr (richtig) läuft, ist groß! + Auskommentierter Code ist gefährlich und dank Versionskontrolle absolut + überflüssig! ::: -* Kommentare erscheinen zwingend nötig +- Kommentare erscheinen zwingend nötig ::: notes - Häufig ein Hinweis auf ungeeignete Wahl der Namen (Klassen, Methoden, - Attribute) und/oder auf ein ungeeignetes Abstraktionsniveau (beispielsweise - Nichtbeachtung des Prinzips der "_Single Responsibility_")! + Häufig ein Hinweis auf ungeeignete Wahl der Namen (Klassen, Methoden, Attribute) + und/oder auf ein ungeeignetes Abstraktionsniveau (beispielsweise Nichtbeachtung + des Prinzips der "*Single Responsibility*")! Der Code soll im **Normalfall** für sich selbst sprechen: **WAS** wird gemacht. Der Kommentar erklärt im Normalfall, **WARUM** der Code das machen soll und die Konzepte und Design-Entscheidungen dahinter. ::: -* Unangemessene Information, z.B. Änderungshistorien +- Unangemessene Information, z.B. Änderungshistorien ::: notes - Hinweise wie "wer hat wann was geändert" gehören in das Versionskontroll- - oder ins Issue-Tracking-System. Die Änderung ist im Code sowieso nicht mehr + Hinweise wie "wer hat wann was geändert" gehören in das Versionskontroll- oder + ins Issue-Tracking-System. Die Änderung ist im Code sowieso nicht mehr sichtbar/nachvollziehbar! ::: - # Code Smells: Schlechte Namen und fehlende Kapselung -```java +``` java public class Studi extends Person { public String n; public int c; @@ -293,36 +277,35 @@ public class Studi extends Person { ``` ::: notes -Nach drei Wochen fragen Sie sich, was `n` oder `c` oder `Studi#prtIf()` wohl -sein könnte! (Ein anderer Programmierer fragt sich das schon beim **ersten** -Lesen.) Klassen und Methoden sollten sich erwartungsgemäß verhalten. - -Wenn Dinge öffentlich angeboten werden, muss man damit rechnen, dass andere -darauf zugreifen. D.h. man kann nicht mehr so einfach Dinge wie die interne -Repräsentation oder die Art der Berechnung austauschen! Öffentliche Dinge -gehören zur Schnittstelle und damit Teil des "Vertrags" mit den Nutzern! +Nach drei Wochen fragen Sie sich, was `n` oder `c` oder `Studi#prtIf()` wohl sein +könnte! (Ein anderer Programmierer fragt sich das schon beim **ersten** Lesen.) +Klassen und Methoden sollten sich erwartungsgemäß verhalten. + +Wenn Dinge öffentlich angeboten werden, muss man damit rechnen, dass andere darauf +zugreifen. D.h. man kann nicht mehr so einfach Dinge wie die interne Repräsentation +oder die Art der Berechnung austauschen! Öffentliche Dinge gehören zur Schnittstelle +und damit Teil des "Vertrags" mit den Nutzern! ::: \bigskip -* Programmierprinzip "**Prinzip der minimalen Verwunderung**" +- Programmierprinzip "**Prinzip der minimalen Verwunderung**" ::: notes - * Klassen und Methoden sollten sich erwartungsgemäß verhalten - * Gute Namen ersparen das Lesen der Dokumentation + - Klassen und Methoden sollten sich erwartungsgemäß verhalten + - Gute Namen ersparen das Lesen der Dokumentation ::: -* Programmierprinzip "**Kapselung/Information Hiding**" +- Programmierprinzip "**Kapselung/Information Hiding**" ::: notes - * Möglichst schlanke öffentliche Schnittstelle - * => "Vertrag" mit Nutzern der Klasse! + - Möglichst schlanke öffentliche Schnittstelle + - =\> "Vertrag" mit Nutzern der Klasse! ::: - # Code Smells: Duplizierter Code -```java +``` java public class Studi { public String getName() { return name; } public String getAddress() { @@ -337,40 +320,35 @@ public class Studi { \bigskip -* Programmierprinzip "**DRY**" => "Don't repeat yourself!" +- Programmierprinzip "**DRY**" =\> "Don't repeat yourself!" ::: notes -Im Beispiel wird das Formatieren der Adresse mehrfach identisch implementiert, -d.h. duplizierter Code. Auslagern in eigene Methode und aufrufen! +Im Beispiel wird das Formatieren der Adresse mehrfach identisch implementiert, d.h. +duplizierter Code. Auslagern in eigene Methode und aufrufen! Kopierter/duplizierter Code ist problematisch: -* Spätere Änderungen müssen an mehreren Stellen vorgenommen werden -* Lesbarkeit/Orientierung im Code wird erschwert (Analogie: Reihenhaussiedlung) -* Verpasste Gelegenheit für sinnvolle Abstraktion! +- Spätere Änderungen müssen an mehreren Stellen vorgenommen werden +- Lesbarkeit/Orientierung im Code wird erschwert (Analogie: Reihenhaussiedlung) +- Verpasste Gelegenheit für sinnvolle Abstraktion! ::: - # Code Smells: Langer Code -* Lange Klassen - * Faustregel: 5 Bildschirmseiten sind viel - -* Lange Methoden - * Faustregel: 1 Bildschirmseite - * [@Martin2009]: deutlich weniger als 20 Zeilen - -* Lange Parameterlisten - * Faustregel: max. 3 ... 5 Parameter - * [@Martin2009]: 0 Parameter ideal, ab 3 Parameter - gute Begründung nötig - -* Tief verschachtelte `if/then`-Bedingungen - * Faustregel: 2 ... 3 Einrückungsebenen sind viel +- Lange Klassen + - Faustregel: 5 Bildschirmseiten sind viel +- Lange Methoden + - Faustregel: 1 Bildschirmseite + - [@Martin2009]: deutlich weniger als 20 Zeilen +- Lange Parameterlisten + - Faustregel: max. 3 ... 5 Parameter + - [@Martin2009]: 0 Parameter ideal, ab 3 Parameter gute Begründung nötig +- Tief verschachtelte `if/then`-Bedingungen + - Faustregel: 2 ... 3 Einrückungsebenen sind viel \bigskip -* Programmierprinzip "**Single Responsibility**" +- Programmierprinzip "**Single Responsibility**" ::: notes Jede Klasse ist für genau **einen Aspekt** des Gesamtsystems verantwortlich @@ -378,25 +356,25 @@ Kopierter/duplizierter Code ist problematisch: \bigskip -::::::::: notes +::: notes ## Lesbarkeit und Übersichtlichkeit leiden -* Der Mensch kann sich nur begrenzt viele Dinge im Kurzzeitgedächtnis merken -* Klassen, die länger als 5 Bildschirmseiten sind, erfordern viel Hin- und +- Der Mensch kann sich nur begrenzt viele Dinge im Kurzzeitgedächtnis merken +- Klassen, die länger als 5 Bildschirmseiten sind, erfordern viel Hin- und Her-Scrollen, dito für lange Methoden -* Lange Methoden sind schwer verständlich (erledigen viele Dinge?) -* Mehr als 3 Parameter kann sich kaum jemand merken, vor allem beim - Aufruf von Methoden -* Die Testbarkeit wird bei zu komplexen Methoden/Klassen und vielen Parametern +- Lange Methoden sind schwer verständlich (erledigen viele Dinge?) +- Mehr als 3 Parameter kann sich kaum jemand merken, vor allem beim Aufruf von + Methoden +- Die Testbarkeit wird bei zu komplexen Methoden/Klassen und vielen Parametern sehr erschwert -* Große Dateien verleiten (auch mangels Übersichtlichkeit) dazu, neuen - Code ebenfalls schluderig zu gliedern +- Große Dateien verleiten (auch mangels Übersichtlichkeit) dazu, neuen Code + ebenfalls schluderig zu gliedern ## Langer Code deutet auch auf eine Verletzung des Prinzips der Single Responsibility hin -* Klassen fassen evtl. nicht zusammengehörende Dinge zusammen +- Klassen fassen evtl. nicht zusammengehörende Dinge zusammen - ```java + ``` java public class Student { private String name; private String phoneAreaCode; @@ -410,12 +388,12 @@ Kopierter/duplizierter Code ist problematisch: ``` Warum sollte sich die Klasse `Student` um die Einzelheiten des Aufbaus einer - Telefonnummer kümmern? Das Prinzip der "_Single Responsibility_" wird hier + Telefonnummer kümmern? Das Prinzip der "*Single Responsibility*" wird hier verletzt! -* Methoden erledigen vermutlich mehr als nur eine Aufgabe +- Methoden erledigen vermutlich mehr als nur eine Aufgabe - ```java + ``` java public void credits() { for (Student s : students) { if (s.hasSemesterFinished()) { @@ -428,21 +406,20 @@ Kopierter/duplizierter Code ist problematisch: // Diese Methode erledigt 4 Dinge: Iteration, Abfrage, Berechnung, Setzen ... ``` - => Erklären Sie die Methode jemandem. Wenn dabei das Wort "und" - vorkommt, macht die Methode höchstwahrscheinlich zu viel! + =\> Erklären Sie die Methode jemandem. Wenn dabei das Wort "und" vorkommt, macht + die Methode höchstwahrscheinlich zu viel! -* Viele Parameter bedeuten oft fehlende Datenabstraktion +- Viele Parameter bedeuten oft fehlende Datenabstraktion - ```java + ``` java Circle makeCircle(int x, int y, int radius); Circle makeCircle(Point center, int radius); // besser! ``` -::::::::: - +::: # Code Smells: Feature Neid -```java +``` java public class CreditsCalculator { public ECTS calculateEcts(Student s) { int semester = s.getSemester(); @@ -459,50 +436,65 @@ public class CreditsCalculator { ``` ::: notes -* Zugriff auf (viele) Interna der anderen Klasse! => Hohe Kopplung der Klassen! -* Methode `CreditsCalculator#calculateEcts()` "möchte" eigentlich in - `Student` sein ... +- Zugriff auf (viele) Interna der anderen Klasse! =\> Hohe Kopplung der Klassen! +- Methode `CreditsCalculator#calculateEcts()` "möchte" eigentlich in `Student` + sein ... ::: - ::: notes # Weiterführende Links -* ["Foundations: Clean Code" (The Odin Project)](https://www.theodinproject.com/lessons/foundations-clean-code) -* ["Documentation Best Practices" (Google Styleguide)](https://github.com/google/styleguide/blob/gh-pages/docguide/best_practices.md) +- ["Foundations: Clean Code" (The Odin + Project)](https://www.theodinproject.com/lessons/foundations-clean-code) +- ["Documentation Best Practices" (Google + Styleguide)](https://github.com/google/styleguide/blob/gh-pages/docguide/best_practices.md) ::: - # Wrap-Up -* Code entsteht nicht zum Selbstzweck => Lesbarkeit ist wichtig +- Code entsteht nicht zum Selbstzweck =\> Lesbarkeit ist wichtig \bigskip -* Code Smells: Code führt zu möglichen (späteren) Problemen +- Code Smells: Code führt zu möglichen (späteren) Problemen - * Richtiges Kommentieren und Dokumentieren + - Richtiges Kommentieren und Dokumentieren ::: notes - In dieser Sitzung haben wir vor allem auf Kommentare geschaut. Zum Thema Dokumentieren - siehe die Einheit zu ["Javadoc"](javadoc.md). + In dieser Sitzung haben wir vor allem auf Kommentare geschaut. Zum Thema + Dokumentieren siehe die Einheit zu ["Javadoc"](javadoc.md). ::: - * Einhalten von Coding Conventions + - Einhalten von Coding Conventions ::: notes - * Regeln zu Schreibweisen und Layout - * Leerzeichen, Einrückung, Klammern - * Zeilenlänge, Umbrüche - * Kommentare + - Regeln zu Schreibweisen und Layout + - Leerzeichen, Einrückung, Klammern + - Zeilenlänge, Umbrüche + - Kommentare ::: - * Einhalten von Prinzipien des objektorientierten Programmierens + - Einhalten von Prinzipien des objektorientierten Programmierens ::: notes - * Jede Klasse ist für genau **einen** Aspekt des Systems verantwortlich. - (_Single Responsibility_) - * Keine Code-Duplizierung! (_DRY_ - Don't repeat yourself) - * Klassen und Methoden sollten sich erwartungsgemäß verhalten - * Kapselung: Möglichst wenig öffentlich zugänglich machen + - Jede Klasse ist für genau **einen** Aspekt des Systems verantwortlich. + (*Single Responsibility*) + - Keine Code-Duplizierung! (*DRY* - Don't repeat yourself) + - Klassen und Methoden sollten sich erwartungsgemäß verhalten + - Kapselung: Möglichst wenig öffentlich zugänglich machen ::: + +::: readings +- @Martin2009 +- @Passig2013 +- @Inden2013 [Kap. 10] +::: + +::: outcomes +- k2: Ich kann typische Programmierprinzipien wie 'Information Hiding', 'DRY', + 'Single Responsibility' erklären +- k3: Ich kann typische Code Smells erkennen und vermeiden +- k3: Ich kann leicht lesbaren von schwer lesbarem Code unterscheiden +- k3: Ich kann Programmierprinzipien anwenden, um den Code sauberer zu gestalten +- k3: Ich kann sinnvolle Kommentare schreiben +::: diff --git a/lecture/quality/src/tdd/anforderungen.md b/lecture/quality/src/tdd/anforderungen.md index 72a764358..529ddf910 100644 --- a/lecture/quality/src/tdd/anforderungen.md +++ b/lecture/quality/src/tdd/anforderungen.md @@ -1,6 +1,6 @@ # Passwort-Prüfer -+ Passwort ist ein String -+ Passwort darf nicht leer sein -+ Passwort muss länger als 4 Zeichen sein -+ Passwort darf nicht länger als 10 Zeichen sein +- Passwort ist ein String +- Passwort darf nicht leer sein +- Passwort muss länger als 4 Zeichen sein +- Passwort darf nicht länger als 10 Zeichen sein diff --git a/lecture/quality/tdd.md b/lecture/quality/tdd.md index fa615cd66..25ec115b7 100644 --- a/lecture/quality/tdd.md +++ b/lecture/quality/tdd.md @@ -1,45 +1,36 @@ --- -title: "Testbarkeit und Testdriven Development (TDD)" -author: "Carsten Gips (HSBI)" -readings: - - "@Beck2014" - - "@Martin2009" - - "@Fowler2011" -tldr: | - Um Code mit JUnit automatisiert testen zu können, muss beim Software-Entwurf die Testbarkeit - mitgedacht werden. Besonders einfach lassen sich Methoden testen, die das Ergebnis ihrer - Berechnung als Rückgabewert zurückliefern und die keine Seiteneffekte aufweisen. Wenn Methoden - den inneren Zustand des eigenen Objekts ändern, kann man dies nur indirekt über den Aufruf - anderer Methoden testen. Je weniger eine Methode berechnet, um so einfacher lassen sich diese - Berechnungen durch JUnit-Tests überprüfen. - - Auch für (JUnit-) Tests gelten die üblichen Regeln des Software-Entwurfs. Insbesondere sollte - sich ein Testfall auf einen Aspekt konzentrieren und möglichst schnell und möglichst unabhängig - von Datenbank- oder Internetverbindungen o.ä. ausführen lassen. Nur wenn die Testfälle nicht - viel Zeit bei der Ausführung benötigen, wird man sie als Entwickler häufiger nach Änderungen - laufen lassen ... - - Mit "Test-Driven Development" (_TDD_) steht eine Methode bereit, wo zuerst ein Test geschrieben - wird und erst danach der Code. Dabei wird der Code nur so weit implementiert, bis der Test - "grün" ist. Danach erfolgt ggf. eine Umstrukturierung der inneren Code-Strukturen (aber kein - Hinzufügen von Funktionalität), und der Zyklus kann erneut beginnen: Test, Code, Refactoring. - Dies führt dazu, dass man nicht "vergessen" kann, Tests zu schreiben. Außerdem erhält man - häufig den minimal nötigen Code, da ja immer nur die Tests erfüllt werden sollen. -outcomes: - - k3: "Einsatz von TDD bei der Softwareentwicklung" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106232&client_id=FH-Bielefeld" -# name: "Quiz TDD (ILIAS)" -youtube: - - link: "https://youtu.be/oqiESd5N1lY" - name: "VL TDD" - - link: "https://youtu.be/4NAcu8I8fJk" - name: "Demo TDD" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/7001d744808baccc1e1e8ba3194d94fe49f56d92bffbed3f327bb75ad1a872b9356d71c2419c08b2b4950546468ab0a30c31a86fa2654419e5c5c118368415d8" - name: "VL TDD" +author: Carsten Gips (HSBI) +title: Testbarkeit und Testdriven Development (TDD) --- +::: tldr +Um Code mit JUnit automatisiert testen zu können, muss beim Software-Entwurf die +Testbarkeit mitgedacht werden. Besonders einfach lassen sich Methoden testen, die +das Ergebnis ihrer Berechnung als Rückgabewert zurückliefern und die keine +Seiteneffekte aufweisen. Wenn Methoden den inneren Zustand des eigenen Objekts +ändern, kann man dies nur indirekt über den Aufruf anderer Methoden testen. Je +weniger eine Methode berechnet, um so einfacher lassen sich diese Berechnungen durch +JUnit-Tests überprüfen. + +Auch für (JUnit-) Tests gelten die üblichen Regeln des Software-Entwurfs. +Insbesondere sollte sich ein Testfall auf einen Aspekt konzentrieren und möglichst +schnell und möglichst unabhängig von Datenbank- oder Internetverbindungen o.ä. +ausführen lassen. Nur wenn die Testfälle nicht viel Zeit bei der Ausführung +benötigen, wird man sie als Entwickler häufiger nach Änderungen laufen lassen ... + +Mit "Test-Driven Development" (*TDD*) steht eine Methode bereit, wo zuerst ein Test +geschrieben wird und erst danach der Code. Dabei wird der Code nur so weit +implementiert, bis der Test "grün" ist. Danach erfolgt ggf. eine Umstrukturierung +der inneren Code-Strukturen (aber kein Hinzufügen von Funktionalität), und der +Zyklus kann erneut beginnen: Test, Code, Refactoring. Dies führt dazu, dass man +nicht "vergessen" kann, Tests zu schreiben. Außerdem erhält man häufig den minimal +nötigen Code, da ja immer nur die Tests erfüllt werden sollen. +::: + +::: youtube +- [VL TDD](https://youtu.be/oqiESd5N1lY) +- [Demo TDD](https://youtu.be/4NAcu8I8fJk) +::: # Design für Testbarkeit @@ -47,7 +38,7 @@ fhmedia: ## Schlechtes Beispiel ::: -```java +``` java public void ausgabe(...) { ... // mache komplexe Berechnungen ... // berechne nette Formatierungen @@ -64,7 +55,7 @@ public void ausgabe(...) { ## Etwas verbessertes Beispiel ::: -```java +``` java private A berechneA(...) { return ...; // mache komplexe Berechnungen } @@ -78,14 +69,14 @@ public void ausgabe(...) { # Anzeichen für schlecht testbares Design -* Keine Rückgabewerte -* Direkte Ausgaben -* Mehr als ein Zweck: - * Berechnung plus Ausgabe - * Mehrere unterschiedliche Berechnungen - * ... -* Seiteneffekte, z.B. Berechnung und direkte Veränderung von Zuständen **anderer** Objekte - +- Keine Rückgabewerte +- Direkte Ausgaben +- Mehr als ein Zweck: + - Berechnung plus Ausgabe + - Mehrere unterschiedliche Berechnungen + - ... +- Seiteneffekte, z.B. Berechnung und direkte Veränderung von Zuständen **anderer** + Objekte # Private Methoden sind nicht (direkt) testbar @@ -93,13 +84,13 @@ public void ausgabe(...) { ::: notes Private Methoden sind nicht ohne Grund "privat": Sie gehören nicht zur - Schnittstelle einer Klasse. Deshalb können sie jederzeit geändert oder - sogar entfernt werden ... + Schnittstelle einer Klasse. Deshalb können sie jederzeit geändert oder sogar + entfernt werden ... - Andererseits steckt hier Funktionalität drin, die man in irgendeiner - Weise absichern sollte. In "Softwarequalität" werden wir dieses Thema - weiter diskutieren. Oft ist es aber hilfreich, auch für die privaten - Methoden ausreichend Unit-Tests zu haben (Stichwort "Refactoring"). + Andererseits steckt hier Funktionalität drin, die man in irgendeiner Weise + absichern sollte. In "Softwarequalität" werden wir dieses Thema weiter + diskutieren. Oft ist es aber hilfreich, auch für die privaten Methoden + ausreichend Unit-Tests zu haben (Stichwort "Refactoring"). ::: \smallskip @@ -107,16 +98,16 @@ public void ausgabe(...) { 2. Sichtbarkeit auf `protected` setzen; Tests im selben Package unterbringen ::: notes - * Testklassen und getestete Klassen im selben Ordner, oder - * Wahl unterschiedlicher Oberordner: `src` für Sourcecode und - `test` für Testklassen und Spiegelung der Package-Struktur - (Eclipse: Testordner über Project-Properties als Source-Ordner hinzufügen!) + - Testklassen und getestete Klassen im selben Ordner, oder + - Wahl unterschiedlicher Oberordner: `src` für Sourcecode und `test` für + Testklassen und Spiegelung der Package-Struktur (Eclipse: Testordner über + Project-Properties als Source-Ordner hinzufügen!) ::: \smallskip -3. JUnit 4/5: [Testmethoden via]{.notes} Annotationen - => Testmethoden mit Produktionscode mischen +3. JUnit 4/5: [Testmethoden via]{.notes} Annotationen =\> Testmethoden mit + Produktionscode mischen \smallskip @@ -125,15 +116,14 @@ public void ausgabe(...) { ::: notes **Nachteile**: - * Umgang mit Strings statt Klassen! - * Keine Compiler-/IDE-Unterstützung! - * Kein Refactoring! + - Umgang mit Strings statt Klassen! + - Keine Compiler-/IDE-Unterstützung! + - Kein Refactoring! ::: - # Was ist ein guter Test? -* Läuft schnell. Läuft schnell. Läuft schnell!!! +- Läuft schnell. Läuft schnell. Läuft schnell!!! ::: notes Langsame Tests werden nicht oft (genug) ausgeführt! @@ -141,21 +131,22 @@ public void ausgabe(...) { \smallskip -* Testet genau einen Aspekt, wenige Assertions +- Testet genau einen Aspekt, wenige Assertions ::: notes - Wenn der Test fehlschlägt, ist offensichtlich, wo nach dem Problem gesucht werden muss. + Wenn der Test fehlschlägt, ist offensichtlich, wo nach dem Problem gesucht + werden muss. ::: \smallskip -* Separiert Abhängigkeiten (Datenbanken, Netzwerk, ...) +- Separiert Abhängigkeiten (Datenbanken, Netzwerk, ...) ::: notes Ein Test, der von Datenbanken o.ä. abhängt, ist normalerweise nicht schnell - (vgl. Regel Eins). Außerdem können bei einem Test, der noch von anderen - Dingen wie Datenbanken o.ä. abhängt, Fehler auftreten, die mit dem eigentlichen - Tests nichts zu tun haben. + (vgl. Regel Eins). Außerdem können bei einem Test, der noch von anderen Dingen + wie Datenbanken o.ä. abhängt, Fehler auftreten, die mit dem eigentlichen Tests + nichts zu tun haben. Dazu werden Stubs und Mocks (vgl. Wahlfach "Softwarequalität") eingesetzt, um diese Abhängigkeiten "selbst in der Hand zu haben", d.h. zu ersetzen und zu @@ -164,70 +155,77 @@ public void ausgabe(...) { \smallskip -* Absicht ist klar zu verstehen +- Absicht ist klar zu verstehen ::: notes - Jeder Entwickler kann sich den Test anschauen und dadurch verstehen, was - vom Produktionscode erwartet wird. + Jeder Entwickler kann sich den Test anschauen und dadurch verstehen, was vom + Produktionscode erwartet wird. ::: - # TDD oder der "Red-Green-Refactor"-Zyklus ![](images/tdd.png){width="60%" web_width="40%"} ::: notes -## Test-Driven Development (_TDD_) +## Test-Driven Development (*TDD*) -1. Schreibe Test => schlägt fehl: "Red" - * Überlegen Sie, wie das Stück Software, welches Sie erstellen wollen, - funktionieren soll und wie Sie das testen würden, wenn es schon - existieren würde. - * Stellen Sie sich vor, wie der (zu testende) Code aufgerufen wird und +1. Schreibe Test =\> schlägt fehl: "Red" + - Überlegen Sie, wie das Stück Software, welches Sie erstellen wollen, + funktionieren soll und wie Sie das testen würden, wenn es schon existieren + würde. + - Stellen Sie sich vor, wie der (zu testende) Code aufgerufen wird und schreiben Sie den Testfall, als ob der Code schon existieren würde. - * Eclipse wird "meckern", weil es den aufgerufenen Code noch nicht gibt. - Nutzen Sie einfach die vorgeschlagene Lösung von Eclipse und lassen - sich den Methodenrumpf für die zu testende Methode(!) von Eclipse generieren. - * Jetzt compiliert der Code und der Test, aber der Test ist immer noch - "rot". + - Eclipse wird "meckern", weil es den aufgerufenen Code noch nicht gibt. + Nutzen Sie einfach die vorgeschlagene Lösung von Eclipse und lassen sich den + Methodenrumpf für die zu testende Methode(!) von Eclipse generieren. + - Jetzt compiliert der Code und der Test, aber der Test ist immer noch "rot". 2. Schreibe genau soviel Code, dass Test OK wird: "Green" - * Implementieren Sie jetzt die Methode, d.h. füllen Sie den generierten - Methodenrumpf mit "Leben". Aber nur genau so viel Code schreiben, bis - der Test "grün" ist. -3. Überarbeite Code-Struktur: "Refactor" (Refactoring ist Thema einer - [späteren VL](refactoring.md) - * Durch das reine Erfüllen des neuen Test gibt es im Laufe der Zeit sehr + - Implementieren Sie jetzt die Methode, d.h. füllen Sie den generierten + Methodenrumpf mit "Leben". Aber nur genau so viel Code schreiben, bis der + Test "grün" ist. +3. Überarbeite Code-Struktur: "Refactor" (Refactoring ist Thema einer [späteren + VL](refactoring.md) + - Durch das reine Erfüllen des neuen Test gibt es im Laufe der Zeit sehr wahrscheinlich doppelten Code oder zu große Methoden o.ä. ... Dies wird durch diesen Schritt konsequent aufgeräumt. - * Eventuell müssen auch (frühere) Testfälle angepasst werden, die durch - die eben implementierte neue Funktionalität ungültig werden. + - Eventuell müssen auch (frühere) Testfälle angepasst werden, die durch die + eben implementierte neue Funktionalität ungültig werden. ::: \vfill -_Anmerkung_: Jeder Zyklus sollte sehr kurz sein! -[=> Pro Stunde schafft man i.d.R. viele Rot/Grün/Refactoring-Zyklen!]{.notes} +*Anmerkung*: Jeder Zyklus sollte sehr kurz sein! [=\> Pro Stunde schafft man i.d.R. +viele Rot/Grün/Refactoring-Zyklen!]{.notes} [Demo: Validierung von Passwörtern]{.ex href="https://youtu.be/4NAcu8I8fJk"} ::: notes ## Vorteile bei konsequenter Anwendung von TDD -* Kein ungetesteter Code mehr: - Es entstehen automatisch viele Unit-Tests und gibt keinen Grund, keine Tests zu schreiben -* Kein unnötiger Code auf Vorrat: Es wird immer genau ein Test erfüllt -* Konzentration auf das Wesentliche -* Vermeidung von Redundanzen durch Refactoring -* Die Testfälle dienen als Dokumentation, da man von erwartetem Verhalten ausgegangen ist - Veraltet im Gegensatz zu separater Dokumentation nicht -* Da der Entwickler zuerst einen Test schreiben muss, ist die Gefahr, dass - er nicht versteht, was der Produktionscode machen soll, minimiert +- Kein ungetesteter Code mehr: Es entstehen automatisch viele Unit-Tests und gibt + keinen Grund, keine Tests zu schreiben +- Kein unnötiger Code auf Vorrat: Es wird immer genau ein Test erfüllt +- Konzentration auf das Wesentliche +- Vermeidung von Redundanzen durch Refactoring +- Die Testfälle dienen als Dokumentation, da man von erwartetem Verhalten + ausgegangen ist Veraltet im Gegensatz zu separater Dokumentation nicht +- Da der Entwickler zuerst einen Test schreiben muss, ist die Gefahr, dass er + nicht versteht, was der Produktionscode machen soll, minimiert ::: - # Wrap-Up -* Testbarkeit muss mitgedacht werden: SW-Design -* Private Methoden auch testen ("Sicherheitsnetz") -* Auch Tests brauchen SW-Design (Kapselung, Single Responsibility, ...) -* _Test first_: Test-Driven Development (_TDD_) +- Testbarkeit muss mitgedacht werden: SW-Design +- Private Methoden auch testen ("Sicherheitsnetz") +- Auch Tests brauchen SW-Design (Kapselung, Single Responsibility, ...) +- *Test first*: Test-Driven Development (*TDD*) + +::: readings +- @Beck2014 +- @Martin2009 +- @Fowler2011 +::: + +::: outcomes +- k3: Einsatz von TDD bei der Softwareentwicklung +::: diff --git a/lecture/quality/testcases.md b/lecture/quality/testcases.md index 797f433d4..ea5eb3c3a 100644 --- a/lecture/quality/testcases.md +++ b/lecture/quality/testcases.md @@ -1,205 +1,44 @@ --- +author: Carsten Gips (HSBI) title: "Testfallermittlung: Wie viel und was muss man testen?" -author: "Carsten Gips (HSBI)" -readings: - - "@vogellaJUnit" - - "@junit4" - - "@Kleuker2019" - - "@Osherove2014" - - "@Spillner2012" - - "@fernunihagenJunit" -tldr: | - Mit Hilfe der Äquivalenzklassenbildung kann man Testfälle bestimmen. Dabei wird der Eingabebereich - für jeden Parameter einer Methode in Bereiche mit gleichem Verhalten der Methode eingeteilt (die - sogenannten "Äquivalenzklassen"). Dabei können einige Äquivalenzklassen (ÄK) gültigen Eingabebereichen - entsprechen ("gültige ÄK"), also erlaubten/erwarteten Eingaben (die zum gewünschten Verhalten führen), - und die restlichen ÄK entsprechen dann ungültigen Eingabebereichen ("ungültige ÄK"), also nicht - erlaubten Eingaben, die von der Methode zurückgewiesen werden sollten. Jede dieser ÄK muss in mindestens - einem Testfall vorkommen, d.h. man bestimmt einen oder mehrere zufällige Werte in den ÄK. Dabei können - über mehrere Parameter hinweg verschiedene gültige ÄK in einem Testfall kombiniert werden. Bei den - ungültigen ÄK kann dagegen immer nur ein Parameter eine ungültige ÄK haben, für die restlichen Parameter - müssen gültige ÄK genutzt werden, und diese werden dabei als durch diesen Testfall "nicht getestet" - betrachtet. - - Zusätzlich entstehen häufig Fehler bei den Grenzen der Bereiche, etwa in Schleifen. Deshalb führt - man zusätzlich noch eine Grenzwertanalyse durch und bestimmt für jede ÄK den unteren und den oberen - Grenzwert und erzeugt aus diesen Werten zusätzliche Testfälle. - - Wenn in der getesteten Methode der Zustand des Objekts eine Rolle spielt, wird dieser wie ein weiterer - Eingabeparameter für die Methode betrachtet und entsprechend in die ÄK-Bildung bzw. GW-Analyse einbezogen. - - Wenn ein Testfall sich aus den gültigen ÄK/GW speist, spricht man auch von einem "Positiv-Test"; wenn - ungültige ÄK/GW genutzt werden, spricht man auch von einem "Negativ-Test". -outcomes: - - k2: "Ich kann Merkmale schlecht testbaren Codes erklären" - - k2: "Ich kann Merkmale guter Unit-Tests erklären" - - k3: "Ich kann Testfällen mittels Äquivalenzklassenbildung und Grenzwertanalyse erstellen" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106546&client_id=FH-Bielefeld" -# name: "Quiz Testfallermittlung (ILIAS)" -youtube: - - link: "https://youtu.be/AR1WWt4AFqI" - name: "VL Testfallermittlung" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/6719b852fd1ce5516a7110089ca465626826ef1e6d35ab62e8ef0ca5a71e2daab68631e2ff891d9562552ba7fa1ec97033e3d6c2ac65cf22b62377d2ceb4ea37" - name: "VL Testfallermittlung" -challenges: | - **ÄK/GW: RSV Flotte Speiche** - - Der RSV Flotte Speiche hat in seiner Mitgliederverwaltung (`MitgliederVerwaltung`) die Methode - `testBeitritt` implementiert. Mit dieser Methode wird geprüft, ob neue Mitglieder in den Radsportverein - aufgenommen werden können. - - ```java - public class MitgliederVerwaltung { - - /** - * Testet, ob ein Mitglied in den Verein aufgenommen werden kann. - * - *

          Interessierte Personen müssen mindestens 16 Jahre alt sein, um aufgenommen - * werden zu können. Die Motivation darf nicht zu niedrig und auch nicht zu hoch - * sein und muss zwischen 4 und 7 (inklusive) liegen, sonst wird der Antrag - * abgelehnt. - * - *

          Der Wertebereich beim Alter umfasst die natürlichen Zahlen zwischen 0 und 99 - * (inklusive), bei der Motivation sind die natürlichen Zahlen zwischen 0 und 10 - * (inklusive) erlaubt. - * - *

          Bei Verletzung der zulässigen Wertebereiche der Parameter wird eine - * IllegalArgumentException geworfen. - * - * @param alter Alter in Lebensjahren, Bereich [0, 99] - * @param motivation Motivation auf einer Scala von 0 bis 10 - * @return true, wenn das Mitglied aufgenommen werden kann, - * sonst false - * @throws IllegalArgumentException, wenn Parameter außerhalb - * der zulässigen Wertebereiche - */ - public boolean testBeitritt(int alter, int motivation) { - // Implementierung versteckt - } - } - ``` - - 1. Führen Sie eine Äquivalenzklassenbildung durch und geben Sie die gefundenen Äquivalenzklassen (_ÄK_) - an: laufende Nummer, Definition (Wertebereiche o.ä.), kurze Beschreibung (gültige/ungültige ÄK, - Bedeutung). - - 2. Führen Sie zusätzlich eine Grenzwertanalyse durch und geben Sie die jeweiligen Grenzwerte (_GW_) an. - - 3. Erstellen Sie aus den ÄK und GW wie in der Vorlesung diskutiert Testfälle. Geben Sie pro Testfall (_TF_) - an, welche ÄK und/oder GW abgedeckt sind, welche Eingaben Sie vorsehen und welche Ausgabe Sie erwarten. - - _Hinweis_: Erstellen Sie separate (zusätzliche) TF für die GW, d.h. integrieren Sie diese _nicht_ in die - ÄK-TF. - - 4. Implementieren Sie die Testfälle in JUnit (JUnit 4 oder 5). - - Fassen Sie die Testfälle der gültigen ÄK in einem parametrisierten Test zusammen. - - Für die ungültigen ÄKs erstellen Sie jeweils eine eigene JUnit-Testmethode. Beachten - Sie, dass Sie auch die Exceptions testen müssen. - - - - - **ÄK/GW: LSF** - - Das LSF bestimmt mit der Methode `LSF#checkStudentCPS`, ob ein Studierender bereits zur - Bachelorarbeit oder Praxisphase zugelassen werden kann: - - ```java - class LSF { - public static Status checkStudentCPS(Student student) { - if (student.credits() >= Status.BACHELOR.credits) return Status.BACHELOR; - else if (student.credits() >= Status.PRAXIS.credits) return Status.PRAXIS; - else return Status.NONE; - } - } - - record Student(String name, int credits, int semester) { } - - enum Status { - NONE(0), PRAXIS(110), BACHELOR(190); // min: 0, max: 210 - - public final int credits; - Status(int credits) { this.credits = credits; } - } - ``` - - 1. Führen Sie eine Äquivalenzklassenbildung für die Methode `LSF#checkStudentCPS` durch. - 2. Führen Sie zusätzlich eine Grenzwertanalyse für die Methode `LSF#checkStudentCPS` durch. - 3. Erstellen Sie aus den ÄK und GW wie in der Vorlesung diskutiert Testfälle. - 4. Implementieren Sie die Testfälle in JUnit (JUnit 4 oder 5). - - Fassen Sie die Testfälle der gültigen ÄK in einem parametrisierten Test zusammen. - - Für die ungültigen ÄKs erstellen Sie jeweils eine eigene JUnit-Testmethode. Beachten - Sie, dass Sie auch die Exceptions testen müssen. - - --- +::: tldr +Mit Hilfe der Äquivalenzklassenbildung kann man Testfälle bestimmen. Dabei wird der +Eingabebereich für jeden Parameter einer Methode in Bereiche mit gleichem Verhalten +der Methode eingeteilt (die sogenannten "Äquivalenzklassen"). Dabei können einige +Äquivalenzklassen (ÄK) gültigen Eingabebereichen entsprechen ("gültige ÄK"), also +erlaubten/erwarteten Eingaben (die zum gewünschten Verhalten führen), und die +restlichen ÄK entsprechen dann ungültigen Eingabebereichen ("ungültige ÄK"), also +nicht erlaubten Eingaben, die von der Methode zurückgewiesen werden sollten. Jede +dieser ÄK muss in mindestens einem Testfall vorkommen, d.h. man bestimmt einen oder +mehrere zufällige Werte in den ÄK. Dabei können über mehrere Parameter hinweg +verschiedene gültige ÄK in einem Testfall kombiniert werden. Bei den ungültigen ÄK +kann dagegen immer nur ein Parameter eine ungültige ÄK haben, für die restlichen +Parameter müssen gültige ÄK genutzt werden, und diese werden dabei als durch diesen +Testfall "nicht getestet" betrachtet. + +Zusätzlich entstehen häufig Fehler bei den Grenzen der Bereiche, etwa in Schleifen. +Deshalb führt man zusätzlich noch eine Grenzwertanalyse durch und bestimmt für jede +ÄK den unteren und den oberen Grenzwert und erzeugt aus diesen Werten zusätzliche +Testfälle. + +Wenn in der getesteten Methode der Zustand des Objekts eine Rolle spielt, wird +dieser wie ein weiterer Eingabeparameter für die Methode betrachtet und entsprechend +in die ÄK-Bildung bzw. GW-Analyse einbezogen. + +Wenn ein Testfall sich aus den gültigen ÄK/GW speist, spricht man auch von einem +"Positiv-Test"; wenn ungültige ÄK/GW genutzt werden, spricht man auch von einem +"Negativ-Test". +::: + +::: youtube +- [VL Testfallermittlung](https://youtu.be/AR1WWt4AFqI) +::: # Hands-On (10 Minuten): Wieviel und was muss man testen? -```java +``` java public class Studi { private int credits = 0; @@ -215,154 +54,147 @@ public class Studi { } ``` -::::::::: notes -## _JEDE_ Methode mindestens testen mit/auf: - -* Positive Tests: Gutfall (Normalfall) => "gültige ÄK/GW" -* Negativ-Tests (Fehlbedienung, ungültige Werte) => "ungültige ÄK/GW" -* Rand- bzw. Extremwerte => GW -* Exceptions +::: notes +## *JEDE* Methode mindestens testen mit/auf: -=> Anforderungen abgedeckt (Black-Box)? +- Positive Tests: Gutfall (Normalfall) =\> "gültige ÄK/GW" +- Negativ-Tests (Fehlbedienung, ungültige Werte) =\> "ungültige ÄK/GW" +- Rand- bzw. Extremwerte =\> GW +- Exceptions -=> Wichtige Pfade im Code abgedeckt (White-Box)? +=\> Anforderungen abgedeckt (Black-Box)? +=\> Wichtige Pfade im Code abgedeckt (White-Box)? ## Praxis -* Je kritischer eine Klasse/Methode/Artefakt ist, um so intensiver testen! -* Suche nach Kompromissen: Testkosten vs. Kosten von Folgefehlern; - beispielsweise kein Test generierter Methoden - -=> "Erzeugen" der Testfälle über die Äquivalenzklassenbildung und -Grenzwertanalyse (siehe nächste Folien). Mehr dann später im Wahlfach "Softwarequalität" ... -::::::::: +- Je kritischer eine Klasse/Methode/Artefakt ist, um so intensiver testen! +- Suche nach Kompromissen: Testkosten vs. Kosten von Folgefehlern; beispielsweise + kein Test generierter Methoden +=\> "Erzeugen" der Testfälle über die Äquivalenzklassenbildung und Grenzwertanalyse +(siehe nächste Folien). Mehr dann später im Wahlfach "Softwarequalität" ... +::: # Äquivalenzklassenbildung ::: notes -Beispiel: Zu testende Methode mit Eingabewert _x_, der zw. 10 und 100 liegen soll +Beispiel: Zu testende Methode mit Eingabewert *x*, der zw. 10 und 100 liegen soll ::: ![](images/aequivalenzklassen.png){width="60%" web_width="30%"} \bigskip -* Zerlegung der Definitionsbereiche in Äquivalenzklassen (ÄK): - * Disjunkte Teilmengen, wobei - * Werte _einer_ ÄK führen zu _gleichartigem_ Verhalten +- Zerlegung der Definitionsbereiche in Äquivalenzklassen (ÄK): -* Annahme: Eingabeparameter sind untereinander unabhängig + - Disjunkte Teilmengen, wobei + - Werte *einer* ÄK führen zu *gleichartigem* Verhalten -* Unterscheidung gültige und ungültige ÄK +- Annahme: Eingabeparameter sind untereinander unabhängig -[[Beispiel: Eingabeparameter x zw. 10 und 100]{.ex}]{.slides} +- Unterscheidung gültige und ungültige ÄK +[[Beispiel: Eingabeparameter x zw. 10 und 100]{.ex}]{.slides} -::::::::: notes +::: notes ## Bemerkungen -Hintergrund: Da die Werte einer ÄK zu gleichartigem Verhalten führen, ist es -egal, _welchen_ Wert man aus einer ÄK für den Test nimmt. +Hintergrund: Da die Werte einer ÄK zu gleichartigem Verhalten führen, ist es egal, +*welchen* Wert man aus einer ÄK für den Test nimmt. -Formal hat man _eine_ ungültige ÄK (d.h. die Menge aller ungültigen Werte). In -der Programmierpraxis macht es aber einen Unterschied, ob es sich um Werte -unterhalb oder oberhalb des erlaubten Wertebereichs handelt (Fallunterscheidung). -Beispiel: Eine Funktion soll Werte zwischen 10 und 100 verarbeiten. Dann sind -alle Werte kleiner 10 oder größer 100 mathematisch gesehen in der selben ÄK -"ungültig". Praktisch macht es aber Sinn, eine ungültige ÄK für "kleiner 10" -und eine weitere ungültige ÄK für "größer 100" zu betrachten ... - -Traditionell betrachtet man nur die Eingabeparameter. Es kann aber Sinn machen, -auch die Ausgabeseite zu berücksichtigen (ist aber u.U. nur schwierig zu -realisieren). +Formal hat man *eine* ungültige ÄK (d.h. die Menge aller ungültigen Werte). In der +Programmierpraxis macht es aber einen Unterschied, ob es sich um Werte unterhalb +oder oberhalb des erlaubten Wertebereichs handelt (Fallunterscheidung). Beispiel: +Eine Funktion soll Werte zwischen 10 und 100 verarbeiten. Dann sind alle Werte +kleiner 10 oder größer 100 mathematisch gesehen in der selben ÄK "ungültig". +Praktisch macht es aber Sinn, eine ungültige ÄK für "kleiner 10" und eine weitere +ungültige ÄK für "größer 100" zu betrachten ... +Traditionell betrachtet man nur die Eingabeparameter. Es kann aber Sinn machen, auch +die Ausgabeseite zu berücksichtigen (ist aber u.U. nur schwierig zu realisieren). ## Faustregeln bei der Bildung von ÄK -* Falls eine Beschränkung einen Wertebereich spezifiziert: - Aufteilung in eine gültige und zwei ungültige ÄK +- Falls eine Beschränkung einen Wertebereich spezifiziert: Aufteilung in eine + gültige und zwei ungültige ÄK - Beispiel: Eingabewert _x_ soll zw. 10 und 100 liegen + Beispiel: Eingabewert *x* soll zw. 10 und 100 liegen - * Gültige ÄK: $[10, 100]$ - * Ungültige ÄKs: $x < 10$ und $100 < x$ + - Gültige ÄK: $[10, 100]$ + - Ungültige ÄKs: $x < 10$ und $100 < x$ -* Falls eine Beschränkung eine minimale und maximale Anzahl von Werten - spezifiziert: - Aufteilung in eine gültige und zwei ungültige ÄK +- Falls eine Beschränkung eine minimale und maximale Anzahl von Werten + spezifiziert: Aufteilung in eine gültige und zwei ungültige ÄK Beispiel: Jeder Studi muss pro Semester an mindestens einer LV teilnehmen, maximal sind 5 LVs erlaubt. - * Gültige ÄK: $1 \le x \le 5$ - * Ungültige ÄKs: $x = 0$ (keine Teilnahme) und $5 < x$ (mehr als 5 Kurse) + - Gültige ÄK: $1 \le x \le 5$ + - Ungültige ÄKs: $x = 0$ (keine Teilnahme) und $5 < x$ (mehr als 5 Kurse) -* Falls eine Beschränkung eine Menge von Werten spezifiziert, die - möglicherweise unterschiedlich behandelt werden: - Für jeden Wert dieser Menge eine eigene gültige ÄK erstellen und zusätzlich - insgesamt eine ungültige ÄK +- Falls eine Beschränkung eine Menge von Werten spezifiziert, die möglicherweise + unterschiedlich behandelt werden: Für jeden Wert dieser Menge eine eigene + gültige ÄK erstellen und zusätzlich insgesamt eine ungültige ÄK Beispiel: Das Hotel am Urlaubsort ermöglicht verschiedene Freizeitaktivitäten: Segway-fahren, Tauchen, Tennis, Golf - * Gültige ÄKs: - * Segway-fahren - * Tauchen - * Tennis - * Golf - * Ungültige ÄK: "alles andere" - -* Falls eine Beschränkung eine Situation spezifiziert, die zwingend erfüllt sein muss: - Aufteilung in eine gültige und eine ungültige ÄK + - Gültige ÄKs: + - Segway-fahren + - Tauchen + - Tennis + - Golf + - Ungültige ÄK: "alles andere" +- Falls eine Beschränkung eine Situation spezifiziert, die zwingend erfüllt sein + muss: Aufteilung in eine gültige und eine ungültige ÄK -_Hinweis_: Werden Werte einer ÄK vermutlich nicht gleichwertig behandelt, dann +*Hinweis*: Werden Werte einer ÄK vermutlich nicht gleichwertig behandelt, dann erfolgt die Aufspaltung der ÄK in kleinere ÄKs. Das ist im Grunde die analoge Überlegung zu mehreren ungültigen ÄKs. ÄKs sollten für die weitere Arbeit einheitlich und eindeutig benannt werden. -Typisches Namensschema: "gÄKn" und "uÄKn" für gültige bzw. ungültige ÄKs mit -der laufenden Nummer $n$. -::::::::: - +Typisches Namensschema: "gÄKn" und "uÄKn" für gültige bzw. ungültige ÄKs mit der +laufenden Nummer $n$. +::: # ÄK: Erstellung der Testfälle -* Jede ÄK durch _mindestens_ **einen TF** abdecken +- Jede ÄK durch *mindestens* **einen TF** abdecken \bigskip -* Dabei pro Testfall - * _mehrere gültige ÄKs_ kombinieren, oder - * genau _eine ungültige ÄK_ untersuchen - [(restl. Werte aus gültigen ÄK auffüllen; diese gelten dann aber - nicht als getestet!)]{.notes} +- Dabei pro Testfall + - *mehrere gültige ÄKs* kombinieren, oder + - genau *eine ungültige ÄK* untersuchen [(restl. Werte aus gültigen ÄK + auffüllen; diese gelten dann aber nicht als getestet!)]{.notes} ::: notes -Im Prinzip muss man zur Erstellung der Testfälle (TF) eine paarweise vollständige Kombination über -die ÄK bilden, d.h. jede ÄK kommt mit jeder anderen ÄK in einem TF zur Ausführung. +Im Prinzip muss man zur Erstellung der Testfälle (TF) eine paarweise vollständige +Kombination über die ÄK bilden, d.h. jede ÄK kommt mit jeder anderen ÄK in einem TF +zur Ausführung. -_Erinnerung_: Annahme: Eingabeparameter sind untereinander unabhängig! => Es reicht, wenn _jede_ -gültige ÄK _einmal_ in einem TF zur Ausführung kommt. => Kombination verschiedener gültiger ÄK -in _einem TF_. +*Erinnerung*: Annahme: Eingabeparameter sind untereinander unabhängig! =\> Es +reicht, wenn *jede* gültige ÄK *einmal* in einem TF zur Ausführung kommt. =\> +Kombination verschiedener gültiger ÄK in *einem TF*. -**Achtung**: Dies gilt nur für die **gültigen** ÄK! Bei den ungültigen ÄKs dürfen diese nicht -miteinander in einem TF kombiniert werden! Bei gleichzeitiger Behandlung verschiedener ungültiger -ÄK bleiben u.U. Fehler unentdeckt, da sich die Wirkungen der ungültigen ÄK überlagern! +**Achtung**: Dies gilt nur für die **gültigen** ÄK! Bei den ungültigen ÄKs dürfen +diese nicht miteinander in einem TF kombiniert werden! Bei gleichzeitiger Behandlung +verschiedener ungültiger ÄK bleiben u.U. Fehler unentdeckt, da sich die Wirkungen +der ungültigen ÄK überlagern! -**Für jeden Testfall (TF) wird aus den zu kombinierenden ÄK ein zufälliger Repräsentant ausgewählt.** +**Für jeden Testfall (TF) wird aus den zu kombinierenden ÄK ein zufälliger +Repräsentant ausgewählt.** ::: - -# ÄK: Beispiel: Eingabewert _x_ soll zw. 10 und 100 liegen +# ÄK: Beispiel: Eingabewert *x* soll zw. 10 und 100 liegen ## Äquivalenzklassen | Eingabe | gültige ÄK | ungültige ÄK | |:--------|:------------------|:----------------| -| _x_ | gÄK1: $[10, 100]$ | uÄK2: $x < 10$ | +| *x* | gÄK1: $[10, 100]$ | uÄK2: $x < 10$ | | | | uÄK3: $100 < x$ | \bigskip @@ -373,10 +205,9 @@ miteinander in einem TF kombiniert werden! Bei gleichzeitiger Behandlung verschi | Testnummer | 1 | 2 | 3 | |:--------------------|:-----|:----------|:----------| | geprüfte ÄK | gÄK1 | uÄK2 | uÄK3 | -| _x_ | 42 | 7 | 120 | +| *x* | 42 | 7 | 120 | | Erwartetes Ergebnis | OK | Exception | Exception | - # Grenzwertanalyse ![](images/grenzwerte.png){width="60%" web_width="30%"} @@ -385,41 +216,40 @@ miteinander in einem TF kombiniert werden! Bei gleichzeitiger Behandlung verschi Beobachtung: Grenzen in Verzweigungen/Schleifen kritisch -* Grenzen der ÄK (kleinste und größte Werte) **zusätzlich** testen - * "gültige Grenzwerte" (*gGW*): Grenzwerte von gültigen ÄK - * "ungültige Grenzwerte" (*uGW*): Grenzwerte von ungültigen ÄK +- Grenzen der ÄK (kleinste und größte Werte) **zusätzlich** testen + - "gültige Grenzwerte" (*gGW*): Grenzwerte von gültigen ÄK + - "ungültige Grenzwerte" (*uGW*): Grenzwerte von ungültigen ÄK ::: notes -Zusätzlich sinnvoll: Weitere grenznahe Werte, d.h. weitere Werte "rechts" und "links" -der Grenze nutzen. +Zusätzlich sinnvoll: Weitere grenznahe Werte, d.h. weitere Werte "rechts" und +"links" der Grenze nutzen. Bildung der Testfälle: ::: -* Jeder GW muss in mind. einem TF vorkommen +- Jeder GW muss in mind. einem TF vorkommen ::: notes -**Pro TF darf ein GW (gültig oder ungültig) verwendet werden, die restlichen Parameter -werden (mit zufälligen Werten) aus gültigen ÄK aufgefüllt, um mögliche Grenzwertprobleme -nicht zu überlagern.** +**Pro TF darf ein GW (gültig oder ungültig) verwendet werden, die restlichen +Parameter werden (mit zufälligen Werten) aus gültigen ÄK aufgefüllt, um mögliche +Grenzwertprobleme nicht zu überlagern.** ::: [[Beispiel: Eingabeparameter x zw. 10 und 100]{.ex}]{.slides} - -# GW: Beispiel: Eingabewert _x_ soll zw. 10 und 100 liegen +# GW: Beispiel: Eingabewert *x* soll zw. 10 und 100 liegen ## Äquivalenzklassen | Eingabe | gültige ÄK | ungültige ÄK | |:--------|:------------------|:----------------| -| _x_ | gÄK1: $[10, 100]$ | uÄK2: $x < 10$ | +| *x* | gÄK1: $[10, 100]$ | uÄK2: $x < 10$ | | | | uÄK3: $100 < x$ | - ## Grenzwertanalyse -[Zusätzliche Testdaten:]{.notes} 9 (uÄK2o) und 10 (gÄK1u) sowie 100 (gÄK1o) und 101 (uÄK3u) +[Zusätzliche Testdaten:]{.notes} 9 (uÄK2o) und 10 (gÄK1u) sowie 100 (gÄK1o) und 101 +(uÄK3u) \pause @@ -428,47 +258,218 @@ nicht zu überlagern.** | Testnummer | 4 | 5 | 6 | 7 | |:--------------------|:------|:------|:----------|:----------| | geprüfter GW | gÄK1u | gÄK1o | uÄK2o | uÄK3u | -| _x_ | 10 | 100 | 9 | 101 | +| *x* | 10 | 100 | 9 | 101 | | Erwartetes Ergebnis | OK | OK | Exception | Exception | - ::: notes -**Hinweis**: Die Ergebnisse der GW-Analyse werden **zusätzlich** zu den Werten aus der ÄK-Analyse -eingesetzt. Für das obige Beispiel würde man also folgende Tests aus der kombinierten ÄK- und -GW-Analyse erhalten: +**Hinweis**: Die Ergebnisse der GW-Analyse werden **zusätzlich** zu den Werten aus +der ÄK-Analyse eingesetzt. Für das obige Beispiel würde man also folgende Tests aus +der kombinierten ÄK- und GW-Analyse erhalten: | Testnummer | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |:--------------------|:-----|:----------|:----------|:------|:------|:----------|:----------| | geprüfte(r) ÄK/GW | gÄK1 | uÄK2 | uÄK3 | gÄK1u | gÄK1o | uÄK2o | uÄK3u | -| _x_ | 42 | 7 | 120 | 10 | 100 | 9 | 101 | +| *x* | 42 | 7 | 120 | 10 | 100 | 9 | 101 | | Erwartetes Ergebnis | OK | Exception | Exception | OK | OK | Exception | Exception | ::: - ::: notes # Anmerkung: Analyse abhängiger Parameter -Wenn das Ergebnis von der Kombination der Eingabewerte abhängt, dann -sollte man dies bei der Äquivalenzklassenbildung berücksichtigen: Die -ÄK sind in diesem Fall in Bezug auf die Kombinationen zu bilden! - -Schauen Sie sich dazu das Beispiel im @Kleuker2019, Abschnitt -"4.3 Analyse abhängiger Parameter" an. +Wenn das Ergebnis von der Kombination der Eingabewerte abhängt, dann sollte man dies +bei der Äquivalenzklassenbildung berücksichtigen: Die ÄK sind in diesem Fall in +Bezug auf die Kombinationen zu bilden! -Die einfache ÄK-Bildung würde in diesem Fall versagen, da die Eingabewerte -nicht unabhängig sind. Leider ist die Betrachtung der möglichen Kombinationen -u.U. eine sehr komplexe Aufgabe ... +Schauen Sie sich dazu das Beispiel im @Kleuker2019, Abschnitt "4.3 Analyse +abhängiger Parameter" an. +Die einfache ÄK-Bildung würde in diesem Fall versagen, da die Eingabewerte nicht +unabhängig sind. Leider ist die Betrachtung der möglichen Kombinationen u.U. eine +sehr komplexe Aufgabe ... Analoge Überlegungen gelten auch für die ÄK-Bildung im Zusammenhang mit -objektorientierter Programmierung. Die Eingabewerte und der Objektzustand -müssen dann _gemeinsam_ bei der ÄK-Bildung betrachtet werden! +objektorientierter Programmierung. Die Eingabewerte und der Objektzustand müssen +dann *gemeinsam* bei der ÄK-Bildung betrachtet werden! Vergleiche @Kleuker2019, Abschnitt "4.4 Äquivalenzklassen und Objektorientierung". ::: - # Wrap-Up - * Gründliches Testen ist ebenso viel Aufwand wie Coden - * Äquivalenzklassenbildung und Grenzwertanalyse +- Gründliches Testen ist ebenso viel Aufwand wie Coden +- Äquivalenzklassenbildung und Grenzwertanalyse + +::: readings +- @vogellaJUnit +- @junit4 +- @Kleuker2019 +- @Osherove2014 +- @Spillner2012 +- @fernunihagenJunit +::: + +::: outcomes +- k2: Ich kann Merkmale schlecht testbaren Codes erklären +- k2: Ich kann Merkmale guter Unit-Tests erklären +- k3: Ich kann Testfällen mittels Äquivalenzklassenbildung und Grenzwertanalyse + erstellen +::: + +::: challenges +**ÄK/GW: RSV Flotte Speiche** + +Der RSV Flotte Speiche hat in seiner Mitgliederverwaltung (`MitgliederVerwaltung`) +die Methode `testBeitritt` implementiert. Mit dieser Methode wird geprüft, ob neue +Mitglieder in den Radsportverein aufgenommen werden können. + +``` java +public class MitgliederVerwaltung { + + /** + * Testet, ob ein Mitglied in den Verein aufgenommen werden kann. + * + *

          Interessierte Personen müssen mindestens 16 Jahre alt sein, um aufgenommen + * werden zu können. Die Motivation darf nicht zu niedrig und auch nicht zu hoch + * sein und muss zwischen 4 und 7 (inklusive) liegen, sonst wird der Antrag + * abgelehnt. + * + *

          Der Wertebereich beim Alter umfasst die natürlichen Zahlen zwischen 0 und 99 + * (inklusive), bei der Motivation sind die natürlichen Zahlen zwischen 0 und 10 + * (inklusive) erlaubt. + * + *

          Bei Verletzung der zulässigen Wertebereiche der Parameter wird eine + * IllegalArgumentException geworfen. + * + * @param alter Alter in Lebensjahren, Bereich [0, 99] + * @param motivation Motivation auf einer Scala von 0 bis 10 + * @return true, wenn das Mitglied aufgenommen werden kann, + * sonst false + * @throws IllegalArgumentException, wenn Parameter außerhalb + * der zulässigen Wertebereiche + */ + public boolean testBeitritt(int alter, int motivation) { + // Implementierung versteckt + } +} +``` + +1. Führen Sie eine Äquivalenzklassenbildung durch und geben Sie die gefundenen + Äquivalenzklassen (*ÄK*) an: laufende Nummer, Definition (Wertebereiche o.ä.), + kurze Beschreibung (gültige/ungültige ÄK, Bedeutung). + +2. Führen Sie zusätzlich eine Grenzwertanalyse durch und geben Sie die jeweiligen + Grenzwerte (*GW*) an. + +3. Erstellen Sie aus den ÄK und GW wie in der Vorlesung diskutiert Testfälle. Geben + Sie pro Testfall (*TF*) an, welche ÄK und/oder GW abgedeckt sind, welche + Eingaben Sie vorsehen und welche Ausgabe Sie erwarten. + + *Hinweis*: Erstellen Sie separate (zusätzliche) TF für die GW, d.h. integrieren + Sie diese *nicht* in die ÄK-TF. + +4. Implementieren Sie die Testfälle in JUnit (JUnit 4 oder 5). + + - Fassen Sie die Testfälle der gültigen ÄK in einem parametrisierten Test + zusammen. + - Für die ungültigen ÄKs erstellen Sie jeweils eine eigene JUnit-Testmethode. + Beachten Sie, dass Sie auch die Exceptions testen müssen. + + + +**ÄK/GW: LSF** + +Das LSF bestimmt mit der Methode `LSF#checkStudentCPS`, ob ein Studierender bereits +zur Bachelorarbeit oder Praxisphase zugelassen werden kann: + +``` java +class LSF { + public static Status checkStudentCPS(Student student) { + if (student.credits() >= Status.BACHELOR.credits) return Status.BACHELOR; + else if (student.credits() >= Status.PRAXIS.credits) return Status.PRAXIS; + else return Status.NONE; + } +} + +record Student(String name, int credits, int semester) { } + +enum Status { + NONE(0), PRAXIS(110), BACHELOR(190); // min: 0, max: 210 + + public final int credits; + Status(int credits) { this.credits = credits; } +} +``` + +1. Führen Sie eine Äquivalenzklassenbildung für die Methode `LSF#checkStudentCPS` + durch. +2. Führen Sie zusätzlich eine Grenzwertanalyse für die Methode + `LSF#checkStudentCPS` durch. +3. Erstellen Sie aus den ÄK und GW wie in der Vorlesung diskutiert Testfälle. +4. Implementieren Sie die Testfälle in JUnit (JUnit 4 oder 5). + - Fassen Sie die Testfälle der gültigen ÄK in einem parametrisierten Test + zusammen. + - Für die ungültigen ÄKs erstellen Sie jeweils eine eigene JUnit-Testmethode. + Beachten Sie, dass Sie auch die Exceptions testen müssen. + + +::: diff --git a/lecture/quality/testing-intro.md b/lecture/quality/testing-intro.md index 4673fa412..af60ac6b3 100644 --- a/lecture/quality/testing-intro.md +++ b/lecture/quality/testing-intro.md @@ -1,121 +1,38 @@ --- -title: "Einführung Softwaretest" -author: "Carsten Gips (HSBI)" -readings: - - "@vogellaJUnit" - - "@junit4" - - "@Kleuker2019" - - "@Osherove2014" - - "@Spillner2012" - - "@fernunihagenJunit" -tldr: | - Fehler schleichen sich durch Zeitdruck und hohe Komplexität schnell in ein Softwareprodukt ein. Die - Folgen können von "ärgerlich" über "teuer" bis hin zu (potentiell) "tödlich" reichen. Richtiges - Testen ist also ein wichtiger Aspekt bei der Softwareentwicklung! - - JUnit ist ein Java-Framework, mit dem Unit-Tests (aber auch andere Teststufen) implementiert werden - können. In JUnit 4 und 5 zeichnet man eine Testmethode mit Hilfe der Annotation `@Test` an der - entsprechenden Methode aus. Dadurch kann man Produktiv- und Test-Code prinzipiell mischen; Best - Practice ist aber das Anlegen eines weiteren Ordners `test/` und das Spiegeln der Package-Strukturen. - Für die zu testende Klasse wird eine korrespondierende Testklasse mit dem Suffix "Test" (Konvention) - angelegt und dort die Testmethoden implementiert. Der IDE muss der neue `test/`-Ordner noch als - Ordner für Sourcen bzw. Tests bekannt gemacht werden. In den Testmethoden baut man den Test auf, - führt schließlich den Testschritt durch (beispielsweise konkreter Aufruf der zu testenden Methode) - und prüft anschließend mit einem `assert*()`, ob das erzielte Ergebnis dem erwarteten Ergebnis - entspricht. Ist alles OK, ist der Test "grün", sonst "rot". - - Da ein fehlschlagendes `assert*()` den Test abbricht, werden eventuell danach folgende Prüfungen - **nicht** mehr durchgeführt und damit ggf. weitere Fehler maskiert. Deshalb ist es gute Praxis, in - einer Testmethode nur einen Testfall zu implementieren und i.d.R. auch nur ein (oder wenige) Aufrufe - von `assert*()` pro Testmethode zu haben. -outcomes: - - k2: "Ich kenne verschiedene Ursachen von Softwarefehlern" - - k3: "Ich kann Tests mit JUnit 4 und 5 unter Nutzung der Annotation `@Test` erstellen" -#quizzes: -# - link: "https://www.hsbi.de/elearning/goto.php?target=tst_1106545&client_id=FH-Bielefeld" -# name: "Quiz JUnit-Basics - nur erster Teil (ILIAS)" -youtube: - - link: "https://youtu.be/WGd83crqu4I" - name: "VL Einführung Softwaretest" - - link: "https://youtu.be/xcogVwHUo5o" - name: "Demo Anlegen von Testfällen mit JUnit" -fhmedia: - - link: "https://www.hsbi.de/medienportal/m/87e2aa545269a6d4c0502a18a3e800c82bc767de66612f488f17e0ff60c3a66097884212745aff556c2b76eee7a0fd8a8225d912c3a39c274020fd5b8055bb10" - name: "VL Einführung Softwaretest" -challenges: | - **Einfache JUnit-Tests I** - - Betrachten Sie die folgende einfache (und nicht besonders sinnvolle) - Klasse `MyList`: - - ```java - public class MyList { - protected final List list = new ArrayList<>(); - - public boolean add(T element) { return list.add(element); } - public int size() { return list.size(); } - } - ``` - - Schreiben Sie mit Hilfe von JUnit (4.x oder 5.x) einige Unit-Tests für - die beiden Methoden `MyList#add` und `MyList#size`. - - - - - **Einfache JUnit-Tests II** - - Betrachten Sie die Methode `String concat(String str)` der Klasse `String` aus dem JDK. - - Implementieren Sie drei verschiedenartige Unit-Testfälle (inklusive der Eingabe- und - Rückgabewerte) für diese Methode mit Hilfe von JUnit (Version 4.x oder 5.x). +author: Carsten Gips (HSBI) +title: Einführung Softwaretest --- +::: tldr +Fehler schleichen sich durch Zeitdruck und hohe Komplexität schnell in ein +Softwareprodukt ein. Die Folgen können von "ärgerlich" über "teuer" bis hin zu +(potentiell) "tödlich" reichen. Richtiges Testen ist also ein wichtiger Aspekt bei +der Softwareentwicklung! + +JUnit ist ein Java-Framework, mit dem Unit-Tests (aber auch andere Teststufen) +implementiert werden können. In JUnit 4 und 5 zeichnet man eine Testmethode mit +Hilfe der Annotation `@Test` an der entsprechenden Methode aus. Dadurch kann man +Produktiv- und Test-Code prinzipiell mischen; Best Practice ist aber das Anlegen +eines weiteren Ordners `test/` und das Spiegeln der Package-Strukturen. Für die zu +testende Klasse wird eine korrespondierende Testklasse mit dem Suffix "Test" +(Konvention) angelegt und dort die Testmethoden implementiert. Der IDE muss der neue +`test/`-Ordner noch als Ordner für Sourcen bzw. Tests bekannt gemacht werden. In den +Testmethoden baut man den Test auf, führt schließlich den Testschritt durch +(beispielsweise konkreter Aufruf der zu testenden Methode) und prüft anschließend +mit einem `assert*()`, ob das erzielte Ergebnis dem erwarteten Ergebnis entspricht. +Ist alles OK, ist der Test "grün", sonst "rot". + +Da ein fehlschlagendes `assert*()` den Test abbricht, werden eventuell danach +folgende Prüfungen **nicht** mehr durchgeführt und damit ggf. weitere Fehler +maskiert. Deshalb ist es gute Praxis, in einer Testmethode nur einen Testfall zu +implementieren und i.d.R. auch nur ein (oder wenige) Aufrufe von `assert*()` pro +Testmethode zu haben. +::: + +::: youtube +- [VL Einführung Softwaretest](https://youtu.be/WGd83crqu4I) +- [Demo Anlegen von Testfällen mit JUnit](https://youtu.be/xcogVwHUo5o) +::: # Software-Fehler und ihre Folgen @@ -123,20 +40,18 @@ challenges: | [[Ursachen für Fehler?]{.ex}]{.slides} - ::: notes # (Einige) Ursachen für Fehler -* Zeit- und Kostendruck -* Mangelhafte Anforderungsanalyse -* Hohe Komplexität -* Mangelhafte Kommunikation -* Keine/schlechte Teststrategie -* Mangelhafte Beherrschung der Technologie -* ... +- Zeit- und Kostendruck +- Mangelhafte Anforderungsanalyse +- Hohe Komplexität +- Mangelhafte Kommunikation +- Keine/schlechte Teststrategie +- Mangelhafte Beherrschung der Technologie +- ... ::: - # Irgendjemand muss mit Deinen Bugs leben! ::: notes @@ -146,152 +61,152 @@ Bedingungen ausprobieren, um zu schauen, wie sie sich verhält, und um die dabei Tage tretenden Bugs zu fixen. Mal abgesehen von der verbesserten *User-Experience* führt weniger fehlerbehaftete -Software auch dazu, dass man seltener mitten in der Nacht geweckt wird, weil irgendwo -wieder ein Server gecrasht ist ... Weniger fehlerbehaftete Software ist auch leichter -zu ändern und zu pflegen! In realen Projekten macht Maintenance den größten Teil an der -Softwareentwicklung aus ... Während Ihre Praktikumsprojekte vermutlich nach der Abgabe -nie wieder angeschaut werden, können echte Projekte viele Jahre bis Jahrzehnte leben! -D.h. irgendwer muss sich dann mit Ihren Bugs herumärgern - vermutlich sogar Sie selbst ;) +Software auch dazu, dass man seltener mitten in der Nacht geweckt wird, weil +irgendwo wieder ein Server gecrasht ist ... Weniger fehlerbehaftete Software ist +auch leichter zu ändern und zu pflegen! In realen Projekten macht Maintenance den +größten Teil an der Softwareentwicklung aus ... Während Ihre Praktikumsprojekte +vermutlich nach der Abgabe nie wieder angeschaut werden, können echte Projekte viele +Jahre bis Jahrzehnte leben! D.h. irgendwer muss sich dann mit Ihren Bugs +herumärgern - vermutlich sogar Sie selbst ;) ::: -> Always code as if the guy who ends up maintaining your code will be a -> violent psychopath who knows where you live. Code for readability. +> Always code as if the guy who ends up maintaining your code will be a violent +> psychopath who knows where you live. Code for readability. > -> \hfill -- [John F. Woods](https://groups.google.com/g/comp.lang.c++/c/rYCO5yn4lXw/m/oITtSkZOtoUJ) +> `\hfill`{=tex} -- [John F. +> Woods](https://groups.google.com/g/comp.lang.c++/c/rYCO5yn4lXw/m/oITtSkZOtoUJ) ::: notes -Dieses Zitat taucht immer mal wieder auf, beispielsweise auf der [OSCON 2014](https://twitter.com/andypiper/status/490952891058757632) ... -Es scheint aber tatsächlich, dass [John F. Woods](https://groups.google.com/g/comp.lang.c++/c/rYCO5yn4lXw/m/oITtSkZOtoUJ) -die ursprüngliche Quelle war (vgl. [Stackoverflow: 876089](https://stackoverflow.com/questions/876089/who-wrote-this-programing-saying-always-code-as-if-the-guy-who-ends-up-maintai#878436)). +Dieses Zitat taucht immer mal wieder auf, beispielsweise auf der [OSCON +2014](https://twitter.com/andypiper/status/490952891058757632) ... Es scheint aber +tatsächlich, dass [John F. +Woods](https://groups.google.com/g/comp.lang.c++/c/rYCO5yn4lXw/m/oITtSkZOtoUJ) die +ursprüngliche Quelle war (vgl. [Stackoverflow: +876089](https://stackoverflow.com/questions/876089/who-wrote-this-programing-saying-always-code-as-if-the-guy-who-ends-up-maintai#878436)). ::: ::: notes Da wir nur wenig Zeit haben und zudem vergesslich sind und obendrein die Komplexität -eines Projekts mit der Anzahl der Code-Zeilen i.d.R. nicht-linear ansteigt, müssen wir -das Testen automatisieren. Und hier kommt JUnit ins Spiel :) +eines Projekts mit der Anzahl der Code-Zeilen i.d.R. nicht-linear ansteigt, müssen +wir das Testen automatisieren. Und hier kommt JUnit ins Spiel :) ::: - # Was wann testen? Wichtigste Teststufen -* **Modultest** - * Testen einer Klasse und ihrer Methoden - * Test auf gewünschtes Verhalten (Parameter, Schleifen, ...) +- **Modultest** + - Testen einer Klasse und ihrer Methoden + - Test auf gewünschtes Verhalten (Parameter, Schleifen, ...) \bigskip -* **Integrationstest** - * Test des korrekten Zusammenspiels mehrerer Komponenten - * Konzentration auf Schnittstellentests +- **Integrationstest** + - Test des korrekten Zusammenspiels mehrerer Komponenten + - Konzentration auf Schnittstellentests \bigskip -* **Systemtest** - * Test des kompletten Systems unter produktiven Bedingungen - * Orientiert sich an den aufgestellten Use Cases - * Funktionale und nichtfunktionale Anforderungen testen +- **Systemtest** + - Test des kompletten Systems unter produktiven Bedingungen + - Orientiert sich an den aufgestellten Use Cases + - Funktionale und nichtfunktionale Anforderungen testen \vfill -=> Verweis auf Wahlfach "Softwarequalität" +=\> Verweis auf Wahlfach "Softwarequalität" # JUnit: Test-Framework für Java ::: notes -**JUnit** --- Open Source Java Test-Framework zur Erstellung und -Durchführung wiederholbarer Tests +**JUnit** --- Open Source Java Test-Framework zur Erstellung und Durchführung +wiederholbarer Tests ::: -* JUnit 3 - * Tests müssen in eigenen Testklassen stehen - * Testklassen müssen von Klasse `TestCase` erben - * Testmethoden müssen mit dem Präfix "`test`" beginnen +- JUnit 3 + - Tests müssen in eigenen Testklassen stehen + - Testklassen müssen von Klasse `TestCase` erben + - Testmethoden müssen mit dem Präfix "`test`" beginnen \bigskip -* **JUnit 4** - * Annotation `@Test` für Testmethoden - * Kein Zwang zu spezialisierten Testklassen [(insbesondere kein Zwang mehr zur Ableitung von `TestCase`)]{.notes} - * Freie Namenswahl für Testmethoden [(benötigen nicht mehr Präfix "`test`")]{.notes} +- **JUnit 4** + - Annotation `@Test` für Testmethoden + - Kein Zwang zu spezialisierten Testklassen [(insbesondere kein Zwang mehr zur + Ableitung von `TestCase`)]{.notes} + - Freie Namenswahl für Testmethoden [(benötigen nicht mehr Präfix + "`test`")]{.notes} ::: notes Damit können prinzipiell auch direkt im Source-Code Methoden als - JUnit-Testmethoden ausgezeichnet werden ... (das empfiehlt sich in der - Regel aber nicht) + JUnit-Testmethoden ausgezeichnet werden ... (das empfiehlt sich in der Regel + aber nicht) ::: \bigskip -* *JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage* - * Erweiterung um mächtigere Annotationen - * Aufteilung in spezialisierte Teilprojekte +- *JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage* + - Erweiterung um mächtigere Annotationen + - Aufteilung in spezialisierte Teilprojekte ::: notes - Das Teilprojekt "JUnit Platform" ist die Grundlage für das JUnit-Framework. Es bietet - u.a. einen Console-Launcher, um Testsuiten manuell in der Konsole zu starten oder über - Builder wie Ant oder Gradle. + Das Teilprojekt "JUnit Platform" ist die Grundlage für das JUnit-Framework. Es + bietet u.a. einen Console-Launcher, um Testsuiten manuell in der Konsole zu + starten oder über Builder wie Ant oder Gradle. - Das Teilprojekt "JUnit Jupiter" ist das neue Programmiermodell zum Schreiben von Tests - in JUnit 5. Es beinhaltet eine TestEngine zum Ausführen der in Jupiter geschriebenen - Tests. + Das Teilprojekt "JUnit Jupiter" ist das neue Programmiermodell zum Schreiben von + Tests in JUnit 5. Es beinhaltet eine TestEngine zum Ausführen der in Jupiter + geschriebenen Tests. - Das Teilprojekt "JUnit Vintage" beinhaltet eine TestEngine zum Ausführen von Tests, - die in JUnit 3 oder JUnit 4 geschrieben sind. + Das Teilprojekt "JUnit Vintage" beinhaltet eine TestEngine zum Ausführen von + Tests, die in JUnit 3 oder JUnit 4 geschrieben sind. ::: ::: notes *Anmerkung*: Wie der Name schon sagt, ist das Framework für Modultests -("Unit-Tests") gedacht. Man kann damit aber auch auf anderen Teststufen -arbeiten! - -*Anmerkung*: Im Folgenden besprechen wir JUnit am Beispiel **JUnit 4**, da diese Version -des Frameworks besonders stark verbreitet ist und JUnit 5 (trotz offiziellem Release) immer -noch stellenweise unfertig wirkt. Auf Unterschiede zu JUnit 5 wird an geeigneter Stelle -hingewiesen (abgesehen von Import-Statements). Mit JUnit 3 sollte nicht mehr aktiv -gearbeitet werden, d.h. insbesondere keine neuen Tests mehr erstellt werden, da diese -Version nicht mehr weiterentwickelt wird. +("Unit-Tests") gedacht. Man kann damit aber auch auf anderen Teststufen arbeiten! + +*Anmerkung*: Im Folgenden besprechen wir JUnit am Beispiel **JUnit 4**, da diese +Version des Frameworks besonders stark verbreitet ist und JUnit 5 (trotz offiziellem +Release) immer noch stellenweise unfertig wirkt. Auf Unterschiede zu JUnit 5 wird an +geeigneter Stelle hingewiesen (abgesehen von Import-Statements). Mit JUnit 3 sollte +nicht mehr aktiv gearbeitet werden, d.h. insbesondere keine neuen Tests mehr +erstellt werden, da diese Version nicht mehr weiterentwickelt wird. ::: - # Anlegen und Organisation der Tests mit JUnit -:::::: columns +::::: columns ::: {.column width="50%"} - \vspace{10mm} -* Anlegen neuer Tests: Klasse auswählen, Kontextmenü `New > JUnit Test Case` - [(IJ: Kontextmenü `Generate > Test`)]{.notes} +- Anlegen neuer Tests: Klasse auswählen, Kontextmenü `New > JUnit Test Case` [(IJ: + Kontextmenü `Generate > Test`)]{.notes} \bigskip -* **Best Practice**: \newline\ Spiegeln der Paket-Hierarchie - * Toplevel-Ordner `test` (statt `src`) - * Package-Strukturen spiegeln - * Testklassen mit Suffix "`Test`" - +- **Best Practice**: `\newline`{=tex} Spiegeln der Paket-Hierarchie + - Toplevel-Ordner `test` (statt `src`) + - Package-Strukturen spiegeln + - Testklassen mit Suffix "`Test`" ::: -::: {.column width="50%"} - -![](images/newJUnit.png){width=80% web_width=40%} +::: {.column width="50%"} +![](images/newJUnit.png){width="80%" web_width="40%"} ::: -:::::: +::::: ::: notes Vorteile dieses Vorgehens: -* Die Testklassen sind aus Java-Sicht im selben Package wie die Source-Klassen, +- Die Testklassen sind aus Java-Sicht im selben Package wie die Source-Klassen, d.h. Zugriff auf Package-sichtbare Methoden etc. ist gewährleistet -* Durch die Spiegelung der Packages in einem separaten Testordner erhält man - eine gute getrennte Übersicht über jeweils die Tests und die Sourcen -* Die Wiederverwendung des Klassennamens mit dem Anhang "Test" erlaubt die +- Durch die Spiegelung der Packages in einem separaten Testordner erhält man eine + gute getrennte Übersicht über jeweils die Tests und die Sourcen +- Die Wiederverwendung des Klassennamens mit dem Anhang "Test" erlaubt die schnelle Erkennung, welche Tests hier vorliegen In der Paketansicht liegen dann die Source- und die Testklassen immer direkt -hintereinander (da sie im selben Paket sind und mit dem selben Namen anfangen) -=> besserer Überblick! - +hintereinander (da sie im selben Paket sind und mit dem selben Namen anfangen) =\> +besserer Überblick! # Anmerkung: Die (richtige) JUnit-Bibliothek muss im Classpath liegen! @@ -299,22 +214,21 @@ Eclipse bringt für JUnit 4 und JUnit 5 die nötigen Jar-Dateien mit und fragt b erstmaligen Anlegen einer neuen Testklasse, ob die für die ausgewählte Version **passenden JUnit-Jars zum Build-Path hinzugefügt** werden sollen. -IntelliJ bringt ebenfalls eine JUnit 4 Bibliothek mit, die zum Projekt als Abhängigkeit -hinzugefügt werden muss. Für JUnit 5 bietet IntelliJ an, die Jar-Dateien herunterzuladen -und in einem passenden Ordner abzulegen. +IntelliJ bringt ebenfalls eine JUnit 4 Bibliothek mit, die zum Projekt als +Abhängigkeit hinzugefügt werden muss. Für JUnit 5 bietet IntelliJ an, die +Jar-Dateien herunterzuladen und in einem passenden Ordner abzulegen. -Alternativ lädt man die Bibliotheken entsprechend der Anleitung unter [junit.org](https://junit.org/) -herunter und bindet sie in das Projekt ein. +Alternativ lädt man die Bibliotheken entsprechend der Anleitung unter +[junit.org](https://junit.org/) herunter und bindet sie in das Projekt ein. ::: - # JUnit 4+5: Definition von Tests Annotation `@Test` vor Testmethode schreiben \bigskip -```java +``` java import org.junit.Test; import static org.junit.Assert.*; @@ -327,21 +241,22 @@ public class FactoryBeispielTest4 { ``` ::: notes -Für JUnit 5 muss statt `org.junit.Test` entsprechend `org.junit.jupiter.api.Test` importiert werden. +Für JUnit 5 muss statt `org.junit.Test` entsprechend `org.junit.jupiter.api.Test` +importiert werden. -Während in JUnit 4 die Testmethoden mit der Sichtbarkeit `public` versehen sein müssen und keine -Parameter haben (dürfen), spielt die Sichtbarkeit in JUnit 5 keine Rolle (und die Testmethoden dürfen Parameter -aufweisen => vgl. Abschnitt "Dependency Injection for Constructors and Methods" in der JUnit-Doku). +Während in JUnit 4 die Testmethoden mit der Sichtbarkeit `public` versehen sein +müssen und keine Parameter haben (dürfen), spielt die Sichtbarkeit in JUnit 5 keine +Rolle (und die Testmethoden dürfen Parameter aufweisen =\> vgl. Abschnitt +"Dependency Injection for Constructors and Methods" in der JUnit-Doku). ::: - # JUnit 4: Ergebnis prüfen Klasse **`org.junit.Assert`** enthält diverse **statische** Methoden zum Prüfen: \bigskip -```java +``` java // Argument muss true bzw. false sein void assertTrue(boolean); void assertFalse(boolean); @@ -356,22 +271,21 @@ void fail(); ``` ::: notes -Für JUnit 5 finden sich die Assert-Methoden im Package `org.junit.jupiter.api.Assertions`. +Für JUnit 5 finden sich die Assert-Methoden im Package +`org.junit.jupiter.api.Assertions`. ::: - # Anmerkung zum statischen Import ::: notes -Bei normalem Import der Klasse `Assert` muss man jeweils den voll qualifizierten Namen -einer statischen Methode nutzen: `Assert.fail()`. +Bei normalem Import der Klasse `Assert` muss man jeweils den voll qualifizierten +Namen einer statischen Methode nutzen: `Assert.fail()`. -Alternative statischer Import: `import static org.junit.Assert.fail;` -=> *Statische Member* der importierten Klasse (oder Interface) -werden über ihre *unqualifizierten Namen* zugreifbar. -**Achtung**: Namenskollisionen möglich! +Alternative statischer Import: `import static org.junit.Assert.fail;` =\> *Statische +Member* der importierten Klasse (oder Interface) werden über ihre *unqualifizierten +Namen* zugreifbar. **Achtung**: Namenskollisionen möglich! -```java +``` java // nur bestimmtes Member importieren import static packageName.className.staticMemberName; // alle statischen Member importieren @@ -379,70 +293,152 @@ import static packageName.className.*; ``` ::: -* Beispiel normaler Import: +- Beispiel normaler Import: - ```java + ``` java import org.junit.Assert; Assert.fail("message"); ``` \bigskip -* Beispiel statischer Import: +- Beispiel statischer Import: - ```java + ``` java import static org.junit.Assert.fail; fail("message"); ``` - # Mögliche Testausgänge bei JUnit -:::::: columns +::::: columns ::: {.column width="55%"} - -![](images/junitErgebnis.png){width=80% web_width=40%} - +![](images/junitErgebnis.png){width="80%" web_width="40%"} ::: -::: {.column width="44%"} - -1. **Error**: [Fehler im Programm (Test)]{.notes} - * Unbehandelte Exception - * Abbruch (Timeout) +::: {.column width="44%"} +1. **Error**: [Fehler im Programm (Test)]{.notes} + - Unbehandelte Exception + - Abbruch (Timeout) \bigskip 2. **Failure**: Testausgang negativ - * Assert fehlgeschlagen - * `Assert.fail()` aufgerufen + - Assert fehlgeschlagen + - `Assert.fail()` aufgerufen \bigskip -3. **OK** - +3. **OK** ::: -:::::: +::::: [[Anmerkung zu Asserts pro Testmethode]{.ex}]{.slides} - ::: notes # Anmerkungen zu Assert -* Pro Testmethode möglichst nur **ein** Assert verwenden! -* Anderenfalls: Schlägt ein Assert fehl, wird der Rest nicht mehr überprüft ... +- Pro Testmethode möglichst nur **ein** Assert verwenden! +- Anderenfalls: Schlägt ein Assert fehl, wird der Rest nicht mehr überprüft ... ::: - # Wrap-Up -* Testen ist genauso wichtig wie Coden -* Richtiges Testen spart Geld, Zeit, ... -* Tests auf verschiedenen Abstraktionsstufen +- Testen ist genauso wichtig wie Coden +- Richtiges Testen spart Geld, Zeit, ... +- Tests auf verschiedenen Abstraktionsstufen \smallskip -* JUnit als Framework für (Unit-) Tests; hier JUnit 4 (mit Ausblick auf JUnit 5) - * Testmethoden mit Annotation `@Test` - * Testergebnis mit `assert*` prüfen +- JUnit als Framework für (Unit-) Tests; hier JUnit 4 (mit Ausblick auf JUnit 5) + - Testmethoden mit Annotation `@Test` + - Testergebnis mit `assert*` prüfen + +::: readings +- @vogellaJUnit +- @junit4 +- @Kleuker2019 +- @Osherove2014 +- @Spillner2012 +- @fernunihagenJunit +::: + +::: outcomes +- k2: Ich kenne verschiedene Ursachen von Softwarefehlern +- k3: Ich kann Tests mit JUnit 4 und 5 unter Nutzung der Annotation `@Test` + erstellen +::: + +::: challenges +**Einfache JUnit-Tests I** + +Betrachten Sie die folgende einfache (und nicht besonders sinnvolle) Klasse +`MyList`: + +``` java +public class MyList { + protected final List list = new ArrayList<>(); + + public boolean add(T element) { return list.add(element); } + public int size() { return list.size(); } +} +``` + +Schreiben Sie mit Hilfe von JUnit (4.x oder 5.x) einige Unit-Tests für die beiden +Methoden `MyList#add` und `MyList#size`. + + + +**Einfache JUnit-Tests II** + +Betrachten Sie die Methode `String concat(String str)` der Klasse `String` aus dem +JDK. + +Implementieren Sie drei verschiedenartige Unit-Testfälle (inklusive der Eingabe- und +Rückgabewerte) für diese Methode mit Hilfe von JUnit (Version 4.x oder 5.x). +::: diff --git a/lecture/readme.md b/lecture/readme.md index d67673b22..d3a7b4c21 100644 --- a/lecture/readme.md +++ b/lecture/readme.md @@ -1,5 +1,6 @@ --- -title: "Vorlesungsunterlagen" -no_pdf: true no_beamer: true +no_pdf: true +title: Vorlesungsunterlagen --- + diff --git a/readme.md b/readme.md index bcc5c27aa..d8da95b09 100644 --- a/readme.md +++ b/readme.md @@ -1,72 +1,63 @@ --- -title: "IFM 2.1: Programmieren 2 (Sommer 2025)" has_license: true no_beamer: true +title: "IFM 2.1: Programmieren 2 (Sommer 2025)" --- - - - -> ... And, lastly, there's the explosive growth in demand, which has led to many people doing -> it who aren't any good at it. Code is merely a means to an end. -> **Programming is an art and code is merely its medium.** -> Pointing a camera at a subject does not make one a proper photographer. There are a lot of -> self-described coders out there who couldn't program their way out of a paper bag. +> ... And, lastly, there's the explosive growth in demand, which has led to many +> people doing it who aren't any good at it. Code is merely a means to an end. +> **Programming is an art and code is merely its medium.** Pointing a camera at a +> subject does not make one a proper photographer. There are a lot of self-described +> coders out there who couldn't program their way out of a paper bag. > -> \hfill -- John Gruber auf [daringfireball.net] - - [daringfireball.net]: https://daringfireball.net/2020/04/cobol_programming_coding - +> `\hfill`{=tex} -- John Gruber auf +> [daringfireball.net](https://daringfireball.net/2020/04/cobol_programming_coding) # Kursbeschreibung Sie haben letztes Semester in **Prog1** die *wichtigsten* Elemente und Konzepte der Programmiersprache Java kennen gelernt. -In diesem Modul geht es darum, diese Kenntnisse sowohl auf der Java- als auch auf der -Methoden-Seite so zu erweitern, dass Sie gemeinsam größere Anwendungen erstellen und pflegen -können. Sie werden fortgeschrittene Konzepte in Java kennenlernen und sich mit etablierten -Methoden in der Softwareentwicklung wie Versionierung von Code, Einhaltung von Coding -Conventions, Grundlagen des Softwaretests, Anwendung von Refactoring, Einsatz von Build-Tools -und Logging auseinander setzen. Wenn uns dabei ein Entwurfsmuster "über den Weg läuft", werden -wir die Gelegenheit nutzen und uns dieses genauer anschauen. - +In diesem Modul geht es darum, diese Kenntnisse sowohl auf der Java- als auch auf +der Methoden-Seite so zu erweitern, dass Sie gemeinsam größere Anwendungen erstellen +und pflegen können. Sie werden fortgeschrittene Konzepte in Java kennenlernen und +sich mit etablierten Methoden in der Softwareentwicklung wie Versionierung von Code, +Einhaltung von Coding Conventions, Grundlagen des Softwaretests, Anwendung von +Refactoring, Einsatz von Build-Tools und Logging auseinander setzen. Wenn uns dabei +ein Entwurfsmuster "über den Weg läuft", werden wir die Gelegenheit nutzen und uns +dieses genauer anschauen. # Überblick Modulinhalte 1. Fortgeschrittene Konzepte in Java ("Classic Java") - - Reguläre Ausdrücke, Annotationen, Reflection - - Generische Programmierung: Generics - - Parallele Programmierung: Threads - - ~~CLI~~, ~~Konfiguration~~, fremde APIs nutzen[^1] - - Graphische Oberflächen mit Swing[^2] + - Reguläre Ausdrücke, Annotationen, Reflection + - Generische Programmierung: Generics + - Parallele Programmierung: Threads + - ~~CLI~~, ~~Konfiguration~~, fremde APIs nutzen[^1] + - Graphische Oberflächen mit Swing[^2] 2. Fortgeschrittene Konzepte in Java ("FP") - - Default-Methoden, Funktionsinterfaces, Methodenreferenzen, Lambdas, Optional, Stream-API + - Default-Methoden, Funktionsinterfaces, Methodenreferenzen, Lambdas, + Optional, Stream-API 3. Versionierung mit Git 4. Softwarequalität - - Testen, Coding Conventions & Smells, Refactoring, Javadoc, Logging + - Testen, Coding Conventions & Smells, Refactoring, Javadoc, Logging 5. Entwurfsmuster - - ~~Strategy~~, Template-Method, ~~Factory-Method~~, ~~Singleton~~, Observer, Visitor, - Command, ... + - ~~Strategy~~, Template-Method, ~~Factory-Method~~, ~~Singleton~~, Observer, + Visitor, Command, ... 6. Bauen von Software - - Gradle, Docker, Continuous Integration (GitHub Workflows) - -(_durchgestrichene Themen nicht im Sommersemester 2025_) - -[^1]: als Teilaufgabe im Praktikum - -[^2]: nur als Wiederholung im Praktikum + - Gradle, Docker, Continuous Integration (GitHub Workflows) +(*durchgestrichene Themen nicht im Sommersemester 2025*) # Team -- [Carsten Gips] (Sprechstunde nach Vereinbarung) -- [BC George] (Sprechstunde nach Vereinbarung) -- Tutoren (siehe ILIAS-Mitgliederliste) - - [Carsten Gips]: https://www.hsbi.de/minden/ueber-uns/personenverzeichnis/carsten-gips - [BC George]: https://www.hsbi.de/minden/ueber-uns/personenverzeichnis/birgit-christina-george - +- [Carsten + Gips](https://www.hsbi.de/minden/ueber-uns/personenverzeichnis/carsten-gips) + (Sprechstunde nach Vereinbarung) +- [BC + George](https://www.hsbi.de/minden/ueber-uns/personenverzeichnis/birgit-christina-george) + (Sprechstunde nach Vereinbarung) +- Tutoren (siehe ILIAS-Mitgliederliste) # Kursformat @@ -79,260 +70,167 @@ wir die Gelegenheit nutzen und uns dieses genauer anschauen. | | G3: Fr, 09:45 - 11:15 Uhr (online, BC) | | | G4: Fr, 11:30 - 13:00 Uhr (online, BC) | -Online-Sitzungen per Zoom (**Zugangsdaten siehe [ILIAS]**). +Online-Sitzungen per Zoom (**Zugangsdaten siehe +[ILIAS](https://www.hsbi.de/elearning/goto.php?target=crs_1486054&client_id=FH-Bielefeld)**). Sie *können* hierzu den Raum J101 bzw. J104 (vgl. Stundenplan) nutzen. - [ILIAS]: https://www.hsbi.de/elearning/goto.php?target=crs_1486054&client_id=FH-Bielefeld - - # Fahrplan -Hier finden Sie einen abonnierbaren [Google Kalender] mit allen Terminen der Veranstaltung zum Einbinden in Ihre Kalender-App. - -| Monat | Tag | Vorlesung | VL-Quiz | Praktikum | -|:-------------------|:----------|:-------------------------------------------------------------------------------------------------------|:-------------|:-------------| -| April | 11. | Orga (**Zoom**), [FAQ] | | | -| | 18. | **Feiertag** | **Feiertag** | **Feiertag** | -| | 25. | [Einführung Versionierung], [Git Basics]; [Lambda-Ausdrücke]; [Gradle] | [Q01] | [B01] | -| Mai | 02. | [Git-Branches], [Branching-Strategien]; [Methodenreferenzen]; [Logging] | [Q02] | [B02] | -| | 09. | [Git-Remotes], [Git-Workflows]; [Stream-API]; [Record-Klassen] | [Q03] | [B03] | -| | 16. | **Station I** 09:00-11:00 Uhr, B40 (Aufteilung siehe [Ankündigung #997]) | | | -| | 23. | [Einführung Testen], [JUnit-Basics]; [Optional]; [Visitor-Pattern] | [Q04] | [B04] | -| | 30. | [Testfallermittlung], [Mocking]; [Default-Methoden]; [Observer-Pattern]; [Continuous Integration (CI)] | [Q05] | [B05] | -| Juni | 06. | [Code-Smells], [Coding-Rules], [Refactoring]; [Javadoc] | [Q06] | [B06] | -| | 13. | **Station II** 09:00-10:30 Uhr, B40 (Aufteilung siehe [Ankündigung #1025]) | | | -| | 20. | [RegExp]; [Template-Method-Pattern], [Command-Pattern]; [Annotationen] | [Q07] | [B07] | -| | 27. | Generics: [Klassen und Methoden], [Bounds und Wildcards], [Type Erasure], [Polymorphie]; [Docker] | [Q08] | [B08] | -| Juli | 04. | [Intro Threads], [Synchronisierung], [Highlevel Threadkonzepte]; [Reflection] | [Q09] | [B09] | -| *Prüfungsphase I* | 09.07. | **Station III** 09:00-10:30 Uhr, B40 (Aufteilung siehe [Ankündigung #1032]) | | | -| *Prüfungsphase II* | 24.09.(?) | **Station IV**, B40 (Aufteilung siehe Ankündigung) | | | - -Abgabe der Übungsblätter jeweils **bis Fr, 08:00 Uhr** [im ILIAS](https://www.hsbi.de/elearning/goto.php?target=exc_1514856&client_id=FH-Bielefeld), Vorstellung der -Lösung im zugehörigen Praktikum. -Bearbeitung der Quizzes jeweils **Sa, 00:00 Uhr (Vorwoche) bis Fr, 08:00 Uhr** [im ILIAS](https://www.hsbi.de/elearning/goto.php?target=fold_1514843&client_id=FH-Bielefeld). - -[Google Kalender]: https://calendar.google.com/calendar/ical/69ecbae80c817d60571a6ec968890b9b7ef0ffea5ce5dad1ef06c46eef7c530f%40group.calendar.google.com/public/basic.ics - -[Prüfungsvorbereitung]: admin/exams.md -[FAQ]: https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/discussions/categories/q-a -[Ankündigung #997]: https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/discussions/997 -[Ankündigung #1025]: https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/discussions/1025 -[Ankündigung #1032]: https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/discussions/1032 - -[Gradle]: lecture/building/gradle.md - - -[Continuous Integration (CI)]: lecture/building/ci.md -[Docker]: lecture/building/docker.md - -[Einführung Versionierung]: lecture/git/git-intro.md -[Git Basics]: lecture/git/git-basics.md -[Git-Branches]: lecture/git/branches.md -[Branching-Strategien]: lecture/git/branching-strategies.md -[Git-Remotes]: lecture/git/remotes.md -[Git-Workflows]: lecture/git/workflows.md - - - - - - - - - - -[Logging]: lecture/quality/logging.md -[Klassen und Methoden]: lecture/java-classic/generics-classes-methods.md -[Bounds und Wildcards]: lecture/java-classic/generics-bounds-wildcards.md -[Type Erasure]: lecture/java-classic/generics-type-erasure.md -[Polymorphie]: lecture/java-classic/generics-polymorphism.md - - -[RegExp]: lecture/java-classic/regexp.md -[Annotationen]: lecture/java-classic/annotations.md -[Reflection]: lecture/java-classic/reflection.md - - - -[Intro Threads]: lecture/java-classic/threads-intro.md -[Synchronisierung]: lecture/java-classic/threads-synchronisation.md -[Highlevel Threadkonzepte]: lecture/java-classic/threads-highlevel.md - -[Lambda-Ausdrücke]: lecture/java-modern/lambdas.md -[Methodenreferenzen]: lecture/java-modern/methodreferences.md -[Stream-API]: lecture/java-modern/stream-api.md -[Optional]: lecture/java-modern/optional.md -[Record-Klassen]: lecture/java-modern/records.md -[Default-Methoden]: lecture/java-modern/defaultmethods.md - - - - - -[Visitor-Pattern]: lecture/pattern/visitor.md -[Observer-Pattern]: lecture/pattern/observer.md -[Command-Pattern]: lecture/pattern/command.md - -[Template-Method-Pattern]: lecture/pattern/template-method.md - - - - -[Javadoc]: lecture/quality/javadoc.md -[Code-Smells]: lecture/quality/smells.md -[Coding-Rules]: lecture/quality/codingrules.md -[Refactoring]: lecture/quality/refactoring.md -[Einführung Testen]: lecture/quality/testing-intro.md -[JUnit-Basics]: lecture/quality/junit-basics.md -[Testfallermittlung]: lecture/quality/testcases.md -[Mocking]: lecture/quality/mockito.md - -[B01]: homework/b01.md -[B02]: homework/b02.md -[B03]: homework/b03.md -[B04]: homework/b04.md -[B05]: homework/b05.md -[B06]: homework/b06.md -[B07]: homework/b07.md -[B08]: homework/b08.md -[B09]: homework/b09.md - -[Q01]: https://www.hsbi.de/elearning/goto.php?target=tst_1527333&client_id=FH-Bielefeld -[Q02]: https://www.hsbi.de/elearning/goto.php?target=tst_1527338&client_id=FH-Bielefeld -[Q03]: https://www.hsbi.de/elearning/goto.php?target=tst_1527339&client_id=FH-Bielefeld -[Q04]: https://www.hsbi.de/elearning/goto.php?target=tst_1527340&client_id=FH-Bielefeld -[Q05]: https://www.hsbi.de/elearning/goto.php?target=tst_1527341&client_id=FH-Bielefeld -[Q06]: https://www.hsbi.de/elearning/goto.php?target=tst_1527342&client_id=FH-Bielefeld -[Q07]: https://www.hsbi.de/elearning/goto.php?target=tst_1527343&client_id=FH-Bielefeld -[Q08]: https://www.hsbi.de/elearning/goto.php?target=tst_1527344&client_id=FH-Bielefeld -[Q09]: https://www.hsbi.de/elearning/goto.php?target=tst_1527345&client_id=FH-Bielefeld - +Hier finden Sie einen abonnierbaren [Google +Kalender](https://calendar.google.com/calendar/ical/69ecbae80c817d60571a6ec968890b9b7ef0ffea5ce5dad1ef06c46eef7c530f%40group.calendar.google.com/public/basic.ics) +mit allen Terminen der Veranstaltung zum Einbinden in Ihre Kalender-App. + +| Monat | Tag | Vorlesung | VL-Quiz | Praktikum | +|:-------------------|:----------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------|:-----------------------| +| April | 11\. | Orga (**Zoom**), [FAQ](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/discussions/categories/q-a) | | | +| | 18\. | **Feiertag** | **Feiertag** | **Feiertag** | +| | 25\. | [Einführung Versionierung](lecture/git/git-intro.md), [Git Basics](lecture/git/git-basics.md); [Lambda-Ausdrücke](lecture/java-modern/lambdas.md); [Gradle](lecture/building/gradle.md) | [Q01](https://www.hsbi.de/elearning/goto.php?target=tst_1527333&client_id=FH-Bielefeld) | [B01](homework/b01.md) | +| Mai | 02\. | [Git-Branches](lecture/git/branches.md), [Branching-Strategien](lecture/git/branching-strategies.md); [Methodenreferenzen](lecture/java-modern/methodreferences.md); [Logging](lecture/quality/logging.md) | [Q02](https://www.hsbi.de/elearning/goto.php?target=tst_1527338&client_id=FH-Bielefeld) | [B02](homework/b02.md) | +| | 09\. | [Git-Remotes](lecture/git/remotes.md), [Git-Workflows](lecture/git/workflows.md); [Stream-API](lecture/java-modern/stream-api.md); [Record-Klassen](lecture/java-modern/records.md) | [Q03](https://www.hsbi.de/elearning/goto.php?target=tst_1527339&client_id=FH-Bielefeld) | [B03](homework/b03.md) | +| | 16\. | **Station I** 09:00-11:00 Uhr, B40 (Aufteilung siehe [Ankündigung #997](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/discussions/997)) | | | +| | 23\. | [Einführung Testen](lecture/quality/testing-intro.md), [JUnit-Basics](lecture/quality/junit-basics.md); [Optional](lecture/java-modern/optional.md); [Visitor-Pattern](lecture/pattern/visitor.md) | [Q04](https://www.hsbi.de/elearning/goto.php?target=tst_1527340&client_id=FH-Bielefeld) | [B04](homework/b04.md) | +| | 30\. | [Testfallermittlung](lecture/quality/testcases.md), [Mocking](lecture/quality/mockito.md); [Default-Methoden](lecture/java-modern/defaultmethods.md); [Observer-Pattern](lecture/pattern/observer.md); [Continuous Integration (CI)](lecture/building/ci.md) | [Q05](https://www.hsbi.de/elearning/goto.php?target=tst_1527341&client_id=FH-Bielefeld) | [B05](homework/b05.md) | +| Juni | 06\. | [Code-Smells](lecture/quality/smells.md), [Coding-Rules](lecture/quality/codingrules.md), [Refactoring](lecture/quality/refactoring.md); [Javadoc](lecture/quality/javadoc.md) | [Q06](https://www.hsbi.de/elearning/goto.php?target=tst_1527342&client_id=FH-Bielefeld) | [B06](homework/b06.md) | +| | 13\. | **Station II** 09:00-10:30 Uhr, B40 (Aufteilung siehe [Ankündigung #1025](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/discussions/1025)) | | | +| | 20\. | [RegExp](lecture/java-classic/regexp.md); [Template-Method-Pattern](lecture/pattern/template-method.md), [Command-Pattern](lecture/pattern/command.md); [Annotationen](lecture/java-classic/annotations.md) | [Q07](https://www.hsbi.de/elearning/goto.php?target=tst_1527343&client_id=FH-Bielefeld) | [B07](homework/b07.md) | +| | 27\. | Generics: [Klassen und Methoden](lecture/java-classic/generics-classes-methods.md), [Bounds und Wildcards](lecture/java-classic/generics-bounds-wildcards.md), [Type Erasure](lecture/java-classic/generics-type-erasure.md), [Polymorphie](lecture/java-classic/generics-polymorphism.md); [Docker](lecture/building/docker.md) | [Q08](https://www.hsbi.de/elearning/goto.php?target=tst_1527344&client_id=FH-Bielefeld) | [B08](homework/b08.md) | +| Juli | 04\. | [Intro Threads](lecture/java-classic/threads-intro.md), [Synchronisierung](lecture/java-classic/threads-synchronisation.md), [Highlevel Threadkonzepte](lecture/java-classic/threads-highlevel.md); [Reflection](lecture/java-classic/reflection.md) | [Q09](https://www.hsbi.de/elearning/goto.php?target=tst_1527345&client_id=FH-Bielefeld) | [B09](homework/b09.md) | +| *Prüfungsphase I* | 09.07. | **Station III** 09:00-10:30 Uhr, B40 (Aufteilung siehe [Ankündigung #1032](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/discussions/1032)) | | | +| *Prüfungsphase II* | 24.09.(?) | **Station IV**, B40 (Aufteilung siehe Ankündigung) | | | + +Abgabe der Übungsblätter jeweils **bis Fr, 08:00 Uhr** [im +ILIAS](https://www.hsbi.de/elearning/goto.php?target=exc_1514856&client_id=FH-Bielefeld), +Vorstellung der Lösung im zugehörigen Praktikum. Bearbeitung der Quizzes jeweils +**Sa, 00:00 Uhr (Vorwoche) bis Fr, 08:00 Uhr** [im +ILIAS](https://www.hsbi.de/elearning/goto.php?target=fold_1514843&client_id=FH-Bielefeld). # Prüfungsform, Note und Credits **Parcoursprüfung**, 5 ECTS (PO23) -Da Sie das Programmierhandwerk erlernen und üben und vertiefen sollen, dürfen Sie im Rahmen -dieser Lehrveranstaltung noch keine KI-gestützten Assistenten benutzen. Lösungen, die dennoch -ganz oder teilweise unter Zuhilfenahme von KI-Unterstützung erstellt wurden, werden wie nicht -abgegeben behandelt. +Da Sie das Programmierhandwerk erlernen und üben und vertiefen sollen, dürfen Sie im +Rahmen dieser Lehrveranstaltung noch keine KI-gestützten Assistenten benutzen. +Lösungen, die dennoch ganz oder teilweise unter Zuhilfenahme von KI-Unterstützung +erstellt wurden, werden wie nicht abgegeben behandelt. ## Prüfung im ersten Zeitraum 1. **Quizzes**: mind. 5 der 9 Quizzes bestanden (ohne Note/Punkte) - (Einzelbearbeitung, fristgerecht bis zur jeweiligen Vorlesung, je Quiz - bis zu 3x wiederholbar, 60% pro Quiz zum Bestehen nötig) -2. **Praktikum**: mind. 5 der 9 Übungsblätter bestanden (ohne Note/Punkte) - (bis zu 3er Teams, alle Aufgaben eines Blattes bearbeitet, individuelle(!) - fristgerechte Abgabe der Lösungen im ILIAS als aussagekräftiges - [*Post Mortem*](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/discussions/981)[^postmortem], + (Einzelbearbeitung, fristgerecht bis zur jeweiligen Vorlesung, je Quiz bis zu 3x + wiederholbar, 60% pro Quiz zum Bestehen nötig) +2. **Praktikum**: mind. 5 der 9 Übungsblätter bestanden (ohne Note/Punkte) (bis zu + 3er Teams, alle Aufgaben eines Blattes bearbeitet, individuelle(!) fristgerechte + Abgabe der Lösungen im ILIAS als aussagekräftiges [*Post + Mortem*](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/discussions/981)[^3], Vorstellung der Lösungen im Praktikum durch jedes Teammitglied, aktive Beteiligung an der Diskussion im Praktikum) 3. **Station I**: ILIAS-Test (30 Minuten in Minden im B40) 4. **Station II**: ILIAS-Test (30 Minuten in Minden im B40) 5. **Station III**: ILIAS-Test (30 Minuten in Minden im B40) -[^postmortem]: **Post Mortem**: Jede Person beschreibt in der ILIAS-Abgabe individuell(!) die - Bearbeitung des jeweiligen Aufgabenblattes zurückblickend mit 200 bis 400 Wörtern. Gehen - Sie dabei aussagekräftig und nachvollziehbar auf folgende Punkte ein: (a) Zusammenfassung: - Was wurde gemacht? (b) Implementierungsdetails: Kurze Beschreibung besonders interessanter - Aspekte der Umsetzung. (c) Was war der schwierigste Teil bei der Bearbeitung? Wie haben Sie - dieses Problem gelöst? (d) Was haben Sie gelernt oder (besser) verstanden? (e) Team: Mit wem - haben Sie zusammengearbeitet? (f) Links zu Ihren Pull-Requests mit der Lösung (erst ab Blatt 04). - Siehe auch https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/discussions/981. - -Station I und II finden im Vorlesungsslot statt ([Aufteilung siehe separate Ankündigung](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/discussions/1025)), -Station III im ersten Prüfungszeitraum (Termin lt. Bekanntgabe vom Prüfungsamt: Mi, 09.07., -[Aufteilung siehe separate Ankündigung](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/discussions/1032)). -Die Punkte der beiden besseren Stationen werden summiert bzw. es wird der Mittelwert der beiden -besten prozentualen Ergebnisse ermittelt zur die Berechnung der Note.[^note] - -[^note]: Wenn die Maximalzahl der Punkte für Station I, II und III identisch ist, wird einfach - die Summe der Punkte der beiden besseren Stationen berechnet und für die Bildung der Gesamtnote - genutzt. Wenn die Maximalzahl der Punkte für Station I, II und III voneinander abweicht, dann - wird jeweils das erreichte prozentuale Ergebnis berechnet und die Gesamtnote über den Mittelwert - der beiden besseren Ergebnisse berechnet. - -**Gesamtnote**: 4.0: ab 50%, alle 5% nächste Teilnote, 1.0: ab 95% (jeweils nur wenn Quizzes -bestanden und Praktikum bestanden) +Station I und II finden im Vorlesungsslot statt ([Aufteilung siehe separate +Ankündigung](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/discussions/1025)), +Station III im ersten Prüfungszeitraum (Termin lt. Bekanntgabe vom Prüfungsamt: Mi, +09.07., [Aufteilung siehe separate +Ankündigung](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/discussions/1032)). +Die Punkte der beiden besseren Stationen werden summiert bzw. es wird der Mittelwert +der beiden besten prozentualen Ergebnisse ermittelt zur die Berechnung der Note.[^4] + +**Gesamtnote**: 4.0: ab 50%, alle 5% nächste Teilnote, 1.0: ab 95% (jeweils nur wenn +Quizzes bestanden und Praktikum bestanden) Bei mind. drei über das Minimum hinaus bestandenen Quizzes und/oder Aufgabenblättern verbessert sich die Gesamtnote um eine Teilnote. -(Hinweise zur [Prüfungsvorbereitung] für Station I bis III) +(Hinweise zur [Prüfungsvorbereitung](admin/exams.md) für Station I bis III) ## Prüfung im zweiten Zeitraum -1. **Station IV**: Schriftliche Prüfung (digitale Klausur) 90 Minuten in Minden im B40 - (Termin lt. Bekanntgabe vom Prüfungsamt: voraussichtlich 24.09.) +1. **Station IV**: Schriftliche Prüfung (digitale Klausur) 90 Minuten in Minden im + B40 (Termin lt. Bekanntgabe vom Prüfungsamt: voraussichtlich 24.09.) **Gesamtnote**: 4.0: ab 50%, alle 5% nächste Teilnote, 1.0: ab 95% -(Hinweise zur [Prüfungsvorbereitung] für Station IV) - +(Hinweise zur [Prüfungsvorbereitung](admin/exams.md) für Station IV) # Materialien ## Literatur -1. ["**Java ist auch eine Insel**"]. Ullenboom, C., Rheinwerk-Verlag, 2021. ISBN - [978-3-8362-8745-6]. -2. ["**Pro Git** (Second Edition)"]. Chacon, S. und Straub, B., Apress, 2014. ISBN - [978-1-4842-0077-3]. -3. ["The Java Tutorials"]. Oracle Corporation, 2024. -4. ["Learn Java"]. Oracle Corporation, 2025. - - ["**Java ist auch eine Insel**"]: https://openbook.rheinwerk-verlag.de/javainsel/index.html - [978-3-8362-8745-6]: https://fhb-bielefeld.digibib.net/openurl?isbn=978-3-8362-8745-6 - ["**Pro Git** (Second Edition)"]: https://git-scm.com/book/en/v2 - [978-1-4842-0077-3]: https://fhb-bielefeld.digibib.net/openurl?isbn=978-1-4842-0077-3 - ["The Java Tutorials"]: https://docs.oracle.com/javase/tutorial/ - ["Learn Java"]: https://dev.java/learn/ +1. ["**Java ist auch eine + Insel**"](https://openbook.rheinwerk-verlag.de/javainsel/index.html). Ullenboom, + C., Rheinwerk-Verlag, 2021. ISBN + [978-3-8362-8745-6](https://fhb-bielefeld.digibib.net/openurl?isbn=978-3-8362-8745-6). +2. ["**Pro Git** (Second Edition)"](https://git-scm.com/book/en/v2). Chacon, S. und + Straub, B., Apress, 2014. ISBN + [978-1-4842-0077-3](https://fhb-bielefeld.digibib.net/openurl?isbn=978-1-4842-0077-3). +3. ["The Java Tutorials"](https://docs.oracle.com/javase/tutorial/). Oracle + Corporation, 2024. +4. ["Learn Java"](https://dev.java/learn/). Oracle Corporation, 2025. ## Tools -- JDK: **Java SE 21 (LTS)** ([Oracle] oder [Alternativen], bitte 64-bit Version nutzen) -- IDE: [Eclipse IDE for Java Developers] oder [IntelliJ IDEA (Community Edition)] oder [Visual - Studio Code] oder [Vim] oder ... -- [Git] - - [Oracle]: https://www.oracle.com/java/technologies/downloads/ - [Alternativen]: https://code.visualstudio.com/docs/languages/java#_install-a-java-development-kit-jdk - [Eclipse IDE for Java Developers]: https://www.eclipse.org/downloads/ - [IntelliJ IDEA (Community Edition)]: https://www.jetbrains.com/idea/ - [Visual Studio Code]: https://code.visualstudio.com/ - [Vim]: https://www.vim.org/ - [Git]: https://git-scm.com/ - +- JDK: **Java SE 21 (LTS)** + ([Oracle](https://www.oracle.com/java/technologies/downloads/) oder + [Alternativen](https://code.visualstudio.com/docs/languages/java#_install-a-java-development-kit-jdk), + bitte 64-bit Version nutzen) +- IDE: [Eclipse IDE for Java Developers](https://www.eclipse.org/downloads/) oder + [IntelliJ IDEA (Community Edition)](https://www.jetbrains.com/idea/) oder + [Visual Studio Code](https://code.visualstudio.com/) oder + [Vim](https://www.vim.org/) oder ... +- [Git](https://git-scm.com/) # Förderungen und Kooperationen ## Förderung durch DH.NRW (Digi Fellowships) -Die Überarbeitung dieser Lehrveranstaltung wurde vom Ministerium für Kultur und Wissenschaft -(MKW) in NRW im Einvernehmen mit der Digitalen Hochschule NRW (DH.NRW) gefördert: -["Fellowships für Innovationen in der digitalen Hochschulbildung"] (*Digi Fellowships*). - -["Fellowships für Innovationen in der digitalen Hochschulbildung"]: https://www.dh.nrw/kooperationen/Digi-Fellows-2 +Die Überarbeitung dieser Lehrveranstaltung wurde vom Ministerium für Kultur und +Wissenschaft (MKW) in NRW im Einvernehmen mit der Digitalen Hochschule NRW (DH.NRW) +gefördert: ["Fellowships für Innovationen in der digitalen +Hochschulbildung"](https://www.dh.nrw/kooperationen/Digi-Fellows-2) (*Digi +Fellowships*). ## Kooperation mit dem DigikoS-Projekt -Diese Vorlesung wurde vom Projekt ["Digitalbaukasten für kompetenzorientiertes Selbststudium"] -(*DigikoS*) unterstützt. Ein vom DigikoS-Projekt ausgebildeter Digital Learning Scout hat -insbesondere die Koordination der digitalen Gruppenarbeiten, des Peer-Feedbacks und der -Postersessions in ILIAS technisch und inhaltlich begleitet. DigikoS wird als Verbundprojekt -von der Stiftung Innovation in der Hochschullehre gefördert. - -["Digitalbaukasten für kompetenzorientiertes Selbststudium"]: https://www.digikos.de +Diese Vorlesung wurde vom Projekt ["Digitalbaukasten für kompetenzorientiertes +Selbststudium"](https://www.digikos.de) (*DigikoS*) unterstützt. Ein vom +DigikoS-Projekt ausgebildeter Digital Learning Scout hat insbesondere die +Koordination der digitalen Gruppenarbeiten, des Peer-Feedbacks und der +Postersessions in ILIAS technisch und inhaltlich begleitet. DigikoS wird als +Verbundprojekt von der Stiftung Innovation in der Hochschullehre gefördert. - ---- +------------------------------------------------------------------------------------ # LICENSE ![](https://licensebuttons.net/l/by-sa/4.0/88x31.png) -Unless otherwise noted, [this work](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture) by -[Carsten Gips](https://github.com/cagix) and [contributors](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/graphs/contributors) -is licensed under [CC BY-SA 4.0](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/LICENSE.md). +Unless otherwise noted, [this +work](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture) by [Carsten +Gips](https://github.com/cagix) and +[contributors](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/graphs/contributors) +is licensed under [CC BY-SA +4.0](https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/LICENSE.md). See the [credits](CREDITS.md) for a detailed list of contributing projects. + +[^1]: als Teilaufgabe im Praktikum + +[^2]: nur als Wiederholung im Praktikum + +[^3]: **Post Mortem**: Jede Person beschreibt in der ILIAS-Abgabe individuell(!) die + Bearbeitung des jeweiligen Aufgabenblattes zurückblickend mit 200 bis 400 + Wörtern. Gehen Sie dabei aussagekräftig und nachvollziehbar auf folgende Punkte + ein: (a) Zusammenfassung: Was wurde gemacht? (b) Implementierungsdetails: Kurze + Beschreibung besonders interessanter Aspekte der Umsetzung. (c) Was war der + schwierigste Teil bei der Bearbeitung? Wie haben Sie dieses Problem gelöst? (d) + Was haben Sie gelernt oder (besser) verstanden? (e) Team: Mit wem haben Sie + zusammengearbeitet? (f) Links zu Ihren Pull-Requests mit der Lösung (erst ab + Blatt 04). Siehe auch + https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/discussions/981. + +[^4]: Wenn die Maximalzahl der Punkte für Station I, II und III identisch ist, wird + einfach die Summe der Punkte der beiden besseren Stationen berechnet und für die + Bildung der Gesamtnote genutzt. Wenn die Maximalzahl der Punkte für Station I, + II und III voneinander abweicht, dann wird jeweils das erreichte prozentuale + Ergebnis berechnet und die Gesamtnote über den Mittelwert der beiden besseren + Ergebnisse berechnet.