Skip to content

8. Ćwiczenie praktyczne – [IMPOTS] z architekturą warstwową

W tym miejscu powracamy do ćwiczenia opisanego w punkcie 4.1. Wychodzimy od wersji z plikami tekstowymi opisanej w punkcie 4.3. Aby przetworzyć ten przykład przy użyciu obiektów, zastosujemy architekturę trójwarstwową:

  • warstwa [dao] (Data Access Object) zajmuje się dostępem do danych. W dalszej części dane te będą najpierw pobierane z pliku tekstowego, a następnie z bazy danych MySQL;
  • warstwa [metier] zajmuje się zagadnieniami biznesowymi, w tym przypadku obliczaniem podatku. Nie zajmuje się ona danymi. Dane te mogą pochodzić z dwóch źródeł:
    • warstwa [dao] dla danych trwałych;
    • warstwa [console] dla danych dostarczanych przez użytkownika.
  • warstwa [console] odpowiada za interakcje z użytkownikiem.

W dalszej części warstwy [dao] i [metier] zostaną zaimplementowane za pomocą odpowiednich klas. Natomiast warstwa [console] zostanie zaimplementowana przez program główny.

Założymy, że wszystkie implementacje warstwy [dao] udostępniają metodę getData(), która zwraca krotkę składającą się z trzech elementów (limity, coeffR, coeffN), które są trzema tablicami danych niezbędnymi do obliczenia podatku. W innych językach nazywa się to interfejsem. Interfejs definiuje metody (w tym przypadku getData), które muszą posiadać klasy implementujące ten interfejs.

Warstwa [metier] zostanie zaimplementowana przez klasę, której konstruktor będzie miał jako parametr odwołanie do warstwy [dao], co zapewni komunikację między tymi dwiema warstwami.

8.1. Warstwa [dao]

Zgromadzimy różne klasy niezbędne dla aplikacji w jednym pliku impots.py. Obiekty z tego pliku zostaną następnie zaimportowane do skryptów, które ich potrzebują.

Zaczniemy od przypadku, w którym dane znajdują się w pliku tekstowym, tak jak w przykładzie z akapitu 4.3.

Kod klasy [ImpotsFile] (impots.py), która implementuje warstwę [dao], wygląda następująco:


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

import math, sys

# --------------------------------------------------------------------------
# klasa wyjątków właściciela
class ImpotsError:
    pass

# --------------------------------------------------------------------------
class Utilitaires:
    """classe de fonctions utilitaires"""
    def cutNewLineChar(self,ligne):
        # usuwa się znak końca linii, jeśli istnieje
        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: nazwa pliku zawierającego dane z tabel granicznych, coeffR, coeffN
        # otwiera się plik
        data=open(IMPOTS,"r")
        # obiekt „Narzędzia”
        u=Utilitaires()
        # utworzenie 3 tabel – zakłada się, że 3 wiersze pliku IMPOTS są poprawne pod względem składniowym
        # — wiersz 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])
        # — wiersz 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])
        # — wiersz 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])
        # koniec
        (self.limites,self.coeffR,self.coeffN)=(limites,coeffR,coeffN)

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

Uwagi:

  • wiersze 7–8: definiuje się klasę ImpotsError wywodzącą się z klasy Exception. Klasa ta nie wnosi nic nowego w stosunku do klasy Exception. Wykorzystuje się ją wyłącznie w celu uzyskania własnej klasy wyjątków. W przyszłości klasa ta może zostać rozbudowana;
  • wiersze 11–18: klasa zawierająca metody pomocnicze. Tutaj metoda cutNewLineChar usuwa ewentualny znak końca linii z ciągu znaków;
  • wiersz 25: otwarcie pliku może spowodować wygenerowanie wyjątku IOError;
  • wiersze 32, 39, 46: wywoływany jest wyjątek własny ImpotsError;
  • Kod jest analogiczny do tego, który omówiono w przykładzie z punktu 4.3.

8.2. Warstwa [metier]

Klasa [ImpotsMetier] (impots.py), która implementuje warstwę [metier], wygląda następująco:


class ImpotsMetier:

    # konstruktor
    # pobieramy wskaźnik do warstwy [dao]
    def __init__(self, dao):
        self.dao=dao

    # obliczenie podatku
    # --------------------------------------------------------------------------
    def calculer(self,marie,enfants,salaire):
        # stan cywilny: tak, nie
        # dzieci: liczba dzieci
        # wynagrodzenie: roczne wynagrodzenie

        # z warstwy [dao] pobierane są dane niezbędne do obliczeń
        (limites, coeffR, coeffN)=self.dao.getData()

       # liczba udziałów
        marie=marie.lower()
        if(marie=="oui"):
            nbParts=float(enfants)/2+2
        else:
            nbParts=float(enfants)/2+1
        # o 1/2 udziału więcej, jeśli jest co najmniej troje dzieci
        if enfants>=3:
            nbParts+=0.5
        # dochód podlegający opodatkowaniu
        revenuImposable=0.72*salaire
        # współczynnik rodzinny
        quotient=revenuImposable/nbParts
        # jest umieszczany na końcu tabeli limitów w celu zakończenia następującej pętli
        limites[len(limites)-1]=quotient
        # obliczenie podatku
        i=0
        while quotient>limites[i] :
            i=i+1
        # ponieważ współczynnik rodzinny umieszczono na końcu tablicy limitów, poprzednia pętla
        # nie może wykraczać poza tablicę limitów
        # teraz można obliczyć podatek
        return math.floor(revenuImposable*(float)(coeffR[i])-nbParts*(float)(coeffN[i]))

Uwagi:

  • wiersze 5–6: konstruktor klasy otrzymuje jako parametr odwołanie do warstwy [dao];
  • wiersz 16: wykorzystuje się metodę getData z warstwy [dao] w celu pobrania danych umożliwiających obliczenie podatku;
  • pozostała część kodu jest analogiczna do tej omówionej w przykładzie z paragrafu 4.3.

8.3. Warstwa [console]

Skrypt implementujący warstwę [console] (impots-03) wygląda następująco:


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

# import z modułu klas Impots*
from impots import *

# ------------------------------------------------ main
# definicja stałych
DATA="data.txt"
RESULTATS="resultats.txt"
IMPOTS="impots.txt"

# dane niezbędne do obliczenia podatku zostały umieszczone w pliku IMPOTS
# w ilości jednego wiersza na tabelę w postaci
# val1:val2:val3,...

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

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

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

# narzędzia
u=Utilitaires()

# przetwarzanie bieżącego wiersza pliku danych
ligne=data.readline()
while(ligne != ''):
    # usuwanie ewentualnego znaku końca wiersza
    ligne=u.cutNewLineChar(ligne)
    # pobieramy 3 pola: „żonaty:dzieci:wynagrodzenie”, które tworzą wiersz
    (marie,enfants,salaire)=ligne.split(",")
    enfants=int(enfants)
    salaire=int(salaire)
    # oblicza się podatek
    impot=metier.calculer(marie,enfants,salaire)
    # zapisujemy wynik
    resultats.write("{0}:{1}:{2}:{3}\n".format(marie,enfants,salaire,impot))
    # odczytuje się nowy wiersz
    ligne=data.readline()
# zamykamy pliki
data.close()
resultats.close()

Uwagi:

  • wiersz 4: importujemy wszystkie obiekty z pliku impots.py, który zawiera definicje klas. Po wykonaniu tej czynności możemy korzystać z tych obiektów tak, jakby znajdowały się w tym samym pliku co skrypt;
  • wiersze 17–21: tworzone są instancje zarówno warstwy [dao], jak i warstwy [metier] wraz z obsługą ewentualnych wyjątków;
  • wiersz 18: tworzymy instancję warstwy [dao], a następnie warstwy [metier]. Zapisujemy odwołanie do warstwy [metier];
  • wiersz 19: obsługujemy dwa wyjątki, które mogą wystąpić;
  • pozostała część kodu jest analogiczna do tej omówionej w przykładzie z paragrafu 4.3.

8.4. Résultats

Te, które uzyskano już w wersjach z tablicami i plikami.

Plik danych 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

Plik danych data.txt:

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

Pliki resultats.txt zawierające uzyskane wyniki:

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