Skip to content

8. Exercício de aplicação – [Cálculo de impostos] com uma arquitetura em camadas

Aqui, retomamos o exercício descrito na Secção 4.1. Começamos com a versão em ficheiro de texto descrita na Secção 4.3. Para tratar este exemplo com objetos, utilizaremos uma arquitetura de três camadas:

  • a camada [DAO] (Data Access Object) trata do acesso aos dados. A seguir, estes dados serão inicialmente encontrados num ficheiro de texto e, posteriormente, numa base de dados MySQL;
  • A camada [de negócios] lida com a lógica de negócios, neste caso, o cálculo de impostos. Não lida com dados. Estes dados podem provir de duas fontes:
    • da camada [DAO] para dados persistentes;
    • a camada [console] para dados fornecidos pelo utilizador.
  • A camada [console] lida com as interações com o utilizador.

A seguir, as camadas [DAO] e [business] serão implementadas, cada uma, utilizando uma classe. A camada [console] será implementada pelo programa principal.

Partiremos do princípio de que todas as implementações da camada [dao] fornecem o método getData(), que devolve uma tupla de três elementos (limits, coeffR, coeffN) — as três matrizes de dados necessárias para calcular o imposto. Noutras linguagens, isto é designado por interface. Uma interface define métodos (neste caso, getData) que as classes que implementam essa interface devem possuir.

A camada [business] será implementada por uma classe cujo construtor recebe uma referência à camada [dao] como parâmetro, garantindo a comunicação entre as duas camadas.

8.1. A camada [DAO]

Vamos combinar as várias classes necessárias para a aplicação num único ficheiro, impots.py. Os objetos neste ficheiro serão então importados para os scripts que os necessitam.

Começamos com o caso em que os dados se encontram num ficheiro de texto, tal como no exemplo da Secção 4.3.

O código da classe [ ImpotsFile] (impots.py) que implementa a camada [DAO] é o seguinte:

#    -*- coding=utf-8 -*-

import math, sys

# --------------------------------------------------------------------------
#    exceptional owner class
class ImpotsError:
    pass

# --------------------------------------------------------------------------
class Utilitaires:
    """classe de fonctions utilitaires"""
    def cutNewLineChar(self,ligne):
        #  delete the end-of-line mark if it exists
        l=len(ligne)
        while(ligne[l-1]=="\n" or ligne[l-1]=="\r"):
            l-=1
        return(ligne[0:l])

# --------------------------------------------------------------------------
class ImpotsFile:
    def __init__(self,IMPOTS):
        #  IMPOTS: the name of the file containing data from the limit tables, coeffR, coeffN
        #  open the file
        data=open(IMPOTS,"r")
        #    a Utilities object
        u=Utilitaires()
        #  create the 3 tables - assume that the 3 rows of IMPOTS are syntactically correct
        #  -- line 1
        ligne=data.readline()
        if ligne== '':
            raise ImpotsError("La premiere ligne du fichier {0} est absente".format(IMPOTS))
        limites=u.cutNewLineChar(ligne).split(":")
        for i in range(len(limites)):
            limites[i]=int(limites[i])
        #  -- line 2
        ligne=data.readline()
        if ligne== '':
            raise ImpotsError("La deuxieme ligne du fichier {0} est absente".format(IMPOTS))
        coeffR=u.cutNewLineChar(ligne).split(":")
        for i in range(len(coeffR)):
            coeffR[i]=float(coeffR[i])
        #  -- line 3
        ligne=data.readline()
        if ligne== '':
            raise ImpotsError("La troisieme ligne du fichier {0} est absente".format(IMPOTS))
        coeffN=u.cutNewLineChar(ligne).split(":")
        for i in range(len(coeffN)):
            coeffN[i]=float(coeffN[i])
        #    end
        (self.limites,self.coeffR,self.coeffN)=(limites,coeffR,coeffN)

    def getData(self):
        return (self.limites, self.coeffR, self.coeffN)

Notas:

  • linhas 7-8: definimos uma classe ImpotsError derivada da classe Exception. Esta classe não acrescenta nada à classe Exception. Utilizamo-la exclusivamente para ter uma classe de exceção personalizada. Esta classe poderá ser expandida posteriormente;
  • linhas 11–18: uma classe de métodos utilitários. Aqui, o método cutNewLineChar remove quaisquer caracteres de quebra de linha de uma string;
  • linha 25: a abertura do ficheiro pode lançar a exceção IOError;
  • linhas 32, 39, 46: a exceção personalizada ImpotsError é lançada;
  • o código é semelhante ao estudado no exemplo da Secção 4.3.

8.2. A camada [de negócios]

A classe [ ImpotsMetier] (impots.py) que implementa a camada [de negócios] é a seguinte:


class ImpotsMetier:
 
    # constructeur
    # on récupère un pointeur sur la couche [dao]
    def __init__(self, dao):
        self.dao=dao
 
    # calcul de l'impôt
    # --------------------------------------------------------------------------
    def calculer(self,marie,enfants,salaire):
        # marié : oui, non
        # enfants : nombre d'enfants
        # salaire : salaire annuel
 
        # on demande à la couche [dao] les données nécessaires au calcul
        (limites, coeffR, coeffN)=self.dao.getData()
 
       # nombre de parts
        marie=marie.lower()
        if(marie=="oui"):
            nbParts=float(enfants)/2+2
        else:
            nbParts=float(enfants)/2+1
        # une 1/2 part de plus si au moins 3 enfants
        if enfants>=3:
            nbParts+=0.5
        # revenu imposable
        revenuImposable=0.72*salaire
        # quotient familial
        quotient=revenuImposable/nbParts
        # est mis à la fin du tableau limites pour arrêter la boucle qui suit
        limites[len(limites)-1]=quotient
        # calcul de l'impôt
        i=0
        while quotient>limites[i] :
            i=i+1
        # du fait qu'on a placé quotient à la fin du tableau limites, la boucle précédente
        # ne peut déborder du tableau limites
        # maintenant on peut calculer l'impôt
        return math.floor(revenuImposable*(float)(coeffR[i])-nbParts*(float)(coeffN[i]))
 

Notas:

  • linhas 5-6: o construtor da classe recebe uma referência à camada [dao] como parâmetro;
  • linha 16: utilizamos o método getData da camada [dao] para recuperar os dados necessários para calcular o imposto;
  • o resto do código é análogo ao estudado no exemplo da Secção 4.3.

8.3. A camada [console]

O script que implementa a camada [console] (impots-03) é o seguinte:

#    -*- coding=utf-8 -*-

#    import of Impots* class module
from impots import *

#    ------------------------------------------------ main
#    definition of constants
DATA="data.txt"
RESULTATS="resultats.txt"
IMPOTS="impots.txt"

#  the data required to calculate the tax has been placed in the IMPOTS file
#  one line per table in the form
#    val1:val2:val3,...

#    instantiation layer [metier]
try:
    metier=ImpotsMetier(ImpotsFile(IMPOTS))
except (IOError, ImpotsError) as infos:
    print ("Une erreur s'est produite : {0}".format(infos))
    sys.exit()

#    reading data
try:
    data=open(DATA,"r")
except:
    print "Impossible d'ouvrir en lecture le fichier des donnees [DATA]"
    sys.exit()

#    open results file
try:
    resultats=open(RESULTATS,"w")
except:  
    print "Impossible de creer le fichier des résultats [RESULTATS]"
    sys.exit()

#    utilities
u=Utilitaires()

#  the current line of the data file is used
ligne=data.readline()
while(ligne != ''):
    #  remove any end-of-line marker
    ligne=u.cutNewLineChar(ligne)
    #  we retrieve the 3 fields married:children:salary which form the line
    (marie,enfants,salaire)=ligne.split(",")
    enfants=int(enfants)
    salaire=int(salaire)
    #  tax calculation
    impot=metier.calculer(marie,enfants,salaire)
    #  enter the result
    resultats.write("{0}:{1}:{2}:{3}\n".format(marie,enfants,salaire,impot))
    #  a new line is read
    ligne=data.readline()
#    close files
data.close()
resultats.close()

Notas:

  • Linha 4: Importamos todos os objetos do ficheiro impots.py, que contém as definições das classes. Depois de feito isto, podemos usar esses objetos como se estivessem no mesmo ficheiro que o script;
  • Linhas 17–21: Instanciamos tanto a camada [dao] como a camada [business], com tratamento para potenciais exceções;
  • Linha 18: instanciamos a camada [dao] e, em seguida, a camada [business]. Guardamos a referência à camada [business];
  • linha 19: tratamos as duas exceções que podem ocorrer;
  • o resto do código é semelhante ao estudado no exemplo da Secção 4.3.

8.4. Resultados

Os mesmos que já foram obtidos nas versões que utilizam matrizes e ficheiros.

O ficheiro de dados impots.txt:

12620:13190:15640:24740:31810:39970:48360:55790:92970:127860:151250:172040:195000:0
0:0.05:0.1:0.15:0.2:0.25:0.3:0.35:0.4:0.45:0.5:0.55:0.6:0.65
0:631:1290.5:2072.5:3309.5:4900:6898.5:9316.5:12106:16754.5:23147.5:30710:39312:49062

O ficheiro de dados data.txt:

oui,2,200000
non,2,200000
oui,3,200000
non,3,200000
oui,5,50000
non,0,3000000

O ficheiro results.txt contendo os resultados:

oui:2:200000:22504.0
non:2:200000:33388.0
oui:3:200000:16400.0
non:3:200000:22504.0
oui:5:50000:0.0
non:0:3000000:1354938.0