14. البنية الطبقية والبرمجة القائمة على الواجهة
14.1. مقدمة
نقترح كتابة تطبيق يعرض درجات طلاب المدارس الإعدادية. يمكن أن يكون لهذا التطبيق بنية متعددة الطبقات:

- طبقة [ui] (واجهة المستخدم) هي الطبقة التي تتفاعل مع مستخدم التطبيق؛
- طبقة [business] تنفذ قواعد العمل الخاصة بالتطبيق، مثل حساب الراتب أو الفاتورة. تستخدم هذه الطبقة البيانات الواردة من المستخدم عبر طبقة [presentation] ومن نظام إدارة قواعد البيانات (DBMS) عبر طبقة [DAO]؛
- طبقة [DAO] (كائنات الوصول إلى البيانات) تدير الوصول إلى البيانات في نظام إدارة قواعد البيانات (DBMS).
هذه هي البنية التي تم استخدامها في |دورة Python 2|. يمكن أيضًا إدخال نسخة معدلة:

فيما يلي الاختلافات عن البنية الطبقية السابقة:
- يقوم البرنامج النصي الرئيسي المسمى [main] أعلاه بتنظيم إنشاء مثيلات الطبقات؛
- لم تعد طبقات [ui، business، dao] تتواصل بالضرورة مع بعضها البعض. وإذا دعت الحاجة إلى ذلك، فإن البرنامج النصي [main] يزودها بالمراجع الخاصة بالطبقات التي تحتاجها؛
يتم تنظيم الكود هنا في مناطق وظيفية مع منسق مركزي:
- المنسق هو البرنامج النصي الرئيسي [main]؛
- طبقات [ui] و [dao] و [business] هي مراكز الخبرة؛
يمكننا تسمية هذه البنية بتنظيم أوركسترالي.
14.2. مثال 1
سنوضح البنية الطبقية باستخدام تطبيق وحدة تحكم بسيط:
- لن تكون هناك قاعدة بيانات؛
- ستدير طبقة [DAO] كيانات الطالب والصف والموضوع والدرجة للتعامل مع درجات الطلاب؛
- ستقوم طبقة [business] بحساب المقاييس بناءً على درجات طالب معين؛
- ستكون طبقة [ui] عبارة عن تطبيق وحدة تحكم يعرض نتائج الطلاب؛
مشروع PyCharm للتطبيق هو كما يلي:
![]() |
ملاحظة: المجلدات باللون الأزرق هي جزء من [Root Sources] لمشروع PyCharm.
14.2.1. كيانات التطبيق
سنشير إلى الفئات التي يتمثل دورها الوحيد في تغليف البيانات باسم الكيانات. يمكن استخدام القواميس لهذا الغرض. وتتمثل ميزة الفئة في أنها تسمح لنا باختبار صحة البيانات المخزنة في الكائن وتوفير طريقة تعرض هوية الكائن كسلسلة.
![]() |
14.2.1.1. كيان [Class]
يمثل كيان [Class] (Class.py) فصلًا دراسيًا في المرحلة الإعدادية:
ملاحظات
- السطر 7: الكيان [Class] مشتق من الكيان [BaseEntity] الذي تمت مناقشته في القسم |فئة BaseEntity|؛
- الأسطر 11–16: يتم تعريف الفئة بواسطة معرف واسم (السطر 16). يتم توفير الخاصية [id] بواسطة فئة [BaseEntity] والاسم بواسطة فئة [Class]؛
- الأسطر 18–30: أداة الحصول/التعيين لخاصية [name]؛
14.2.1.2. كيان [Subject]
فئة [Subject] (subject.py) هي كما يلي:
ملاحظات
- السطر 7: الفئة [Class] مشتقة من الفئة [BaseEntity]؛
- الأسطر 11–17: يتم تعريف الموضوع من خلال معرّفه [id] واسمه [name] ووزنه [coefficient]؛
- الأسطر 19–50: متغيرات الحصول/التعيين لسمات الفئة؛
14.2.1.3. كيان [Student]
فئة [Student] (student.py) هي كما يلي:
ملاحظات
- السطر 9: الفئة [Student] مشتقة من الفئة [BaseEntity]؛
- الأسطر 13–20: يتميز الطالب برقمه التعريفي [id]، واسم عائلته [lastName]، واسمه الأول [firstName]، وفصله الدراسي [class]. المعلمة الأخيرة هي مرجع إلى كائن [Class]؛
- الأسطر 22–65: متغيرات الحصول/التعيين لسمات الفئة؛
14.2.1.4. كيان [Note]
فئة [Note] (note.py) هي كما يلي:
ملاحظات
- السطر 8: فئة [Note] مشتقة من فئة [BaseEntity]؛
- الأسطر 12–20: يتميز كائن [Note] بمعرفه [id]، وقيمة الدرجة [value]، وإشارة [student] إلى الطالب الذي حصل على هذه الدرجة، وإشارة إلى المادة [subject] المرتبطة بالدرجة؛
- الأسطر 22–75: متغيرات الحصول/التعيين لسمات الفئة؛
14.2.2. تكوين التطبيق
![]() |
يقوم ملف [config.py] بتهيئة البيئة للنص البرمجي الرئيسي [main] (1) وكذلك للاختبارات (2). تحتوي جميع هذه البرامج النصية على عبارة [import config] في بداية الكود. لاحظ أن الدليل الذي يحتوي على البرنامج النصي المستهدف بواسطة الأمر [python script] يصبح تلقائيًا جزءًا من مسار Python. لذلك، إذا كان [config] موجودًا في نفس الدليل الذي يحتوي على البرامج النصية التي تحتوي على عبارة [import config]، فسيتم العثور عليه. الملفان [1] و[2] متطابقان هنا. قد لا يكون هذا هو الحال دائمًا.
ملف [config.sys] هو كما يلي:
- الأسطر 11–14: الدلائل التي يجب أن تكون جزءًا من مسار Python (sys.path)؛
- يتيح الدليل [f"{root_dir}/02/entities"] الوصول إلى الفئات [BaseEntity] و [MyException]؛
- يوفر المجلد [f"{script_dir}/../entities"] الوصول إلى الفئات [Student] و [Class] و [Subject] و [Grade]؛
- يوفر المجلد [f"{script_dir}/../interfaces"] الوصول إلى واجهات التطبيق؛
- يوفر المجلد [f"{script_dir}/../services"] الوصول إلى الفئات التي تنفذ الواجهات؛
14.2.3. اختبار الكيانات
![]() |
هنا، سنكتب اختبارات يتم تنفيذها بواسطة أداة تسمى [unittest]. يأتي PyCharm مزودًا بعدة أطر عمل للاختبار. يمكنك اختيار أحدها في إعدادات PyCharm:

- في [4]، تتوفر عدة أطر عمل للاختبار:

14.2.3.1. فئة الاختبار [TestBaseEntity]
سيكون نص الاختبار [TestBaseEntity] كما يلي:
ملاحظات
- السطر 1: نستورد الوحدة النمطية [unittest]، التي توفر طرق الاختبار المختلفة؛
- الأسطر 3–6: نقوم بتكوين التطبيق بحيث يمكن العثور على الفئات اللازمة للاختبار؛
- السطر 9: يجب أن تمتد فئة اختبار [unittest] إلى فئة [unittest.TestCase]؛
- السطران 11 و27: يجب أن يبدأ اسم وظائف الاختبار بـ [test]، وإلا فلن يتم التعرف عليها؛
- الأسطر 13-16: نستورد الفئات التي نحتاجها؛
- في فئة الاختبار هذه، نريد التحقق من سلوك الطريقتين [BaseEntity.fromdict] (السطر 34) و [BaseEntity.fromjson] (السطر 18). تحتوي فئة [Note] على خصائص تشير إلى فئات أخرى. نريد التحقق من أن الطريقتين السابقتين تنشئان كائنات [Note] صالحة؛
- السطر 18: نقوم بإنشاء كائن [Note] من كائن JSON؛
- السطر 21: نتحقق من أن الكائن الذي تم إنشاؤه هو بالفعل من النوع [Note]. الطريقة [assertIsInstance] هي طريقة تابعة لفئة [unittest.TestCase]، وهي الفئة الأم لفئة [TestBaseEntity]؛
- السطر 22: نتحقق من أن [note.student] هو بالفعل من النوع [Student]؛
- السطر 23: نتحقق من أن [note.student.class] هو بالفعل من النوع [Class]؛
- السطر 24: نتحقق من أن [note.subject] هو بالفعل من النوع [Subject]؛
- الأسطر 33–42: نقوم بنفس الشيء مع طريقة [BaseEntity.fromdict]؛
هناك عدة طرق لتشغيل الاختبارات:
![]() |
- في [1-2]، نقوم بتشغيل [TestBaseEntity] باستخدام إطار عمل [UnitTest]؛
- في [3-5]، تفشل الاختبارات. تشير [UnitTests] إلى أنها لم تعثر على أي اختبارات لتشغيلها؛
تفشل الاختبارات بسبب بنية كود [TestBaseEntity]:
ما يسبب مشاكل لإطار عمل [UnitTest] هو وجود كود قابل للتنفيذ (الأسطر 3–6) قبل تعريف فئة الاختبار (السطر 9).
لذلك، نعيد تنظيم الكود على النحو التالي:
- الأسطر 6–10: نُعرّف دالة [setUp]. لهذه الدالة دور محدد: يتم تنفيذها قبل كل دالة اختبار (test_note1، test_note2)؛
بمجرد الانتهاء من ذلك، يؤدي تنفيذ فئة [TestBaseEntity] إلى النتائج التالية:
![]() |
هذه المرة، تم تنفيذ كلتا طريقتي الاختبار ونجحت الاختبارات.
لنرى ماذا يحدث عندما يفشل الاختبار. لنعدل الكود في [test_note1] على النحو التالي:
- السطر 2: نتحقق من أن 1==2؛
نتائج التنفيذ هي كما يلي:
![]() |
يمكنك معرفة سبب الخطأ بالنقر على الاختبار الفاشل [2]:
![]() |
- في [7-8]، سبب الخطأ؛
هناك طريقة أخرى لتشغيل فئة الاختبار وهي تشغيلها في محطة طرفية:
![]() |
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\troiscouches\v01\tests>python -m unittest TestBaseEntity.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.026s
OK
تشير السطر 6 إلى نجاح كلا الاختبارين (لقد أزلنا الخطأ 1==2)؛
أخيرًا، هناك طريقة ثالثة لتشغيل فئة الاختبار [TestBaseEntity]، لا تزال في محطة طرفية، وهي كما يلي. ننهي فئة الاختبار بالسطور 6-7 التالية؛
…
self.assertIsInstance(note.élève.classe, Classe)
self.assertIsInstance(note.matière, Matière)
if __name__ == '__main__':
unittest.main()
- السطر 6: المتغير [__name__] هو الاسم الممنوح للبرنامج النصي الذي يتم تنفيذه. عندما يكون البرنامج النصي هو الذي تم تشغيله بواسطة الأمر [python script.py]، يكون المتغير [__name__] هو [__main__] (شرطتان سفليتان قبل وبعد المعرف). وبالتالي، يتم تنفيذ السطر 7 فقط عندما يتم تشغيل البرنامج النصي [TestBaseEntity] بواسطة الأمر [python TestBaseEntity.py]. تعمل العبارة [unittest.main()] على تشغيل البرنامج النصي عبر إطار عمل [UnitTest]. فيما يلي مثال على ذلك:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\troiscouches\v01\tests>python TestBaseEntity.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.013s
OK
14.2.3.2. فئة الاختبار [TestEntities]
فئة الاختبار [TestEntities] هي كما يلي:
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 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 | |
- الغرض من البرنامج النصي للاختبار هو اختبار مُعيّنات الفئة: للتحقق من أنه لا يمكن تعيين قيم غير صحيحة لسمات الكيانات المختلفة؛
- الأسطر 11–24: نختبر عدم إمكانية تعيين معرّف غير صالح لطالب. وبما أننا نمرر القيمة 'x' في السطر 16 كمعرّف للطالب، فإننا نتوقع حدوث استثناء. لذا يجب أن ننتقل إلى الأسطر 20–22؛
- السطر 21: عرض رسالة الخطأ؛
- السطر 22: استرداد رمز الخطأ (انظر القسم |كيان MyException|)؛
- السطر 24: نتحقق (نؤكد) من أن رمز الخطأ هو 1. هنا، نتحقق من أمرين:
- أن خطأً قد حدث بالفعل؛
- أن رمز الخطأ هو 1؛
- تتكرر هذه العملية مع الدوال في الأسطر 24–213؛
- الأسطر 215–222: نختبر ما إذا كان الإجراء يرمي استثناءً من نوع معين؛
- السطر 220: نشير إلى أن الاختبار ناجح إذا ألقى استثناءً من النوع [MyException]؛
النتائج
نقوم بتشغيل البرنامج النصي للاختبار:
![]() |
النتائج التي تم الحصول عليها هي كما يلي:
Testing started at 09:39 ...
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.1.2\plugins\python-ce\helpers\pycharm\_jb_unittest_runner.py" --path C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v01/tests/TestEntités.py
Launching unittests with arguments python -m unittest C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v01/tests/TestEntités.py in C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\troiscouches\v01\tests
code erreur=1, message=MyException[1, L'identifiant d'une entité <class 'Elève.Elève'> doit être un entier >=0]
code erreur=1, message=MyException[1, L'identifiant d'une entité <class 'Classe.Classe'> doit être un entier >=0]
code erreur=1, message=MyException[1, L'identifiant d'une entité <class 'Matière.Matière'> doit être un entier >=0]
code erreur=1, message=MyException[1, L'identifiant d'une entité <class 'Note.Note'> doit être un entier >=0]
code erreur=21, message=MyException[21, Le nom de la matière 1 doit être une chaîne de caractères non vide]
code erreur=22, message=MyException[22, Le coefficient de la matière y doit être un réel >=0]
code erreur=31, message=MyException[31, L'attribut x de la note 1 doit être un nombre dans l'intervalle [0,20]]
code erreur=32, message=MyException[32, L'attribut [y] de la note 1 doit être de type Elève ou dict ou json. Erreur : Expecting value: line 1 column 1 (char 0)]
code erreur=33, message=MyException[33, L'attribut [z] de la note 1 doit être de type Matière ou dict ou json. Erreur : Expecting value: line 1 column 1 (char 0)]
code erreur=41, message=MyException[41, Le nom de l'élève 1 doit être une chaîne de caractères non vide]
code erreur=42, message=MyException[42, Le prénom de l'élève 1 doit être une chaîne de caractères non vide]
code erreur=43, message=MyException[43, L'attribut [t] de l'élève 1 doit être de type Classe ou dict ou json. Erreur : Expecting value: line 1 column 1 (char 0)]
Ran 14 tests in 0.040s
OK
Process finished with exit code 0
هنا، تم اجتياز جميع الاختبارات
14.2.4. طبقة [dao]

تنفذ طبقة [dao] واجهة [InterfaceDao] [1]. ويتم تنفيذ ذلك بواسطة فئة [Dao] (2). ويختبر البرنامج النصي [tests_dao] (3) أساليب طبقة [dao].
14.2.4.1. واجهة [InterfaceDao]
الواجهة هي عقد بين الكود المستدعي والكود المستدعى. والكود المستدعى هو الذي يوفر الواجهة:
![]() |
- لا يعرف الكود المستدعي [1] كيفية تنفيذ الكود المستدعى [3]. إنه يعرف فقط كيفية استدعائه. وتخبره الواجهة [2] بكيفية القيام بذلك. تحدد هذه الواجهة مجموعة من الأساليب/الوظائف التي سيتم استخدامها للتفاعل مع الكود المستدعى. تُعرف هذه الواجهة أيضًا باسم API (واجهة برمجة التطبيقات)؛
ستوفر طبقة [dao] الواجهة التالية:
- [get_classes] تعرض قائمة الفصول الدراسية في المدرسة الإعدادية؛
- [get_subjects] تعرض قائمة المواد التي تُدرس في المدرسة الإعدادية؛
- [get_students] تعرض قائمة الطلاب في المدرسة الإعدادية؛
- [get_grades] تعرض قائمة بدرجات جميع الطلاب؛
- [get_grades_for_student_by_id] تعرض درجات طالب معين؛
- [get_student_by_id] تعرض الطالب المحدد برقمه التعريفي؛
سيستخدم كود الاستدعاء هذه الطرق فقط. ولا يحتاج إلى معرفة كيفية تنفيذها. ويمكن أن تأتي البيانات من مصادر مختلفة (مبرمجة بشكل ثابت، من قاعدة بيانات، من ملفات نصية، إلخ) دون التأثير على كود الاستدعاء. وهذا ما يُسمى بالبرمجة القائمة على الواجهة.
يحتوي Python 3 على مفهوم مشابه لمفهوم الواجهة: الفئة المجردة. سنستخدمها. سنقوم بتجميع واجهات هذا المثال في مجلد [interfaces].
نحدد فئة مجردة [InterfaceDao] (InterfaceDao.py) لطبقة [dao]:
ملاحظات:
- السطر 2: ABC = الفئة الأساسية المجردة. نقوم باستيراد فئة ABC من الوحدة النمطية [abc]، بالإضافة إلى الزخرفة [abstractmethod] المستخدمة في الأسطر 10 و15 و20 و25 و30 و35؛
- السطر 8: تسمى الفئة المجردة [InterfaceDao] وتشتق من فئة [ABC]؛
- يتم تزيين أساليب الفئة المجردة بزخرفة [@abstractmethod]، مما يجعل الأسلوب المزخرف أسلوبًا مجردًا: لا يتم تعريف كوده. ومع ذلك، نقوم بتضمين كود هناك: عبارة [pass]، التي لا تفعل شيئًا؛
- لا يمكن إنشاء مثيل للفئة المجردة [InterfaceDao]. ولا يمكن إنشاء مثيلات إلا للفئات المشتقة من [InterfaceDao] التي قامت بتنفيذ جميع أساليب [InterfaceDao]. لذلك، إذا أنشأنا فئتين [Dao1] و [Dao2] مشتقتين من الفئة [InterfaceDao]، فستقوم كلتاهما بتنفيذ الطرق المجردة لـ [InterfaceDao]. وبالتالي يمكننا القول إنهما تنفذان الواجهة [InterfaceDao]؛
- اللغات التي تدعم كل من الواجهات والفئات المجردة تخصص دورًا مختلفًا للواجهة عن الفئة المجردة. لا تحتوي الواجهة على سمات ولا يمكن إنشاء مثيل لها. يمكن للفئة تنفيذ واجهة عن طريق تعريف جميع أساليبها؛
14.2.4.2. تنفيذ [Dao]
تنفذ فئة [Dao] (dao.py) واجهة [InterfaceDao] على النحو التالي:
ملاحظات:
- الأسطر 1-7: نقوم باستيراد الكيانات وواجهة [InterfaceDao]؛
- السطر 11: الفئة [Dao] مشتقة من الفئة المجردة [InterfaceDao]. نقول إنها تنفذ واجهة [InterfaceDao]؛
- السطر 14: لا تحتوي المنشئة على معلمات. وهي تضمّن أربع قوائم:
- الأسطر 15–18: قائمة الفئات؛
- الأسطر 19-22: قائمة المواد؛
- الأسطر 23-28: قائمة الطلاب؛
- الأسطر 29–38: قائمة الدرجات؛
- الأسطر 40–44: تنفيذ أساليب [واجهة Dao]. هنا، لا نقوم بتعريفها لكي نرى رسالة الخطأ التي تصدرها لغة Python؛
قد يبدو برنامج الاختبار كما يلي [tests-dao.py]:
ملاحظة: البرنامج النصي [tests-dao.py] ليس [unittest] لأنه لا يحتوي على أي طرق تبدأ أسماؤها بـ [test_].
التعليقات واضحة بذاتها. تستخدم الأسطر 11–25 واجهة طبقة [dao]. لا توجد هنا أي افتراضات حول التنفيذ الفعلي للطبقة. في السطر 9، نقوم بإنشاء مثيل لطبقة [dao].
نتائج تشغيل هذا البرنامج النصي هي كما يلي:
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/troiscouches/v01/tests/tests_dao.py
Traceback (most recent call last):
File "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v01/tests/tests_dao.py", line 9, in <module>
daoImpl = Dao()
TypeError: Can't instantiate abstract class Dao with abstract methods get_classes, get_matières, get_notes, get_notes_for_élève_by_id, get_élève_by_id, get_élèves
Process finished with exit code 1
نلاحظ حدوث خطأ فور إنشاء مثيل للفئة [Dao] (السطر 3 أعلاه). يُخبرنا مترجم Python 3 أنه لا يمكنه إنشاء مثيل للفئة لأننا لم نُعرّف الطرق المجردة [get_classes، get_subjects، get_grades، get_grades_for_student_by_id، get_student_by_id، get_students].
يدعم PyCharm أيضًا الفئات المجردة ويقدم تعريفًا لأساليبها:
![]() |
- في [1]، انقر بزر الماوس الأيمن على الكود؛
- في [2-3]، حدد [إنشاء / تنفيذ الطرق] لتنفيذ الطرق المفقودة لفئة [Dao]؛
- في [4]، حدد الطرق المراد تنفيذها — في هذه الحالة، جميعها؛
بمجرد الانتهاء من ذلك، يكمل PyCharm فئة [Dao] على النحو التالي:
نكمل فئة [Dao] على النحو التالي:
- الأسطر 5–19 واضحة؛
- الأسطر 29–36: الطريقة التي تُرجع الطالب الذي تم تمرير معرّفه. إذا لم يكن الطالب موجودًا، يتم إثارة استثناء؛
- السطر 31: تسمح لك الدالة [filter] بتصفية قائمة:
- المعلمة الأولى هي معيار التصفية؛
- المعلمة الثانية هي القائمة المراد تصفيتها، وهي في هذه الحالة قائمة الطلاب؛
- السطر 31: يتم تنفيذ معيار التصفية للقائمة باستخدام دالة [f(e:Student) -> bool]. يتم تطبيق هذا على كل عنصر من عناصر القائمة المراد تصفيتها. إذا كان العنصر يفي بمعيار التصفية، يتم الاحتفاظ به في القائمة المصفّاة؛ وإلا، يتم استبعاده. هنا، يمكننا إما:
- تحديد اسم الدالة f وتنفيذها في مكان آخر. عندئذ يصبح استدعاء الدالة [filter] هو [filter(f, self.get_students)]؛
- تقديم تعريف الدالة f. عندئذٍ يصبح استدعاء الدالة [filter] هو [filter(f(e :Student){…}, self.get_students())]، حيث يمثل [e] عنصرًا من القائمة التي تم تصفيتها، أي طالبًا. وهذا ما تم فعله هنا. سيكون تعريف الدالة f هنا هو [f(e :Student){return e.id == student_id)]: يتم اختيار الطالب فقط إذا كان رقم هويته [id] مطابقًا للرقم الذي يتم البحث عنه. يمكن استبدال هذه الدالة بما يُسمى دالة لامدا: [lambda e: e.id == student_id]:
- e: يمثل معلمة الدالة f، وهي هنا طالب. يمكنك استخدام أي اسم تريده؛
- e.id==student_id هو معيار التصفية: يتم اختيار الطالب [e] فقط إذا كان رقم هويته [id] مطابقًا للرقم الذي يتم البحث عنه؛
- السطر 31: تُرجع الدالة [filter] القائمة المُصفاة كنوع لا ينتمي إلى نوع [list]، لكن يمكن تحويله إلى نوع [list]. وهذا ما نقوم به هنا باستخدام التعبير [list(filtered_list)]؛
- السطران 33-34: إذا كانت القائمة المفلترة فارغة، فهذا يعني أن الطالب الذي يتم البحث عنه غير موجود. عندئذ يتم إثارة استثناء؛
- السطر 36: إذا وصلنا إلى هذه النقطة، فهذا يعني أنه لم يتم إثارة أي استثناء. عندئذ نعلم أننا استرجعنا قائمة تحتوي على عنصر واحد (لا يوجد طالبان يحملان رقم [id] نفسه). لذلك نُرجع العنصر الأول من القائمة؛
- الأسطر 21–27: يجب أن تُرجع الطريقة [get_notes_for_élève_by_id] الدرجات الخاصة بالطالب الذي تم تمرير رقم [id] الخاص به إليها؛
- السطور 22–23: نبدأ بالبحث عن الطالب الذي يحمل الرقم التعريفي [student_id] باستخدام طريقة [get_student_by_id]، التي قمنا للتو بتعليقها. قد تحدث استثناء إذا كان الطالب الذي نبحث عنه غير موجود. وبما أنه لا يوجد كتلة try/catch حول العبارة في السطر 23، فسيتم تمرير الاستثناء إلى الكود المستدعي. وهذا هو السلوك المطلوب؛
- السطران 24-25: بمجرد استرداد الطالب، نسترد جميع درجاته. نقوم بذلك مرة أخرى باستخدام مرشح:
- المرشح هو [filter(criterion, self_getnotes()]. وبالتالي، فإن القائمة المراد تصفيةها هي قائمة جميع الدرجات لجميع الطلاب في المدرسة؛
- يتم التعبير عن معيار التصفية باستخدام دالة [lambda]: lambda n: n.student.id == student_id. المعلمة n هي عنصر من القائمة المراد تصفية، أي درجة. يحتوي النوع [Note] على خاصية [student] تمثل الطالب صاحب الدرجة. لذلك، يجب أن يكون [n.student.id]، الذي يمثل معرف ذلك الطالب، مساوياً لمعرف الطالب الذي نبحث عنه؛
ثم نقوم بتشغيل البرنامج النصي [tests-dao.py].
ثم نحصل على النتائج التالية:
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/troiscouches/v01/tests/tests_dao.py
{"id": 1, "nom": "classe1"}
{"id": 2, "nom": "classe2"}
{"id": 1, "nom": "matière1", "coefficient": 1}
{"id": 2, "nom": "matière2", "coefficient": 2}
{"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}
{"id": 21, "nom": "nom2", "prénom": "prénom2", "classe": {"id": 1, "nom": "classe1"}}
{"id": 32, "nom": "nom3", "prénom": "prénom3", "classe": {"id": 2, "nom": "classe2"}}
{"id": 42, "nom": "nom4", "prénom": "prénom4", "classe": {"id": 2, "nom": "classe2"}}
{"id": 1, "valeur": 10, "élève": {"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}, "matière": {"id": 1, "nom": "matière1", "coefficient": 1}}
{"id": 2, "valeur": 12, "élève": {"id": 21, "nom": "nom2", "prénom": "prénom2", "classe": {"id": 1, "nom": "classe1"}}, "matière": {"id": 1, "nom": "matière1", "coefficient": 1}}
{"id": 3, "valeur": 14, "élève": {"id": 32, "nom": "nom3", "prénom": "prénom3", "classe": {"id": 2, "nom": "classe2"}}, "matière": {"id": 1, "nom": "matière1", "coefficient": 1}}
{"id": 4, "valeur": 16, "élève": {"id": 42, "nom": "nom4", "prénom": "prénom4", "classe": {"id": 2, "nom": "classe2"}}, "matière": {"id": 1, "nom": "matière1", "coefficient": 1}}
{"id": 5, "valeur": 6, "élève": {"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}, "matière": {"id": 2, "nom": "matière2", "coefficient": 2}}
{"id": 6, "valeur": 8, "élève": {"id": 21, "nom": "nom2", "prénom": "prénom2", "classe": {"id": 1, "nom": "classe1"}}, "matière": {"id": 2, "nom": "matière2", "coefficient": 2}}
{"id": 7, "valeur": 10, "élève": {"id": 32, "nom": "nom3", "prénom": "prénom3", "classe": {"id": 2, "nom": "classe2"}}, "matière": {"id": 2, "nom": "matière2", "coefficient": 2}}
{"id": 8, "valeur": 12, "élève": {"id": 42, "nom": "nom4", "prénom": "prénom4", "classe": {"id": 2, "nom": "classe2"}}, "matière": {"id": 2, "nom": "matière2", "coefficient": 2}}
{"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}
élève n° 11 = {"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}
note de l'élève n° 11 = {"id": 1, "valeur": 10, "élève": {"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}, "matière": {"id": 1, "nom": "matière1", "coefficient": 1}}
note de l'élève n° 11 = {"id": 5, "valeur": 6, "élève": {"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}, "matière": {"id": 2, "nom": "matière2", "coefficient": 2}}
Process finished with exit code 0
لاحظ أنه عند عرض الدرجة (العملية مشابهة بالنسبة للكائنات الأخرى)، لدينا أيضًا:
- الطالب المرتبط بالدرجة؛
- المادة المشار إليها بالدرجة؛
يتم إنتاج هذه النتيجة بواسطة الدالة [BaseEntity.asdict] (انظر قسم "الرابط").
14.2.5. طبقة [business]
![]() | ![]() |
- [InterfaceMétier] هي واجهة طبقة [business]؛
- [Business] هي فئة التنفيذ لطبقة [business]؛
- [TestBusiness] هي فئة [UnitTest] لاختبار فئة [Business]؛
14.2.5.1. واجهة [BusinessInterface]
ستقوم طبقة [business] بتنفيذ واجهة [BusinessInterface] التالية (BusinessInterface.py):
- تُرجع [get_stats_for_student] الدرجات الخاصة بالطالب idStudent مع معلومات عنه: المتوسط المرجح، وأدنى درجة، وأعلى درجة. يتم تغليف هذه المعلومات في كائن من النوع [StudentStats]؛
14.2.5.2. كيان [StatsForStudent]
نوع [StatsForStudent] (StatsForStudent.py)، الذي يغلف إحصائيات الطالب (الدرجات، الحد الأدنى، الحد الأقصى، المتوسط المرجح)، هو كما يلي:
ملاحظات:
- السطر 8: الفئة [StatsForStudent] مشتقة من الفئة [BaseEntity]؛
- الأسطر 13–22: خصائص الفئة؛
- معرف [id] من [BaseEntity]؛
- الطالب [student] الذي تم تغليف إحصاءاته؛
- درجاته [grades]؛
- المتوسط المرجح [weighted_average]؛
- أدنى درجة حصل عليها [min]؛
- أعلى درجة له [max]؛
- نحن لا نحدد متغيرات الحصول/التعيين لهذه السمات. نفترض أن طبقة [business] تنشئ كائنات من هذا النوع وأنها لا تنشئ كائنات غير صالحة؛
- الأسطر 23–33: تُرجع الدالة [__str__] سلسلة تحتوي على خصائص الكائن؛
14.2.5.3. تنفيذ [Business]
سيكون تنفيذ [Business] (Metier.py) لواجهة [BusinessInterface] كما يلي:
ملاحظات
- السطر 7: الفئة [Métier] مشتقة من الفئة [InterfaceMétier]. ومن المعتاد القول إنها تنفذ واجهة [InterfaceMétier]؛
- الأسطر 9–12: يأخذ المنشئ معلمة واحدة، وهي مرجع إلى طبقة [dao]. في السطر 10، لاحظ أننا قمنا بتعيين النوع [InterfaceDao] للمعلمة [dao]. لا نتوقع تنفيذًا محددًا، بل مجرد تنفيذ يحترم واجهة [DaoInterface]. هنا، لا يهم ذلك لأن Python لن تأخذ هذا النوع في الاعتبار، ولكن من الأفضل العمل مع الواجهات بدلاً من التنفيذات المحددة. يصبح تعديل الكود أسهل عندئذٍ؛
- الأسطر 19-60: تنفيذ طريقة [get_stats_for_élève]؛
- السطر 19: تتلقى الطريقة معلمة واحدة، وهي [idElève] للطالب الذي نريد إحصائياته؛
- السطر 24: نطلب درجات الطالب من طبقة [dao]. ينتج عن هذا الطلب استثناء إذا كان الطالب غير موجود. لا يتم التعامل مع هذا الاستثناء (لا يوجد try/catch) وبالتالي يتم تمريره مرة أخرى إلى الكود المستدعي؛
- السطر 25: نصل إلى هذه النقطة إذا لم تحدث أي استثناءات. [student_grades] هو إذن قاموس يحتوي على مفتاحين [student, grade]:
- السطر 25: نسترد معلومات عن الطالب (اسمه، فصله، إلخ)؛
- السطر 26: نسترد درجاته؛
- الأسطر 28–31: نتحقق مما إذا كان لدى الطالب أي درجات. إذا لم يكن لديه، فلا توجد إحصائيات لحسابها؛
- السطر 31: نُرجع كائن [StatsForStudent] تم إنشاؤه من قاموس باستخدام الطريقة [BaseEntity.fromdict]؛
- الأسطر 33–54: نستخدم درجات الطالب لحساب الإحصائيات المطلوبة. يجب أن تكون تعليقات الكود كافية للفهم؛
- الأسطر 56-60: نُرجع كائن [StatsForStudent] تم إنشاؤه من قاموس باستخدام طريقة [BaseEntity.fromdict]؛
14.2.5.4. اختبار طبقة [business]
قد يبدو نص [UnitTest] الخاص بطبقة [business] كما يلي (TestMétier.py):
ملاحظات
- الأسطر 6–9: تُستخدم دالة [setUp] هنا لتكوين مسار Python الخاص بالاختبار؛
- السطر 16: نقوم بإنشاء مثيل لطبقة [dao]؛
- السطر 17: نقوم بإنشاء مثيل لطبقة [business] ونستخدم طريقة [get_stats_for_student] الخاصة بها لحساب الإحصائيات الخاصة بالطالب رقم 11؛
- السطر 19: يتم عرض [StatsForStudent] الناتج. نظرًا لأن [StatsForStudent] مشتق من [BaseEntity]، يتم عرض سلسلة JSON الخاصة بـ [StatsForStudent] هنا؛
- السطر 21: نتحقق من الدرجة الدنيا للطالب؛
- السطر 22: نتحقق من أعلى درجة حصل عليها؛
- السطر 23: نختبر أن المتوسط المرجح هو 7.333، بدقة تصل إلى 10⁻³. بشكل عام، لا يمكن مقارنة الأعداد الحقيقية بدقة لأنها عادةً ما يتم تمثيلها داخليًا على أنها تقريبية فقط؛
نتائج الاختبار هي كما يلي:
Testing started at 18:17 ...
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.1.2\plugins\python-ce\helpers\pycharm\_jb_unittest_runner.py" --path C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v01/tests/TestMétier.py
Launching unittests with arguments python -m unittest C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v01/tests/TestMétier.py in C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\troiscouches\v01\tests
Ran 1 test in 0.015s
OK
stats=Elève={"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}, notes=[10 6], max=10, min=6, moyenne pondérée=7.33
Process finished with exit code 0
14.2.6. طبقة [ui]

- في [1]، واجهة طبقة [ui]؛
- في [2]، تنفيذ هذه الواجهة؛
- في [3]، البرنامج النصي الرئيسي للتطبيق؛
14.2.6.1. الواجهة [InterfaceUi]
ستكون واجهة طبقة [UI] كما يلي:
ملاحظات
- السطران 9-10: ستحتوي طبقة [UI] على طريقة واحدة فقط، وهي [run]؛
14.2.6.2. تنفيذ [Console]
يتم تنفيذ طبقة [console] بواسطة البرنامج النصي [Console.py] التالي:
- الأسطر 3-5: استيراد جميع الواجهات؛
- السطر 11: تنفذ فئة [Console] واجهة [InterfaceUi]؛
- الأسطر 12-17: يتلقى منشئ فئة [Console] مرجعًا إلى طبقة [business] كمعلمة. لاحظ أننا أعطينا هذه المعلمة النوع [BusinessInterface] للتأكيد على أننا نعمل مع واجهات بدلاً من تطبيقات محددة؛
- السطر 24: تنفيذ طريقة [run] للواجهة؛
- السطر 27: حلقة تتوقف عند استيفاء الشرط الموجود في السطر 31؛
- السطر 29: إدخال البيانات المكتوبة على لوحة المفاتيح. تتلقى الدالة [input] معلمة اختيارية: الرسالة التي سيتم عرضها على الشاشة لطلب الإدخال. يتم استرداد هذا الإدخال دائمًا كسلسلة. تزيل الدالة [strip] أي مسافات بيضاء في بداية أو نهاية السلسلة؛
- الأسطر 34–39: نتحقق من صحة الإدخال، وهو رقم هوية الطالب. يجب أن يكون عددًا صحيحًا >= 1. تذكر أن الإدخال تم إدخاله كسلسلة؛
- السطر 36: نحاول تحويل المدخلات إلى عدد صحيح في النظام العشري. تثير الدالة [int] استثناءً إذا لم يكن ذلك ممكنًا؛
- السطر 37: نصل إلى هذه النقطة فقط إذا لم تحدث أي استثناءات. نتحقق من أن العدد الصحيح الذي تم استرداده هو بالفعل >=1؛
- السطور 38–39: نتعامل مع الاستثناء. إذا حدث استثناء، تظل المتغير [ok] من السطر 34 مضبوطًا على [False]؛
- الأسطر 41-43: إذا كان الإدخال غير صحيح، يتم عرض رسالة خطأ وإعادة تشغيل الحلقة (السطر 43)؛
- الأسطر 45–48: نحسب الإحصائيات الخاصة بالطالب الذي تم إدخال معرّفه؛
- السطر 46: يتم استخدام طريقة [get_stats_for_student] من طبقة [business]. ترمي هذه الطريقة استثناءً إذا كان الطالب غير موجود. يتم التعامل مع هذا الاستثناء في السطرين 47 و48. ونحن نعلم أن طبقتي [DAO] و[business] ترميان استثناء [MyException]؛
14.3. البرنامج النصي الرئيسي [main]
النص البرمجي الرئيسي [main] هو كما يلي (main.py):
- الأسطر 1–4: تكوين مسار Python للتطبيق؛
- الأسطر 6-9: استيراد الفئات والواجهات المطلوبة؛
- السطر 14: إنشاء مثيل لطبقة [DAO]؛
- السطر 16: إنشاء مثيل لطبقة [business]؛
- السطر 18: إنشاء مثيل لطبقة [ui]؛
- السطر 20: بدء واجهة المستخدم؛
- الأسطر 13–20: عادةً، لا يتم إلقاء أي استثناءات من هذه الأسطر. يتم التقاط أي استثناءات تنتشر من طبقات [DAO] و[business] بواسطة طبقة [Console]. تعتبر معالجة الاستثناءات فنًا صعبًا عندما لا تفهم تمامًا الطبقات المستخدمة (وهذا ليس هو الحال هنا). في حالة الشك، يمكن إضافة كود لالتقاط أي نوع من الاستثناءات التي قد يتم إلقاءها بواسطة الكود المنفذ. هذا ما يتم هنا، الأسطر 21–23. نلتقط أي استثناء مشتق من [BaseException]، أي جميع الاستثناءات؛
- الأسطر 24-25: لا تقوم جملة [finally] بأي شيء هنا. إنها موجودة فقط للسماح بتعليق الأسطر 21-23. في الواقع، في وضع التصحيح، لا يُنصح بالتقاط الاستثناءات. في هذه الحالة، يقوم مترجم Python بالتقاطها ثم الإبلاغ عن رقم السطر الذي حدث فيه الاستثناء. هذه معلومات أساسية. عندما يتم تعليق الأسطر 21–23، يضمن وجود الأسطر 24–25 وجود كتلة try/catch صحيحة من الناحية النحوية. بدونها، يثير Python خطأً؛
فيما يلي مثال على التنفيذ:
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/troiscouches/v01/main/main.py
Numéro de l'élève (>=1 et * pour arrêter) : 11
Elève={"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}, notes=[10 6], max=10, min=6, moyenne pondérée=7.33
Numéro de l'élève (>=1 et * pour arrêter) : 1
L'erreur suivante s'est produite : MyException[10, L'élève d'identifiant 1 n'existe pas]
Numéro de l'élève (>=1 et * pour arrêter) : *
Process finished with exit code 0
14.4. المثال 2
يهدف هذا المثال الجديد للبنى ذات الطبقات إلى توضيح مزايا البرمجة القائمة على الواجهات. يسهل هذا النهج صيانة التطبيق واختباره. سنستخدم مرة أخرى بنية من ثلاث طبقات:

سيتم تنفيذ كل طبقة بطريقتين مختلفتين. نريد أن نظهر أن تنفيذ طبقة ما يمكن تغييره بسهولة مع تأثير ضئيل على الطبقات الأخرى.
14.4.1. طبقة [dao]

تبدو واجهة [InterfaceDao] كما يلي:
- الأسطر 8–10: الطريقة [do_something_in_dao_layer] هي الطريقة الوحيدة للواجهة؛
تقوم فئة [DaoImpl1] بتنفيذ واجهة [InterfaceDao] على النحو التالي:
تقوم فئة [DaoImpl2] بتنفيذ واجهة [InterfaceDao] على النحو التالي:
14.4.2. طبقة [الأعمال]

واجهة [BusinessInterface] هي كما يلي:
- الأسطر 8–10: الطريقة [do_something_in_business_layer] هي الطريقة الوحيدة في الواجهة؛
تنفذ فئة [AbstractBaseMétier] واجهة [InterfaceMétier] على النحو التالي:
- السطر 8: تنبثق عن فئة [AbstractBaseMétier] فئتان:
- [BusinessInterface]: تنفذ الفئة [AbstractBusinessBase] هذه الواجهة في الأسطر 19–22. في الواقع، نرى أنها لم تنفذ الطريقة [do_something_in_business_layer]، التي أعلنت عنها على أنها مجردة (السطر 20). وسيكون الأمر متروكًا للفئات المشتقة لتنفيذ الطريقة؛
- [ABC] للوصول إلى تعليقات [@abstractmethod]؛
- الترتيب مهم: إذا عكسناه هنا، فإن Python تثير خطأً في وقت التشغيل؛
هذه هي المرة الأولى التي نستخدم فيها الوراثة المتعددة (الوراثة من فئات متعددة). ترث فئة [AbstractBaseMétier] الخصائص من كل من فئتي [InterfaceMétier] و [ABC].
- الأسطر 9–17: نُعرّف الخاصية [dao]، والتي ستكون مرجعًا لطبقة [dao]؛
الواجهة مصممة ليتم تنفيذها. عندما تتشارك تطبيقات مختلفة في خصائص، من المفيد وضع هذه الخصائص في فئة أصلية لتجنب التكرار. وهذا هو الحال هنا مع الخاصية [dao]. عادةً ما تكون الفئة الأصلية مجردة دائمًا لأنها لا تنفذ جميع أساليب الواجهة.
تنفذ فئة [BusinessImpl1] واجهة [BusinessInterface] على النحو التالي:
- السطر 4: الفئة [BusinessImpl1] مشتقة من الفئة [AbstractBusinessBase]. وبالتالي، فإنها ترث الخاصية [dao] من هذه الفئة؛
- الأسطر 6-9: تنفيذ واجهة [BusinessInterface] التي لم تنفذها الفئة الأم [AbstractBusinessBase]؛
- السطر 9: يتم استخدام طبقة [dao]؛
تنفذ الفئة [BusinessImpl2] الواجهة [BusinessInterface] بطريقة مماثلة:
14.4.3. طبقة [ui]

واجهة [InterfaceUi] هي كما يلي:
- الأسطر 8–10: الطريقة الوحيدة للواجهة؛
تقوم فئة [AbstractBaseUi] بتنفيذ واجهة [InterfaceUi] على النحو التالي:
- فئة [AbstractBaseUi] هي فئة مجردة (السطر 20). يجب أن تُشتق منها لتنفيذ واجهة [InterfaceUi]؛
- الأسطر 9–17: تحتوي فئة [AbstractBaseUi] على مرجع إلى طبقة [business]؛
فئة التنفيذ [UiImpl1] هي كما يلي:
- السطر 4: الفئة [UiImpl1] مشتقة من الفئة [AbstractBaseUi] وبالتالي ترث خاصية [business] الخاصة بها. يتم استخدام هذه الخاصية في السطر 9؛
فئة التنفيذ [UiImpl2] مشابهة:
- السطر 4: الفئة [UiImpl2] مشتقة من الفئة [AbstractBaseUi] وبالتالي ترث خاصية [business] الخاصة بها. يتم استخدام هذه الخاصية في السطر 9؛
14.4.4. ملفات التكوين

- تقوم ملفات [config1، config2] بتكوين التطبيق بطريقتين مختلفتين؛
- ملف [main] هو البرنامج النصي الرئيسي للتطبيق؛
ملف [config1] هو كما يلي:
- الأسطر 2–16: تكوين مسار Python للتطبيق؛
- الأسطر 18–31: إنشاء مثيلات لطبقات [DAO، business، UI]. لتنفيذ واجهاتها، نختار في كل مرة أول تنفيذ مدمج؛
- الأسطر 33–35: نضيف مراجع الطبقات إلى التكوين. هنا، لا يحتاج البرنامج النصي الرئيسي سوى إلى طبقة [ui]؛
ملف [config2] مشابه ويقوم بتنفيذ كل واجهة باستخدام التنفيذ الثاني المتاح:
14.4.5. البرنامج النصي الرئيسي [main]

النص البرمجي الرئيسي هو كما يلي:
يأخذ هذا البرنامج النصي معلمة واحدة:
- [config1] لاستخدام التكوين رقم 1؛
- [config2] لاستخدام التكوين رقم 2؛
يخزن Python المعلمات في قائمة [sys.argv]:
- sys.argv[0] هو اسم البرنامج النصي، وهنا [main]. هذه المعلمة موجودة دائمًا؛
- sys.argv[1] هو المعلمة الأولى التي يتم تمريرها إلى البرنامج النصي، و sys.argv[2] هي الثانية، ...
- السطر 8: نسترد عدد المعلمات؛
- الأسطر 9–11: نتحقق من وجود حجة بالفعل وأن قيمتها هي إما [config1] أو [config2]. إذا لم يكن الأمر كذلك، يتم عرض رسالة خطأ (السطر 10) ونخرج من البرنامج (السطر 11)؛
بمجرد معرفة التكوين المطلوب، نحتاج إلى تنفيذ هذا التكوين. على سبيل المثال، إذا تم اختيار التكوين 1، نحتاج إلى تنفيذ الكود:
المشكلة هنا هي أن التكوين المراد استخدامه مخزن في متغير، وهو [sys.argv[1]. لاستيراد وحدة نمطية اسمها مخزن في متغير، نحتاج إلى استخدام حزمة [importlib] (السطر 2).
- السطر 14: نستورد الوحدة النمطية التي يوجد اسمها في [sys.argv[1]
- السطر 15: بمجرد الانتهاء من ذلك، نقوم بتنفيذ دالة [configure] الخاصة بهذا الوحدة النمطية. نسترد قاموس [config] الذي يمثل تكوين التطبيق؛
- السطر 18: نعلم أن الإشارة إلى طبقة [ui] موجودة في config['ui']. نستخدمها لاستدعاء طريقة [do_something_in_ui_layer]. نعلم أن هذه الطريقة ستستدعي طريقة في طبقة [business]، والتي بدورها ستستدعي طريقة في طبقة [dao]؛
على سبيل المثال، تكون الدالة [do_something_in_ui_layer] كما يلي:
- يستخدم السطر 6 أعلاه الخاصية [business] لفئة [UiImpl1]، السطر 1. ومع ذلك، في التكوين [config1]، تمت كتابة ما يلي:
# métier
métier = MétierImpl1()
métier.dao = dao
# ui
ui = UiImpl1()
ui.métier = métier
- السطر 6: الخاصية [business] في [UIImpl1] هي مرجع إلى فئة [BusinessImpl1] (السطر 2). وبالتالي، سيتم تنفيذ الأسلوب [do_something_in_ui_layer] الخاص بفئة [BusinessImpl1]؛
في فئة [MétierUiImpl1]، مكتوب:
- السطر 6: الطريقة التي تستدعيها طبقة [ui] تستدعي بدورها طريقة للخاصية [dao] في فئة [BusinessImpl1]؛
ومع ذلك، في التكوين [config1]، تمت كتابة ما يلي:
# dao
dao = DaoImpl1()
# métier
métier = MétierImpl1()
métier.dao = dao
- السطر 5: الخاصية [BusinessImpl1.dao] من النوع [DaoImpl1] (السطر 2)؛
ما نريد إظهاره هنا هو أن البرنامج النصي [main] لا يحتاج إلى الاهتمام بطبقتي [business] و [DAO]. بل يحتاج فقط إلى الاهتمام بطبقة [UI]، حيث تم إنشاء الاتصالات بين هذه الطبقة والطبقات الأخرى من خلال التكوين.

لتمرير المعلمة [config1] أو [config2] إلى البرنامج النصي [main]، اتبع الخطوات التالية:

- في [1-2]، قم بإنشاء ما يُسمى تكوين وقت التشغيل؛
- في [3]، قم بتسمية هذا التكوين حتى تتمكن من العثور عليه لاحقًا؛
- في [4]، حدد البرنامج النصي المراد تشغيله. إذا اتبعت الإجراء الموضح في [1-2]، فسيكون البرنامج النصي الصحيح قد تم تحديده بالفعل؛
- في [5]، أدخل المعلمات المراد تمريرها إلى البرنامج النصي هنا. هنا، نمرر السلسلة [config1] لإرشاد البرنامج النصي لاستخدام التكوين رقم 1؛
- في [6]، قم بتأكيد تكوين التنفيذ؛

- في [1-2]، اعرض سياقات التنفيذ الحالية؛
- في [3]، حدد سياق التنفيذ الموجود وقم بنسخه [4]؛

- في [5]، الاسم الممنوح للتكوين الجديد. هذا هو التكوين الذي يقوم بتشغيل البرنامج النصي [main] [6] عن طريق تمرير المعلمة [config2] إليه [7]؛
تتوفر تكوينات التنفيذ في الزاوية العلوية اليمنى من نافذة PyCharm:

ما عليك سوى اختيار [2] أو [3] ثم النقر على [4] لتشغيل البرنامج النصي [main] باستخدام المعلمة [config1] أو [config2].
مع [config1]، يؤدي تشغيل [main] إلى النتائج التالية:
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/troiscouches/v02/main/main.py config1
34
Process finished with exit code 0
مع [config2]، يؤدي تشغيل [main] إلى النتائج التالية:
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/troiscouches/v02/main/main.py config2
-10
Process finished with exit code 0
ندعو القارئ إلى التحقق من هذه النتائج.













