الأصناف المتداخلة والداخلية
جزء من قسم البرمجة كائنية التوجه في رحلة C++ على Coddy. الدرس 38 من 104.
الفئة المتداخلة هي فئة مُعرَّفة داخل فئة أخرى. تساعد هذه التقنية على تنظيم التعليمات البرمجية من خلال تجميع الأنواع المرتبطة معًا، ويمكنها تعزيز التغليف من خلال إخفاء تفاصيل التنفيذ.
class LinkedList {
public:
class Node { // فئة متداخلة
int data;
Node* next;
public:
Node(int d) : data(d), next(nullptr) {}
friend class LinkedList;
};
private:
Node* head;
public:
LinkedList() : head(nullptr) {}
};الفئة المتداخلة Node مرتبطة ارتباطًا وثيقًا بـ LinkedList — فهي لا تكون منطقية إلا في ذلك السياق. ومن خلال تداخلها، نوضح هذه العلاقة ونتجنب تلويث النطاق العام باسم عام مثل "Node".
تتبع الفئات المتداخلة قواعد الوصول نفسها التي تعلمتها. لا يمكن الوصول إلى الفئة المتداخلة المُعلنة في القسم الخاص إلا داخل الفئة الخارجية.
عند التصريح عنه باستخدام public، يمكن للكود الخارجي استخدامه باستخدام عامل النطاق:
LinkedList::Node node(42); // فقط إذا كان Node publicتفصيل مهم حول الاتجاه: تُعدّ Nested class عضوًا في class الحاوية، ولها حقوق الوصول نفسها التي يتمتع بها أي عضو آخر، لذا يمكنها الوصول إلى الأعضاء الخاصة في class الحاوية من دون إعلان friend. لكن العكس ليس تلقائيًا: فلا تملك class الحاوية صلاحية الوصول إلى الأعضاء الخاصة في Nested class.
لهذا السبب يصرّح Node بالعبارة friend class LinkedList; في المثال أعلاه: يمنح تصريح friend الصنف LinkedList المحيط إمكانية الوصول إلى الأعضاء الخاصة بـ Node، وهو الاتجاه الوحيد الذي لا يحدث تلقائيًا.
التحدي
سهللنَبْنِ نظامًا بسيطًا لإدارة المهام، حيث تُنظَّم المهام ضمن مشروع. هذا سيناريو مثالي للفئات المتداخلة: لا يكون لـ Task معنى إلا ضمن سياق Project، لذلك سنضعها متداخلة بداخله للحفاظ على تنظيم الشفرة وتجنّب تلويث namespace العام.
ستنشئ ملفين لتنظيم شفرتك:
Project.h: عرّف فئةProjectتحتوي على فئةTaskمتداخلة.يجب التصريح عن فئة
Taskالمتداخلة ضمن القسم public فيProjectحتى يمكن الوصول إليها من الخارج. يجب أن تخزّن اسم مهمة (string) وحالة اكتمال (bool). امنحها مُنشئًا يأخذ الاسم ويضبط المهمة على أنهاincompleteافتراضيًا. أدرج الدوالcomplete()لوضع علامةdoneعليها، وisComplete()للتحقق من حالتها، وgetName()لاسترداد الاسم.يجب أن تخزّن فئة
Projectنفسها اسم مشروع (string) ومجموعة من مؤشراتTask. وفّر مُنشئًا يأخذ اسم المشروع، ودالةaddTask(const std::string& taskName)تنشئ مهمة جديدة وتضيفها إلى المشروع، ودالةcompleteTask(const std::string& taskName)تبحث عن مهمة بالاسم وتكملها، ودالةdisplayStatus()تعرض اسم المشروع متبوعًا بحالة كل مهمة.بما أن
Taskمتداخلة داخلProject، يمكن لفئةProjectالتعامل مع المهام مباشرةً. لا تنسَ تنظيف المهام المخصّصةdynamicallyفي المُدمّر.main.cpp: اجمع كل شيء معًا من خلال قراءة اسم مشروع واسمي مهمتين من الإدخال (ثلاثة أسطر إجمالًا). أنشئProjectبالاسم المُعطى، وأضف المهمتين إليه، ثم اعرض الحالة الأولية. أكمل المهمة الأولى واعرض الحالة مرة أخرى لإظهار التغيير.يجب أن يكون تنسيق الإخراج الخاص بـ
displayStatus()كما يلي:Project: <project_name> - <task_name>: [incomplete] - <task_name>: [complete]استخدم
[complete]أو[incomplete]بناءً على حالة كل مهمة.
يوضح هذا التحدي كيف تساعد الفئات المتداخلة في تنظيم الأنواع المرتبطة: فئة Task مرتبطة ارتباطًا وثيقًا بـ Project، ولن يكون لها معنى بمفردها. ومن خلال تداخلها، نوضّح هذه العلاقة بجلاء في بنية شفرتنا.
جرّب بنفسك
#include <iostream>
#include <string>
#include "Project.h"
using namespace std;
int main() {
// اقرأ المدخلات
string projectName;
string task1Name;
string task2Name;
getline(cin, projectName);
getline(cin, task1Name);
getline(cin, task2Name);
// TODO: أنشئ Project بالاسم المعطى
// TODO: أضف كلتا المهمتين إلى المشروع
// TODO: اعرض الحالة الأولية
// TODO: أكمل المهمة الأولى
// TODO: اعرض الحالة مرة أخرى لإظهار التغيير
return 0;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1أساسيات الـ OOP
الملفات الخارجيةبناء وتجميع (Compilation) لغة C++ملفات الـ Header وملفات الـ Sourceالـ Namespaces والـ Scopeمقدمة في الـ OOP في C++الـ Classes مقابل الـ Objectsالمؤشر 'this'الـ Methods (الدوال الأعضاء)الـ Attributes (بيانات الأعضاء)أساسيات الـ Ctors والـ Dtorsمراجعة - آلة حاسبة بسيطة4خصائص الـ Class
أعضاء الـ Instance مقابل الـ Staticدوال الـ Getters والـ Settersدوال الأعضاء الثابتة (Const)الكلمة المفتاحية Mutableالدوال والمتغيرات الساكنة (Static)الدوال والفئات الصديقة (Friend)مراجعة - مدير الحساب البنكي7الوراثة
الوراثة الأساسيةمستويات الوصول في الوراثةترتيب استدعاء الـ Ctor والـ Dtorإعادة تعريف الدوال (Method Overriding)الدوال الافتراضية والـ VTableالوراثة المتعددةالوراثة الافتراضيةمراجعة - هيكلية الموظفين10نظرة عامة على STL
نظرة عامة وفلسفة STLحاويات STLالـ Iteratorsخوارزميات STLالـ Functors وتعبيرات Lambdaمراجعة - تكرار الكلمات13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factory و Abstract Factoryنمط Builderنمط Observerنمط Strategy2إدارة الذاكرة
ذاكرة Stack مقابل Heapالمؤشرات والمراجعالذاكرة الديناميكية (new/delete)المؤشرات الذكية في C++RAII في C++مراجعة - مدير المصفوفات الديناميكية5التغليف (Encapsulation)
محددات الوصول في C++محددات الوصول بشكل متعمقإخفاء المعلوماتStruct مقابل Classالأصناف المتداخلة والداخليةمراجعة - نظام سجلات الطلاب8تعدد الأشكال (Polymorphism)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالتحميل الزائد للدوال (Function Overloading)مراجعة الدوال الافتراضية (Virtual Functions)الدوال الافتراضية البحتة (Pure Virtual Functions)الأصناف المجردة (Abstract Classes)تصميم الواجهات (Interface) في C++التحويل الديناميكي (Dynamic Casting) و RTTIملخص - حاسبة الأشكال11مفاهيم OOP المتقدمة
التركيب مقابل الوراثةالـ Mixins عبر CRTPنمط Pimplمحو النوع (Type Erasure)أصناف Enum والأنواع القويةمعالجة الاستثناءات في OOPتسلسلات الاستثناءات المخصصة14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط قالب الطريقةنمط الحالةنمط التركيبRAII كنمط3المنشئات والموادم
المنشئ الافتراضيالمنشئ ذو المعاملاتمنشئ النسخمنشئ النقلقوائم تهيئة المنشئالمنشئات المفوضةتعمق في الموادمقاعدة الثلاثة / الخمسة / الصفرمراجعة - فئة String6تحميل العوامل (Operator Overloading)
مقدمة في تحميل العواملتحميل العوامل الحسابيةتحميل عوامل المقارنةعوامل Streamتحميل عامل التعيينتحميل العوامل [] و ()عوامل تحويل النوعمراجعة - Matrix Class9القوالب
قوالب الدوالقوالب الأصنافتخصيص القوالبالقوالب المتغيرةأساسيات SFINAE و Type Traitsمراجعة - الحاويات العامةتدرّب بنفسك: مترجم C++ عبر الإنترنت