Skip to content

8. تمرين تطبيقي – [حساب الضرائب] باستخدام بنية متعددة الطبقات

نعيد هنا النظر في التمرين الموصوف في القسم 4.1. نبدأ بنسخة الملف النصي الموصوفة في القسم 4.3. لمعالجة هذا المثال باستخدام الكائنات، سنستخدم بنية ثلاثية المستويات:

  • تتولى طبقة [DAO] (كائن الوصول إلى البيانات) الوصول إلى البيانات. في ما يلي، ستوجد هذه البيانات أولاً في ملف نصي، ثم في قاعدة بيانات MySQL؛
  • تتولى طبقة [الأعمال] معالجة منطق الأعمال، وفي هذه الحالة حساب الضرائب. وهي لا تتعامل مع البيانات. ويمكن أن تأتي هذه البيانات من مصدرين:
    • طبقة [DAO] للبيانات الدائمة؛
    • طبقة [console] للبيانات التي يقدمها المستخدم.
  • تتولى طبقة [console] التعامل مع المستخدم.

فيما يلي، سيتم تنفيذ كل من طبقتي [DAO] و[business] باستخدام فئة. وسيتم تنفيذ طبقة [console] بواسطة البرنامج الرئيسي.

سنفترض أن جميع تطبيقات طبقة [dao] توفر طريقة getData()، التي تُرجع مجموعة من ثلاثة عناصر (limits، coeffR، coeffN) — وهي مصفوفات البيانات الثلاثة المطلوبة لحساب الضريبة. في لغات أخرى، يُطلق على هذا اسم واجهة. تحدد الواجهة الطرق (هنا، getData) التي يجب أن تتوفر في الفئات التي تنفذ هذه الواجهة.

سيتم تنفيذ طبقة [business] بواسطة فئة يأخذ منشئها مرجعًا إلى طبقة [dao] كمعلمة، مما يضمن الاتصال بين الطبقتين.

8.1. طبقة [DAO]

سنجمع الفئات المختلفة المطلوبة للتطبيق في ملف واحد، impots.py. سيتم بعد ذلك استيراد الكائنات الموجودة في هذا الملف إلى البرامج النصية التي تحتاج إليها.

نبدأ بالحالة التي تكون فيها البيانات في ملف نصي، كما في المثال الوارد في القسم 4.3.

فيما يلي كود فئة [ ImpotsFile] (impots.py) التي تنفذ طبقة [DAO]:

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

import math, sys

# --------------------------------------------------------------------------
#    exceptional owner class
class ImpotsError:
    pass

# --------------------------------------------------------------------------
class Utilitaires:
    """classe de fonctions utilitaires"""
    def cutNewLineChar(self,ligne):
        #  delete the end-of-line mark if it exists
        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: the name of the file containing data from the limit tables, coeffR, coeffN
        #  open the file
        data=open(IMPOTS,"r")
        #    a Utilities object
        u=Utilitaires()
        #  create the 3 tables - assume that the 3 rows of IMPOTS are syntactically correct
        #  -- line 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])
        #  -- line 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])
        #  -- line 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])
        #    end
        (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. طبقة [الأعمال]

فيما يلي فئة [ ImpotsMetier] (impots.py) التي تنفذ طبقة [الأعمال]:


class ImpotsMetier:
 
    # constructeur
    # on récupère un pointeur sur la couche [dao]
    def __init__(self, dao):
        self.dao=dao
 
    # calcul de l'impôt
    # --------------------------------------------------------------------------
    def calculer(self,marie,enfants,salaire):
        # marié : oui, non
        # enfants : nombre d'enfants
        # salaire : salaire annuel
 
        # on demande à la couche [dao] les données nécessaires au calcul
        (limites, coeffR, coeffN)=self.dao.getData()
 
       # nombre de parts
        marie=marie.lower()
        if(marie=="oui"):
            nbParts=float(enfants)/2+2
        else:
            nbParts=float(enfants)/2+1
        # une 1/2 part de plus si au moins 3 enfants
        if enfants>=3:
            nbParts+=0.5
        # revenu imposable
        revenuImposable=0.72*salaire
        # quotient familial
        quotient=revenuImposable/nbParts
        # est mis à la fin du tableau limites pour arrêter la boucle qui suit
        limites[len(limites)-1]=quotient
        # calcul de l'impôt
        i=0
        while quotient>limites[i] :
            i=i+1
        # du fait qu'on a placé quotient à la fin du tableau limites, la boucle précédente
        # ne peut déborder du tableau limites
        # maintenant on peut calculer l'impôt
        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 -*-

#    import of Impots* class module
from impots import *

#    ------------------------------------------------ main
#    definition of constants
DATA="data.txt"
RESULTATS="resultats.txt"
IMPOTS="impots.txt"

#  the data required to calculate the tax has been placed in the IMPOTS file
#  one line per table in the form
#    val1:val2:val3,...

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

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

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

#    utilities
u=Utilitaires()

#  the current line of the data file is used
ligne=data.readline()
while(ligne != ''):
    #  remove any end-of-line marker
    ligne=u.cutNewLineChar(ligne)
    #  we retrieve the 3 fields married:children:salary which form the line
    (marie,enfants,salaire)=ligne.split(",")
    enfants=int(enfants)
    salaire=int(salaire)
    #  tax calculation
    impot=metier.calculer(marie,enfants,salaire)
    #  enter the result
    resultats.write("{0}:{1}:{2}:{3}\n".format(marie,enfants,salaire,impot))
    #  a new line is read
    ligne=data.readline()
#    close files
data.close()
resultats.close()

ملاحظات:

  • السطر 4: نقوم باستيراد جميع الكائنات من ملف impots.py، الذي يحتوي على تعريفات الفئات. وبمجرد الانتهاء من ذلك، يمكننا استخدام هذه الكائنات كما لو كانت موجودة في نفس ملف البرنامج النصي؛
  • الأسطر 17–21: نقوم بإنشاء مثيل لكل من طبقة [dao] وطبقة [business] مع معالجة الاستثناءات المحتملة؛
  • السطر 18: نقوم بإنشاء مثيل لطبقة [dao] ثم طبقة [business]. نقوم بتخزين المرجع إلى طبقة [business
  • السطر 19: نتعامل مع الاستثناءين اللذين قد يحدثان؛
  • باقي الكود مشابه لما تمت دراسته في المثال الوارد في القسم 4.3.

8.4. النتائج

نفس النتائج التي تم الحصول عليها بالفعل في الإصدارات التي تستخدم المصفوفات والملفات.

ملف البيانات 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

ملف results.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