Skip to content

8. Esercizio di applicazione – [Calcolo delle imposte] con un'architettura a livelli

Qui riprendiamo l'esercizio descritto nella Sezione 4.1. Partiamo dalla versione con file di testo descritta nella Sezione 4.3. Per gestire questo esempio con gli oggetti, useremo un'architettura a tre livelli:

  • il livello [DAO] (Data Access Object) gestisce l'accesso ai dati. Di seguito, questi dati saranno inizialmente presenti in un file di testo, poi in un database MySQL;
  • Il livello [business] gestisce la logica di business, in questo caso il calcolo delle imposte. Non gestisce i dati. Questi dati possono provenire da due fonti:
    • il livello [DAO] per i dati persistenti;
    • il livello [console] per i dati forniti dall'utente.
  • Il livello [console] gestisce le interazioni con l'utente.

Di seguito, i livelli [DAO] e [business] saranno implementati ciascuno utilizzando una classe. Il livello [console] sarà implementato dal programma principale.

Supporremo che tutte le implementazioni del livello [dao] forniscano il metodo getData(), che restituisce una tupla di tre elementi (limits, coeffR, coeffN)—i tre array di dati necessari per calcolare l’imposta. In altri linguaggi, questa è chiamata interfaccia. Un’interfaccia definisce i metodi (in questo caso, getData) che le classi che implementano tale interfaccia devono avere.

Il livello [business] sarà implementato da una classe il cui costruttore accetta come parametro un riferimento al livello [dao], garantendo la comunicazione tra i due livelli.

8.1. Il livello [DAO]

Combineremo le varie classi necessarie per l'applicazione in un unico file, impots.py. Gli oggetti in questo file verranno poi importati negli script che ne hanno bisogno.

Iniziamo con il caso in cui i dati si trovano in un file di testo, come nell'esempio della Sezione 4.3.

Il codice della classe [ ImpotsFile] (impots.py) che implementa il livello [DAO] è il seguente:

#    -*- 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)

Note:

  • righe 7-8: definiamo una classe ImpotsError derivata dalla classe Exception. Questa classe non aggiunge nulla alla classe Exception. La usiamo esclusivamente per avere una classe di eccezione personalizzata. Questa classe potrebbe essere ampliata in seguito;
  • righe 11–18: una classe di metodi di utilità. Qui, il metodo cutNewLineChar rimuove eventuali caratteri di interruzione di riga da una stringa;
  • riga 25: l'apertura del file potrebbe generare l'eccezione IOError;
  • righe 32, 39, 46: viene generata l'eccezione personalizzata ImpotsError;
  • il codice è simile a quello studiato nell'esempio della Sezione 4.3.

8.2. Il livello [business]

La classe [ ImpotsMetier] (impots.py) che implementa il livello [business] è la seguente:


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]))
 

Note:

  • righe 5-6: il costruttore della classe riceve come parametro un riferimento al livello [dao];
  • riga 16: utilizziamo il metodo getData del livello [dao] per recuperare i dati necessari al calcolo dell'imposta;
  • il resto del codice è analogo a quello studiato nell'esempio della Sezione 4.3.

8.3. Il livello [console]

Lo script che implementa il livello [console] (impots-03) è il seguente:

#    -*- 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()

Note:

  • Riga 4: importiamo tutti gli oggetti dal file impots.py, che contiene le definizioni delle classi. Una volta fatto ciò, possiamo utilizzare questi oggetti come se fossero nello stesso file dello script;
  • Righe 17–21: Istanziamo sia il livello [dao] che il livello [business] con la gestione delle potenziali eccezioni;
  • riga 18: istanziamo il livello [dao] e poi il livello [business]. Memorizziamo il riferimento al livello [business];
  • riga 19: gestiamo le due eccezioni che potrebbero verificarsi;
  • il resto del codice è simile a quello studiato nell'esempio della Sezione 4.3.

8.4. Risultati

Gli stessi già ottenuti nelle versioni che utilizzano array e file.

Il file di dati 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

Il file di dati data.txt:

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

Il file results.txt contenente i risultati:

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