Skip to content

8. Anwendungsübung – [Steuerberechnung] mit einer mehrschichtigen Architektur

Hier greifen wir die in Abschnitt 4.1 beschriebene Übung wieder auf. Wir beginnen mit der in Abschnitt 4.3 beschriebenen Textdatei-Version. Um dieses Beispiel mit Objekten zu bearbeiten, verwenden wir eine dreistufige Architektur:

  • Die [DAO]-Schicht (Data Access Object) übernimmt den Datenzugriff. Im Folgenden befinden sich diese Daten zunächst in einer Textdatei, später in einer MySQL-Datenbank;
  • Die [Business]-Schicht ist für die Geschäftslogik zuständig, in diesem Fall für die Steuerberechnung. Sie verarbeitet keine Daten. Diese Daten können aus zwei Quellen stammen:
    • der [DAO]-Schicht für persistente Daten;
    • die [Konsole]-Schicht für vom Benutzer bereitgestellte Daten.
  • Die [Console]-Schicht ist für die Interaktion mit dem Benutzer zuständig.

Im Folgenden werden die [DAO]- und die [Business]-Schicht jeweils mithilfe einer Klasse implementiert. Die [Console]-Schicht wird durch das Hauptprogramm implementiert.

Wir gehen davon aus, dass alle Implementierungen der [dao]-Schicht die Methode getData() bereitstellen, die ein Tupel mit drei Elementen (limits, coeffR, coeffN) zurückgibt – die drei Datenarrays, die zur Berechnung der Steuer benötigt werden. In anderen Sprachen wird dies als Schnittstelle bezeichnet. Eine Schnittstelle definiert Methoden (hier getData), über die Klassen verfügen müssen, die diese Schnittstelle implementieren.

Die [business]-Schicht wird durch eine Klasse implementiert, deren Konstruktor eine Referenz auf die [dao]-Schicht als Parameter entgegennimmt, wodurch die Kommunikation zwischen den beiden Schichten sichergestellt wird.

8.1. Die [DAO]-Schicht

Wir werden die verschiedenen für die Anwendung erforderlichen Klassen in einer einzigen Datei, impots.py, zusammenfassen. Die Objekte in dieser Datei werden dann in die Skripte importiert, die sie benötigen.

Wir beginnen mit dem Fall, in dem sich die Daten in einer Textdatei befinden, wie im Beispiel in Abschnitt 4.3.

Der Code für die Klasse [ ImpotsFile] (impots.py), die die [DAO]-Schicht implementiert, lautet wie folgt:

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

Anmerkungen:

  • Zeilen 7–8: Wir definieren eine Klasse „ImpotsError“, die von der Klasse „Exception“ abgeleitet ist. Diese Klasse fügt der Klasse „Exception“ nichts hinzu. Wir verwenden sie ausschließlich, um eine benutzerdefinierte Ausnahmeklasse zu haben. Diese Klasse könnte später erweitert werden;
  • Zeilen 11–18: eine Klasse mit Hilfsmethoden. Hier entfernt die Methode *cutNewLineChar* alle Zeilenumbruchzeichen aus einer Zeichenkette;
  • Zeile 25: Beim Öffnen der Datei kann die Ausnahme „IOError“ ausgelöst werden;
  • Zeilen 32, 39, 46: Die benutzerdefinierte Ausnahme „ImpotsError“ wird ausgelöst;
  • Der Code ähnelt dem im Beispiel in Abschnitt 4.3 behandelten.

8.2. Die [Business]-Schicht

Die Klasse [ ImpotsMetier] (impots.py), die die [Geschäftsschicht] implementiert, sieht wie folgt aus:


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

Anmerkungen:

  • Zeilen 5–6: Der Klassenkonstruktor erhält eine Referenz auf die [dao]-Schicht als Parameter;
  • Zeile 16: Wir verwenden die Methode getData der [dao]-Schicht, um die für die Berechnung der Steuer erforderlichen Daten abzurufen;
  • Der Rest des Codes entspricht dem im Beispiel in Abschnitt 4.3 behandelten.

8.3. Die [console]-Schicht

Das Skript zur Implementierung der [console]-Schicht (impots-03) lautet wie folgt:

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

Hinweise:

  • Zeile 4: Wir importieren alle Objekte aus der Datei impots.py, die die Klassendefinitionen enthält. Sobald dies geschehen ist, können wir diese Objekte so verwenden, als befänden sie sich in derselben Datei wie das Skript;
  • Zeilen 17–21: Wir instanziieren sowohl die [dao]-Schicht als auch die [business]-Schicht und behandeln dabei mögliche Ausnahmen;
  • Zeile 18: Wir instanziieren die [dao]-Schicht und anschließend die [business]-Schicht. Wir speichern die Referenz auf die [business]-Schicht;
  • Zeile 19: Wir behandeln die beiden Ausnahmen, die auftreten können;
  • Der Rest des Codes ähnelt dem im Beispiel in Abschnitt 4.3 behandelten.

8.4. Ergebnisse

Die gleichen wie die bereits in den Versionen mit Arrays und Dateien erzielten.

Die Datendatei 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

Die Datendatei data.txt:

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

Die Datei „results.txt“ mit den Ergebnissen:

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