7. Schichtarchitektur und schnittstellenbasierte Programmierung
7.1. Einführung
Wir schlagen vor, eine Anwendung zu schreiben, die die Noten von Schülern der Mittelstufe anzeigt. Diese Anwendung wird eine mehrschichtige Architektur aufweisen:
![]() |
- Die [Präsentationsschicht] ist die Schicht, die mit dem Benutzer der Anwendung interagiert.
- Die [Business-Logik]-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.
Wir werden diese Architektur 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-Logik]-Schicht berechnet Kennzahlen auf der Grundlage der Noten eines bestimmten Schülers,
- die [Präsentationsschicht] ist eine Konsolenanwendung, die die von der [Geschäftslogikschicht] berechneten Ergebnisse anzeigt.
Das Visual Studio-Projekt für die Anwendung sieht wie folgt aus:
![]() |
7.2. Die Entitäten der Anwendung
Entitäten sind Objekte. Hier werden wir für jede der Entitäten vier Klassen haben: Student, Klasse, Fach und Note.
![]() |
Die Datei [entities.py] enthält vier Klassen.
Die Klasse [Class] repräsentiert eine Mittelschulklasse:
class Classe:
# constructeur
def __init__(self,id,nom):
# on mémorise les paramètres
self.id=id
self.nom=nom
# toString
def __str__(self):
return "Classe[{0},{1}]".format(self.id,self.nom)
- Zeilen 3–6: Eine Klasse wird durch eine ID (Zeile 5) und einen Namen (Zeile 6) definiert;
- Zeilen 9–10: die Methode zur Anzeige der Klasse.
Die Klasse [Subject] sieht wie folgt aus:
class Matiere:
# constructeur
def __init__(self,id,nom,coefficient):
# on mémorise les paramètres
self.id=id
self.nom=nom
self.coefficient=coefficient
# toString
def __str__(self):
return "Matiere[{0},{1},{2}]".format(self.id,self.nom,self.coefficient)
- Zeilen 3–7: Ein Fach wird durch seine ID (Zeile 5), seinen Namen (Zeile 6) und seine Gewichtung (Zeile 7) definiert;
- Zeilen 10–11: Die Methode zur Anzeige des Subjekts.
Die Klasse [Student] sieht wie folgt aus:
class Eleve:
# constructeur
def __init__(self,id,nom,prenom,classe):
# on mémorise les paramètres
self.id=id
self.nom=nom
self.prenom=prenom
self.classe=classe
# toString
def __str__(self):
return "Eleve[{0},{1},{2},{3}]".format(self.id,self.prenom,self.nom,self.classe)
- Zeilen 3–8: Ein Schüler wird durch seine ID (Zeile 5), seinen Nachnamen (Zeile 6), seinen Vornamen (Zeile 7) und seine Klasse (Zeile 8) charakterisiert. Dieser letzte Parameter ist eine Referenz auf ein [Class]-Objekt;
- Zeilen 11–12: die Methode zur Anzeige des Schülers.
Die Klasse [Grade] sieht wie folgt aus:
class Note:
# constructeur
def __init__(self,id,valeur,eleve,matiere):
# on mémorise les paramètres
self.id=id
self.valeur=valeur
self.eleve=eleve
self.matiere=matiere
# toString
def __str__(self):
return "Note[{0},{1},{2},{3}]".format(self.id,self.valeur,self.eleve,self.matiere)
- Zeilen 3–8: Ein [Grade]-Objekt wird durch seine ID (Zeile 5), den Notenwert (Zeile 6), einen Verweis auf den Schüler, der diese Note erhalten hat (Zeile 7), und einen Verweis auf das Fach, für das die Note vergeben wurde (Zeile 8), charakterisiert;
- Zeilen 11–12: Die Methode zur Anzeige des [Note]-Objekts.
7.3. Die [dao]-Ebene
![]() |
Die [dao]-Schicht stellt der [business]-Schicht die folgende Schnittstelle zur Verfügung:
- getClasses gibt die Liste der Mittelschulklassen zurück;
- getMatieres gibt die Liste der Fächer zurück;
- getStudents gibt die Liste der Schüler zurück;
- getGrades gibt eine Liste der Noten zurück.
Die [Business]-Schicht verwendet ausschließlich diese Methoden. Sie muss nicht wissen, wie diese implementiert sind. Diese Daten können daher aus verschiedenen Quellen stammen (fest codierte Werte, eine Datenbank, Textdateien usw.), ohne dass dies Auswirkungen auf die [Business]-Schicht hat. Dies wird als schnittstellenbasierte Programmierung bezeichnet.
Die [Dao]-Klasse implementiert diese Schnittstelle wie folgt:
- Zeile 4: Wir importieren das Modul, das die von der [DAO]-Schicht verwalteten Entitäten enthält;
- Zeile 8: Der Konstruktor hat keine Parameter. Er enthält vier fest codierte Listen:
- Zeilen 10–12: die Liste der Klassen;
- Zeilen 14–16: die Liste der Fächer;
- Zeilen 18–22: die Liste der Schüler;
- Zeilen 24–32: die Liste der Noten.
- Zeilen 39–52: Die vier Methoden der [dao]-Layer-Schnittstelle geben lediglich einen Verweis auf die vier vom Konstruktor erstellten Listen zurück.
Ein Testprogramm könnte wie folgt aussehen:
Die Kommentare sprechen für sich. In den Zeilen 11–24 wird die [dao]-Layer-Schnittstelle verwendet. Hier werden keine Annahmen über die tatsächliche Implementierung des Layers getroffen. In Zeile 8 instanziieren wir den [dao]-Layer. Hier treffen wir Annahmen: Klassenname und Konstruktortyp. Es gibt Lösungen, mit denen wir diese Abhängigkeit vermeiden können.
Die Ergebnisse der Ausführung dieses Skripts lauten wie folgt:
7.4. Die [Geschäfts-]Ebene
![]() |
Die [Business]-Schicht implementiert die folgende Schnittstelle:
- getClasses gibt die Liste der Mittelschulklassen zurück;
- getMatieres gibt die Liste der Fächer zurück;
- getStudents gibt die Liste der Schüler zurück;
- getGrades gibt eine Liste der Noten zurück;
- getStatsForStudent gibt die Noten für den Schüler idStudent zusammen mit Informationen zu diesen zurück: gewichteter Durchschnitt, niedrigste Note, höchste Note.
Die [Präsentationsschicht] verwendet ausschließlich diese Methoden. Sie muss nicht wissen, wie diese implementiert sind.
Die Methode getStatsForStudent gibt ein Objekt vom Typ [StatsForStudent] wie folgt zurück:
class StatsForEleve:
# constructeur
def __init__(self, eleve, notes):
# on mémorise les paramètres
self.eleve=eleve
self.notes=notes
# on s'arrête s'il n'y a pas de notes
if len(notes)==0:
return
# exploitation des notes
sommePonderee=0
sommeCoeff=0
self.max=-1
self.min=21
for note in notes:
valeur=note.valeur
coeff=note.matiere.coefficient
sommeCoeff+=coeff
sommePonderee+=valeur*coeff
if valeur<self.min:
self.min=valeur
if valeur>self.max:
self.max=valeur
# calcul de la moyenne de l'élève
self.moyennePonderee=float(sommePonderee)/sommeCoeff
# toString
def __str__(self):
# cas de l'élève sans notes
if len(self.notes)==0:
return "Eleve={0}, notes=[]".format(self.eleve)
# cas de l'élève avec notes
str=""
for note in self.notes:
str+="{0} ".format(note.valeur)
return "Eleve={0}, notes=[{1}], max={2}, min={3}, moyenne={4}".format(self.eleve, str, self.max, self.min, self.moyennePonderee)
- Zeile 3: Der Konstruktor nimmt zwei Parameter entgegen:
- eine Referenz auf den Schüler vom Typ [Student], für den die Kennzahlen berechnet werden;
- eine Referenz auf seine Noten, eine Liste von [Grade]-Objekten.
- Zeilen 8–9: Wenn die Liste der Noten leer ist, brechen wir hier ab.
- Andernfalls, Zeilen 11–25: Die folgenden Kennzahlen werden berechnet:
- self.weightedAverage: der durch die Fachkoeffizienten gewichtete Durchschnitt des Schülers;
- self.min: die niedrigste Note des Schülers;
- self.max: die höchste Note des Schülers.
- Zeile 28: Die Methode zur Anzeige der Klasse in dem in Zeile 36 angegebenen Format.
Ein Testskript für diese Klasse könnte wie folgt aussehen:
Die Bildschirmausgabe sieht wie folgt aus:
Kehren wir zu unserer mehrschichtigen Architektur zurück:
![]() |
Die Klasse [Business] implementiert die [Business]-Schicht wie folgt:
class Metier:
# constructeur
def __init__(self,dao):
# on mémorise la référence sur la couche [dao]
self.dao=dao
#-----------
# interface
#-----------
# les indicateurs sur les notes
def getStatsForEleve(self,idEleve):
# Stats pour l'élève de n° idEleve
# recherche de l'élève
trouve=False
i=0
eleves=self.getEleves()
while not trouve and i<len(eleves):
trouve=eleves[i].id==idEleve
i+=1
# a-t-on trouvé ?
if not trouve:
raise RuntimeError("L'eleve [{0}] n'existe pas".format(idEleve))
else:
eleve=eleves[i-1]
# liste de toutes les notes
notes=[]
for note in self.getNotes():
# on ajoute à notes, toutes les notes de l'élève
if note.eleve.id==idEleve:
notes.append(note)
# on rend le résultat
return StatsForEleve(eleve,notes)
# la liste des classes
def getClasses(self):
return self.dao.getClasses()
# la liste des matières
def getMatieres(self):
return self.dao.getMatieres()
# la liste des élèves
def getEleves(self):
return self.dao.getEleves()
# la liste des notes
def getNotes(self):
return self.dao.getNotes()
- Zeilen 3–5: Der Konstruktor erhält eine Referenz auf die [dao]-Schicht. Die [business]-Schicht muss über diese Referenz verfügen. Hier stellen wir sie über ihren Konstruktor bereit. Andere Lösungen sind möglich. In einer horizontal dargestellten Schichtenarchitektur muss jede Schicht eine Referenz auf die Schicht rechts von ihr haben;
- Zeilen 36–49: Die Methoden getClasses, getSubjects, getStudents und getGrades delegieren den Aufruf einfach an die gleichnamigen Methoden in der [dao]-Schicht;
- Zeile 12: Die Methode getStatsForStudent erhält als Parameter die Studenten-ID, für die Statistiken zurückgegeben werden sollen.
- Zeile 17: Der Student wird in der Liste aller Studenten gesucht;
- Zeilen 18–20: die Suchschleife;
- Zeile 23: Wenn der Student nicht gefunden wird, wird eine Ausnahme ausgelöst;
- ansonsten, Zeile 25: Der gefundene Schüler wird gespeichert;
- Zeilen 28–31: Wir durchsuchen alle Mittelschulklassen nach denen, die zu dem gespeicherten Schüler gehören;
- Sobald sie gefunden sind, kann das angeforderte StatsForEleve-Objekt erstellt werden.
7.5. Die [console]-Ebene
![]() |
Die [console]-Ebene wird durch das folgende Skript implementiert:
- Zeile 10: Instanziierung sowohl der [dao]- als auch der [business]-Schicht. Dies ist die einzige Abhängigkeit unseres Codes von der Implementierung dieser Schichten;
- Zeile 34: Wir verwenden die Schnittstelle der [business]-Schicht;
- Zeile 19: Die Methode
stripentfernt führende und nachstehende Leerzeichen aus der Zeichenkette; - Zeile 20: „break“ beendet eine Schleife;
- Zeile 24: Versuch, die eingegebene Zeichenkette in eine dezimale Ganzzahl zu konvertieren;
- Zeile 29:
okist nur dann wahr, wenn Zeile 25 ausgeführt wurde; - Zeile 31: „continue“ ermöglicht es, die Schleife in der Mitte fortzusetzen;
- Zeile 34: Berechnung der Kennzahlen;
- Zeile 35: fängt die RuntimeError-Ausnahme ab, die möglicherweise aus der [Business]-Schicht stammt.
Hier ist ein Beispiel für die Ausführung:






