7. Arquitetura em camadas e programação por interfaces
7.1. Introduction
Propomos escrever uma aplicação que permita a visualização das notas dos alunos de uma escola secundária. Esta aplicação terá uma arquitetura multicamadas:
![]() |
- a camada [présentation] é a camada que interage com o utilizador da aplicação.
- A camada [metier] implementa as regras de gestão da aplicação, tais como o cálculo de um salário ou de uma fatura. Esta camada utiliza dados provenientes do utilizador através da camada [présentation] e da camada SGBD através da camada [dao].
- A camada [dao] (Data Access Objects) gere o acesso aos dados da camada SGBD.
Vamos ilustrar esta arquitetura com uma aplicação de consola simples:
- não haverá base de dados,
- a camada [dao] irá gerir as entidades Eleve, Classe, Matiere e Note, permitindo a gestão das notas dos alunos,
- a camada [métier] permitirá calcular indicadores com base nas notas de um aluno específico,
- a camada [présentation] será uma aplicação de consola que apresentará os resultados calculados pela camada [métier].
O projeto do Visual Studio da aplicação é o seguinte:
![]() |
7.2. As entidades da aplicação
As entidades são objetos. Teremos aqui quatro classes para cada uma das entidades Eleve, Classe, Matiere e Note.
![]() |
O ficheiro [entites.py] agrupa quatro turmas.
A turma [Classe] representa uma turma do ensino básico:
class Classe:
# construtor
def __init__(self,id,nom):
# guardam-se os parâmetros
self.id=id
self.nom=nom
# toString
def __str__(self):
return "Classe[{0},{1}]".format(self.id,self.nom)
- linhas 3-6: uma turma é definida por um n.º id (linha 5) e um nom (linha 6);
- linhas 9-10: o método de exibição da turma.
A classe [Matiere] é a seguinte:
class Matiere:
# fabricante
def __init__(self,id,nom,coefficient):
# memorização dos parâmetros
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 n.º (linha 5), pelo seu nome (linha 6) e pelo seu coeficiente (linha 7);
- linhas 10-11: o método de exibição da disciplina.
A classe [Eleve] é a seguinte:
class Eleve:
# fabricante
def __init__(self,id,nom,prenom,classe):
# memorização dos parâmetros
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 n.º (linha 5), o seu apelido (linha 6), o seu nome próprio (linha 7) e a sua turma (linha 8). Este último parâmetro é uma referência a um objeto [Classe];
- linhas 11-12: o método de visualização do aluno.
A classe [Note] é a seguinte:
class Note:
# fabricante
def __init__(self,id,valeur,eleve,matiere):
# memorização dos parâmetros
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 [Note] é caracterizado pelo seu n.º (linha 5), pelo valor da nota (linha 6), por uma referência ao aluno que obteve essa nota (linha 7) e por uma referência à disciplina a que a nota se refere (linha 8);
- linhas 11-12: o método de exibição do objeto [Note].
7.3. A camada [dao]
![]() |
A camada [dao] disponibilizará a seguinte interface à camada [métier]:
- getClasses apresenta a lista das turmas do ensino básico;
- getMatieres apresenta a lista das disciplinas;
- getEleves apresenta a lista de alunos;
- getNotes apresenta a lista de notas.
A camada [métier] utilizará apenas estes métodos. Não precisa de saber como são implementados. Estes dados podem, assim, provir de diferentes fontes (armazenados em memória, de uma base de dados, de ficheiros de texto, etc.) sem que isso tenha impacto na camada [métier]. A isto chama-se programação por interfaces.
A classe [Dao] implementa esta interface da seguinte forma:
# -*- coding=utf-8 -*-
# importação do módulo de entidades
from entites import *
class Dao:
# construtor
def __init__(self):
# instanciamos as classes
classe1=Classe(1,"classe1")
classe2=Classe(2,"classe2")
self.classes=[classe1,classe2]
# as disciplinas
matiere1=Matiere(1,"matiere1",1)
matiere2=Matiere(2,"matiere2",2)
self.matieres=[matiere1,matiere2]
# os alunos
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]
# as notas
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
#-----------
# lista de turmas
def getClasses(self):
return self.classes
# lista de disciplinas
def getMatieres(self):
return self.matieres
# lista de alunos
def getEleves(self):
return self.eleves
# lista de notas
def getNotes(self):
return self.notes
- linha 4: importa-se o módulo que contém as entidades manipuladas pela camada [dao];
- linha 8: o construtor não tem parâmetros. Ele cria de forma estática quatro listas:
- linhas 10-12: a lista de classes;
- linhas 14-16: a lista das 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] limitam-se a devolver uma referência às quatro listas criadas pelo construtor.
Um programa de teste poderia ser o seguinte:
# -*- coding=utf-8 -*-
# importação do módulo de entidades e da camada [dao]
from entites import *
from dao import *
# instanciação da camada [dao]
dao=Dao()
# lista de classes
for classe in dao.getClasses():
print classe
# lista de classes
for matiere in dao.getMatieres():
print matiere
# lista de classes
for eleve in dao.getEleves():
print eleve
# lista de classes
for note in dao.getNotes():
print note
Os comentários são autoexplicativos. 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, fazem-se suposições: nome da classe e tipo de construtor. Existem soluções que permitem evitar esta dependência.
Os resultados da execução deste script são os seguintes:
7.4. A camada [métier]
![]() |
A camada [métier] implementa a seguinte interface:
- getClasses apresenta a lista das turmas do ensino básico;
- getMatieres apresenta a lista das disciplinas;
- getEleves apresenta a lista de alunos;
- getNotes apresenta a lista de notas;
- getStatsForEleve apresenta as notas do aluno n.º idEleve, bem como informações sobre as mesmas: média ponderada, nota mais baixa, nota mais alta.
A camada [présentation] utilizará apenas estes métodos. Não precisa de saber como são implementados.
O método getStatsForEleve devolve um objeto do tipo [StatsForEleve] com as seguintes características:
class StatsForEleve:
# fabricante
def __init__(self, eleve, notes):
# os parâmetros são guardados
self.eleve=eleve
self.notes=notes
# pára-se se não houver notas
if len(notes)==0:
return
# análise das notas
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
# cálculo da média do aluno
self.moyennePonderee=float(sommePonderee)/sommeCoeff
# toString
def __str__(self):
# caso do aluno sem notas
if len(self.notes)==0:
return "Eleve={0}, notes=[]".format(self.eleve)
# caso do aluno com notas
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 [Eleve] para o qual se calculam os indicadores;
- uma referência às suas notas, uma lista de objetos [Note].
- linhas 8-9: se a lista de notas estiver vazia, o processo não prossegue.
- caso contrário, linhas 11-25, calculam-se os seguintes indicadores:
- self.moyennePonderee: a média do aluno ponderada pelos coeficientes das disciplinas;
- self.min: a nota mais baixa do aluno;
- self.max: a sua nota mais alta.
- linha 28: o método de apresentação da turma na forma indicada na linha 36.
Um script de teste desta classe poderia ser o seguinte:
# -*- coding=utf-8 -*-
# importação dos módulos das entidades, da camada [dao] e da camada [metier]
from entites import *
from dao import *
from metier import *
# uma turma
classe1=Classe(1,"6e A")
# um aluno nesta turma
paul_durand=Eleve(1,"durand","paul",classe1)
# três disciplinas
maths=Matiere(1,"maths",1)
francais=Matiere(2,"francais",2)
anglais=Matiere(3,"anglais",3)
# as notas nessas disciplinas para o aluno
note_maths=Note(1,10,paul_durand,maths)
note_francais=Note(2,12,paul_durand,francais)
note_anglais=Note(3,14,paul_durand,anglais)
# são apresentados os indicadores
print StatsForEleve(paul_durand,[note_maths, note_francais,note_anglais])
Os resultados apresentados no ecrã são os seguintes:
Voltemos à nossa arquitetura em camadas:
![]() |
A classe [Metier] implementa a camada [métier] da seguinte forma:
class Metier:
# construtor
def __init__(self,dao):
# a referência é guardada na camada [dao]
self.dao=dao
#-----------
# interface
#-----------
# os indicadores nas notas
def getStatsForEleve(self,idEleve):
# Estatísticas para o aluno com o n.º idEleve
# pesquisa do aluno
trouve=False
i=0
eleves=self.getEleves()
while not trouve and i<len(eleves):
trouve=eleves[i].id==idEleve
i+=1
# foi encontrado?
if not trouve:
raise RuntimeError("L'eleve [{0}] n'existe pas".format(idEleve))
else:
eleve=eleves[i-1]
# Lista de todas as notas
notes=[]
for note in self.getNotes():
# adiciona-se às notas todas as notas do aluno
if note.eleve.id==idEleve:
notes.append(note)
# apresenta o resultado
return StatsForEleve(eleve,notes)
# a lista das turmas
def getClasses(self):
return self.dao.getClasses()
# a lista de disciplinas
def getMatieres(self):
return self.dao.getMatieres()
# a lista de alunos
def getEleves(self):
return self.dao.getEleves()
# a lista de notas
def getNotes(self):
return self.dao.getNotes()
- linhas 3-5: o construtor recebe uma referência à camada [dao]. A camada [métier] deve possuir essa referência. Neste caso, esta é-lhe fornecida através do seu construtor. Seria possível imaginar outras soluções. Numa arquitetura em camadas representada horizontalmente, cada camada deve possuir uma referência à camada que se encontra à sua direita;
- linhas 36-49: os métodos getClasses, getMatieres, getEleves, getNotes limitam-se a delegar a chamada aos métodos com os mesmos nomes da camada [dao];
- linha 12: o método getStatsForEleve recebe como parâmetro o número do aluno para o qual se devem apresentar os indicadores.
- 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 tiver sido encontrado, é lançada uma exceção;
- caso contrário, linha 25: o aluno encontrado é guardado;
- linhas 28-31: procura-se, entre todas as notas do colégio, aquelas que pertencem ao aluno memorizado;
- quando as encontrarmos, podemos construir o objeto StatsForEleve solicitado.
7.5. A camada [console]
![]() |
A camada [console] é implementada pelo seguinte script:
# -*- coding=utf-8 -*-
# importação do módulo de entidades, do módulo [dao] e do módulo [metier]
from entites import *
from dao import *
from metier import *
# ----------- camada [console]
# instanciação da camada [metier]
metier=Metier(Dao())
# pedido/resposta
fini=False
while not fini:
# pergunta
print "numero de l'eleve (>=1 et * pour arreter) : "
# resposta
reponse=raw_input()
# concluído?
if reponse.strip()=="*":
break
# a entrada está correta?
ok=False
try:
idEleve=int(reponse,10)
ok=idEleve>=1
except:
pass
# dados corretos?
if not ok:
print "Saisie incorrecte. Recommencez..."
continue
# cálculo
try:
print metier.getStatsForEleve(idEleve)
except RuntimeError,erreur:
print "L'erreur suivante s'est produite : {0}".format(erreur)
- linha 10: instanciação simultânea das camadas [dao] e [métier]. Esta é a única dependência do nosso código em relação à implementação destas camadas;
- linha 34: utiliza-se a interface da camada [métier];
- linha 19: o método strip remove os espaços no início e no fim da cadeia;
- linha 20: break permite sair de um ciclo;
- linha 24: tenta-se converter a cadeia introduzida num número inteiro decimal;
- linha 29: ok só é verdadeiro se tiver-se passado pela linha 25;
- linha 31: continue permite voltar a entrar no meio do ciclo;
- linha 34: cálculo dos indicadores;
- linha 35: intercepta-se a exceção RuntimeError, que pode ser gerada pela camada [métier].
Eis um exemplo de execução:






