Skip to content

8. Application Exercise - [IMPOTS] with layered architecture

Here we revisit the exercise described in Section 4.1. We start with version with text files described in Section 4.3. To process this example with objects, we will use a three-tier architecture:

  • the [dao] layer (Data Access Object) handles data access. Later on, this data will first be found in a text file, then in a MySQL database;
  • the [metier] layer handles business logic, in this case tax calculation. It does not handle data. This data can come from two sources:
    • the [dao] layer for persistent data;
    • the [console] layer for user-provided data.
  • The [console] layer handles user interactions.

In what follows, the [dao] and [metier] layers will each be implemented using a class. The [console] layer will be implemented by the main program.

We will assume that all implementations of the [dao] layer provide the getData() method, which returns a tuple of three elements (limits, coeffR, coeffN), which are the three data arrays required to calculate the tax. In other languages, this is called an interface. An interface defines methods (here getData) that classes implementing this interface must have.

The [metier] layer will be implemented by a class whose constructor will take a reference to the [dao] layer as a parameter, which will ensure communication between the two layers.

8.1. The [dao] layer

We will combine the various classes required by the application into a single file, impots.py. The objects in this file will then be imported into the scripts that require them.

We start with the case where the data is in a text file, as in the example in section 4.3.

The code for class [ImpotsFile] (impots.py), which implements the [dao] layer, is as follows:


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

Notes:

  • lines 7-8: we define a class ImpotsError derived from the Exception class. This class adds nothing to the Exception class. We use it solely to have a custom exception class. This class could be enhanced later;
  • lines 11-18: a class of utility methods. Here, the cutNewLineChar method removes any line-end characters from a string;
  • line 25: opening the file may trigger the IOError exception;
  • lines 32, 39, 46: the custom exception ImpotsError is thrown;
  • the code is similar to that examined in the example in Section 4.3.

8.2. The [metier] layer

The class [ImpotsMetier] (impots.py), which implements the layer [metier], is as follows:


class ImpotsMetier:
 
    # manufacturer
    # retrieve a pointer to the [dao] layer
    def __init__(self, dao):
        self.dao=dao
 
    # tAX CALCULATION
    # --------------------------------------------------------------------------
    def calculer(self,marie,enfants,salaire):
        # married: yes, no
        # children: number of children
        # salary: annual salary
 
        # the [dao] layer is asked for the data required for the calculation
        (limites, coeffR, coeffN)=self.dao.getData()
 
       # number of shares
        marie=marie.lower()
        if(marie=="oui"):
            nbParts=float(enfants)/2+2
        else:
            nbParts=float(enfants)/2+1
        # an additional 1/2 share if at least 3 children
        if enfants>=3:
            nbParts+=0.5
        # taxable income
        revenuImposable=0.72*salaire
        # family quotient
        quotient=revenuImposable/nbParts
        # is set at the end of the limit table to stop the following loop
        limites[len(limites)-1]=quotient
        # tAX CALCULATION
        i=0
        while quotient>limites[i] :
            i=i+1
        # because we've placed quotient at the end of the limit array, the previous loop
        # cannot exceed the limits of the board
        # now we can calculate the tax
        return math.floor(revenuImposable*(float)(coeffR[i])-nbParts*(float)(coeffN[i]))
 

Notes:

  • lines 5-6: the class constructor receives a reference to the [dao] layer as a parameter;
  • line 16: we use the getData method of the [dao] layer to retrieve the data needed to calculate the tax;
  • the rest of the code is similar to that discussed in the example in Section 4.3.

8.3. The [console] layer

The script implementing the [console] layer (impots-03) is as follows:


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

Notes:

  • Line 4: We import all objects from the impots.py file, which contains the class definitions. Once this is done, we can use these objects as if they were in the same file as the script;
  • Lines 17–21: We instantiate both the [dao] layer and the [metier] layer, handling any exceptions that may occur;
  • Line 18: We instantiate the [dao] layer, then the [metier] layer. We store the reference to the [metier] layer;
  • line 19: we handle the two exceptions that may occur;
  • The rest of the code is similar to that discussed in the example in Section 4.3.

8.4. Results

The same as those already obtained in the versions using arrays and files.

The data file 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

The data file data.txt:

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

The resultats.txt file containing the results:

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