Skip to content

8. Toepassingsopdracht – [IMPOTS] met gelaagde architectuur

We nemen hier de oefening uit paragraaf 4.1 opnieuw op. We gaan uit van de versie met tekstbestanden die in paragraaf 4.3 wordt beschreven. Om dit voorbeeld met objecten te verwerken, gebruiken we een drielaagse architectuur:

  • de laag [dao] (Data Access Object) zorgt voor de toegang tot de gegevens. In het vervolg zullen deze gegevens eerst in een tekstbestand worden gevonden en vervolgens in een database MySQL;
  • de laag [metier] houdt zich bezig met de bedrijfsprocessen, in dit geval de berekening van de belasting. Deze laag houdt zich niet bezig met de gegevens. Deze gegevens kunnen uit twee bronnen afkomstig zijn:
    • de laag [dao] voor persistente gegevens;
    • de laag [console] voor door de gebruiker ingevoerde gegevens.
  • de laag [console] houdt zich bezig met de interacties met de gebruiker.

Hierna zullen de lagen [dao] en [metier] elk met behulp van een klasse worden geïmplementeerd. De laag [console] zal door het hoofdprogramma worden geïmplementeerd.

We gaan ervan uit dat alle implementaties van de laag [dao] de methode getData() bieden, die een tuple met drie elementen retourneert (limieten, coeffR, coeffN), de drie gegevensarrays die nodig zijn voor de berekening van de belasting. In andere programmeertalen wordt dit een interface genoemd. Een interface definieert methoden (hier getData) die de klassen die deze interface implementeren, moeten hebben.

De laag [metier] zal worden geïmplementeerd door een klasse waarvan de constructor als parameter een verwijzing naar de laag [dao] heeft, wat de communicatie tussen de twee lagen waarborgt.

8.1. De laag [dao]

We gaan de verschillende klassen die nodig zijn voor de toepassing samenvoegen in één enkel bestand impots.py. De objecten uit dit bestand worden vervolgens geïmporteerd in de scripts die ze nodig hebben.

We beginnen met het geval waarin de gegevens in een tekstbestand staan, zoals in het voorbeeld in paragraaf 4.3.

De code van de klasse [ImpotsFile] (impots.py), die de laag [dao] implementeert, is als volgt:


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

import math, sys

# --------------------------------------------------------------------------
# eigen uitzonderingsklasse
class ImpotsError:
    pass

# --------------------------------------------------------------------------
class Utilitaires:
    """classe de fonctions utilitaires"""
    def cutNewLineChar(self,ligne):
        # de regeleinde-markering wordt verwijderd indien deze aanwezig is
        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: de naam van het bestand met de gegevens van de limiettabellen, coeffR, coeffN
        # het bestand wordt geopend
        data=open(IMPOTS,"r")
        # een object ‘Hulpprogramma’s’
        u=Utilitaires()
        # aanmaken van de 3 tabellen – we gaan ervan uit dat de 3 regels van IMPOTS syntactisch correct zijn
        # -- regel 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])
        # -- regel 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])
        # -- regel 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])
        # einde
        (self.limites,self.coeffR,self.coeffN)=(limites,coeffR,coeffN)

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

Opmerkingen:

  • regels 7-8: er wordt een klasse ImpotsError gedefinieerd die is afgeleid van de klasse Exception. Deze klasse voegt niets toe aan de klasse Exception. Deze wordt alleen gebruikt om een eigen uitzonderingsklasse te hebben. In de toekomst zou deze klasse kunnen worden uitgebreid;
  • regels 11-18: een klasse met hulpprogramma's. Hier verwijdert de methode cutNewLineChar eventuele regeleinden uit een tekenreeks;
  • regel 25: het openen van het bestand kan de uitzondering IOError veroorzaken;
  • regels 32, 39, 46: de eigen uitzondering ImpotsError wordt gegenereerd;
  • de code is vergelijkbaar met die welke in het voorbeeld van paragraaf 4.3 is besproken.

8.2. De laag [metier]

De klasse [ImpotsMetier] (impots.py) die de laag [metier] implementeert, is als volgt:


class ImpotsMetier:

    # constructor
    # we halen een pointer op de laag [dao] op
    def __init__(self, dao):
        self.dao=dao

    # belastingberekening
    # --------------------------------------------------------------------------
    def calculer(self,marie,enfants,salaire):
        # gehuwd: ja, nee
        # kinderen: aantal kinderen
        # salaris: jaarsalaris

        # de benodigde gegevens voor de berekening worden opgevraagd uit de laag [dao]
        (limites, coeffR, coeffN)=self.dao.getData()

       # aantal aandelen
        marie=marie.lower()
        if(marie=="oui"):
            nbParts=float(enfants)/2+2
        else:
            nbParts=float(enfants)/2+1
        # een half aandeel extra bij ten minste 3 kinderen
        if enfants>=3:
            nbParts+=0.5
        # belastbaar inkomen
        revenuImposable=0.72*salaire
        # gezinsquotiënt
        quotient=revenuImposable/nbParts
        # wordt aan het einde van de tabel met drempels geplaatst om de daaropvolgende lus te beëindigen
        limites[len(limites)-1]=quotient
        # berekening van de belasting
        i=0
        while quotient>limites[i] :
            i=i+1
        # omdat de gezinsquotiënt aan het einde van de limiettabel is geplaatst, wordt de voorgaande lus
        # kan niet buiten de grenzen van de limiettabel vallen
        # nu kunnen we de belasting berekenen
        return math.floor(revenuImposable*(float)(coeffR[i])-nbParts*(float)(coeffN[i]))

Opmerkingen:

  • regels 5-6: de constructor van de klasse ontvangt als parameter een verwijzing naar de laag [dao];
  • regel 16: de methode getData van de laag [dao] wordt gebruikt om de gegevens op te halen die nodig zijn voor de berekening van de belasting;
  • de rest van de code is vergelijkbaar met die welke in het voorbeeld van paragraaf 4.3 is besproken.

8.3. De laag [console]

Het script dat de laag [console] (impots-03) implementeert, is als volgt:


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

# de module met de klassen Impots* importeren
from impots import *

# ------------------------------------------------ main
# definitie van de constanten
DATA="data.txt"
RESULTATS="resultats.txt"
IMPOTS="impots.txt"

# de gegevens die nodig zijn voor de berekening van de belasting zijn in het bestand IMPOTS geplaatst
# met één regel per tabel in de vorm
# val1:val2:val3,...

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

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

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

# hulpprogramma's
u=Utilitaires()

# de huidige regel van het gegevensbestand wordt verwerkt
ligne=data.readline()
while(ligne != ''):
    # eventueel einde-van-regel-teken verwijderen
    ligne=u.cutNewLineChar(ligne)
    # de 3 velden 'gehuwd:kinderen:salaris' die de regel vormen, worden opgehaald
    (marie,enfants,salaire)=ligne.split(",")
    enfants=int(enfants)
    salaire=int(salaire)
    # de belasting wordt berekend
    impot=metier.calculer(marie,enfants,salaire)
    # het resultaat wordt opgeschreven
    resultats.write("{0}:{1}:{2}:{3}\n".format(marie,enfants,salaire,impot))
    # er wordt een nieuwe regel ingelezen
    ligne=data.readline()
# de bestanden worden gesloten
data.close()
resultats.close()

Opmerkingen:

  • regel 4: alle objecten worden geïmporteerd uit het bestand impots.py, dat de klassedefinities bevat. Zodra dit is gebeurd, kunnen deze objecten worden gebruikt alsof ze zich in hetzelfde bestand als het script bevinden;
  • regels 17-21: zowel de laag [dao] als de laag [metier] worden geïnstantieerd, met afhandeling van eventuele uitzonderingen;
  • regel 18: we instantiëren de laag [dao] en vervolgens de laag [metier]. We slaan de referentie op in de laag [metier];
  • regel 19: de twee uitzonderingen die kunnen optreden, worden afgehandeld;
  • de rest van de code is vergelijkbaar met die in het voorbeeld van paragraaf 4.3.

8.4. Résultats

Deze zijn al verkregen in de versies met tabellen en bestanden.

Het gegevensbestand 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

Het gegevensbestand data.txt:

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

De resultats.txt-bestanden met de verkregen resultaten:

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