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:
Pliki resultats.txt zawierające uzyskane wyniki:




