14. Schichtarchitektur und schnittstellenbasierte Programmierung
14.1. Einleitung
Wir schlagen vor, eine Anwendung zu schreiben, die die Noten von Mittelschülern anzeigt. Diese Anwendung kann eine mehrschichtige Architektur haben:

- Die [ui]-Schicht (User Interface) ist die Schicht, die mit dem Benutzer der Anwendung interagiert;
- die [Business]-Schicht implementiert die Geschäftsregeln der Anwendung, wie beispielsweise die Berechnung eines Gehalts oder einer Rechnung. Diese Schicht nutzt Daten vom Benutzer über die [Präsentations]-Schicht und aus dem DBMS über die [DAO]-Schicht;
- die [DAO]-Schicht (Data Access Objects) verwaltet den Zugriff auf Daten im DBMS (Datenbankmanagementsystem).
Dies ist die Architektur, die im |Python-2-Kurs| verwendet wurde. Es kann auch eine Variante eingeführt werden:

Die Unterschiede zur vorherigen Schichtstruktur sind wie folgt:
- Ein Hauptskript namens [main] organisiert die Instanziierung der Schichten;
- Die Schichten [ui, business, dao] kommunizieren nicht mehr zwangsläufig miteinander. Falls dies erforderlich ist, stellt das [main]-Skript ihnen die Referenzen zu den benötigten Schichten zur Verfügung;
Der Code ist hier in Funktionsbereiche mit einem zentralen Koordinator gegliedert:
- Der Orchestrator ist das Hauptskript [main];
- die Schichten [ui], [dao] und [business] sind die Kompetenzzentren;
Wir könnten diese Struktur als orchestrale Organisation bezeichnen.
14.2. Beispiel 1
Wir werden die Schichtenarchitektur anhand einer einfachen Konsolenanwendung veranschaulichen:
- Es wird keine Datenbank geben;
- die [DAO]-Schicht verwaltet die Entitäten „Student“, „Class“, „Subject“ und „Grade“, um die Noten der Schüler zu verarbeiten;
- Die [Business]-Schicht berechnet Kennzahlen auf der Grundlage der Noten eines bestimmten Schülers;
- Die [UI]-Schicht ist eine Konsolenanwendung, die die Ergebnisse der Schüler anzeigt;
Das PyCharm-Projekt für die Anwendung sieht wie folgt aus:
![]() |
Hinweis: Die blau markierten Ordner sind Teil des [Root Sources] des PyCharm-Projekts.
14.2.1. Die Entitäten der Anwendung
Wir bezeichnen Klassen, deren einzige Aufgabe darin besteht, Daten zu kapseln, als Entitäten. Zu diesem Zweck könnten auch Dictionaries verwendet werden. Der Vorteil einer Klasse besteht darin, dass sie es uns ermöglicht, die Gültigkeit der im Objekt gespeicherten Daten zu prüfen und eine Methode bereitzustellen, die die Identität des Objekts als Zeichenkette zurückgibt.
![]() |
14.2.1.1. Die Entität [Class]
Die Entität [Class] (Class.py) repräsentiert eine Mittelschulklasse:
Anmerkungen
- Zeile 7: Die Entität [Class] leitet sich von der Entität [BaseEntity] ab, die im Abschnitt |Die Klasse BaseEntity| behandelt wurde;
- Zeilen 11–16: Eine Klasse wird durch eine ID und einen Namen definiert (Zeile 16). Die Eigenschaft [id] wird von der Klasse [BaseEntity] bereitgestellt, der Name von der Klasse [Class];
- Zeilen 18–30: Getter/Setter für das Attribut [name];
14.2.1.2. Die Entität [Subject]
Die Klasse [Subject] (subject.py) sieht wie folgt aus:
Anmerkungen
- Zeile 7: Die Klasse [Class] leitet sich von der Klasse [BaseEntity] ab;
- Zeilen 11–17: Ein Subjekt wird durch seine ID [id], seinen Namen [name] und sein Gewicht [coefficient] definiert;
- Zeilen 19–50: Getter/Setter für die Klassenattribute;
14.2.1.3. Die Entität [Student]
Die Klasse [Student] (student.py) sieht wie folgt aus:
Anmerkungen
- Zeile 9: Die Klasse [Student] leitet sich von der Klasse [BaseEntity] ab;
- Zeilen 13–20: Ein Student wird durch seine ID [id], seinen Nachnamen [lastName], seinen Vornamen [firstName] und seine Klasse [class] charakterisiert. Der letztgenannte Parameter ist eine Referenz auf ein [Class]-Objekt;
- Zeilen 22–65: Getter/Setter für die Klassenattribute;
14.2.1.4. Die Entität [Note]
Die Klasse [Note] (note.py) sieht wie folgt aus:
Notizen
- Zeile 8: Die Klasse [Note] leitet sich von der Klasse [BaseEntity] ab;
- Zeilen 12–20: Ein [Note]-Objekt ist durch seine ID [id], den Notenwert [value], eine Referenz [student] auf den Schüler, der diese Note erhalten hat, und eine Referenz auf das mit der Note verbundene Fach [subject] gekennzeichnet;
- Zeilen 22–75: Getter/Setter für die Klassenattribute;
14.2.2. Anwendungskonfiguration
![]() |
Die Datei [config.py] konfiguriert die Umgebung sowohl für das Hauptskript [main] (1) als auch für die Tests (2). Alle diese Skripte enthalten am Anfang des Codes eine [import config]-Anweisung. Beachten Sie, dass das Verzeichnis, in dem sich das vom Befehl [python script] angegebene Skript befindet, automatisch Teil des Python-Pfads ist. Befindet sich [config] also im selben Verzeichnis wie die Skripte, die die [import config]-Anweisung enthalten, wird es gefunden. Die Dateien [1] und [2] sind hier identisch. Dies ist jedoch nicht immer der Fall.
Die Datei [config.sys] sieht wie folgt aus:
- Zeilen 11–14: Die Verzeichnisse, die Teil des Python-Pfads (sys.path) sein müssen;
- Das Verzeichnis [f"{root_dir}/02/entities"] bietet Zugriff auf die Klassen [BaseEntity] und [MyException];
- Der Ordner [f"{script_dir}/../entities"] bietet Zugriff auf die Klassen [Student], [Class], [Subject] und [Grade];
- Der Ordner [f"{script_dir}/../interfaces"] bietet Zugriff auf die Schnittstellen der Anwendung;
- Der Ordner [f"{script_dir}/../services"] bietet Zugriff auf die Klassen, die die Schnittstellen implementieren;
14.2.3. Entity-Tests
![]() |
Hier schreiben wir Tests, die von einem Tool namens [unittest] ausgeführt werden. PyCharm enthält mehrere Test-Frameworks. Sie können eines davon in der PyCharm-Konfiguration auswählen:

- In [4] stehen mehrere Test-Frameworks zur Verfügung:

14.2.3.1. Die Testklasse [TestBaseEntity]
Das Testskript [TestBaseEntity] sieht wie folgt aus:
Anmerkungen
- Zeile 1: Wir importieren das Modul [unittest], das die verschiedenen Testmethoden bereitstellt;
- Zeilen 3–6: Wir konfigurieren die Anwendung so, dass die für das Testen benötigten Klassen gefunden werden können;
- Zeile 9: Eine [unittest]-Testklasse muss die Klasse [unittest.TestCase] erweitern;
- Zeilen 11, 27: Testfunktionen müssen einen Namen haben, der mit [test] beginnt, andernfalls werden sie nicht erkannt;
- Zeilen 13–16: Wir importieren die benötigten Klassen;
- In dieser Testklasse wollen wir das Verhalten der Methoden [BaseEntity.fromdict] (Zeile 34) und [BaseEntity.fromjson] (Zeile 18) überprüfen. Die Klasse [Note] verfügt über Eigenschaften, die Verweise auf andere Klassen sind. Wir wollen sicherstellen, dass die beiden vorgenannten Methoden gültige [Note]-Objekte erstellen;
- Zeile 18: Wir erstellen ein [Note]-Objekt aus einem JSON-Objekt;
- Zeile 21: Wir überprüfen, ob das erstellte Objekt tatsächlich vom Typ [Note] ist. Die Methode [assertIsInstance] ist eine Methode der Klasse [unittest.TestCase], die die übergeordnete Klasse der Klasse [TestBaseEntity] ist;
- Zeile 22: Wir überprüfen, ob [note.student] tatsächlich vom Typ [Student] ist;
- Zeile 23: Wir überprüfen, ob [note.student.class] tatsächlich vom Typ [Class] ist;
- Zeile 24: Wir überprüfen, ob [note.subject] tatsächlich vom Typ [Subject] ist;
- Zeilen 33–42: Wir machen dasselbe mit der Methode [BaseEntity.fromdict];
Es gibt mehrere Möglichkeiten, die Tests auszuführen:
![]() |
- In [1-2] führen wir [TestBaseEntity] unter Verwendung des [UnitTest]-Frameworks aus;
- In [3-5] schlagen die Tests fehl. [UnitTests] weist darauf hin, dass keine auszuführenden Tests gefunden wurden;
Die Tests schlagen aufgrund der Struktur des [TestBaseEntity]-Codes fehl:
Was beim [UnitTest]-Framework zu Problemen führt, ist das Vorhandensein von ausführbarem Code (Zeilen 3–6) vor der Definition der Testklasse (Zeile 9).
Wir ordnen den Code daher wie folgt neu:
- Zeilen 6–10: Wir definieren eine [setUp]-Funktion. Diese Funktion hat eine bestimmte Aufgabe: Sie wird vor jeder Testfunktion (test_note1, test_note2) ausgeführt;
Sobald dies geschehen ist, liefert die Ausführung der Klasse [TestBaseEntity] die folgenden Ergebnisse:
![]() |
Diesmal wurden beide Testmethoden ausgeführt und die Tests bestanden.
Schauen wir uns an, was passiert, wenn ein Test fehlschlägt. Ändern wir den Code in [test_note1] wie folgt:
- Zeile 2: Wir prüfen, ob 1 == 2;
Die Ergebnisse der Ausführung lauten wie folgt:
![]() |
Sie können die Ursache des Fehlers herausfinden, indem Sie auf den fehlgeschlagenen Test [2] klicken:
![]() |
- in [7-8] die Ursache des Fehlers;
Eine weitere Möglichkeit, eine Testklasse auszuführen, besteht darin, sie in einem Terminal auszuführen:
![]() |
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\troiscouches\v01\tests>python -m unittest TestBaseEntity.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.026s
OK
Zeile 6 zeigt an, dass beide Tests bestanden wurden (wir haben den Fehler „1==2“ behoben);
Schließlich gibt es noch eine dritte Möglichkeit, die Testklasse [TestBaseEntity] auszuführen, ebenfalls im Terminal. Wir beenden die Testklasse mit den folgenden Zeilen 6–7;
…
self.assertIsInstance(note.élève.classe, Classe)
self.assertIsInstance(note.matière, Matière)
if __name__ == '__main__':
unittest.main()
- Zeile 6: Die Variable [__name__] ist der Name des Skripts, das gerade ausgeführt wird. Wenn das Skript mit dem Befehl [python script.py] gestartet wird, ist die Variable [__name__] gleich [__main__] (zwei Unterstriche vor und nach dem Bezeichner). Daher wird Zeile 7 nur ausgeführt, wenn das Skript [TestBaseEntity] mit dem Befehl [python TestBaseEntity.py] gestartet wird. Die Anweisung [unittest.main()] startet die Ausführung des Skripts über das [UnitTest]-Framework. Hier ein Beispiel:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\troiscouches\v01\tests>python TestBaseEntity.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.013s
OK
14.2.3.2. Die Testklasse [TestEntities]
Die Testklasse [TestEntities] sieht wie folgt aus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 | |
- Der Zweck des Testskripts besteht darin, die Setter der Klasse zu testen: Es soll überprüft werden, ob den Attributen der verschiedenen Entitäten keine falschen Werte zugewiesen werden können;
- Zeilen 11–24: Wir testen, ob einem Studenten keine ungültige ID zugewiesen werden kann. Da wir in Zeile 16 den Wert 'x' als ID des Studenten übergeben, erwarten wir, dass eine Ausnahme auftritt. Wir sollten daher zu den Zeilen 20–22 übergehen;
- Zeile 21: Anzeige der Fehlermeldung;
- Zeile 22: Abrufen des Fehlercodes (siehe Abschnitt |Die MyException-Entität|);
- Zeile 24: Wir überprüfen (assert), ob der Fehlercode 1 ist. Hier überprüfen wir zwei Dinge:
- dass tatsächlich ein Fehler aufgetreten ist;
- dass der Fehlercode 1 ist;
- dieser Vorgang wird mit den Funktionen in den Zeilen 24–213 wiederholt;
- Zeilen 215–222: Wir testen, ob eine Aktion eine Ausnahme eines bestimmten Typs auslöst;
- Zeile 220: Wir geben an, dass der Test erfolgreich ist, wenn er eine Ausnahme vom Typ [MyException] auslöst;
Ergebnisse
Wir führen das Testskript aus:
![]() |
Die erhaltenen Ergebnisse lauten wie folgt:
Testing started at 09:39 ...
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.1.2\plugins\python-ce\helpers\pycharm\_jb_unittest_runner.py" --path C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v01/tests/TestEntités.py
Launching unittests with arguments python -m unittest C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v01/tests/TestEntités.py in C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\troiscouches\v01\tests
code erreur=1, message=MyException[1, L'identifiant d'une entité <class 'Elève.Elève'> doit être un entier >=0]
code erreur=1, message=MyException[1, L'identifiant d'une entité <class 'Classe.Classe'> doit être un entier >=0]
code erreur=1, message=MyException[1, L'identifiant d'une entité <class 'Matière.Matière'> doit être un entier >=0]
code erreur=1, message=MyException[1, L'identifiant d'une entité <class 'Note.Note'> doit être un entier >=0]
code erreur=21, message=MyException[21, Le nom de la matière 1 doit être une chaîne de caractères non vide]
code erreur=22, message=MyException[22, Le coefficient de la matière y doit être un réel >=0]
code erreur=31, message=MyException[31, L'attribut x de la note 1 doit être un nombre dans l'intervalle [0,20]]
code erreur=32, message=MyException[32, L'attribut [y] de la note 1 doit être de type Elève ou dict ou json. Erreur : Expecting value: line 1 column 1 (char 0)]
code erreur=33, message=MyException[33, L'attribut [z] de la note 1 doit être de type Matière ou dict ou json. Erreur : Expecting value: line 1 column 1 (char 0)]
code erreur=41, message=MyException[41, Le nom de l'élève 1 doit être une chaîne de caractères non vide]
code erreur=42, message=MyException[42, Le prénom de l'élève 1 doit être une chaîne de caractères non vide]
code erreur=43, message=MyException[43, L'attribut [t] de l'élève 1 doit être de type Classe ou dict ou json. Erreur : Expecting value: line 1 column 1 (char 0)]
Ran 14 tests in 0.040s
OK
Process finished with exit code 0
Hier wurden alle Tests bestanden
14.2.4. Die [dao]-Schicht

Die [dao]-Schicht implementiert die Schnittstelle [InterfaceDao] [1]. Diese wird durch die Klasse [Dao] (2) implementiert. Das Skript [tests_dao] (3) testet die Methoden der [dao]-Schicht.
14.2.4.1. Schnittstelle [InterfaceDao]
Eine Schnittstelle ist eine Vereinbarung zwischen aufrufendem und aufgerufenem Code. Es ist der aufgerufene Code, der die Schnittstelle bereitstellt:
![]() |
- Der aufrufende Code [1] kennt die Implementierung des aufgerufenen Codes [3] nicht. Er weiß lediglich, wie er ihn aufrufen muss. Die Schnittstelle [2] sagt ihm, wie. Diese Schnittstelle definiert eine Reihe von Methoden/Funktionen, die zur Interaktion mit dem aufgerufenen Code verwendet werden. Diese Schnittstelle wird auch als API (Application Programming Interface) bezeichnet;
Die [dao]-Schicht wird die folgende Schnittstelle bereitstellen:
- [get_classes] gibt die Liste der Mittelschulklassen zurück;
- [get_subjects] gibt die Liste der an der Mittelschule unterrichteten Fächer zurück;
- [get_students] gibt die Liste der Schüler der Mittelschule zurück;
- [get_grades] gibt eine Liste aller Noten der Schüler zurück;
- [get_grades_for_student_by_id] gibt die Noten eines bestimmten Schülers zurück;
- [get_student_by_id] gibt einen Schüler zurück, der anhand seiner ID identifiziert wird;
Der aufrufende Code verwendet ausschließlich diese Methoden. Er muss nicht wissen, wie sie implementiert sind. Die Daten können dann aus verschiedenen Quellen stammen (fest codiert, aus einer Datenbank, aus Textdateien usw.), ohne dass dies Auswirkungen auf den aufrufenden Code hat. Dies wird als schnittstellenbasierte Programmierung bezeichnet.
Python 3 verfügt über ein Konzept, das dem einer Schnittstelle ähnelt: die abstrakte Klasse. Wir werden diese verwenden. Wir werden die Schnittstellen für dieses Beispiel im Ordner [interfaces] zusammenfassen.
Wir definieren eine abstrakte Klasse [InterfaceDao] (InterfaceDao.py) für die [dao]-Schicht:
Anmerkungen:
- Zeile 2: ABC = Abstract Base Class. Wir importieren die ABC-Klasse aus dem Modul [abc] sowie den Dekorator [abstractmethod], der in den Zeilen 10, 15, 20, 25, 30 und 35 verwendet wird;
- Zeile 8: Die abstrakte Klasse heißt [InterfaceDao] und leitet sich von der Klasse [ABC] ab;
- Die Methoden der abstrakten Klasse sind mit dem Dekorator [@abstractmethod] versehen, wodurch die dekorierte Methode zu einer abstrakten Methode wird: Ihr Code ist nicht definiert. Wir fügen dort jedoch Code ein: die Anweisung [pass], die nichts tut;
- Die abstrakte Klasse [InterfaceDao] kann nicht instanziiert werden. Nur von [InterfaceDao] abgeleitete Klassen, die alle Methoden von [InterfaceDao] implementiert haben, können instanziiert werden. Wenn wir also zwei Klassen [Dao1] und [Dao2] erstellen, die von der Klasse [InterfaceDao] abgeleitet sind, implementieren beide die abstrakten Methoden von [InterfaceDao]. Man könnte daher sagen, dass sie die Schnittstelle [InterfaceDao] implementieren;
- Sprachen, die sowohl Schnittstellen als auch abstrakte Klassen unterstützen, weisen der Schnittstelle eine andere Rolle zu als der abstrakten Klasse. Eine Schnittstelle hat keine Attribute und kann nicht instanziiert werden. Eine Klasse kann eine Schnittstelle implementieren, indem sie alle ihre Methoden definiert;
14.2.4.2. [Dao]-Implementierung
Die Klasse [Dao] (dao.py) implementiert die Schnittstelle [InterfaceDao] wie folgt:
Anmerkungen:
- Zeilen 1–7: Wir importieren die Entitäten und die Schnittstelle [InterfaceDao];
- Zeile 11: Die Klasse [Dao] leitet sich von der abstrakten Klasse [InterfaceDao] ab. Wir sagen, dass sie die Schnittstelle [InterfaceDao] implementiert;
- Zeile 14: Der Konstruktor hat keine Parameter. Er enthält vier fest codierte Listen:
- Zeilen 15–18: die Liste der Klassen;
- Zeilen 19–22: die Liste der Fächer;
- Zeilen 23–28: die Liste der Schüler;
- Zeilen 29–38: die Liste der Noten;
- Zeilen 40–44: Implementierung der Methoden der [Dao-Schnittstelle]. Hier definieren wir sie nicht, um die von Python ausgegebene Fehlermeldung zu sehen;
Ein Testprogramm könnte wie folgt aussehen [tests-dao.py]:
Hinweis: Das Skript [tests-dao.py] ist kein [unittest], da es keine Methoden enthält, deren Namen mit [test_] beginnen.
Die Kommentare sind selbsterklärend. In den Zeilen 11–25 wird die Schnittstelle der [dao]-Schicht verwendet. Hier werden keine Annahmen über die tatsächliche Implementierung der Schicht getroffen. In Zeile 9 instanziieren wir die [dao]-Schicht.
Die Ergebnisse der Ausführung dieses Skripts lauten wie folgt:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v01/tests/tests_dao.py
Traceback (most recent call last):
File "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v01/tests/tests_dao.py", line 9, in <module>
daoImpl = Dao()
TypeError: Can't instantiate abstract class Dao with abstract methods get_classes, get_matières, get_notes, get_notes_for_élève_by_id, get_élève_by_id, get_élèves
Process finished with exit code 1
Wir sehen, dass ein Fehler auftritt, sobald die Klasse [Dao] instanziiert wird (Zeile 3 oben). Der Python-3-Interpreter teilt uns mit, dass er die Klasse nicht instanziieren kann, da wir die abstrakten Methoden [get_classes, get_subjects, get_grades, get_grades_for_student_by_id, get_student_by_id, get_students] nicht definiert haben.
PyCharm unterstützt ebenfalls abstrakte Klassen und bietet an, deren Methoden zu definieren:
![]() |
- Klicken Sie in [1] mit der rechten Maustaste auf den Code;
- in [2-3] wählen Sie [Generate / Implement Methods], um die fehlenden Methoden der Klasse [Dao] zu implementieren;
- Wählen Sie in [4] die zu implementierenden Methoden aus – in diesem Fall alle;
Sobald dies erledigt ist, vervollständigt PyCharm die Klasse [Dao] wie folgt:
Wir vervollständigen die Klasse [Dao] wie folgt:
- Die Zeilen 5–19 sind unkompliziert;
- Zeilen 29–36: Die Methode, die den Schüler mit der übergebenen ID zurückgibt. Wenn der Schüler nicht existiert, wird eine Ausnahme ausgelöst;
- Zeile 31: Mit der Funktion [filter] können Sie eine Liste filtern:
- Der erste Parameter ist das Filterkriterium;
- der zweite Parameter ist die zu filternde Liste, in diesem Fall die Liste der Studenten;
- Zeile 31: Das Filterkriterium für die Liste wird mithilfe einer Funktion [f(e:Student) -> bool] implementiert. Diese wird auf jedes Element der zu filternden Liste angewendet. Erfüllt das Element das Filterkriterium, bleibt es in der gefilterten Liste erhalten; andernfalls wird es ausgeschlossen. Hier haben wir zwei Möglichkeiten:
- den Namen der Funktion f angeben und sie an anderer Stelle implementieren. Der Aufruf der Funktion [filter] lautet dann [filter(f, self.get_students)];
- die Definition der Funktion f bereitstellen. Der Aufruf der Funktion [filter] lautet dann [filter(f(e :Student){…}, self.get_students())], wobei [e] ein Element der gefilterten Liste darstellt, d. h. einen Studenten. Dies ist hier der Fall. Die Definition der Funktion f wäre hier [f(e :Student){return e.id == student_id)]: Ein Schüler wird nur ausgewählt, wenn seine ID-Nummer [id] mit der gesuchten übereinstimmt. Eine solche Funktion kann durch eine sogenannte Lambda-Funktion ersetzt werden: [lambda e: e.id == student_id]:
- e: steht für den Parameter der Funktion f, hier einen Studenten. Sie können einen beliebigen Namen verwenden;
- e.id==student_id ist das Filterkriterium: Ein Student [e] wird nur ausgewählt, wenn seine ID [id] mit der gesuchten übereinstimmt;
- Zeile 31: Die Funktion [filter] gibt die gefilterte Liste als einen Typ zurück, der nicht vom Typ [list] ist, aber in den Typ [list] konvertiert werden kann. Genau das tun wir hier mit dem Ausdruck [list(filtered_list)];
- Zeilen 33–34: Ist die gefilterte Liste leer, bedeutet dies, dass der gesuchte Schüler nicht existiert. In diesem Fall wird eine Ausnahme ausgelöst;
- Zeile 36: Wenn wir diesen Punkt erreichen, bedeutet dies, dass keine Ausnahme ausgelöst wurde. Wir wissen dann, dass wir eine Liste mit einem Element abgerufen haben (es gibt keine zwei Schüler mit derselben [id]-Nummer). Wir geben daher das erste Element der Liste zurück;
- Zeilen 21–27: Die Methode [get_notes_for_élève_by_id] muss die Noten für den Schüler zurückgeben, dessen [id] an sie übergeben wird;
- Zeilen 22–23: Wir beginnen damit, den Schüler mit der ID [student_id] mithilfe der Methode [get_student_by_id] zu suchen, die wir gerade auskommentiert haben. Es kann zu einer Ausnahme kommen, wenn der gesuchte Schüler nicht existiert. Da es keinen try/catch-Block um die Anweisung in Zeile 23 gibt, wird die Ausnahme an den aufrufenden Code weitergeleitet. Dies ist das gewünschte Verhalten;
- Zeilen 24–25: Sobald der Schüler abgerufen wurde, holen wir alle seine Noten ab. Dazu verwenden wir erneut einen Filter:
- Der Filter lautet [filter(criterion, self_getnotes())]. Die zu filternde Liste ist somit die Liste aller Noten aller Schüler der Schule;
- Das Filterkriterium wird mithilfe einer [lambda]-Funktion ausgedrückt: lambda n: n.student.id == student_id. Der Parameter n ist ein Element der zu filternden Liste, d. h. eine Note. Der Typ [Note] verfügt über eine Eigenschaft [student], die den Schüler repräsentiert, dem die Note gehört. Daher muss [n.student.id], das die ID dieses Schülers darstellt, mit der ID des gesuchten Schülers übereinstimmen;
Anschließend führen wir das Skript [tests-dao.py] aus.
Wir erhalten dann folgende Ergebnisse:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v01/tests/tests_dao.py
{"id": 1, "nom": "classe1"}
{"id": 2, "nom": "classe2"}
{"id": 1, "nom": "matière1", "coefficient": 1}
{"id": 2, "nom": "matière2", "coefficient": 2}
{"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}
{"id": 21, "nom": "nom2", "prénom": "prénom2", "classe": {"id": 1, "nom": "classe1"}}
{"id": 32, "nom": "nom3", "prénom": "prénom3", "classe": {"id": 2, "nom": "classe2"}}
{"id": 42, "nom": "nom4", "prénom": "prénom4", "classe": {"id": 2, "nom": "classe2"}}
{"id": 1, "valeur": 10, "élève": {"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}, "matière": {"id": 1, "nom": "matière1", "coefficient": 1}}
{"id": 2, "valeur": 12, "élève": {"id": 21, "nom": "nom2", "prénom": "prénom2", "classe": {"id": 1, "nom": "classe1"}}, "matière": {"id": 1, "nom": "matière1", "coefficient": 1}}
{"id": 3, "valeur": 14, "élève": {"id": 32, "nom": "nom3", "prénom": "prénom3", "classe": {"id": 2, "nom": "classe2"}}, "matière": {"id": 1, "nom": "matière1", "coefficient": 1}}
{"id": 4, "valeur": 16, "élève": {"id": 42, "nom": "nom4", "prénom": "prénom4", "classe": {"id": 2, "nom": "classe2"}}, "matière": {"id": 1, "nom": "matière1", "coefficient": 1}}
{"id": 5, "valeur": 6, "élève": {"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}, "matière": {"id": 2, "nom": "matière2", "coefficient": 2}}
{"id": 6, "valeur": 8, "élève": {"id": 21, "nom": "nom2", "prénom": "prénom2", "classe": {"id": 1, "nom": "classe1"}}, "matière": {"id": 2, "nom": "matière2", "coefficient": 2}}
{"id": 7, "valeur": 10, "élève": {"id": 32, "nom": "nom3", "prénom": "prénom3", "classe": {"id": 2, "nom": "classe2"}}, "matière": {"id": 2, "nom": "matière2", "coefficient": 2}}
{"id": 8, "valeur": 12, "élève": {"id": 42, "nom": "nom4", "prénom": "prénom4", "classe": {"id": 2, "nom": "classe2"}}, "matière": {"id": 2, "nom": "matière2", "coefficient": 2}}
{"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}
élève n° 11 = {"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}
note de l'élève n° 11 = {"id": 1, "valeur": 10, "élève": {"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}, "matière": {"id": 1, "nom": "matière1", "coefficient": 1}}
note de l'élève n° 11 = {"id": 5, "valeur": 6, "élève": {"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}, "matière": {"id": 2, "nom": "matière2", "coefficient": 2}}
Process finished with exit code 0
Beachten Sie, dass bei der Anzeige einer Note (der Vorgang ist für andere Objekte ähnlich) zusätzlich Folgendes gilt:
- den mit der Note verbundenen Schüler;
- das Fach, auf das sich die Note bezieht;
Dieses Ergebnis wird von der Funktion [BaseEntity.asdict] erzeugt (siehe Abschnitt „Link“).
14.2.5. Die [business]-Ebene
![]() | ![]() |
- [InterfaceMétier] ist die Schnittstelle der [business]-Schicht;
- [Business] ist die Implementierungsklasse der [Business]-Schicht;
- [TestBusiness] ist eine [UnitTest]-Klasse zum Testen der [Business]-Klasse;
14.2.5.1. Schnittstelle [BusinessInterface]
Die [Business]-Schicht implementiert die folgende [BusinessInterface]-Schnittstelle (BusinessInterface.py):
- [get_stats_for_student] gibt die Noten für den Schüler idStudent zusammen mit Informationen zu diesen zurück: gewichteter Durchschnitt, niedrigste Note, höchste Note. Diese Informationen sind in einem Objekt vom Typ [StudentStats] gekapselt;
14.2.5.2. Die Entität [StatsForStudent]
Der Typ [StatsForStudent] (StatsForStudent.py), der die Statistiken eines Studenten (Noten, Min, Max, gewichteter Durchschnitt) kapselt, sieht wie folgt aus:
Anmerkungen:
- Zeile 8: Die Klasse [StatsForStudent] leitet sich von der Klasse [BaseEntity] ab;
- Zeilen 13–22: die Eigenschaften der Klasse;
- eine Kennung [id] von [BaseEntity];
- der Student [student], dessen Statistiken gekapselt sind;
- seine Noten [grades];
- seine gewichtete Durchschnittsnote [weighted_average];
- seine niedrigste Note [min];
- seine Höchstnote [max];
- Wir definieren keine Getter/Setter für diese Attribute. Wir gehen davon aus, dass die [business]-Schicht Objekte dieses Typs erstellt und keine ungültigen Objekte erzeugt;
- Zeilen 23–33: Die Funktion [__str__] gibt eine Zeichenkette zurück, die die Eigenschaften des Objekts enthält;
14.2.5.3. Die [Business]-Implementierung
Die [Business]-Implementierung (Metier.py) der [BusinessInterface]-Schnittstelle sieht wie folgt aus:
Anmerkungen
- Zeile 7: Die Klasse [Métier] leitet sich von der Klasse [InterfaceMétier] ab. Üblicherweise sagt man, dass sie die Schnittstelle [InterfaceMétier] implementiert;
- Zeilen 9–12: Der Konstruktor nimmt einen einzigen Parameter entgegen, einen Verweis auf die [dao]-Schicht. Beachten Sie in Zeile 10, dass wir dem Parameter [dao] den Typ [InterfaceDao] zugewiesen haben. Wir erwarten keine bestimmte Implementierung, sondern lediglich eine Implementierung, die die Schnittstelle [DaoInterface] einhält. Hier spielt das keine Rolle, da Python diesen Typ nicht berücksichtigt, aber es ist gute Praxis, mit Schnittstellen statt mit konkreten Implementierungen zu arbeiten. Der Code lässt sich dann leichter ändern;
- Zeilen 19–60: Implementierung der Methode [get_stats_for_élève];
- Zeile 19: Die Methode erhält einen einzigen Parameter, die [idElève] des Schülers, für den wir die Statistiken wünschen;
- Zeile 24: Wir fordern die Noten des Schülers von der [dao]-Schicht an. Diese Anfrage führt zu einer Ausnahme, wenn der Schüler nicht existiert. Diese Ausnahme wird nicht abgefangen (kein try/catch) und daher an den aufrufenden Code zurückgegeben;
- Zeile 25: Wir erreichen diesen Punkt, wenn keine Ausnahme aufgetreten ist. [student_grades] ist dann ein Wörterbuch mit zwei Schlüsseln [student, grade]:
- Zeile 25: Wir rufen Informationen über den Schüler ab (Name, Klasse usw.);
- Zeile 26: Wir rufen seine Noten ab;
- Zeilen 28–31: Wir prüfen, ob der Schüler Noten hat. Ist dies nicht der Fall, gibt es keine Statistiken zu berechnen;
- Zeile 31: Wir geben ein Objekt [StatsForStudent] zurück, das mithilfe der Methode [BaseEntity.fromdict] aus einem Wörterbuch erstellt wurde;
- Zeilen 33–54: Wir verwenden die Noten des Schülers, um die angeforderten Statistiken zu berechnen. Die Code-Kommentare sollten zum Verständnis ausreichen;
- Zeilen 56–60: Wir geben ein [StatsForStudent]-Objekt zurück, das mithilfe der Methode [BaseEntity.fromdict] aus einem Wörterbuch erstellt wurde;
14.2.5.4. Testen der [business]-Schicht
Ein [UnitTest]-Skript für die [business]-Schicht könnte wie folgt aussehen (TestMétier.py):
Anmerkungen
- Zeilen 6–9: Die Funktion [setUp] wird hier verwendet, um den Python-Pfad des Tests zu konfigurieren;
- Zeile 16: Wir instanziieren die [dao]-Schicht;
- Zeile 17: Wir instanziieren die [business]-Schicht und verwenden deren Methode [get_stats_for_student], um die Statistiken für den Studenten Nr. 11 zu berechnen;
- Zeile 19: Das Ergebnis [StatsForStudent] wird angezeigt. Da [StatsForStudent] von [BaseEntity] abgeleitet ist, wird hier die JSON-Zeichenkette von [StatsForStudent] angezeigt;
- Zeile 21: Wir prüfen die Mindestnote des Schülers;
- Zeile 22: Wir prüfen die Höchstnote des Schülers;
- Zeile 23: Wir prüfen, ob der gewichtete Durchschnitt 7,333 beträgt, mit einer Genauigkeit von 10⁻³. Im Allgemeinen ist es nicht möglich, reelle Zahlen exakt zu vergleichen, da sie intern meist nur als Näherungswerte dargestellt werden;
Die Testergebnisse lauten wie folgt:
Testing started at 18:17 ...
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.1.2\plugins\python-ce\helpers\pycharm\_jb_unittest_runner.py" --path C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v01/tests/TestMétier.py
Launching unittests with arguments python -m unittest C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v01/tests/TestMétier.py in C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\troiscouches\v01\tests
Ran 1 test in 0.015s
OK
stats=Elève={"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}, notes=[10 6], max=10, min=6, moyenne pondérée=7.33
Process finished with exit code 0
14.2.6. Die [ui]-Ebene

- in [1] die Schnittstelle der [ui]-Schicht;
- in [2], die Implementierung dieser Schnittstelle;
- in [3], das Hauptskript der Anwendung;
14.2.6.1. Schnittstelle [InterfaceUi]
Die Schnittstelle der [UI]-Schicht sieht wie folgt aus:
Anmerkungen
- Zeilen 9–10: Die [UI]-Ebene wird nur eine Methode haben, [run];
14.2.6.2. Die [Console]-Implementierung
Die [Console]-Schicht wird durch das folgende Skript [Console.py] implementiert:
- Zeilen 3–5: Import aller Schnittstellen;
- Zeile 11: Die Klasse [Console] implementiert die Schnittstelle [InterfaceUi];
- Zeilen 12–17: Der Konstruktor der Klasse [Console] erhält als Parameter eine Referenz auf die [business]-Schicht. Beachten Sie, dass wir diesem Parameter den Typ [BusinessInterface] zugewiesen haben, um zu betonen, dass wir mit Schnittstellen und nicht mit konkreten Implementierungen arbeiten;
- Zeile 24: Implementierung der Methode [run] der Schnittstelle;
- Zeile 27: eine Schleife, die endet, wenn die Bedingung in Zeile 31 erfüllt ist;
- Zeile 29: Eingabe von Daten, die über die Tastatur eingegeben wurden. Die Funktion [input] erhält einen optionalen Parameter: die Meldung, die auf dem Bildschirm angezeigt werden soll, um zur Eingabe aufzufordern. Diese Eingabe wird immer als Zeichenkette abgerufen. Die Funktion [strip] entfernt alle führenden oder nachfolgenden Leerzeichen aus der Zeichenkette;
- Zeilen 34–39: Wir überprüfen, ob die Eingabe, eine Studenten-ID, gültig ist. Sie muss eine ganze Zahl >= 1 sein. Beachten Sie, dass die Eingabe als Zeichenkette eingegeben wurde;
- Zeile 36: Wir versuchen, die Eingabe in eine Ganzzahl zur Basis 10 umzuwandeln. Die Funktion [int] löst eine Ausnahme aus, wenn dies nicht möglich ist;
- Zeile 37: Wir erreichen diesen Punkt nur, wenn keine Ausnahme aufgetreten ist. Wir überprüfen, ob die abgerufene Ganzzahl tatsächlich >= 1 ist;
- Zeilen 38–39: Wir behandeln die Ausnahme. Wenn eine Ausnahme aufgetreten ist, bleibt die Variable [ok] aus Zeile 34 auf [False] gesetzt;
- Zeilen 41–43: Wenn die Eingabe falsch war, wird eine Fehlermeldung angezeigt und die Schleife neu gestartet (Zeile 43);
- Zeilen 45–48: Wir berechnen die Statistiken für den Studenten, dessen ID eingegeben wurde;
- Zeile 46: Es wird die Methode [get_stats_for_student] aus der [business]-Schicht verwendet. Diese Methode löst eine Ausnahme aus, wenn der Student nicht existiert. Diese Ausnahme wird in den Zeilen 47–48 behandelt. Wir wissen, dass die [DAO]- und [business]-Schichten die Ausnahme [MyException] auslösen;
14.3. Das Hauptskript [main]
Das Hauptskript [main] sieht wie folgt aus (main.py):
- Zeilen 1–4: Konfigurieren des Python-Pfads der Anwendung;
- Zeilen 6–9: Importieren der benötigten Klassen und Schnittstellen;
- Zeile 14: Instanziieren der [DAO]-Schicht;
- Zeile 16: Instanziieren der [Business]-Schicht;
- Zeile 18: Instanziieren der [ui]-Schicht;
- Zeile 20: Starten der Benutzeroberfläche;
- Zeilen 13–20: Normalerweise werden in diesen Zeilen keine Ausnahmen ausgelöst. Alle Ausnahmen, die aus den [DAO]- und [Business]-Schichten weitergeleitet werden, werden von der [Console]-Schicht abgefangen. Die Ausnahmebehandlung ist eine schwierige Kunst, wenn man die verwendeten Schichten nicht vollständig versteht (was hier nicht der Fall ist). Im Zweifelsfall kann Code hinzugefügt werden, um jede Art von Ausnahme abzufangen, die vom ausführenden Code ausgelöst werden könnte. Genau das geschieht hier in den Zeilen 21–23. Wir fangen jede von [BaseException] abgeleitete Ausnahme ab, d. h. alle Ausnahmen;
- Zeilen 24–25: Die [finally]-Klausel hat hier keine Funktion. Sie dient lediglich dazu, die Zeilen 21–23 auskommentieren zu können. Tatsächlich ist es im Debug-Modus nicht ratsam, Ausnahmen abzufangen. In diesem Fall fängt der Python-Interpreter sie ab und meldet anschließend die Zeilennummer, in der die Ausnahme aufgetreten ist. Dies ist eine wesentliche Information. Wenn die Zeilen 21–23 auskommentiert sind, gewährleistet das Vorhandensein der Zeilen 24–25 einen syntaktisch korrekten try/catch-Block. Ohne sie löst Python einen Fehler aus;
Hier ist ein Beispiel für die Ausführung:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v01/main/main.py
Numéro de l'élève (>=1 et * pour arrêter) : 11
Elève={"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}, notes=[10 6], max=10, min=6, moyenne pondérée=7.33
Numéro de l'élève (>=1 et * pour arrêter) : 1
L'erreur suivante s'est produite : MyException[10, L'élève d'identifiant 1 n'existe pas]
Numéro de l'élève (>=1 et * pour arrêter) : *
Process finished with exit code 0
14.4. Beispiel 2
Dieses neue Beispiel für mehrschichtige Architekturen soll die Vorteile der schnittstellenbasierten Programmierung veranschaulichen. Dieser Ansatz erleichtert die Wartung und das Testen von Anwendungen. Wir werden erneut eine dreischichtige Architektur verwenden:

Jede Schicht wird auf zwei verschiedene Arten implementiert. Wir möchten zeigen, dass die Implementierung einer Schicht leicht geändert werden kann, ohne die anderen Schichten wesentlich zu beeinträchtigen.
14.4.1. Die [dao]-Schicht

Die Schnittstelle [InterfaceDao] sieht wie folgt aus:
- Zeilen 8–10: Die Methode [do_something_in_dao_layer] ist die einzige Methode der Schnittstelle;
Die Klasse [DaoImpl1] implementiert die Schnittstelle [InterfaceDao] wie folgt:
Die Klasse [DaoImpl2] implementiert die Schnittstelle [InterfaceDao] wie folgt:
14.4.2. Die [Business-]Schicht

Die [BusinessInterface]-Schnittstelle sieht wie folgt aus:
- Zeilen 8–10: Die Methode [do_something_in_business_layer] ist die einzige Methode in der Schnittstelle;
Die Klasse [AbstractBaseMétier] implementiert die Schnittstelle [InterfaceMétier] wie folgt:
- Zeile 8: Die Klasse [AbstractBaseMétier] ist die Basis für zwei Klassen:
- [BusinessInterface]: Die Klasse [AbstractBusinessBase] implementiert diese Schnittstelle in den Zeilen 19–22. Tatsächlich sehen wir, dass sie die Methode [do_something_in_business_layer] nicht implementiert hat, die sie als abstrakt deklariert hat (Zeile 20). Es ist Aufgabe der abgeleiteten Klassen, die Methode zu implementieren;
- [ABC] für den Zugriff auf die [@abstractmethod]-Annotationen;
- Die Reihenfolge ist entscheidend: Wenn wir sie hier umkehren, löst Python einen Laufzeitfehler aus;
Dies ist das erste Mal, dass wir Mehrfachvererbung (Vererbung von mehreren Klassen) verwenden. Die Klasse [AbstractBaseMétier] erbt Eigenschaften sowohl von der Klasse [InterfaceMétier] als auch von der Klasse [ABC].
- Zeilen 9–17: Wir definieren die Eigenschaft [dao], die eine Referenz auf die [dao]-Schicht sein wird;
Eine Schnittstelle ist dazu gedacht, implementiert zu werden. Wenn verschiedene Implementierungen Eigenschaften gemeinsam nutzen, ist es sinnvoll, diese in einer übergeordneten Klasse unterzubringen, um Duplikate zu vermeiden. Dies ist hier bei der Eigenschaft [dao] der Fall. Die übergeordnete Klasse ist in der Regel immer abstrakt, da sie nicht alle Methoden der Schnittstelle implementiert.
Die Klasse [BusinessImpl1] implementiert die Schnittstelle [BusinessInterface] wie folgt:
- Zeile 4: Die Klasse [BusinessImpl1] leitet sich von der Klasse [AbstractBusinessBase] ab. Sie erbt daher die Eigenschaft [dao] von dieser Klasse;
- Zeilen 6–9: Implementierung der Schnittstelle [BusinessInterface], die die übergeordnete Klasse [AbstractBusinessBase] nicht implementiert hat;
- Zeile 9: Die [dao]-Schicht wird verwendet;
Die Klasse [BusinessImpl2] implementiert die Schnittstelle [BusinessInterface] auf ähnliche Weise:
14.4.3. Die [ui]-Schicht

Die Schnittstelle [InterfaceUi] sieht wie folgt aus:
- Zeilen 8–10: die einzige Methode der Schnittstelle;
Die Klasse [AbstractBaseUi] implementiert die Schnittstelle [InterfaceUi] wie folgt:
- Die Klasse [AbstractBaseUi] ist eine abstrakte Klasse (Zeile 20). Sie muss abgeleitet werden, um die Schnittstelle [InterfaceUi] zu implementieren;
- Zeilen 9–17: Die Klasse [AbstractBaseUi] verfügt über eine Referenz auf die [business]-Schicht;
Die Implementierungsklasse [UiImpl1] sieht wie folgt aus:
- Zeile 4: Die Klasse [UiImpl1] leitet sich von der Klasse [AbstractBaseUi] ab und erbt daher deren Eigenschaft [business]. Diese wird in Zeile 9 verwendet;
Die Implementierungsklasse [UiImpl2] ist ähnlich:
- Zeile 4: Die Klasse [UiImpl2] leitet sich von der Klasse [AbstractBaseUi] ab und erbt daher deren Eigenschaft [business]. Diese wird in Zeile 9 verwendet;
14.4.4. Die Konfigurationsdateien

- Die Dateien [config1, config2] konfigurieren die Anwendung auf zwei verschiedene Arten;
- Die Datei [main] ist das Hauptskript der Anwendung;
Die Datei [config1] sieht wie folgt aus:
- Zeilen 2–16: Konfiguration des Python-Pfads der Anwendung;
- Zeilen 18–31: Instanziierung der [DAO-, Business-, UI-]Schichten. Zur Implementierung ihrer Schnittstellen wählen wir jedes Mal die erste erstellte Implementierung;
- Zeilen 33–35: Wir fügen die Schichtenreferenzen zur Konfiguration hinzu. Hier benötigt das Hauptskript nur die [ui]-Schicht;
Die Datei [config2] ist ähnlich und implementiert jede Schnittstelle mit der zweiten verfügbaren Implementierung:
14.4.5. Das Hauptskript [main]

Das Hauptskript lautet wie folgt:
Dieses Skript benötigt einen Parameter:
- [config1], um Konfiguration Nr. 1 zu verwenden;
- [config2], um Konfiguration Nr. 2 zu verwenden;
Python speichert die Parameter in einer Liste [sys.argv]:
- sys.argv[0] ist der Name des Skripts, hier [main]. Dieser Parameter ist immer vorhanden;
- sys.argv[1] ist der erste an das Skript übergebene Parameter, sys.argv[2] ist der zweite, …
- Zeile 8: Wir ermitteln die Anzahl der Parameter;
- Zeilen 9–11: Wir prüfen, ob tatsächlich ein Argument vorhanden ist und ob dessen Wert entweder [config1] oder [config2] ist. Ist dies nicht der Fall, wird eine Fehlermeldung angezeigt (Zeile 10) und wir beenden das Programm (Zeile 11);
Sobald die gewünschte Konfiguration bekannt ist, müssen wir diese Konfiguration ausführen. Wenn beispielsweise Konfiguration 1 gewählt wurde, müssen wir den folgenden Code ausführen:
Das Problem hierbei ist, dass die zu verwendende Konfiguration in einer Variablen gespeichert ist, nämlich [sys.argv[1]. Um ein Modul zu importieren, dessen Name in einer Variablen gespeichert ist, müssen wir das [importlib]-Paket verwenden (Zeile 2).
- Zeile 14: Wir importieren das Modul, dessen Name in [sys.argv[1]] steht
- Zeile 15: Sobald dies erledigt ist, führen wir die Funktion [configure] dieses Moduls aus. Wir rufen ein Wörterbuch [config] ab, das die Konfiguration der Anwendung enthält;
- Zeile 18: Wir wissen, dass sich ein Verweis auf die [ui]-Schicht in config['ui'] befindet. Wir verwenden ihn, um die Methode [do_something_in_ui_layer] aufzurufen. Wir wissen, dass diese Methode eine Methode in der [business]-Schicht aufruft, die wiederum eine Methode in der [dao]-Schicht aufruft;
Die Funktion [do_something_in_ui_layer] sieht beispielsweise wie folgt aus:
- In Zeile 6 oben wird die Eigenschaft [business] der Klasse [UiImpl1] aus Zeile 1 verwendet. In der Konfiguration [config1] wurde jedoch Folgendes geschrieben:
# métier
métier = MétierImpl1()
métier.dao = dao
# ui
ui = UiImpl1()
ui.métier = métier
- Zeile 6: Die Eigenschaft [business] von [UIImpl1] ist eine Referenz auf die Klasse [BusinessImpl1] (Zeile 2). Daher wird die Methode [do_something_in_ui_layer] der Klasse [BusinessImpl1] ausgeführt;
In der Klasse [MétierUiImpl1] steht geschrieben:
- Zeile 6: Die von der [ui]-Schicht aufgerufene Methode ruft wiederum eine Methode der [dao]-Eigenschaft der Klasse [BusinessImpl1] auf;
In der Konfiguration [config1] wurde jedoch Folgendes geschrieben:
# dao
dao = DaoImpl1()
# métier
métier = MétierImpl1()
métier.dao = dao
- Zeile 5: Die Eigenschaft [BusinessImpl1.dao] ist vom Typ [DaoImpl1] (Zeile 2);
Was wir hier zeigen wollen, ist, dass sich das Skript [main] nicht um die Schichten [business] und [DAO] kümmern muss. Es muss sich nur um die Schicht [UI] kümmern, da die Verbindungen zwischen dieser Schicht und den anderen durch die Konfiguration hergestellt wurden.

Um den Parameter [config1] oder [config2] an das Skript [main] zu übergeben, gehen Sie wie folgt vor:

- Erstellen Sie in [1-2] eine sogenannte Laufzeitkonfiguration;
- Geben Sie dieser Konfiguration in [3] einen Namen, damit Sie sie später wiederfinden können;
- Wählen Sie in [4] das auszuführende Skript aus. Wenn Sie die Schritte in [1-2] befolgt haben, ist das richtige Skript bereits ausgewählt;
- Geben Sie in [5] die Parameter ein, die an das Skript übergeben werden sollen. Hier übergeben wir die Zeichenfolge [config1], um das Skript anzuweisen, Konfiguration Nr. 1 zu verwenden;
- Bestätigen Sie in [6] die Ausführungskonfiguration;

- Zeigen Sie in [1-2] die vorhandenen Ausführungskontexte an;
- Wählen Sie in [3] den vorhandenen Ausführungskontext aus und duplizieren Sie ihn [4];

- in [5] den Namen für die neue Konfiguration. Dies ist die Konfiguration, die das Skript [main] [6] ausführt, indem sie ihm den Parameter [config2] [7] übergibt;
Die Ausführungskonfigurationen sind in der oberen rechten Ecke des PyCharm-Fensters verfügbar:

Wählen Sie einfach [2] oder [3] aus und klicken Sie dann auf [4], um das Skript [main] mit dem Parameter [config1] oder [config2] auszuführen.
Mit [config1] liefert die Ausführung von [main] die folgenden Ergebnisse:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v02/main/main.py config1
34
Process finished with exit code 0
Mit [config2] liefert die Ausführung von [main] folgende Ergebnisse:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v02/main/main.py config2
-10
Process finished with exit code 0
Der Leser ist eingeladen, diese Ergebnisse zu überprüfen.













