مخطّط الأصناف
درس 3 من 6
3مخطّط الأصناف
المخطّط الذي يصير كودًا وجداولًا حرفيًا. وأهم ما فيه ليس المستطيلات بل الأرقام على الخطوط، وقد نفّذنا المخطّط كما هو فأثبتت الأرقام نفسها، وكشفت خطأً في ترتيب التحقّقات.
ماذا تشاهد في الرسم: كل رقم قاعدة: العضو حتى خمس إعارات، والنسخة في إعارة واحدة على الأكثر، والكتاب نسخ كثيرة.
خارطة هذا الجزء
المفاهيم بالترتيب، اضغط أيًّا منها للقفز إليه:
طريقان إلى نفس الفكرة
مخطط الأصناف مثل شجرة العائلة مع عقود الزواج: من مرتبط بمن، وكم واحدًا يمكن أن يكون في كل طرف. الرقم الصغير على الخط («واحد» أو «كثير») هو أهم ما في الرسم، لأنه يقرر شكل الجداول والكود لاحقًا.
الصنف صندوق بسمات وتوابع، والتعدد 0..1 و1 و* يحدد المفاتيح الأجنبية وجداول الربط. التركيب composition يعني أن الجزء يموت مع الكل، والتجميع aggregation يعني أنه ينتقل. وصنف الارتباط يظهر حين تملك العلاقة نفسها بيانات، مثل تاريخ الاستعارة.
المتطلبات قبل البداية
الجزءان الأول والثاني
قواعد المكتبة الأربع، وأسماؤها المستخرجة: عضو، كتاب، نسخة، استعارة.
صنف واحد في أي لغة
تعرف كيف تكتب صنفًا بخصائص ودوال. سنحوّل المخطّط إلى بايثون ونشغّله.
استعداد للأرقام
«١» و«٠..١» و«٠..٥» و«*» ليست زينة: كل منها قاعدة تجارية مختلفة، وسنثبت ذلك بتشغيل الكود.
المفاهيم الأساسية
ما الأشياء في هذا النظام، وكيف تتصل، وبأي أعداد؟ لا ترتيب ولا زمن، هذه صورة ساكنة.
ثلاثة طوابق: الاسم، ثم الخصائص (بيانات)، ثم العمليات (أفعال). والرموز: + عام، و- خاص، و# محمي.
الرقم على الخط هو القاعدة التجارية نفسها. «نسخة ← استعارة» بتعدّدية 0..1 تقول إن النسخة عند عضو واحد أو لا أحد. لو كتبتها 0..* لصار النظام يسمح باستعارة النسخة الواحدة مرتين، رقم واحد، نظام مختلف.
حين يكون للعلاقة نفسها بيانات، تصير صنفًا. «الاستعارة» ليست خطًّا بين العضو والنسخة بل شيئًا له تاريخ إعارة وتاريخ إرجاع، وهذا أكثر ما يُنسى في النماذج المبتدئة.
ارتباط (خط): شيئان مستقلان يعرف أحدهما الآخر. تجميع (ماسة فارغة): جزء يمكن أن ينتقل. تركيب (ماسة مملوءة): جزء يموت مع الكل. وراثة (سهم مثلّث): «نوع من».
اسأل «هل س نوع من ص في كل حالة؟» لو كانت الإجابة «أحيانًا» فالصحيح تركيبة لا وراثة. والنماذج التي تفشل غالبًا ترث حيث كان يجب أن تحتوي.
كيف يعمل؟
شكل 1، مكتبتنا بأربعة أصناف. الماسة المملوءة تقول إن النسخة لا معنى لها بلا كتاب
شكل 2، كيف تختار نوع الخط: ثلاثة أسئلة تكفي
| التعدّدية | تُقرأ | في مثالنا |
|---|---|---|
1 | واحد بالضبط، إلزامي | النسخة تنتمي لكتاب واحد |
0..1 | صفر أو واحد | النسخة عليها استعارة نشطة أو لا |
0..5 | من صفر إلى خمسة | استعارات العضو النشطة |
1..* | واحد على الأقل | لو اشترطنا نسخة واحدة لكل كتاب |
0..* أو * | أي عدد، بما فيه صفر | نسخ الكتاب، والقاعدة الرابعة تسمح بصفر |
التمرين: نفّذ المخطّط، ثم اكسر كل قاعدة فيه
المخطّط أعلاه ليس رسمًا نظريًّا: كتبناه بايثون كما هو وشغّلناه. كل تعدّدية صارت سطرًا في الكود، وكل قاعدة صارت تحقّقًا:
class Book: # the bibliographic record
def __init__(self, isbn, title):
self.isbn, self.title = isbn, title
self.copies = [] # Book 1 --- 0..* Copy
def available(self):
return [c for c in self.copies if c.loan is None]
class Copy: # the physical thing on the shelf
def __init__(self, book, barcode):
self.book, self.barcode = book, barcode
self.loan = None # Copy 1 --- 0..1 Loan
book.copies.append(self)
class Loan: # the association class
def __init__(self, member, copy, today):
if copy.loan is not None:
raise ValueError("copy %s is already on loan" % copy.barcode)
if len(member.active()) >= MAX_LOANS:
raise ValueError("%s already holds %d copies" % (member.name, MAX_LOANS))
if member.overdue(today):
raise ValueError("%s is overdue and may not borrow" % member.name)
self.member, self.copy = member, copy
self.borrowed_on, self.due_on = today, today + timedelta(days=LOAN_DAYS)
self.returned_on = None
copy.loan = self
member.loans.append(self)0..* صار قائمة داخل Book، و0..1 صار حقلًا واحدًا داخل Copy. القائمة تعني «كثير»، والحقل الواحد يعني «واحد أو لا شيء».Loan ليس خطًّا: له ثلاثة تواريخ ودالة إرجاع. ولهذا استطاع أن يحمل التحقّقات كلها.$ python3 library.py book 'Dune' has 7 copies, 7 available -- rule 2: one copy, one member at a time -- refused: copy D-01 is already on loan -- rule 3a: at most 5 copies -- Muath now holds 5 copies refused: Muath already holds 5 copies -- rule 3b: no borrowing while overdue -- overdue loans on 2026-03-21 : 5 refused: Muath already holds 5 copies <-- the wrong reason -- returning frees the copy, and the member -- copy D-01 free again: True borrowing works again: 1 active loan
ولمّا عزلنا القاعدة الثالثة-ب على عضو عنده استعارة واحدة متأخّرة، ظهرت الرسالة الصحيحة. واختبرنا القاعدة الرابعة باستنفاد كل النسخ:
$ python3 order.py -- rule 3b isolated: overdue with only ONE loan -- active loans: 1 | overdue: 1 refused: Amal is overdue and may not borrow <-- correct here -- rule 4: exhaust every copy of a book -- copies: 3 | available: 0 | title still: Solaris
| في المخطّط | في الكود | في قاعدة البيانات |
|---|---|---|
| صنف | class | جدول |
| خصيصة | حقل في الكائن | عمود |
1 ← 0..* | قائمة في الطرف الواحد | مفتاح أجنبي في الطرف الكثير |
0..1 | حقل واحد قابل للعدم | مفتاح أجنبي فريد وقابل للعدم |
| صنف ارتباط | صنف بتواريخه | جدول وصل بأعمدة إضافية |
| تركيب (ماسة مملوءة) | الجزء يُنشأ من الكل | حذف متتالٍ (cascade) |
اعتقاد شائع × الحقيقة
| الاعتقاد | الحقيقة |
|---|---|
| «التعدّدية تفصيلة جمالية.» | تغيير 0..1 إلى 0..* يسمح باستعارة النسخة الواحدة مرتين. رقم واحد، نظام مختلف. |
| «الاستعارة مجرّد خط بين العضو والنسخة.» | الخط لا يحمل تاريخًا. ولمّا احتجنا تاريخ إعارة وإرجاع صار الخط صنفًا. |
| «التجميع والتركيب شيء واحد.» | التركيب يعني أن الجزء يموت مع الكل (حذف متتالٍ)، والتجميع يعني أنه ينتقل. قرار حذف مختلف. |
| «الوراثة تقلّل التكرار دائمًا.» | تقلّله حين تكون «نوع من» صحيحة في كل حالة. وإلا فهي تربط صنفين لا علاقة لهما بعقد خاطئ. |
| «المخطّط الصحيح يعني نظامًا صحيحًا.» | مخططنا كان صحيحًا، والتشغيل كشف أن ترتيب التحقّقات يعطي المستخدم سببًا مضلّلًا. |
| «نرسم كل الخصائص والدوال.» | ارسم ما يجيب عن سؤال المخطّط. قائمة كاملة من ثلاثين حقلًا لا تُقرأ ولا تُراجع. |
جرّب بنفسك
النسخة لا معنى لها بلا كتاب، وتُحذف معه. أي خط؟
«يموت الجزء مع الكل» هي تعريف التركيب، وتصير في قاعدة البيانات حذفًا متتاليًا. التجميع يعني أن الجزء ينتقل إلى كل آخر، وهذا غير صحيح هنا: نسخة من «Dune» لا تصبح نسخة من كتاب آخر.
كتبت 0..* بين العضو والاستعارة بدل 0..5. ما أول عطل سيظهر؟
التعدّدية هي القاعدة. و0..* تعني «أي عدد»، فالحدّ الذي وضعه أمين المكتبة يختفي من النموذج، ولن تجد سطرًا واحدًا في الكود يذكّرك به.
بطاقات مراجعة سريعة
0..1 تعني؟1←0..* في قاعدة البيانات؟تحقّق من فهمك
1. بجملة واحدة: لماذا صارت «الاستعارة» صنفًا لا خطًّا؟
لأن للعلاقة نفسها بيانات: تاريخ الإعارة وتاريخ الاستحقاق وتاريخ الإرجاع. والخط لا يملك مكانًا يحفظ فيه قيمة، فأي علاقة تحتاج حقلًا واحدًا تصير صنفًا، وهذا بالضبط ما يسمّى «صنف الارتباط».
2. اشرح بالتفصيل أثر تغيير تعدّدية «نسخة ← استعارة» من 0..1 إلى 0..*.
في المخطّط يبدو التغيير حرفًا. وفي الكود يتحوّل copy.loan من حقل واحد إلى قائمة، فيسقط التحقّق if copy.loan is not None لأنه لم يعد له معنى. والنتيجة أن نسختين من الاستعارة النشطة يمكن أن تُسجّلا على النسخة نفسها، فيستعير شخصان الكتاب الواحد المادي، وهو شيء مستحيل في الواقع. وفي قاعدة البيانات يسقط قيد الفرادة على المفتاح الأجنبي. قاعدة واحدة تختفي من ثلاث طبقات بسبب حرف.
3. (اكتشف الخطأ) زميل ورّث Member من Book لأن «الاثنين لهما اسم». ما الخطأ؟
اشتراك خصيصة ليس علاقة «نوع من». العضو ليس نوعًا من الكتاب، والوراثة عقد يقول إن كل ما يصحّ على الكتاب يصحّ على العضو، فسيورّث isbn وcopies إلى العضو بلا معنى. الصحيح: لا علاقة بينهما، أو، إن تكرّر الاسم في أصناف كثيرة، صنف أساس مجرّد لما هو مشترك فعلًا، لا لما هو متشابه شكلًا.
4. أمين المكتبة يقول: «الكتاب النادر لا يُستعار، يُقرأ في القاعة فقط». كيف تنمذجها؟
ثلاثة خيارات ولكل ثمن. حقل is_reference: bool على Copy: أبسط شيء، ويكفي لو كان الفرق قاعدة واحدة. وراثة ReferenceCopy من Copy: صحيحة مفهوميًّا، لكنها تضيف صنفًا لفرق سلوكي صغير. نوع مرجعي (صنف CopyType): الأفضل لو توقّعنا أنواعًا أخرى لها قواعد. والقرار يتبع سؤالًا واحدًا: هل سيزيد عدد الأنواع؟ إن كان الجواب نعم فالثالث، وإلا فالأول.
5. (قرار) الفريق يريد توليد الكود من المخطّط تلقائيًّا. ما ردّك؟
أن التوليد يحلّ الجزء السهل ويترك الصعب. الأصناف والحقول والمفاتيح الأجنبية تُولَّد فعلًا، لكن ترتيب التحقّقات ورسائل الخطأ والمسارات البديلة لا يحملها المخطّط أصلًا، وقد رأينا بأنفسنا أن هذا بالضبط هو موضع العطل. وأخطر من ذلك أن الكود المولَّد يخلق نسخة ثانية من الحقيقة: إما أن يُحرَّر المخطّط دائمًا (فيصير عملًا مضاعفًا) أو يتعفّن (فيصير مضلّلًا). ولّد الهيكل مرة واحدة في البداية إن أردت، واعتبر الكود بعد ذلك هو المصدر.
مصادر هذا الدرس
مرتّبة بالنوع. ابدأ بالتوثيق الرسمي إذا تبي المرجع الدقيق، وبالمقال أو الفيديو إذا تبي الشرح أولًا.
خلّصت الدرس؟
سجّل الدخول ليُحفظ تقدّمك.