7. البنية الطبقية والبرمجة القائمة على الواجهة
7.1. مقدمة
نقترح كتابة تطبيق يعرض درجات طلاب المرحلة الإعدادية. سيكون لهذا التطبيق بنية متعددة الطبقات:
![]() |
- طبقة [العرض] هي الطبقة التي تتفاعل مع مستخدم التطبيق.
- طبقة [منطق الأعمال] هي التي تنفذ قواعد العمل الخاصة بالتطبيق، مثل حساب الراتب أو الفاتورة. تستخدم هذه الطبقة البيانات الواردة من المستخدم عبر طبقة [العرض] ومن نظام إدارة قواعد البيانات (DBMS) عبر طبقة [DAO].
- تدير طبقة [DAO] (كائنات الوصول إلى البيانات) الوصول إلى البيانات في نظام إدارة قواعد البيانات (DBMS).
سنوضح هذه البنية باستخدام تطبيق وحدة تحكم بسيط:
- لن تكون هناك قاعدة بيانات،
- ستدير طبقة [DAO] كيانات الطالب والصف والموضوع والدرجة للتعامل مع درجات الطلاب،
- ستقوم طبقة [منطق الأعمال] بحساب المقاييس بناءً على درجات طالب معين،
- وستكون طبقة [العرض] عبارة عن تطبيق وحدة تحكم يعرض النتائج التي تحسبها طبقة [الأعمال].
مشروع Visual Studio للتطبيق هو كما يلي:
![]() |
7.2. كيانات التطبيق
الكيانات هي كائنات. هنا، سيكون لدينا أربع فئات لكل كيان من الكيانات: الطالب، والصف، والمادة، والدرجة.
![]() |
يحتوي ملف [entities.py] على أربع فئات.
تمثل فئة [Class] فصلاً دراسيًا في المرحلة الإعدادية:
class Classe:
# constructeur
def __init__(self,id,nom):
# on mémorise les paramètres
self.id=id
self.nom=nom
# toString
def __str__(self):
return "Classe[{0},{1}]".format(self.id,self.nom)
- الأسطر 3–6: يتم تعريف الفئة بواسطة معرف (السطر 5) واسم (السطر 6)؛
- السطور 9-10: طريقة عرض الفئة.
فئة [Subject] هي كما يلي:
class Matiere:
# constructeur
def __init__(self,id,nom,coefficient):
# on mémorise les paramètres
self.id=id
self.nom=nom
self.coefficient=coefficient
# toString
def __str__(self):
return "Matiere[{0},{1},{2}]".format(self.id,self.nom,self.coefficient)
- الأسطر 3-7: يتم تعريف الموضوع من خلال معرّفه (السطر 5)، واسمه (السطر 6)، ووزنه (السطر 7)؛
- السطور 10-11: طريقة عرض الموضوع.
فئة [Student] هي كما يلي:
class Eleve:
# constructeur
def __init__(self,id,nom,prenom,classe):
# on mémorise les paramètres
self.id=id
self.nom=nom
self.prenom=prenom
self.classe=classe
# toString
def __str__(self):
return "Eleve[{0},{1},{2},{3}]".format(self.id,self.prenom,self.nom,self.classe)
- الأسطر 3–8: يتم تمييز الطالب برقمه التعريفي (السطر 5)، واسم عائلته (السطر 6)، واسمه الأول (السطر 7)، وفصله (السطر 8). هذا المعامل الأخير هو مرجع إلى كائن [Class]؛
- السطور 11-12: الطريقة الخاصة بعرض الطالب.
فئة [Grade] هي كما يلي:
class Note:
# constructeur
def __init__(self,id,valeur,eleve,matiere):
# on mémorise les paramètres
self.id=id
self.valeur=valeur
self.eleve=eleve
self.matiere=matiere
# toString
def __str__(self):
return "Note[{0},{1},{2},{3}]".format(self.id,self.valeur,self.eleve,self.matiere)
- الأسطر 3-8: يتميز كائن [Grade] برقمه التعريفي (السطر 5)، وقيمة الدرجة (السطر 6)، وإشارة إلى الطالب الذي حصل على هذه الدرجة (السطر 7)، وإشارة إلى المادة التي تم منح الدرجة فيها (السطر 8)؛
- الأسطر 11-12: طريقة عرض كائن [Note].
7.3. طبقة [dao]
![]() |
ستوفر طبقة [dao] الواجهة التالية لطبقة [business]:
- تُرجع getClasses قائمة بفصول المرحلة الإعدادية؛
- تُرجع getMatieres قائمة المواد الدراسية؛
- تُرجع getStudents قائمة الطلاب؛
- تُرجع getGrades قائمة بالدرجات.
ستستخدم طبقة [الأعمال] هذه الطرق فقط. ولا تحتاج إلى معرفة كيفية تنفيذها. وبالتالي، يمكن أن تأتي هذه البيانات من مصادر مختلفة (قيم مبرمجة، قاعدة بيانات، ملفات نصية، إلخ) دون التأثير على طبقة [الأعمال]. ويُعرف هذا بالبرمجة القائمة على الواجهة.
تنفذ فئة [Dao] هذه الواجهة على النحو التالي:
- السطر 4: نستورد الوحدة النمطية التي تحتوي على الكيانات التي تتعامل معها طبقة [DAO]؛
- السطر 8: لا يحتوي المنشئ على معلمات. وهو يرمز أربع قوائم بشكل ثابت:
- الأسطر 10–12: قائمة الفئات؛
- الأسطر 14–16: قائمة المواد؛
- الأسطر 18-22: قائمة الطلاب؛
- الأسطر 24-32: قائمة الدرجات.
- الأسطر 39–52: تعيد الطرق الأربع لواجهة طبقة [dao] ببساطة مرجعًا إلى القوائم الأربع التي أنشأها المنشئ.
قد يبدو برنامج الاختبار كما يلي:
التعليقات واضحة بذاتها. تستخدم الأسطر 11–24 واجهة طبقة [dao]. لا توجد هنا أي افتراضات حول التنفيذ الفعلي للطبقة. في السطر 8، نقوم بإنشاء مثيل لطبقة [dao]. هنا نضع افتراضات: اسم الفئة ونوع المنشئ. هناك حلول تسمح لنا بتجنب هذه التبعية.
نتائج تشغيل هذا البرنامج النصي هي كما يلي:
7.4. طبقة [الأعمال]
![]() |
تنفذ طبقة [الأعمال] الواجهة التالية:
- تُرجع getClasses قائمة بفصول المرحلة الإعدادية؛
- تُرجع getMatieres قائمة المواد الدراسية؛
- تُرجع getStudents قائمة الطلاب؛
- تُرجع getGrades قائمة بالدرجات؛
- تُرجع getStatsForStudent الدرجات الخاصة بالطالب idStudent مع معلومات عنها: المتوسط المرجح، وأدنى درجة، وأعلى درجة.
ستستخدم طبقة [العرض] هذه الطرق فقط. ولا تحتاج إلى معرفة كيفية تنفيذها.
تُرجع طريقة getStatsForStudent كائنًا من النوع [StatsForStudent] على النحو التالي:
class StatsForEleve:
# constructeur
def __init__(self, eleve, notes):
# on mémorise les paramètres
self.eleve=eleve
self.notes=notes
# on s'arrête s'il n'y a pas de notes
if len(notes)==0:
return
# exploitation des notes
sommePonderee=0
sommeCoeff=0
self.max=-1
self.min=21
for note in notes:
valeur=note.valeur
coeff=note.matiere.coefficient
sommeCoeff+=coeff
sommePonderee+=valeur*coeff
if valeur<self.min:
self.min=valeur
if valeur>self.max:
self.max=valeur
# calcul de la moyenne de l'élève
self.moyennePonderee=float(sommePonderee)/sommeCoeff
# toString
def __str__(self):
# cas de l'élève sans notes
if len(self.notes)==0:
return "Eleve={0}, notes=[]".format(self.eleve)
# cas de l'élève avec notes
str=""
for note in self.notes:
str+="{0} ".format(note.valeur)
return "Eleve={0}, notes=[{1}], max={2}, min={3}, moyenne={4}".format(self.eleve, str, self.max, self.min, self.moyennePonderee)
- السطر 3: يأخذ المنشئ معلمتين:
- مرجع إلى الطالب من النوع [Student] الذي يتم حساب المقاييس له؛
- مرجع إلى درجاته، وهي قائمة من كائنات [Grade].
- السطران 8-9: إذا كانت قائمة الدرجات فارغة، نتوقف هنا.
- وإلا، الأسطر 11-25: يتم حساب المقاييس التالية:
- self.weightedAverage: متوسط الطالب المرجح بمعاملات المواد؛
- self.min: أدنى درجة للطالب؛
- self.max: أعلى درجة حصل عليها الطالب.
- السطر 28: طريقة عرض الفصل بالصيغة المحددة في السطر 36.
يمكن أن يكون نص الاختبار لهذه الفئة كما يلي:
يكون إخراج الشاشة كما يلي:
لنعد إلى بنية الطبقات لدينا:
![]() |
تنفذ فئة [Business] الطبقة [business] على النحو التالي:
class Metier:
# constructeur
def __init__(self,dao):
# on mémorise la référence sur la couche [dao]
self.dao=dao
#-----------
# interface
#-----------
# les indicateurs sur les notes
def getStatsForEleve(self,idEleve):
# Stats pour l'élève de n° idEleve
# recherche de l'élève
trouve=False
i=0
eleves=self.getEleves()
while not trouve and i<len(eleves):
trouve=eleves[i].id==idEleve
i+=1
# a-t-on trouvé ?
if not trouve:
raise RuntimeError("L'eleve [{0}] n'existe pas".format(idEleve))
else:
eleve=eleves[i-1]
# liste de toutes les notes
notes=[]
for note in self.getNotes():
# on ajoute à notes, toutes les notes de l'élève
if note.eleve.id==idEleve:
notes.append(note)
# on rend le résultat
return StatsForEleve(eleve,notes)
# la liste des classes
def getClasses(self):
return self.dao.getClasses()
# la liste des matières
def getMatieres(self):
return self.dao.getMatieres()
# la liste des élèves
def getEleves(self):
return self.dao.getEleves()
# la liste des notes
def getNotes(self):
return self.dao.getNotes()
- الأسطر 3–5: يتلقى المنشئ مرجعًا إلى طبقة [dao]. يجب أن تحتوي طبقة [business] على هذا المرجع. هنا، نوفره عبر منشئها. هناك حلول أخرى ممكنة. في بنية الطبقات الممثلة أفقيًا، يجب أن تحتوي كل طبقة على مرجع إلى الطبقة الموجودة على يمينها؛
- الأسطر 36-49: تقوم الطرق getClasses و getSubjects و getStudents و getGrades ببساطة بتفويض الاستدعاء إلى الطرق التي تحمل الأسماء نفسها في طبقة [dao]؛
- السطر 12: تتلقى الطريقة getStatsForStudent كمعلمة رقم هوية الطالب الذي سيتم إرجاع إحصائياته.
- السطر 17: سيتم البحث عن الطالب في قائمة جميع الطلاب؛
- الأسطر 18-20: حلقة البحث؛
- السطر 23: إذا لم يتم العثور على الطالب، يتم إصدار استثناء؛
- وإلا، السطر 25: يتم تخزين الطالب الذي تم العثور عليه؛
- الأسطر 28-31: نبحث في جميع صفوف المرحلة الإعدادية عن تلك التي تنتمي إلى الطالب المخزن؛
- بمجرد العثور عليها، يمكن إنشاء كائن StatsForEleve المطلوب.
7.5. طبقة [console]
![]() |
يتم تنفيذ طبقة [console] بواسطة البرنامج النصي التالي:
- السطر 10: إنشاء مثيل لكل من طبقتي [dao] و[business]. هذه هي التبعية الوحيدة التي يعتمد عليها كودنا في تنفيذ هاتين الطبقتين؛
- السطر 34: نستخدم واجهة طبقة [business]؛
- السطر 19: تزيل طريقة strip المسافات البادئة والختامية من السلسلة؛
- السطر 20: break يخرج من الحلقة؛
- السطر 24: محاولة تحويل السلسلة المدخلة إلى عدد صحيح عشري؛
- السطر 29:
okتكون صحيحة فقط إذا تم تنفيذ السطر 25؛ - السطر 31: تسمح continue بإعادة تشغيل الحلقة من المنتصف؛
- السطر 34: حساب المؤشرات؛
- السطر 35: يلتقط استثناء RuntimeError الذي قد ينشأ من طبقة [الأعمال].
فيما يلي مثال على التنفيذ:






