7. Arquitetura em camadas e programação baseada em interfaces
7.1. Introdução
Propomos escrever uma aplicação que apresente as notas dos alunos do ensino básico. Esta aplicação terá uma arquitetura em camadas:
![]() |
- A camada [de apresentação] é a camada que interage com o utilizador da aplicação.
- A camada [lógica de negócio] implementa as regras de negócio da aplicação, tais como o cálculo de um salário ou de uma fatura. Esta camada utiliza dados do utilizador através da camada [apresentação] e do SGBD através da camada [DAO].
- A camada [DAO] (Data Access Objects) gere o acesso aos dados no SGBD.
Iremos ilustrar esta arquitetura com uma aplicação de consola simples:
- não haverá base de dados,
- a camada [DAO] irá gerir as entidades Aluno, Turma, Disciplina e Nota para tratar das notas dos alunos,
- a camada [lógica de negócio] calculará métricas com base nas notas de um aluno específico,
- a camada [de apresentação] será uma aplicação de consola que exibe os resultados calculados pela camada [de negócios].
O projeto do Visual Studio para a aplicação é o seguinte:
![]() |
7.2. As entidades da aplicação
As entidades são objetos. Aqui, teremos quatro classes para cada uma das entidades: Aluno, Turma, Disciplina e Nota.
![]() |
O ficheiro [entities.py] contém quatro classes.
A classe [Class] representa uma turma do ensino básico:
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)
- linhas 3–6: uma classe é definida por um ID (linha 5) e um nome (linha 6);
- linhas 9-10: o método para exibir a classe.
A classe [Subject] é a seguinte:
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)
- linhas 3-7: uma disciplina é definida pelo seu ID (linha 5), pelo seu nome (linha 6) e pelo seu peso (linha 7);
- linhas 10-11: o método para apresentar a disciplina.
A classe [Student] é a seguinte:
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)
- linhas 3–8: um aluno é caracterizado pelo seu ID (linha 5), apelido (linha 6), nome próprio (linha 7) e turma (linha 8). Este último parâmetro é uma referência a um objeto [Class];
- linhas 11-12: o método para apresentar o aluno.
A classe [Grade] é a seguinte:
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)
- linhas 3-8: um objeto [Grade] é caracterizado pelo seu ID (linha 5), o valor da nota (linha 6), uma referência ao aluno que recebeu essa nota (linha 7) e uma referência à disciplina para a qual a nota foi atribuída (linha 8);
- linhas 11-12: o método para exibir o objeto [Note].
7.3. A camada [dao]
![]() |
A camada [dao] fornecerá a seguinte interface à camada [business]:
- getClasses devolve a lista de turmas do ensino básico;
- getMatieres devolve a lista de disciplinas;
- getStudents devolve a lista de alunos;
- getGrades devolve uma lista de notas.
A camada [de negócios] utilizará apenas estes métodos. Não precisa de saber como são implementados. Estes dados podem, portanto, provir de várias fontes (valores codificados, uma base de dados, ficheiros de texto, etc.) sem afetar a camada [de negócios]. Isto é conhecido como programação baseada em interfaces.
A classe [Dao] implementa esta interface da seguinte forma:
- linha 4: importamos o módulo que contém as entidades tratadas pela camada [DAO];
- linha 8: o construtor não tem parâmetros. Ele codifica fixamente quatro listas:
- linhas 10–12: a lista de classes;
- linhas 14–16: a lista de disciplinas;
- linhas 18–22: a lista de alunos;
- linhas 24–32: a lista de notas.
- linhas 39–52: os quatro métodos da interface da camada [dao] simplesmente devolvem uma referência às quatro listas construídas pelo construtor.
Um programa de teste pode ter o seguinte aspeto:
Os comentários falam por si. As linhas 11–24 utilizam a interface da camada [dao]. Não há aqui quaisquer pressupostos sobre a implementação real da camada. Na linha 8, instanciamos a camada [dao]. Aqui fazemos pressupostos: nome da classe e tipo do construtor. Existem soluções que nos permitem evitar esta dependência.
Os resultados da execução deste script são os seguintes:
7.4. A camada [de negócios]
![]() |
A camada [de negócios] implementa a seguinte interface:
- getClasses devolve a lista de turmas do ensino básico;
- getMatieres devolve a lista de disciplinas;
- getStudents devolve a lista de alunos;
- getGrades devolve uma lista de notas;
- getStatsForStudent devolve as notas do aluno idStudent, juntamente com informações sobre as mesmas: média ponderada, nota mais baixa, nota mais alta.
A camada [de apresentação] utilizará apenas estes métodos. Não precisa de saber como são implementados.
O método getStatsForStudent devolve um objeto do tipo [StatsForStudent] da seguinte forma:
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)
- Linha 3: O construtor recebe dois parâmetros:
- uma referência ao aluno do tipo [Student] para quem as métricas são calculadas;
- uma referência às suas notas, uma lista de objetos [Grade].
- linhas 8-9: se a lista de notas estiver vazia, paramos aqui.
- Caso contrário, linhas 11–25: são calculadas as seguintes métricas:
- self.weightedAverage: a média do aluno ponderada pelos coeficientes das disciplinas;
- self.min: a nota mais baixa do aluno;
- self.max: a nota mais alta do aluno.
- linha 28: o método para apresentar a turma no formato especificado na linha 36.
Um script de teste para esta classe poderia ser o seguinte:
A saída no ecrã é a seguinte:
Voltemos à nossa arquitetura em camadas:
![]() |
A classe [Business] implementa a camada [business] da seguinte forma:
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()
- linhas 3–5: o construtor recebe uma referência à camada [dao]. A camada [business] deve ter essa referência. Aqui, fornecemos essa referência através do seu construtor. Outras soluções são possíveis. Numa arquitetura em camadas representada horizontalmente, cada camada deve ter uma referência à camada à sua direita;
- linhas 36-49: os métodos getClasses, getSubjects, getStudents e getGrades simplesmente delegam a chamada aos métodos com os mesmos nomes na camada [dao];
- linha 12: o método getStatsForStudent recebe como parâmetro o ID do aluno para o qual as estatísticas devem ser devolvidas.
- linha 17: o aluno será procurado na lista de todos os alunos;
- linhas 18–20: o ciclo de pesquisa;
- linha 23: se o aluno não for encontrado, é lançada uma exceção;
- caso contrário, linha 25: o aluno encontrado é armazenado;
- linhas 28–31: pesquisamos em todos os anos do ensino básico aqueles que pertencem ao aluno armazenado;
- assim que forem encontradas, o objeto StatsForEleve solicitado pode ser construído.
7.5. A camada [console]
![]() |
A camada [console] é implementada pelo seguinte script:
- linha 10: instância das camadas [dao] e [business]. Esta é a única dependência que o nosso código tem em relação à implementação destas camadas;
- linha 34: utilizamos a interface da camada [business];
- Linha 19: O método strip remove os espaços à esquerda e à direita da string;
- linha 20: break sai de um loop;
- linha 24: tenta converter a string introduzida num inteiro decimal;
- linha 29:
oké verdadeiro apenas se a linha 25 tiver sido executada; - linha 31: continue permite que o loop recomece a partir do meio;
- linha 34: cálculo dos indicadores;
- linha 35: captura a exceção RuntimeError que pode ter origem na camada [business].
Aqui está um exemplo de execução:






