8. 应用练习 - [IMPOTS] 配合 分层架构
![]() |
此处我们将重现第 4.1 节中描述的练习。我们以第 4.3 节中描述的文本文件版本为基础。为了使用对象处理此示例,我们将采用三层架构:
![]() |
- [dao] 层(数据访问对象)负责数据访问。后续中,这些数据将首先从文本文件中获取,随后从 MySQL 数据库中获取;
- [metier]层负责处理业务逻辑,此处即税款计算。该层不直接处理数据,数据来源可能有两种:
- [dao] 层用于持久化数据;
- [console] 层用于处理用户提供的数据。
- [console] 层负责与用户的交互。
接下来,[dao] 和 [metier] 层将分别通过一个类来实现。而 [console] 层则由主程序来实现。
假设 [dao] 层的所有实现都提供了 getData() 方法,该方法返回一个包含三个元素的元组(限额、coeffR、 coeffN),即计算税款所需的三个数据数组。在其他编程语言中,这被称为接口。接口定义了方法(此处为 getData),实现该接口的类必须具备这些方法。
[metier]层将由一个类来实现,该类的构造函数将接受对[dao]层的引用作为参数,从而确保两层之间的通信。
8.1. [dao]层
我们将把应用程序所需的各个类整合到同一个文件 impots.py 中。随后,该文件中的对象将被导入到需要它们的脚本中。
我们首先处理数据位于文本文件中的情况,如第4.3节中的示例。
![]() |
实现 [dao] 层的 [ImpotsFile] 类( ,即 impots.py)的代码如下:
# -*- 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 个表 - 假设 IMPOTS 中的 3 行在语法上正确
# -- 第 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 行:定义了一个从类 Exception 派生的类 ImpotsError。该类并未对类 Exception 进行任何扩展。 仅用于创建专有异常类。该类日后可能会被扩展;
- 第 11-18 行:一个实用方法类。此处的 cutNewLineChar 方法用于移除字符串中可能存在的换行符;
- 第 25 行:打开文件时可能抛出 IOError 异常;
- 第 32、39、46 行:抛出专有异常 ImpotsError;
- 该代码与第4.3节示例中分析的代码类似。
8.2. [metier]层
![]() |
实现 [metier] 层的类 [ImpotsMetier] (impots.py) 如下所示:
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
# 若子女至少3名,则增加1/2份额
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行:调用[dao]层中的getData方法,以获取用于计算税款的数据;
- 其余代码与第4.3节示例中的代码类似。
8.3. [console] 层
![]() |
实现 [console] 层(impots-03)的脚本如下:
# -*- coding=utf-8 -*-
# 导入 Impots* 类模块
from impots import *
# ------------------------------------------------ 主函数
# 常量定义
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文件:




