8. Практичне завдання — [IMPOTS] із багатошаровою архітектурою
![]() |
Тут ми повертаємося до вправи, описаної в розділі 4.1. Ми виходимо з версії з текстовими файлами, описаної в розділі 4.3. Щоб обробити цей приклад за допомогою об’єктів, ми будемо використовувати трирівневу архітектуру:
![]() |
- шар [dao] (Data Access Object) відповідає за доступ до даних. Далі ці дані спочатку будуть знаходитися у текстовому файлі, а потім — у базі даних MySQL;
- рівень [metier] відповідає за бізнес-задачі, в даному випадку — за розрахунок податку. Він не оперує даними. Дані можуть надходити з двох джерел:
- рівень [dao] — для постійних даних;
- шар [console] — для даних, що надаються користувачем.
- шар [console] відповідає за взаємодію з користувачем.
Далі шари [dao] та [metier] будуть реалізовані за допомогою окремих класів. Шар [console] буде реалізований головною програмою.
Припустимо, що всі реалізації шару [dao] надають метод getData(), який повертає трійку елементів (межі, coeffR, coeffN), які є трьома масивами даних, необхідними для розрахунку податку. В інших мовах це називається інтерфейсом. Інтерфейс визначає методи (у даному випадку getData), які повинні мати класи, що реалізують цей інтерфейс.
Рівень [metier] буде реалізовано класом, конструктор якого матиме як параметр посилання на рівень [dao], що забезпечить взаємодію між цими двома рівнями.
8.1. Рівень [dao]
Ми об’єднаємо різні класи, необхідні для роботи програми, в одному файлі impots.py. Потім об’єкти цього файлу будуть імпортовані у скрипти, які їх потребують.
Почнемо з випадку, коли дані містяться у текстовому файлі, як у прикладі з параграфа 4.3.
![]() |
Код класу [ImpotsFile] (impots.py), який реалізує шар [dao], є таким:
# -*- coding=utf-8 -*-
import math, sys
# --------------------------------------------------------------------------
# власний клас винятків
class ImpotsError:
pass
# --------------------------------------------------------------------------
class Utilitaires:
"""classe de fonctions utilitaires"""
def cutNewLineChar(self,ligne):
# видаляємо маркер кінця рядка, якщо він є
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: ім’я файлу, що містить дані таблиць меж, coeffR, coeffN
# відкривається файл
data=open(IMPOTS,"r")
# об’єкт «Утиліти»
u=Utilitaires()
# створення 3 таблиць — припускаємо, що 3 рядки файлу IMPOTS є синтаксично правильними
# — рядок 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])
# — рядок 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])
# — рядок 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])
# кінець
(self.limites,self.coeffR,self.coeffN)=(limites,coeffR,coeffN)
def getData(self):
return (self.limites, self.coeffR, self.coeffN)
Примітки:
- рядки 7–8: визначається клас ImpotsError, похідний від класу Exception. Цей клас нічого не додає до класу Exception. Його використовують лише для того, щоб мати власний клас винятків. Згодом цей клас можна буде розширити;
- рядки 11–18: клас службових методів. Тут метод cutNewLineChar видаляє можливий символ кінця рядка з символьного рядка;
- рядок 25: під час відкриття файлу може бути згенеровано виняток IOError;
- рядки 32, 39, 46: генерується власне виключення ImpotsError;
- код аналогічний тому, що розглядався у прикладі в розділі 4.3.
8.2. Рівень [metier]
![]() |
Клас [ImpotsMetier] (impots.py), який реалізує рівень [metier], має такий вигляд:
class ImpotsMetier:
# конструктор
# отримуємо покажчик на шар [dao]
def __init__(self, dao):
self.dao=dao
# розрахунок податку
# --------------------------------------------------------------------------
def calculer(self,marie,enfants,salaire):
# одружений: так, ні
# діти: кількість дітей
# зарплата: річна зарплата
# необхідні для розрахунку дані запитуються з шару [dao]
(limites, coeffR, coeffN)=self.dao.getData()
# кількість часток
marie=marie.lower()
if(marie=="oui"):
nbParts=float(enfants)/2+2
else:
nbParts=float(enfants)/2+1
# на 1/2 частки більше, якщо є щонайменше 3 дітей
if enfants>=3:
nbParts+=0.5
# оподатковуваний дохід
revenuImposable=0.72*salaire
# сімейний коефіцієнт
quotient=revenuImposable/nbParts
# вказується в кінці таблиці обмежень для припинення наступного циклу
limites[len(limites)-1]=quotient
# розрахунок податку
i=0
while quotient>limites[i] :
i=i+1
# оскільки коефіцієнт розміщено в кінці таблиці граничних значень, попередній цикл
# не може виходити за межі таблиці обмежень
# тепер можна обчислити податок
return math.floor(revenuImposable*(float)(coeffR[i])-nbParts*(float)(coeffN[i]))
Примітки:
- рядки 5–6: конструктор класу отримує як параметр посилання на шар [dao];
- рядок 16: використовується метод getData шару [dao] для отримання даних, необхідних для розрахунку податку;
- решта коду аналогічна тій, що розглядалася у прикладі в розділі 4.3.
8.3. Рівень [console]
![]() |
Скрипт, що реалізує шар [console] (impots-03), має такий вигляд:
# -*- coding=utf-8 -*-
# імпорт модуля класів Impots*
from impots import *
# ------------------------------------------------ main
# визначення констант
DATA="data.txt"
RESULTATS="resultats.txt"
IMPOTS="impots.txt"
# дані, необхідні для розрахунку податку, були розміщені у файлі IMPOTS
# по одному рядку на таблицю у вигляді
# val1:val2:val3,...
# інстанціювання шару [metier]
try:
metier=ImpotsMetier(ImpotsFile(IMPOTS))
except (IOError, ImpotsError) as infos:
print ("Une erreur s'est produite : {0}".format(infos))
sys.exit()
# читання даних
try:
data=open(DATA,"r")
except:
print "Impossible d'ouvrir en lecture le fichier des donnees [DATA]"
sys.exit()
# відкриття файлу результатів
try:
resultats=open(RESULTATS,"w")
except:
print "Impossible de creer le fichier des résultats [RESULTATS]"
sys.exit()
# допоміжні функції
u=Utilitaires()
# обробка поточного рядка файлу даних
ligne=data.readline()
while(ligne != ''):
# видалення можливого символу кінця рядка
ligne=u.cutNewLineChar(ligne)
# витягуються 3 поля «одружений:діти:зарплата», що утворюють рядок
(marie,enfants,salaire)=ligne.split(",")
enfants=int(enfants)
salaire=int(salaire)
# розраховується податок
impot=metier.calculer(marie,enfants,salaire)
# записуємо результат
resultats.write("{0}:{1}:{2}:{3}\n".format(marie,enfants,salaire,impot))
# зчитується новий рядок
ligne=data.readline()
# закриваємо файли
data.close()
resultats.close()
Примітки:
- рядок 4: імпортуються всі об’єкти з файлу impots.py, який містить визначення класів. Після цього ці об’єкти можна використовувати так, ніби вони знаходяться в тому самому файлі, що й скрипт;
- рядки 17–21: створюються екземпляри одночасно шару [dao] та шару [metier] з обробкою можливих винятків;
- рядок 18: створюємо екземпляр шару [dao], а потім шару [metier]. Зберігаємо посилання на шар [metier];
- рядок 19: обробляються два винятки, які можуть виникнути;
- решта коду аналогічна тій, що розглядалася у прикладі в розділі 4.3.
8.4. Résultats
Ті, що вже були отримані у версіях з масивами та файлами.
Файл даних 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
Файл даних data.txt:
Файли resultats.txt з отриманими результатами:




