15. تمرين تطبيقي - الإصدار 4

هنا نعيد النظر في التمرين الموصوف في قسم |الإصدار 3| ونقوم الآن بتنفيذه باستخدام الفئات والواجهات. سنكتب تطبيقين:
سيكون التطبيق 1 كما يلي:

سيقوم البرنامج النصي الرئيسي [main] بإنشاء مثيل لطبقة [DAO] وطبقة [business]:
- ستكون طبقة [DAO] مسؤولة عن إدارة البيانات المخزنة في ملفات نصية ثم في قاعدة بيانات لاحقًا؛
- ستكون طبقة [business] مسؤولة عن حساب الضريبة؛
في هذا التطبيق، لن يكون هناك أي إدخال من المستخدم: ستوجد بيانات دافعي الضرائب في ملف نصي سيتم تمرير اسمه إلى الوحدة النمطية [main].
في التطبيق 2، سيقوم المستخدم بإدخال بيانات دافعي الضرائب عبر لوحة المفاتيح. وستتطور البنية بعد ذلك على النحو التالي:

- تتولى طبقة [DAO] (كائن الوصول إلى البيانات) الوصول إلى البيانات الخارجية
- تتولى طبقة [business] معالجة منطق الأعمال، وفي هذه الحالة حساب الضرائب. ولا تتولى معالجة البيانات. ويمكن أن تأتي هذه البيانات من مصدرين:
- طبقة [DAO] للبيانات الدائمة؛
- طبقة [UI] للبيانات التي يقدمها المستخدم.
- تتولى طبقة [ui] (واجهة المستخدم) التعامل مع المستخدم؛
- [main] تعمل كمنسق؛
فيما يلي، سيتم تنفيذ كل من طبقات [dao] و[business] و[ui] باستخدام فئة. وستكون طبقتا [business] و[dao] متماثلتين في كلا التطبيقين. ولهذا السبب تم دمجهما في نسخة واحدة من تمرين التطبيق.
15.1. الإصدار 4 – التطبيق 1
يحسب الإصدار 4 الضريبة لقائمة من دافعي الضرائب المخزنة في ملف نصي. وله البنية التالية:

15.1.1. الكيانات

الكيانات هي فئات بيانات. وتتمثل مهمتها في تغليف البيانات وتوفير أدوات الحصول/التعيين التي تسمح بالتحقق من صحة البيانات. يتم تبادل الكيانات بين الطبقات. يمكن لكيان واحد الانتقال من طبقة [ui] إلى طبقة [dao] والعكس صحيح.
15.1.1.1. فئة [ImpôtsError]
سنستخدم فئة استثناء مخصصة:
بمجرد أن تواجه طبقات [business] و [DAO] مشكلة، فإنها ستثير هذا الاستثناء. وهو مشتق من فئة [MyException]. وبالتالي، يتم استخدامه على النحو التالي: [raise ImpôtsError(error_code, error_message)].
15.1.1.2. فئة [AdminData]
تغلف فئة [AdminData] الثوابت المستخدمة في حسابات الضرائب:
- السطر 5: تمتد فئة [AdminData] من فئة [BaseEntity] الموضحة في قسم |BaseEntity|. تذكر أن الفئات التي تمتد من فئة [BaseEntity] يجب أن تحدد:
- سمة فئة [excluded_keys] (السطر 7) التي تسرد خصائص الكائن المستبعدة عند تحويل الكائن إلى قاموس؛
- طريقة ثابتة [get_allowed_keys] (الأسطر 10–26) التي تُرجع قائمة بالخصائص المقبولة عند تهيئة الكائن باستخدام قاموس؛
لم نستخدم أدوات التعيين للتحقق من صحة البيانات المستخدمة لتهيئة كائن [AdminData]. وذلك لأن هذا الكائن فريد ومُعرَّف بواسطة التكوين، وبالتالي من غير المرجح أن يحتوي على أخطاء.
15.1.1.3. فئة [TaxPayer]
ستقوم فئة [TaxPayer] بنمذجة دافع الضرائب:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | |
ملاحظات:
- تغلف فئة [TaxPayer] دافع الضرائب؛
- السطر 7: الفئة [TaxPayer] مشتقة من الفئة [BaseEntity]. ولذلك، فإنها تحتوي على معرف [id]؛
- السطر 20: لا توجد خصائص مستبعدة من حالة كائن [AdminData]؛
- الأسطر 22–25: خصائص الفئة. يتم شرحها في الأسطر 9–17؛
- الأسطر 27–58: متغيرات الحصول لسمات الفئة؛
- الأسطر 60-161: دوال التعيين لسمات الفئة. تذكر أن ميزة الفئة التي تغلف البيانات مقارنة بالقاموس البسيط هي أن الفئة يمكنها التحقق من صحة خصائصها باستخدام دوال التعيين الخاصة بها؛
15.1.2. طبقة [dao]

سنقوم بتجميع تطبيقات الطبقة في مجلد [services]. ستقوم هذه الفئات بتنفيذ الواجهات المحددة في مجلد [interfaces].

15.1.2.1. واجهة [InterfaceImpôtsDao]
ستقوم طبقة [dao] بتنفيذ واجهة [InterfaceImpôtsDao] التالية (ملف InterfaceImpôtsDao.py):
تحدد الواجهة ثلاث طرق:
- [get_admindata]: هي الطريقة التي تسترد جدول الشرائح الضريبية. لاحظ أنه لم يتم توفير أي معلومات حول كيفية الحصول على هذه البيانات. لاحقًا، سيتم العثور عليها أولاً في ملف نصي ثم في قاعدة بيانات. وسيكون الأمر متروكًا للفئات التي تنفذ الواجهة للتكيف مع طريقة تخزين البيانات. لذلك سيكون لدينا فئة واحدة لاسترداد الشرائح الضريبية من ملف نصي وأخرى لاستردادها من قاعدة بيانات. وستنفذ كلتاهما طريقة [get_admindata]؛
- [get_taxpayers_data]: هي الطريقة التي تسترد بيانات دافعي الضرائب. مرة أخرى، لا نحدد المكان الذي ستوجد فيه. سنتعامل فقط مع الحالة التي تكون فيها في ملف نصي؛
- [write_taxpayers_results]: هي الطريقة التي ستحفظ نتائج حساب الضرائب. لا نحدد المكان. سنتعامل فقط مع الحالة التي يتم فيها حفظ النتائج في ملف نصي. سيكون المعامل [taxpayers_results] هو قائمة النتائج المراد حفظها؛
15.1.2.2. فئة [AbstractImpôtsDao]
سيتم تنفيذ طبقة [dao] بواسطة فئتين:
- ستقوم إحداهما باسترداد البيانات (دافعو الضرائب، النتائج، شرائح الضرائب) من ملفات نصية؛
- والأخرى ستسترد البيانات (دافعو الضرائب، النتائج) من ملفات نصية والشرائح الضريبية من قاعدة بيانات؛
وستختلف الفئتان فقط في طريقة تعاملهما مع الشرائح الضريبية. وسيتم إدارة بيانات دافعي الضرائب ونتائج حساب الضرائب بنفس الطريقة. ولهذا السبب، سنقوم بإدارتها في فئة أم [AbstractImpôtsDao]. وسيتم إدارة التعامل المحدد مع الشرائح الضريبية في فئتين فرعيتين:
- ستقوم الفئة [ImpôtsDaoWithAdminDataInJsonFile] باسترداد الشرائح الضريبية من ملف نصي بتنسيق JSON؛
- ستقوم الفئة [ImpôtsDaoWithAdminDataInDatabase] باسترداد الشرائح الضريبية من قاعدة بيانات؛
وستكون الفئة الأصلية [AbstractImpôtsDao] على النحو التالي:
- السطر 13: تنفذ الفئة [AbstractImpôtsDao] الواجهة [InterfaceImpôtsDao]. وبالتالي، فهي تحتوي على الطرق الثلاث لهذه الواجهة:
- [get_taxpayers_data]: السطر 31؛
- [write_taxpayers_results]: السطر 35؛
- [get_admindata]: السطر 40. لن يتم تنفيذ هذه الطريقة بواسطة فئة [AbstractImpôtsDao]، لذا تم إعلانها على أنها مجردة (السطر 39)؛
- السطر 16: يتلقى المنشئ قاموس [config] يحتوي على المعلومات التالية:
- [taxpayersFilename]: اسم الملف النصي الذي يحتوي على بيانات دافعي الضرائب؛
- [resultsFilename]: اسم الملف النصي الذي سيتم تخزين النتائج فيه؛
- [errorsFilename]: اسم الملف النصي الذي يسرد الأخطاء التي تمت مواجهتها أثناء معالجة ملف [taxpayersFilename]؛
طريقة [get_taxpayers_data] هي كما يلي:
- السطر 4: سيتم وضع بيانات دافع الضرائب (متزوج، أطفال، راتب) في قائمة من الكائنات من النوع [TaxPayer]؛
- السطران 8-9: نفتح ملف نصي للمكلف بالقراءة. محتواه بالصيغة التالية:
مقارنة بالإصدارات السابقة:
- يبدأ كل سطر في ملف [taxpayersFilename] برقم تعريف دافع الضرائب، وهو رقم واحد؛
- يُسمح بالتعليقات والأسطر الفارغة؛
- سنقوم بمعالجة الأخطاء. وبالتالي، يجب وضع علامة "غير صالح" على الأسطر 17 و19 و21. يتم تسجيل الأخطاء في ملف منفصل؛
لنواصل مراجعة الكود:
- السطر 4: يتم نقل البيانات من الملف النصي إلى قائمة [taxPayersData]؛
- الأسطر 14-31: يتم قراءة ملف دافعي الضرائب سطراً سطراً؛
- السطر 14: يتم الوصول إلى نهاية الملف عند قراءة سطر فارغ (لا شيء — ولا حتى حرف نهاية السطر \r\n)؛
- السطر 20: يتم تجاهل الأسطر الفارغة والتعليقات. يعتبر السطر تعليقًا إذا كان الحرف الأول هو الحرف # بعد إزالة المسافات البيضاء قبل النص وبعده؛
- السطر 24: يتكون السطر الصحيح من أربعة حقول مفصولة بفواصل. يتم استرداد هذه الحقول. يفشل تعيين البيانات إلى تابع مكون من أربعة عناصر إذا لم يتم تعيين أربع نقاط بيانات بالضبط؛
- السطر 25: إذا كان أي من الحقول الأربعة المسترجعة [id, married, children, salary] غير صالح، فإن طريقة [BaseEntity.fromdict] ستثير استثناء [MyException]؛
- السطران 25-26: تتم إضافة كائن [TaxPayer] إلى قائمة دافعي الضرائب [taxpayers_data]؛
- الأسطر 27-29: يتم تجميع أي أخطاء في قائمة [errors]. تم إنشاء هذه القائمة في السطر 6؛
- الأسطر 33–36: يتم حفظ قائمة الأخطاء التي تمت مواجهتها في ملف نصي [errorsFilename]. هناك نوعان من الأخطاء:
- لم يكن للصف العدد الصحيح من الحقول المتوقعة؛
- المعلومات الموجودة في الصف غير صحيحة وفشل إنشاء كائن [TaxPayer]؛
- الأسطر 39–41: يتم اكتشاف أي خطأ (BaseException) ونقله عن طريق تغليفه في نوع [TaxPayerError]؛
- الأسطر 42–45: في جميع الحالات، سواء كانت ناجحة أم لا، يتم إغلاق ملف نصي دافع الضرائب إذا كان مفتوحًا؛
يجب أن تنتج طريقة [write_taxpayers_results] ملف JSON بالصيغة التالية:
[
{
"id": 1,
"marié": "oui",
"enfants": 2,
"salaire": 55555,
"impôt": 2814,
"surcôte": 0,
"taux": 0.14,
"décôte": 0,
"réduction": 0
},
{
"id": 2,
"marié": "oui",
"enfants": 2,
"salaire": 50000,
"impôt": 1384,
"surcôte": 0,
"taux": 0.14,
"décôte": 384,
"réduction": 347
},
{
"id": 3,
"marié": "oui",
"enfants": 3,
"salaire": 50000,
"impôt": 0,
"surcôte": 0,
"taux": 0.14,
"décôte": 720,
"réduction": 0
},
…
]
طريقة [write_taxpayers_results] هي كما يلي:
- السطر 2: تتلقى الطريقة قائمة بالمكلفين [taxpayers] التي يجب حفظها في ملف نصي [self.taxpayers_results_filename] بتنسيق JSON؛
- السطر 10: إنشاء ملف النتائج بتنسيق UTF-8؛
- السطر 12: نقدم هنا دالة [map]، التي تكون صيغتها هنا [map (function, list1)]. تُطبق الدالة [function] على كل عنصر من عناصر [list1] وتُنتج عنصرًا جديدًا يُضاف إلى قائمة [list2]. وأخيرًا، لكل i:
liste2[i]=fonction(liste1[i])
هنا، [list1] هي القائمة [taxPayers]، وهي قائمة من الكائنات من النوع [TaxPayer]. يتم التعبير عن الدالة [function] هنا على أنها ما يُسمى بدالة [lambda] التي تصف التحويل المطبق على عنصر [taxpayer] من القائمة [taxpayers]: يتم استبدال كل عنصر [taxpayer] بقاموسه [taxpayer.asdict()]. وأخيرًا، فإن القائمة الناتجة [list2] هي قائمة القواميس الخاصة بالعناصر الموجودة في قائمة [taxpayers]؛
- السطر 12: النتيجة التي تعيدها الدالة [map] ليست القائمة [list2] بل كائن من النوع [map]. للحصول على [list2]، يجب استخدام التعبير [list(mapping)] (السطر 14)؛
- السطر 14: يتم حفظ القائمة [list2] بتنسيق JSON في الملف [self.taxpayers_results_filename]؛
- الأسطر 15-17: يتم التقاط أي نوع من الاستثناءات وتغليفها في [ImpôtsError] قبل إعادة طرحها (السطر 17)؛
- الأسطر 19-21: في جميع الحالات، سواء نجحت أم لا، يتم إغلاق ملف النتائج إذا كان مفتوحًا؛
15.1.2.3. فئة [ImpôtsDaoWithAdminDataInJsonFile]
ستشتق فئة [ImpôtsDaoWithAdminDataInJsonFile] من فئة [AbstractImpôtsDao] وستنفذ طريقة [getAdminData] التي لم تنفذها فئتها الأم. وستسترد بيانات إدارة الضرائب من ملف JSON:
{
"limites": [9964, 27519, 73779, 156244, 0],
"coeffr": [0, 0.14, 0.3, 0.41, 0.45],
"coeffn": [0, 1394.96, 5798, 13913.69, 20163.45],
"plafond_qf_demi_part": 1551,
"plafond_revenus_celibataire_pour_reduction": 21037,
"plafond_revenus_couple_pour_reduction": 42074,
"valeur_reduc_demi_part": 3797,
"plafond_decote_celibataire": 1196,
"plafond_decote_couple": 1970,
"plafond_impot_couple_pour_decote": 2627,
"plafond_impot_celibataire_pour_decote": 1595,
"abattement_dixpourcent_max": 12502,
"abattement_dixpourcent_min": 437
}
فئة [ImpôtsDaoWithAdminDataInJsonFile] هي كما يلي:
- السطر 11: ترث فئة [ImpôtsDaoWithAdminDataInJsonFile] من فئة [AbstractImpôtsDao]. وبالتالي، فإنها تنفذ واجهة [InterfaceImpôtsDao]؛
- السطر 13: يتلقى المنشئ كمعلمة قاموسًا يحتوي على المعلومات الواردة في الأسطر 14-17؛
- السطر 20: يتم تهيئة الفئة الأم؛
- السطر 24: يتم فتح ملف JSON الذي يحتوي على بيانات إدارة الضرائب؛
- السطر 25: يتم فتح ملف UTF-8 الذي يحتوي على بيانات سلطة الضرائب؛
- السطر 27: تتم قراءة محتويات الملف ووضعها في كائن [self.admindata] من النوع [AdminData]. يجب أن تتطابق المفاتيح في ملف JSON مع الخصائص المقبولة لكائن [AdminData]؛ وإلا، فإن طريقة [fromdict] ستقوم بإلقاء استثناء؛
- الأسطر 28-30: معالجة الاستثناءات. يتم تغليف أي استثناءات قد تحدث في نوع [ImpôtsError] قبل إعادة طرحها؛
- الأسطر 32-34: يتم إغلاق الملف إذا كان مفتوحًا؛
- الأسطر 42-43: تنفيذ طريقة [get_admindata] للواجهة [InterfaceImpôtsDao]؛
15.1.3. طبقة [business]

15.1.3.1. واجهة [InterfaceImpôtsMétier]
ستكون واجهة طبقة [الأعمال] كما يلي:
- تحدد واجهة [BusinessTaxInterface] طريقة واحدة:
- السطر 12: تحسب الطريقة [calculate_tax] الضريبة لمكلف واحد [taxpayer]. [admindata] هو كائن [AdminData] الذي يغلف بيانات إدارة الضرائب؛
- السطر 12: لا تُرجع طريقة [calculate_tax] أي نتيجة. يتم تضمين البيانات التي تم الحصول عليها (الضريبة، الرسوم الإضافية، الخصم، التخفيض، المعدل) في المعلمة [taxpayer]: قبل الاستدعاء، تكون هذه السمات فارغة؛ وبعد الاستدعاء، يتم تهيئتها؛
15.1.3.2. فئة [BusinessTaxes]
تنفذ فئة [ImpôtsMétier] واجهة [InterfaceImpôtsMétier] على النحو التالي:

طرق الفئة مشتقة من الوحدة النمطية [impôts_module_02] في القسم |الوحدة النمطية [impots.v02.modules.impôts_module_02]|. وقد قمنا بتحديد معلمات الطريقة في اثنتين فقط:
- taxpayer(id, married, children, salary, tax, discount, surcharge, reduction, rate): الكائن الذي يمثل دافع الضرائب وضرائبه؛
- admindata: الكائن الذي يغلف بيانات إدارة الضرائب؛
نوضح التغييرات التي تم إجراؤها باستخدام طريقة؛
- السطر 3: طريقة [calculate_tax] هي الطريقة الوحيدة في واجهة [InterfaceImpôtsMétier]. وهي تأخذ معلمتين:
- [tapPayer]: دافع الضرائب الذي يتم حساب الضريبة له؛
- [admindata]: الكائن الذي يغلف بيانات إدارة الضرائب؛
- يتم تغليف نتائج الحساب في المعلمة [taxpayer] (الأسطر 40–50). وبالتالي، فإن محتوى هذا الكائن يختلف قبل وبعد استدعاء الطريقة؛
15.1.4. اختبارات طبقات [dao] و [business]

- [TestDaoMétier] هي فئة UnitTest لاختبار طبقات [dao] و [business]؛
- [config] هو ملف تكوين الاختبار؛
تكوين [config] هو كما يلي:
- الأسطر 4–23: نقوم بتكوين مسار Python للاختبارات؛
- الأسطر 32–41: إنشاء مثيلات لطبقتي [dao] و[business]. تخزين مراجعهما في قاموس [config]؛
- السطر 44: إرجاع هذا القاموس؛
فئة الاختبار [TestDaoMétier] هي كما يلي:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | |
تعليقات
- السطر 11: فئة الاختبار تمتد من فئة [unittest.TestCase]؛
- الأسطر 13–19: في UnitTest، يتم تنفيذ الأسلوب [setUp] قبل كل أسلوب من أساليب [test_]؛
- السطر 16: يتم استرداد التكوين من البرنامج النصي [config] الذي تمت مناقشته سابقًا؛
- السطر 18: يتم تخزين مرجع إلى طبقة [business]؛
- السطر 19: نطلب كائن [AdminData] — الذي يغلف بيانات إدارة الضرائب — من طبقة [DAO] ونخزنه؛
- الأسطر 21–173: 11 اختبارًا تم التحقق من نتائجها على الموقع الإلكتروني الرسمي للضرائب لعام 2019 |https://www3.impots.gouv.fr/simulateur/calcul_impot/2019/simplifie/index.htm|؛
- الأسطر 21–33: تم إنشاء جميع الاختبارات باستخدام نفس القالب؛
- السطر 22: استيراد فئة [TaxPayer]؛
- السطر 24: دافع الضرائب قيد الاختبار؛
- السطر 25: النتائج المتوقعة؛
- السطر 26: إنشاء كائن [TaxPayer] الخاص بالمكلف؛
- السطر 27: حساب الضريبة المستحقة عليه. النتيجة موجودة في [taxpayer]؛
- الأسطر 29-33: التحقق من النتائج التي تم الحصول عليها؛
- السطر 29: نتحقق من مبلغ الضريبة لأقرب يورو. وقد أظهرت الاختبارات بالفعل أن النتائج التي توصلت إليها الخوارزمية في هذا المستند قد تختلف عن الأرقام الرسمية بما يصل إلى 1 يورو؛
يؤدي إجراء الاختبارات إلى النتائج التالية:

15.1.5. البرنامج النصي الرئيسي

يتم تكوين البرنامج النصي الرئيسي بواسطة البرنامج النصي [config] التالي:
وهو مشابه للذي يستخدم لاختبار طبقات [business] و [dao].
النص البرمجي الرئيسي [main.py] هو كما يلي:
ملاحظات
- الأسطر 2–4: نسترد تكوين التطبيق. ونعلم أيضًا أن مسار Python الخاص بالتطبيق قد تم إنشاؤه؛
- الأسطر 9–11: نسترد الإشارات إلى طبقتي [business] و[DAO]؛
- السطر 15: نسترد البيانات من إدارة الضرائب؛
- السطر 17: نسترد قائمة دافعي الضرائب الذين يجب حساب ضرائبهم؛
- الأسطر 19-20: إذا كانت هذه القائمة فارغة، يتم إثارة استثناء؛
- الأسطر 22-25: نحسب الضريبة لمختلف كائنات [taxpayer] باستخدام طبقة [business]؛
- السطر 27: أصبحت [taxpayers] الآن قائمة بكائنات [TaxPayer] حيث تم تعيين قيم للسمات (tax، discount، surcharge، reduction، rate). يتم كتابة هذه القائمة في ملف JSON؛
- الأسطر 28-30: اكتشاف أي أخطاء محتملة؛
- الأسطر 31-33: يتم تنفيذها في جميع الحالات؛
يؤدي تشغيل البرنامج النصي إلى نفس النتائج كما في الإصدارات السابقة. كان ملف أخطاء دافعي الضرائب ميزة جديدة في هذا الإصدار. بعد تشغيل البرنامج النصي [main]، يكون محتواه كما يلي:
Analyse du fichier C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\v04\main\01/../../data/input/taxpayersdata.txt
Ligne 17, not enough values to unpack (expected 4, got 2)
Ligne 19, too many values to unpack (expected 4)
Ligne 21, MyException[1, L'identifiant d'une entité <class 'TaxPayer.TaxPayer'> doit être un entier >=0]
كانت الأسطر الخاطئة كما يلي:
15.2. الإصدار 4 – التطبيق 2
في هذا الإصدار، يقوم المستخدم بإدخال قائمة دافعي الضرائب عبر لوحة المفاتيح. ستكون بنية التطبيق على النحو التالي:

تظهر وحدة جديدة: طبقة [ui] (واجهة المستخدم)، والتي ستتفاعل مع المستخدم. ستحتوي هذه الطبقة على واجهة وسيتم تنفيذها بواسطة فئة.

15.2.1. واجهة [InterfaceImpôtsUi]
ستحتوي واجهة [InterfaceImpôtsUi] على طريقة واحدة فقط، وهي تلك الموجودة في الأسطر 8–10. سيتم تنفيذ الواجهة هنا باستخدام تطبيق وحدة التحكم، ولكن يمكن أيضًا تنفيذها باستخدام واجهة مستخدم رسومية. لن تكون المعلمات التي يتم تمريرها إلى طريقة [run] هي نفسها في كلا التنفيذين. للتغلب على هذه المشكلة، فإن النهج المعتاد هو:
- عدم تمرير أي معلمات إلى طريقة [run] (أو تمرير الحد الأدنى من المعلمات)؛
- تمرير المعلمات إلى منشئ الفئة التي تنفذ الواجهة. قد تختلف هذه المعلمات من تنفيذ لآخر. يتم تخزين هذه المعلمات كسمات للفئة؛
- التأكد من أن طريقة [run] تستخدم سمات الفئة هذه (self.x)؛
تسمح هذه الطريقة بواجهة عامة جدًا يتم تحديدها بواسطة معلمات منشئات كل فئة تنفيذ. وقد تم استخدام هذه الطريقة بالفعل في الإصدار المعياري رقم 1.
15.2.2. فئة [ImpôtsConsole]
تنفذ فئة [ImpôtsConsole] واجهة [InterfaceImpôtsUi] على النحو التالي:
- السطر 9: تنفذ فئة [TaxConsole] واجهة [TaxUiInterface]؛
- السطر 11: يتلقى منشئ الفئة معلمة، وهي قاموس [config] الذي يحتوي على تكوين التطبيق؛
- السطر 13: يتم استرداد البيانات من سلطة الضرائب لحساب الضريبة؛
- السطر 14: يتم تخزين مرجع إلى طبقة [business]؛
- السطر 16: تنفيذ طريقة [run] للواجهة؛
- الأسطر 19–53: تفاعل المستخدم. يتضمن ذلك
- طلب ثلاث معلومات من دافع الضرائب (الحالة الاجتماعية، والأطفال، والراتب)؛
- حساب الضريبة المستحقة عليه؛
- عرض النتيجة؛
- ينتهي الحوار عندما يجيب المستخدم بـ * على السؤال الأول؛
- الأسطر 20–27: يسأل البرنامج عما إذا كان دافع الضرائب متزوجًا ويتحقق من صحة الإجابة؛
- الأسطر 29-31: إذا أجاب المستخدم بـ "*" على السؤال، ينتهي الحوار؛
- الأسطر 32-39: يُسأل دافع الضرائب عن عدد أطفاله، ويتم التحقق من صحة الإجابة؛
- الأسطر 40-47: يُطلب من دافع الضرائب ذكر راتبه السنوي، ويتم التحقق من صحة الإجابة؛
- الأسطر 48-50: باستخدام هذه المعلومات، تحسب طبقة [الأعمال] ضريبة دافع الضرائب؛
- السطر 52: يتم عرض مبلغ الضريبة؛
15.2.3. النص البرمجي الرئيسي
يتم تكوين البرنامج النصي الرئيسي [main] بواسطة ملف [config] التالي:
النص البرمجي الرئيسي هو كما يلي (main.py):
- الأسطر 1-4: استرداد تكوين التطبيق؛
- السطر 10: استرداد مرجع إلى طبقة [ui]؛
- الأسطر 12-21: بنية الكود هي نفسها كما في التطبيق السابق: الكود مغلف في كتلة try/catch لالتقاط أي استثناءات محتملة؛
- السطر 15: نطلب من طبقة [ui] التنفيذ: ثم تبدأ تفاعلات المستخدم؛
- الأسطر 16–18: التقاط أي استثناءات محتملة؛
فيما يلي مثال على التنفيذ:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/v04/main/02/main.py
Le contribuable est-il marié / pacsé (oui/non) (* pour arrêter) : oui
Nombre d'enfants : 3
Salaire annuel : 200000
Impôt du contribuable = {"id": 0, "marié": "oui", "enfants": 3, "salaire": 200000, "impôt": 42842, "surcôte": 17283, "taux": 0.41, "décôte": 0, "réduction": 0}
Le contribuable est-il marié / pacsé (oui/non) (* pour arrêter) : *
Travail terminé...
Process finished with exit code 0