Skip to content

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

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

所得结果的resultats.txt文件:

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