7. Layered architecture and interface-based programming
7.1. Introduction
We propose to write an application that displays the grades of middle school students. This application will have a multi-layer architecture:
![]() |
- The [présentation] layer is the layer that interacts with the application user.
- The [metier] layer implements the application's business logic, such as calculating a salary or an invoice. This layer uses data from the user via the [présentation] layer and from SGBD via the [dao] layer.
- The [dao] layer (Data Access Objects) manages access to data from SGBD.
We will illustrate this architecture with a simple console application:
- there will be no database,
- the [dao] layer will manage the Student, Class, Subject, and Grade entities to handle student grades,
- the [métier] layer will calculate metrics based on a specific student’s grades,
- the [présentation] layer will be a console application that displays the results calculated by the [métier] layer.
The Visual Studio project for the application is as follows:
![]() |
7.2. The application's entities
Entities are objects. Here, we will have four classes for each of the entities: Student, Class, Subject, and Grade.
![]() |
The file [entites.py] contains four classes.
The [Classe] class represents a middle school class:
class Classe:
# manufacturer
def __init__(self,id,nom):
# save parameters
self.id=id
self.nom=nom
# toString
def __str__(self):
return "Classe[{0},{1}]".format(self.id,self.nom)
- lines 3–6: a class is defined by a number id (line 5) and a name (line 6);
- lines 9-10: the class display method.
The class [Matiere] is as follows:
class Matiere:
# manufacturer
def __init__(self,id,nom,coefficient):
# save parameters
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)
- lines 3–7: a subject is defined by its ID (line 5), its name (line 6), and its weight (line 7);
- lines 10-11: the method for displaying the subject.
The [Eleve] class is as follows:
class Eleve:
# manufacturer
def __init__(self,id,nom,prenom,classe):
# save parameters
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)
- lines 3–8: a student is identified by their ID (line 5), last name (line 6), first name (line 7), and class (line 8). The last parameter is a reference to an object [Classe];
- lines 11-12: the method for displaying the student.
The [Note] class is as follows:
class Note:
# manufacturer
def __init__(self,id,valeur,eleve,matiere):
# save parameters
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)
- lines 3-8: a [Note] object is characterized by its ID (line 5), the grade value (line 6), a reference to the student who received this grade (line 7), and a reference to the subject for which the grade was given (line 8);
- lines 11-12: the display method for the [Note] object.
7.3. The [dao] layer
![]() |
The [dao] layer will provide the following interface to the [métier] layer:
- getClasses returns the list of middle school classes;
- getMatieres returns the list of subjects;
- getEleves returns the list of students;
- getNotes returns the list of grades.
The [métier] layer will use only these methods. It does not need to know how they are implemented. The data can therefore come from various sources (hard-coded values, a database, text files, etc.) without affecting the [métier] layer. This is called interface-based programming.
The [Dao] class implements this interface as follows:
# -*- coding=utf-8 -*-
# import entity module
from entites import *
class Dao:
# manufacturer
def __init__(self):
# classes are instantiated
classe1=Classe(1,"classe1")
classe2=Classe(2,"classe2")
self.classes=[classe1,classe2]
# materials
matiere1=Matiere(1,"matiere1",1)
matiere2=Matiere(2,"matiere2",2)
self.matieres=[matiere1,matiere2]
# students
eleve11=Eleve(11,"nom1","prenom1",classe1)
eleve21=Eleve(21,"nom2","prenom2",classe1)
eleve32=Eleve(32,"nom3","prenom3",classe2)
eleve42=Eleve(42,"nom4","prenom4",classe2)
self.eleves=[eleve11,eleve21,eleve32,eleve42]
# the notes
note1=Note(1,10,eleve11,matiere1)
note2=Note(2,12,eleve21,matiere1)
note3=Note(3,14,eleve32,matiere1)
note4=Note(4,16,eleve42,matiere1)
note5=Note(5,6,eleve11,matiere2)
note6=Note(6,8,eleve21,matiere2)
note7=Note(7,10,eleve32,matiere2)
note8=Note(8,12,eleve42,matiere2)
self.notes=[note1,note2,note3,note4,note5,note6,note7,note8]
#-----------
# interface
#-----------
# class list
def getClasses(self):
return self.classes
# list of materials
def getMatieres(self):
return self.matieres
# list of students
def getEleves(self):
return self.eleves
# lIST OF NOTES
def getNotes(self):
return self.notes
- line 4: we import the module containing the entities handled by the [dao] layer;
- line 8: the constructor has no parameters. It hard-codes four lists:
- lines 10–12: the list of classes;
- lines 14–16: the list of subjects;
- lines 18–22: the list of students;
- lines 24–32: the list of grades.
- lines 39–52: the four methods of the [dao] interface simply return a reference to the four lists constructed by the constructor.
A test program might look like this:
# -*- coding=utf-8 -*-
# import of entity module and [dao] layer
from entites import *
from dao import *
# instantiation layer [dao]
dao=Dao()
# class list
for classe in dao.getClasses():
print classe
# class list
for matiere in dao.getMatieres():
print matiere
# class list
for eleve in dao.getEleves():
print eleve
# class list
for note in dao.getNotes():
print note
The comments speak for themselves. Lines 11–24 use the interface of the [dao] layer. There are no assumptions here about the actual implementation of the layer. On line 8, we instantiate the [dao] layer. Here, we make assumptions: class name and constructor type. There are solutions that allow us to avoid this dependency.
The results of running this script are as follows:
7.4. The [métier] layer
![]() |
The layer [métier] implements the following interface:
- getClasses returns the list of middle school classes;
- getMatieres returns the list of subjects;
- getEleves returns the list of students;
- getNotes returns the list of grades;
- getStatsForEleve returns the grades for student # idEleve along with information about them: weighted average, lowest grade, highest grade.
The [présentation] layer will use only these methods. It does not need to know how they are implemented.
The method getStatsForEleve returns an object of type [StatsForEleve] as follows:
class StatsForEleve:
# manufacturer
def __init__(self, eleve, notes):
# save parameters
self.eleve=eleve
self.notes=notes
# we stop if there are no notes
if len(notes)==0:
return
# using 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
# calculation of student average
self.moyennePonderee=float(sommePonderee)/sommeCoeff
# toString
def __str__(self):
# students without grades
if len(self.notes)==0:
return "Eleve={0}, notes=[]".format(self.eleve)
# student with grades
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)
- Line 3: The constructor receives two parameters:
- a reference to the student of type [Eleve] for whom indicators are calculated;
- a reference to their grades, a list of [Note] objects.
- lines 8-9: if the list of grades is empty, we stop here.
- otherwise, lines 11–25: the following indicators are calculated:
- self.moyennePonderee: the student’s average weighted by the subject coefficients;
- self.min: the student’s lowest grade;
- self.max: the student’s highest grade.
- Line 28: the method for displaying the class in the format specified in line 36.
A test script for this class might look like this:
# -*- coding=utf-8 -*-
# import of entity modules, [dao] layer and [metier] layer
from entites import *
from dao import *
from metier import *
# a class
classe1=Classe(1,"6e A")
# one student in this class
paul_durand=Eleve(1,"durand","paul",classe1)
# three materials
maths=Matiere(1,"maths",1)
francais=Matiere(2,"francais",2)
anglais=Matiere(3,"anglais",3)
# grades in these subjects for the student
note_maths=Note(1,10,paul_durand,maths)
note_francais=Note(2,12,paul_durand,francais)
note_anglais=Note(3,14,paul_durand,anglais)
# indicators are displayed
print StatsForEleve(paul_durand,[note_maths, note_francais,note_anglais])
The screen results are as follows:
Let’s return to our layered architecture:
![]() |
The class [Metier] implements the layer [métier] as follows:
class Metier:
# manufacturer
def __init__(self,dao):
# the reference is stored on layer [dao]
self.dao=dao
#-----------
# interface
#-----------
# score indicators
def getStatsForEleve(self,idEleve):
# Stats for student no. idEleve
# student research
trouve=False
i=0
eleves=self.getEleves()
while not trouve and i<len(eleves):
trouve=eleves[i].id==idEleve
i+=1
# have we found?
if not trouve:
raise RuntimeError("L'eleve [{0}] n'existe pas".format(idEleve))
else:
eleve=eleves[i-1]
# list of all notes
notes=[]
for note in self.getNotes():
# all the student's notes are added to notes
if note.eleve.id==idEleve:
notes.append(note)
# we return the result
return StatsForEleve(eleve,notes)
# list of classes
def getClasses(self):
return self.dao.getClasses()
# list of materials
def getMatieres(self):
return self.dao.getMatieres()
# list of students
def getEleves(self):
return self.dao.getEleves()
# list of notes
def getNotes(self):
return self.dao.getNotes()
- lines 3-5: the constructor receives a reference to the [dao] layer. The [métier] layer must have this reference. Here, we provide it via its constructor. Other solutions are possible. In a horizontally represented layered architecture, each layer must have a reference to the layer to its right;
- Lines 36–49: methods getClasses, getMatieres, getEleves, and getNotes simply delegate the call to the methods of the same names in the [dao] layer;
- line 12: the method getStatsForEleve receives as a parameter the student ID for whom indicators must be returned.
- line 17: the student is searched for in the list of all students;
- lines 18–20: the search loop;
- line 23: if the student is not found, an exception is thrown;
- otherwise, line 25: the found student is stored;
- lines 28–31: we search through all the middle school grades for those belonging to the stored student;
- once they are found, the requested StatsForEleve object can be constructed.
7.5. The [console] layer
![]() |
The [console] layer is implemented by the following script:
# -*- coding=utf-8 -*-
# import entity module, [dao] module, [metier] module
from entites import *
from dao import *
from metier import *
# ----------- layer [console]
# instantiation layer [metier]
metier=Metier(Dao())
# request/response
fini=False
while not fini:
# question
print "numero de l'eleve (>=1 et * pour arreter) : "
# answer
reponse=raw_input()
# finished?
if reponse.strip()=="*":
break
# is the input correct?
ok=False
try:
idEleve=int(reponse,10)
ok=idEleve>=1
except:
pass
# correct data?
if not ok:
print "Saisie incorrecte. Recommencez..."
continue
# calculation
try:
print metier.getStatsForEleve(idEleve)
except RuntimeError,erreur:
print "L'erreur suivante s'est produite : {0}".format(erreur)
- line 10: instantiation of both the [dao] and [métier] layers. This is the only dependency our code has on the implementation of these layers;
- line 34: we use the interface of the [métier] layer;
- line 19: the strip method removes leading and trailing spaces from the string;
- line 20:
*break*exits a loop; - line 24: we attempt to convert the entered string to a decimal integer;
- line 29: ok is true only if line 25 has been executed;
- line 31: continue allows the loop to restart from the middle;
- line 34: calculation of indicators;
- line 35: the exception RuntimeError is caught, which may originate from the layer [métier].
Here is an example of execution:






