البحث في الموقع
المحتوى عن 'الوراثة'.
-
كان الغرض من البرمجة الكائنية (Object oriented programing اختصارًا OOP) هو السماح للمبرمجين بتسهيل تقسيم البرامج حسب وظيفتها؛ فالمبرمجون يُنشؤون "كائنات" ويضبطون بعض الخاصيات ثم يطلبون من تلك الكائنات أن تقوم بأشياءٍ معيّنة. مثلًا لدينا "الأكواب" عبارة عن كائنات، وتلك الأكواب لها خاصيات معيّنة مثل المادة المصنوعة منها (زجاج، أو بلاستيك، أو معدن) والسعة القصوى لها، كما يمكن إجراء عمليات عليها مثل ملء كوب. لكن عمومًا تنطوي كل تلك الأنواع تحت لواء "الأكواب" وإن اختلفت خاصياتها. أنُشِئت الأصناف (classes) في PHP لغرض التجريد (abstraction) والتغليف (encapsulation). والصنف هو مجموعة من المتغيرات والدوال التي تؤدي وظيفة مشابهة؛ وتساعد الأصناف في تجنب المشاكل الأمنية وذلك بفصل الواجهة (interface) عن طريقة التطبيق (implementation)، وتضيف الأصناف قيودًا إلى الوصول إلى المتغيرات والدوال. يُعرَّف الصنف بكتابة الكلمة المحجوزة class يليها اسم الصنف، ومن المستحسن اتباع طرق الكتابة التقليدية في أسماء الأصناف، حيث يبدأ اسم الصنف بحرفٍ كبير؛ هذا مثالٌ عن تعريف صنف بسيط: <?php class SimpleClass { // التعليمات البرمجية } ?> الشيفرة الموجودة ضمن الصنف لا تنفذ مباشرةً، إذ علينا أولًا أن قومة بإنشاء بإشاء كائن (object) من ذاك الصنف، الكائن هو نسخة من الصنف تكون جميع متغيرات ودوال ذاك الصنف جزءًا من خاصياتها (properties). يمكن إنشاء كائن من صنف كالآتي: object_name = new ClassName(); الخاصيات والدوال المتغيرات التي تكون عضوًا بالصنف تسمى خاصيات (properties)، وتُعرَّف عبر استخدام إحدى محددات الوصول public (عام) أو protected (محمي) أو private (خاص)، ويأتي بعدها التعريف الاعتيادي للمتغيرات، ويمكن أن تُسنَد القيم إليها مباشرةً، لكن يجب أن تكون تلك القيم ثابتة، أي لا تحتوي تعابير رياضية أو قيم معادة من دوال. أما الدوال الأعضاء في الأصناف، فتسمى توابع methods، وتُعرَّف كغيرها من الدوال، لكن مع الانتباه إلى ضرورة تحديد مجال الدالة (عامة أو محمية أو خاصة) كما في المثال الآتي: <?php class SimpleClass { // تعريف متغير أو خاصية public $var = 'a default value'; // تعريف دالة public function displayVar() { echo $this->var; } } ?> المتغير $this متوفر داخل دوال الصنف تلقائيًا (أي ليس عليك إنشاؤه)، وهو يشير إلى الكائن الذي قام بإنشاء نسخة من الصنف، ويُستعمل للوصول إلى الخاصيات أو الدوال الموجودة في الصنف. لاحظ عدم وجود رمز الدولار ($) قبل اسم المتغير عند الوصول إليه عبر $this. مثالٌ عن ما سبق: <?php class ClassName { // تعريف متغير public public $class_variable; // الدالة البانية function __construct() { $this->class_variable = 60 * 60; echo "this is the constructor <br>"; } // إعادة متغير في الصنف function get_global() { return $this->class_variable; } // تعديل متغير في الصنف function set_global($value) { $this->class_variable = $value; } // إظهار قيمة متغير في الصنف public function reset_display() { $this->private_function(); echo $this->get_global()." <br>"; } // دالة خاصة private function private_function() { $this->class_variable = 60 * 60; } } $object_name = new ClassName(); echo $object_name->get_global()."<br>"; $object_name->set_global(231); echo $object_name->get_global()."<br>"; $object_name->reset_display(); ?> لاحظ المفاهيم الآتية في المثال السابق التي شرحنا بعضها أعلاه: المتغير الذي يكون متاحًا للوصول في كل الصنف ($class_variable) يُعرَّف بالكلمة المحجوزة public؛ أما المتغيرات المحلية المُعرَّفة داخل دالة لا يمكن الوصول إليها إلا من تلك الدالة. يمكن الوصول إلى متغيرات أو دوال الصنف باستخدام $this->variable_name; و $this->function_name(); على التوالي وبالترتيب. إذ أنَّ $this يُشير إلى الصنف نفسه. الدالة البانية (constructor) هي دالة ذات معنى خاص في الأصناف؛ حيث تُشغَّل هذه الدالة عندما نُنشِئ كائنًا من ذاك الصنف، ويمكن أيضًا إرسال وسائط إلى الدالة البانية. سنشرح الدالة البانية والهادمة لاحقًا. ناتج السكربت السابق: it is constructor 3600 231 3600 ملاحظة: لا يُنصح بتعديل قيم خاصيات الفئات يدويًا، وإنما استعمل دوالًا خاصةً لهذا الغرض، فمثلًا لو كان عندك صنفٌ وظيفته حساب كمية الماء اللازمة لمدينة ما، ولديك خاصية اسمها population تُمثِّل عدد سكان المدينة، فلا تسمح بالوصول إليها مباشرةً، وإنما اكتب دالةً اسمها setPopulation مثلًا، واجعلها تُعدِّل قيمة عدد السكان وكل ما يتعلق بها من الحسابات تلقائيًا: $obj->setPopulation(200000); الوراثة نحاول دومًا عندما نبرمج ألّا نعيد كتابة الشيفرة مرارًا وتكرارًا، وأن نفصل بين تطبيق الشيفرة والواجهة (interface) لأسباب تتعلق بالحماية. الخيار الجديد المتاح أمامنا الآن هو استعمال الوراثة للقيام بالأمور السابقة بكفاءة. الوراثة في البرمجة كالوراثة في حياتنا، إذ يرث والدك بعض الخاصيات من جدك ويُعدِّلها أيضًا، وأنت أيضًا ترث بعض الخاصيات من والدك وتعدلها وتضيف غيرها. تسمى هذه العملية في البرمجة بالمصطلح inheritance. يمكن لأي صنف أن يوسِّع أو يشتق (extend) أصنافًا أخرى ويمكنه أن يصل إلى المتغيرات والدوال العامة والمحمية فيها فقط (أي لا يستطيع أن يصل إلى الدوال الخاصة private). <?php /** * الوراثة في PHP */ class Grandfather { // متغير عام public $global_variable; // الدالة البانية للصنف grandfather function __construct() { $this->global_variable = 56; echo "I am grandfather <br>"; } function default_function() { echo "this is default function in grandfather <br>"; } private function private_function() { echo "this is private function in grandfather <br>"; } protected function protected_function() { echo "this is protected function in grandfather <br>"; } public function public_function() { echo "this is public function in grandfather <br>"; } } /** * هذا صنف فرعي مشتق من الصنف Grandfather * وسيرث كل خاصياته عدا الخاصة (private) منها */ class Father extends Grandfather { // متغير عام public $father_var; function __construct() { // السطر الآتي مساوٌ للسطر => parent::__construct(); Grandfather::__construct(); $this->father_var = 256; echo "I am father <br>"; } public function display_all() { $this->default_function(); $this->protected_function(); $this->public_function(); echo "I am father's display_all <br>"; parent::public_function(); } } /** * هذا الصنف الابن يرث من الصنف الأب * ويرث أيضًا (بشكلٍ غير مباشر) من الصنف الجد */ class Child extends Father { // الدالة البانية في الصنف الابن function __construct() { Grandfather::__construct(); echo "I am child <br>"; } // يُعدِّل الابن في دالة موجودة في الأب // تسمى هذه العملية «إعادة تعريف الدوال» function display_all() { echo "function from father<br>"; // استدعاء دالة من الصنف الأب parent::display_all(); echo "new added in child<br>"; } } $obj = new Father(); $obj->display_all(); echo "<br><br><br>Child object call<br><br>"; $obj2 = new Child(); $obj2->display_all(); ?> الناتج: I am grandfather I am father this is default function in grandfather this is protected function in grandfather this is public function in grandfather I am father's display_all this is public function in grandfather Child object call I am grandfather I am child function from father this is default function in grandfather this is protected function in grandfather this is public function in grandfather I am father's display_all this is public function in grandfather new added in child يعطي المثال السابق صورةً كاملةً عن الوراثة، لنحاول فهمه: الصنف Child يرِث من الصنف Father، الذي بدوره يرث الصنف Grandfather؛ وهذا يُسمى الوراثة متعددة المستويات. الصنف الفرعي (subclass أي الصنف الذي يقوم بالوراثة) يمكنه الوصول إلى جميع الخاصيات غير الخاصة (private) للصنف الموروث (يسمى أيضًا superclass). يمكن للصنف الفرعي أن يستدعي دوال الصنف الموروث عبر استعمال الصيغة الآتية parent::function_name() (يمكن استعمالها لمستوى وراثة وحيد فقط، أي يمكن للصنف Child أن يستعملها لاستدعاء دوال الصنف Father، ويمكن للصنف Father أن يستعملها للصنف Grandfather؛ لكن لا يمكن أن يستعملها الصنف Child لاستدعاء دوال الصنف Grandfather.) أو الصيغة الآتية SuperClass_name:function_name() التي يمكن استعمالها لاستدعاء دوال الصنف Grandfather من الصنف Child. يمكن أن يُعدِّل الصنف الفرعي في دوال الصنف الأب، وذلك يُعرَف بإعادة التعريف (overriding). لا تدعم لغة PHP الوراثة المتعددة؛ أي لا يمكن للصنف أن يرث أكثر من صنف واحد. محددات الوصول لقد تطرقنا سابقًا إلى موضوع محددات الوصول بشكل مبسط، حان الوقت الآن لشرحها بالتفصيل. هنالك كلماتٌ محجوزةٌ تسمى محددات الوصول توضع قبل تعريف المتغيرات أو الدوال الأعضاء في الصنف، وتعيّن مَن الذي يستطيع الوصول إلى ذاك المتغير أو الدالة، وهي: public (عام): ذاك المتغير أو الدالة يمكن الوصول إليه من داخل الصنف أو من خارجه private (خاص): لا يمكن الوصول إلى المتغير أو الدالة إلا من داخل الصنف نفسه protected (محمي): يسمح بالوصول إلى المتغير أو الدالة من الصنف نفسه والصنف المُشتَق منه فقط final (نهائي): هذه الدالة لا يمكن إسناد قيمة أخرى إليه أو تعريفها في الأصناف المُشتقَة (لا يمكن استخدام final مع الخصائص/المُتغيّرات). نستعمل عادةً المُحدِّد public للخاصيات أو الدوال التي تريد الوصول إليها من خارج الصنف، أما private فتستعمل للأجزاء الداخلية التي لا يلزم الوصول إليها من خارج الصنف، أما protected فهي للخاصيات التي تستعمل في بنية الصنف (كما في private) لكن من المقبول توريثها إلى أصنافٍ أخرى كي يعدلوها بما يلائم. فمثلًا، لو كنا نكتب صنفًا للاتصال بقاعدة البيانات، فسيكون مقبض الاتصال (connection handle) لقاعدة البيانات مخزنًا في متغير خاص، لأنه يستعمل داخل الصنف فقط، ولا يجب أن يكون متوفرًا لمستخدم هذا الصنف؛ أما عنوان الشبكة لمضيف خادوم قواعد البيانات، فهو خاص بالصنف، ولا يجب على المستخدم تعديله، لكن من المقبول تعديله من صنفٍ يرث هذا الصنف. أما الطلبيات التي تُجرى على قاعدة البيانات، فيجب أن تكون عامة، كي يستطيع مستخدم الصنف الوصول إليها. الدالة البانية والدالة الهادمة ذكرنا الدالة البانية سابقًا ومررنا عليها سريعًا، وقلنا وقتها أنَّ الدالة البانية تُنفَّذ عند إنشاء كائن من الصنف، وهي مناسبة لعمليات تهيئة المتغيرات وإعطائها قيمةً ابتدائيةً. تحمل هذه الدالة اسم __construct. يجدر بالذكر أنَّ الدالة البانية للصنف الأب لا تستدعى تلقائيًا عند إنشاء كائن للصنف الابن، ويجب استدعاؤها يدويًا عبر parent::__construct() ضمن الدالة البانية للصنف الابن. لكن إن لم تُعرَّف دالة بانية للصنف الابن، فسيرث الدالة البانية للصنف الأب تلقائيًا. مثالٌ عليها سيوضح ما سبق: <?php class BaseClass { function __construct() { print "In BaseClass constructor\n"; } } class SubClass extends BaseClass { function __construct() { parent::__construct(); print "In SubClass constructor\n"; } } class OtherSubClass extends BaseClass { // سيرث هذا الصنف الدالة البانية للصنف الأب } // ستنفذ الدالة البانية للصنف BaseClass $obj = new BaseClass(); // ستنفذ الدالة البانية للصنف BaseClass // وستنفذ الدالة البانية للصنف SubClass $obj = new SubClass(); // ستنفذ الدالة البانية للصنف BaseClass $obj = new OtherSubClass(); ?> أما الدالة الهادمة، فهي الدالة التي تُنفَّذ عندما لا يعود الكائن موجودًا، وتُعرَّف -كما الدالة البانية- بذكر اسمها __destruct، ولا تستدعى الدالة الهادمة للصنف الأب إن أُعيد تعريفها في الصنف الابن. <?php class MyDestructableClass { function __construct() { print "In constructor\n"; $this->name = "MyDestructableClass"; } function __destruct() { print "Destroying " . $this->name . "\n"; } } $obj = new MyDestructableClass(); // الناتج: In constructor echo "We will destroy \$obj \n"; unset($obj); // الناتج: Destroying MyDestructableClass echo '$obj Destroyed'; ?> معرفة نوع الكائنات لقد رأيت كيف أنَّ الوراثة هي أمرٌ مهمٌ في البرمجة الكائنية، ولكن قد تختلط عليك الكائنات، ولن تدري لأي صنفٍ تنتمي. يأتي الحل مع الكلمة المحجوزة instanceof التي يمكن استعمالها كأحد المعاملات، فستُعيد TRUE إن كان الكائن المذكور اسمه على يسارها تابعًا لصنفٍ ما أو لصنفٍ مشتقٍ من الصنف المذكور على يمينها. على سبيل المثال: $obj = new SubClass(); if ($obj instanceof SubClass) { } if ($obj instanceof BaseClass) { } ناتج العبارتان الشرطيتان السابقتان هو TRUE لأن $obj هو كائنٌ ينتمي إلى الصنف SubClass (أول عبارة شرطية)، وينتمي إلى صنفٍ مشتقٍ من BaseClass (العبارة الشرطية الثانية). أما لو أردت أن تعلم إن كان الكائن ينتمي إلى صنفٍ مشتقٍ من الصنف المذكور، فاستعمل الدالة is_subclass_of()، الذي تقبل وسيطين، أولهما هو الكائن الذي سنختبره، والثاني هو اسم الصنف الأب. <?php class BaseClass {} class SubClass extends BaseClass {} $obj = new SubClass(); if ($obj instanceof SubClass) { echo 'instanceof is TRUE'; } if (is_subclass_of($obj, 'SubClass')) { echo 'is_subclass_of is TRUE'; } ?> ستجد أن instanceof في المثال السابق ستعطي TRUE، بينما ستعطي is_subclass_of() القيمة FALSE، لأن $obj ليس كائنًا لصنف مشتق من الصنف SubClass، وإنما هو من الصنف SubClass نفسه. تحديد الصنف في معاملات الدوال لنقل أنك تريد تمرير كائن من صنف معيّن إلى دالة، ولا تريد السماح بتمرير سلسلة نصية أو رقم لها بدلًا من الكائن ذي الصنف المحدد؛ تستطيع فعل ذلك بذكر اسم الصنف قبل المعامل أثناء تعريف الدالة، كما يلي: <?php class Numbers { public function print_random() { echo rand(, 10); } } class Chars {} function random (Numbers $num) { $num->print_random(); } $char = new Chars(); random($char); // fatal error: Argument 1 passed to random() must be an instance of Numbers ?> الدوال "السحرية" عندما تشاهد دالةً يبدأ اسمها بشرطتين سفليتين، فاعلم أنها دالة سحرية (Magic function)، وهي متوفرة من PHP، وتحجز PHP جميع أسماء الدوال التي تبدأ بشرطتين سفليتين على أنها دالة سحرية، لذا يُنصَح بتجنب استعمال هذا النمط من التسمية لتلافي حدوث تضارب في المستقبل مع الدوال السحرية التي قد تعرفها الإصدارات الحديثة من PHP. لقد رأينا سابقًا الدالتين البانية __construct() والهادمة __destruct()، وسنتحدث هنا عن الدوال __get() و __set() و __call() و __toString(). تُعرَّف الدالتان __get() و __set() داخل الأصناف، تستعمل الدالة __get() عندما تحاول قراءة متغير غير مُعرَّف من متغيرات الصنف، وتُستعمَل الدالة __set() عند محاولة إسناد قيمة لمتغير غير موجود، انظر إلى المثال الآتي: <?php class MagicClass { public $name = 'Magic'; private $age = 123; public function __get($var) { echo "getting: $var "; echo $this->$var; } public function __set($var, $value) { echo "setting: $var to $value"; $this->$var = $value; } } $obj = new MagicClass(); echo $obj->name; // الناتج: Magic $obj->age = 123; // أصبح بإمكاننا -باستعمال الدوال السحرية المُعرفة في الصنف- الوصول إلى متغير ذي وصولٍ خاص، لا يجدر بنا فعل ذلك عمومًا. echo $obj->age; // الناتج: getting: age 123 echo $obj->last_name; // ستحاول الدالة __get الحصول على قيمة الخاصية last_name، لكنها غير موجودة، وسيظهر خطأ من مرتبة Notice لإشارة إلى ذلك. ?> قد نستفيد من الدالتين __get() و __set() بإظهار رسالة خطأ عند محاولة الوصول إلى عناصر غير موجودة (أو لا يُمسَح لنا بالوصول إليها). أما دالة __call() فغرضها مشابه لغرض __get() إلا أنها تُستعمَل عند محاولة استدعاء دالة غير موجودة. ستستدعى الدالة السحرية __toString() في حال تمت محاولة طباعة الكائن كسلسلة نصية. هذه الدالة بسيطة جدًا وتعمل كما يلي: <?php class MagicClass { public function __toString() { return "I'm Magical! :-)"; } // ... } $obj = new MagicClass(); echo $obj; ?> مصادر مقالات Classes in PHP و Inheritance in PHP لصاحبها Harish Kumar.فص ل Objects في كتاب Practical PHP Programming. فصل البرمجة غرضية التوجه في كتاب تعلم البرمجة بلغة PHP. صفحات Introduction و The Basics و Constructors and Destructors و Visibility و Object Inheritance و Magic Methods و Type Hinting في دليل PHP وغيرها.
- 4 تعليقات
-
- 8
-
- php
- البرمجة كائنية التوجه
- (و 9 أكثر)
-
مقدمة إلى المفهوم الكائني تُعتبر لغة سي شارب لغة برمجة كائنيّة صرفة pure object oriented programming language فكلّ ما تشاهده أمامك في سي شارب عبارة عن كائن. سيكون هذا الدّرس نظريًّا بعض الشيء ولكن فهمه بالشكل الصحيح يُعدّ أمرًا حيويًّا للبرمجة باستخدام سي شارب. ولنكن واقعيين، فإنّ هذا الدرس يُعتبر مدخلًا مبسّطًا للغاية إلى هذا الموضوع المهم والضخم ولا يمكن اعتباره بأيّ حال من الأحوال مرجعًا للبرمجة كائنيّة التوجّه. ستحصل -بعد قراءتك لهذا الدرس- على المعرفة الضروريّة للتمييز بين الأصناف classes والكائنات objects وفهم العلاقة بينهما. بالإضافة إلى فهم المبادئ الأوليّة للوراثة والتغليف. لكي نفهم ما هو الصنف وما هو الكائن اسمع منّي هذه القصّة: نتبع نحن البشر إلى ما يسمّى بالصنف الإنساني. يُعرّف هذا الصنف المزايا التي يجب أن يتمتّع بها كلّ إنسان. فمثلًا لكلّ إنسان اسم وطول ووزن ولون عينان وبصمة إبهام مميّزة تميّزه عن أيّ إنسان آخر. يُعرّف الصنف class الإنسانيّ هذه الصفات السابقة، بحيث أنّ كلّ كائن object إنسانيّ من هذا الصنف تكون له مثل هذه الصفات ولكنّ مع مجموعة خاصّة من القيم لها. فمثلًا الكائن من الصنف الإنساني هو إنسان قد يكون اسمه سعيد وطوله 180 سم ولون عينيه أسود وله بصمة إبهام مميّزة، وهذا الإنسان يختلف عن كائن إنسانيّ آخر، اسمه عمّار وطوله 175 سم ولون عينيه بنيّ وله أيضًا بصمة إبهام مميّزة خاصّة به، وهكذا. ندعو الصفات السابقة بالخصائص Properties، فالصنف Class يعرّف الخصائص، أمّا الكائن Object فيتمتّع بهذه الخصائص ولكن مع مجموعة قيم لها تميّزه عن كائنٍ آخر. أمر آخر، يُعرّف الصنف الإنساني أيضًا سلوكيّات أو إجراءات معيّنة خاصّة للكائنات التي تعود للصنف الإنسانيّ. فهناك مثلًا سلوكيّات المشي والجري والضحك. وفي الغالب أنّ كل كائن يُعبّر عن هذه السلوكيّات بشكل يراعي خصوصيّته. فلكلّ منّا أسلوب مختلف في الضحك. كما يمتلك كلّ منّا أسلوب مختلف في المشي والجري، فقد تميّز إنسانًا لا ترى وجهه من خلال مشيته فقط وهذا أمر واردٌ جدًّا. مثل هذه السلوكيّات Methods نصطلح عليها في البرمجة بالتوابع. فالصنف الإنسانيّ يُعرّف وجود مثل هذه السلوكيّات ولكلّ كائن إنسانيّ الحريّة في التعبير عن هذه السلوكيّات بالشكل الذي يرغبه. التابع في البرمجة يضم تعليمات برمجية يجري تنفيذها عند استدعائه. يعالج ويتعامل هذا التابع عادةً مع الخصائص والتوابع الأخرى الموجودة ضمن نفس الكائن. نسمي التوابع والخصائص بأعضاء الصنف class members وهناك أعضاء أخرى سنتناولها في الدروس التالية. المبادئ العامة للمفهوم كائني التوجه هناك المئات من المقالات والكتب التي تتحدّث عن المفهوم الكائنيّ من منظورات مختلفة، وهناك أساليب متعدّدة تسمح بتحليل المسألة المطروحة وتصميمها وفق أسلوب كائنيّ أو ما يُعرف بالتصميم والتحليل كائنيّ التوجّه OOAD. ولكن يكفيك أن تعرف الآن أنّ هناك مبدآن أساسيّان ينبغي أن تتمتّع بها أيّ لغة برمجة تدعم المفهوم كائنيّ التوجّه وهما: التغليف Encapsulation والوراثة Inheritance. وهناك مفهوم مهم آخر يستند إلى الوراثة وهو التعدّديّة الشكلية Polymorphism. التغليف Encapsulation وهو مبدأ جوهري في البرمجة كائنيّة التوجّه، وهو أحد أسباب ظهور هذا المفهوم. يُقرّر هذا المبدأ أنّه ليس من المفترض أن نطّلع على آلية العمل الداخلية للكائن. ما يهمنا هو استخدام الكائن وتحقيق الغرض المطلوب بصرف النظر عن التفاصيل الداخليّة له. تأمّل المثال البسيط التالي: عندما نقود السيّارة ونريد زيادة سرعتها فإنّنا بكلّ بساطة نضغط على مدوسة الوقود. لا أعتقد أنّ أحدًا يهتمّ بالآلية الميكانيكيّة التي تقف وراء الضغط على مدوسة الوقود. فالمطلوب هو زيادة سرعة السيّارة فحسب دون الاهتمام بالتفاصيل الداخليّة. فالسيّارة تُغلّف encapsulate التفاصيل الميكانيكيّة الداخليّة التي تقف وراء زيادة سرعة السيّارة. السيّارة في هذا المثال هو كائن Object. وعمليّة زيادة السرعة هي سلوكيّة (تابع) Method من كائن السيّارة. هناك مثال آخر كثيرًا ما نراه أثناء تجوّلنا في الشوارع ومحطّات القطار وصالات الانتظار، وهو آلات تحضير المشروبات الساخنة. نقف أمام الآلة نُدخل النقود ثمّ نضغط على زرّ محدّد لنحصل على المشروب الساخن الذي نرغب به. لا نهتمّ عادةً بالتفاصيل الداخليّة التي تحدث ضمن الآلة عندما نضغط أحد الأزرار للحصول على كوب من القهوة. فالآلة هنا تُعتبر كائنًا، وعمليّة الحصول على كوب من القهوة هي سلوكيّة Method من هذا الكائن. فهذه الآلة تعمل على تغليف encapsulate التفاصيل الداخليّة لعمليّة التحضير، فكلّ ما نفعله هو ضغط الزر ومن ثمّ نحصل على الناتج المطلوب. فإذا ما أُجري تعديل في الآلة بحيث تتغيّر طريقة تحضير مشروب ساخن لجعله أفضل وأكثر لذّة، فإنّ ذلك لن يؤثّر مطلقًا على أسلوب التعامل مع الآلة للحصول على نفس المشروب، ولن نلاحظ هذا التعديل إلّا بعد تذوّقنا للمشروب وملاحظة الفرق في المذاق. الوراثة Inheritance تُعتبر الوراثة من أهم أشكال إعادة الاستخدام للمكوّنات البرمجيّة، حيث يعمل الصنف الجديد على الاستفادة من المكوّنات الموجودة مسبقًا ضمن الصنف الذي "يرث" منه ويجري عليها بعض التعديلات التي تناسبه على نحو مخصّص. فبدلًا من إنشاء صنف جديد من الصفر، يمكننا إنشاء صنف يعتمد على صنف آخر ويستفيد من خصائصه وسلوكيّاته (توابعه) الموجودة مسبقًا ثمّ يكيّفها أو يضيف عليها. نسمّي الصنف الأساسي الذي نرث منه بالصنف الأب. أمّا الصنف الذي يقوم بعمليّة الوراثة فنسمّيه بالصنف الابن أو بالصنف المشتق. لتثبيت الفكرة لنتناول المثال التالي. في المدارس هناك ثلاثة أنواع أساسيّة من الأشخاص المتواجدين فيها: الطلاب والمدرّسون والإداريّون. يمكننا بناء صنف عام يُمثّل أي شخص يعمل في المدرسة وليكن SchoolMember يحتوي هذا الصنف على خصائص مثل: الاسم والكنية واسم الأب واسم الأم وتاريخ الميلاد ورقم الهاتف. يمكننا البناء على هذا الصنف عندما نريد إنشاء أصناف أكثر "تخصّصًا" منه. مثل الصنف الذي يُعبّر عن الطلاب Student والصنف الذي يُعبّر عن المدرّسين Teacher، والصنف المُعبّر عن الإداريين Staff. يرث كلّ صنف منها من الصنف الأب SchoolMember فيصبح لكلّ منها نفس الخصائص الموجودة ضمن الصنف SchoolMember بشكل تلقائيّ. من الواضح أنّ الصنف Student مخصّص أكثر من الصنف SchoolMember فهو يحتوي بالإضافة إلى الخصائص الموجودة في SchoolMember خصائص فريدة خاصّة به. فمثلًا من الممكن أن يحتوي على الخاصيّة التي تعبّر عن الصفّ الحالي Grade وعن السلوك العام Behavior للطالب، أمّا صنف المدرّس Teacher فمن الممكن أن يحتوي (بالإضافة إلى الخصائص الموجودة ضمن SchoolMember) على خاصيّة Course التي تُعبّر عن المقرّر الذي يدرّسه (رياضيّات، فيزياء ...الخ) والخاصيّة WeeklyHours التي تعبّر عن عدد الساعات التدريسيّة الأسبوعيّة المكلّف بها. وينطبق نفس المفهوم تمامًا على الصنف Staff الذي يعبّر عن الموظّفين الإداريين في المدرسة. فالوراثة تنتقل بنا من الشكل الأكثر عموميّةً SchoolMember إلى الشكل الأكثر تخصيصًا مثل Student. وفي الحقيقة كان من الممكن أن نتابع عمليّة الوراثة اعتبارًا من الصنف Staff فهناك قسم التوجيّه وهناك أمانة السر والإدارة وغيرها، وكلّ منها يمكن أن يرث من الصنف Staff. التعددية الشكلية Polymorphism بفرض أنّنا نريد بناء برنامج يحاكي الحركة الانتقاليّة لعدّة أنواع من الحيوانات لدراسة حيويّة. كلّ من أصناف السمكة Fish والطائر Bird والضفدع Frog ترث من الصنف Animal الذي يمثّل أيّ حيوان. بفرض أنّ الصنف Animal يحتوي على سلوكيّة (تابع) اسمها Move (تُعبّر عن الانتقال)، فكما نعلم أنّ هذه السلوكيّة ستصبح وبشكل تلقائي موجودة ضمن أيّ صنف يرث من الصنف Animal، وهنا تكمن التعدديّة الشكليّة. فكل صنف من الأصناف Fish وBird وFrog يُعبّر عن عملية الانتقال Move بشكل مختلف. فالسمكة ربما تنتقل عن طريق السباحة مترًا واحدًا عند استدعاء التابع Move. أمّأ الطائر Bird فمن الممكن أي يطير مسافة 10 متر عند كل استدعاء للتابع Move، وأخيرًا فإنّه من الممكن للضفدع أن يقفز مسافة 20 سنتيمتر كلّما استدعي التابع Move. فالتابع Move المعرّف ضمن الصنف Animal يمكن التعبير عنه بأشكال متعدّدة ضمن الأصناف الأبناء Fish وBird وFrog كلٌّ بحسب حاجته. الخلاصة تعرّفنا في هذا الدرس على المفهوم العام للبرمجة كائنيّة التوجّه وتعاملنا مع التغليف حيث لا تهمّنا التفاصيل الداخلية لآلية العمل. والوراثة التي تتعلّق بمفهوم إعادة الاستخدام والانتقال من العام (الأب) إلى المخصّص (الابن). بالإضافة إلى التعدديّة الشكليّة التي تسمح لنا بإكساب سلوكيّات مخصّصة للأصناف الأبناء تنسجم مع طبيعتها. سنتناول في الدروس التالية هذه المفاهيم بشكل تطبيقي في سي شارب.
-
بعد أن تعرّفنا في الدّرس السابق على أساسيات البرمجة كائنية التوجّه، سنُكمل في هذا الجزء ما بدأناه وسنتعلّم بعض المبادئ المتقدّمة حول البرمجة كائنيّة التّوجه. تغيير قيمة متغير الصنف في الدّرس السّابق تعلّمنا كيفيّة تعريف مُتغيّر داخل صنف وكيفيّة الوصول إليه، في هذا الجزء من هذا الدّرس سنتعرّف على كيفيّة استخدام هذه المُتغيّرات بشكل أكثر واقعيّة، لنقل مثلا بأنّنا نمتلك صنفًا باسم Car (سيارة)، ونريد أن ننشئ كائنات من هذا الصّنف، بحيثُ يكون لكل كائن قيم مُختلفة لمُتغيّرات الصّنف، انظر ما يلي: class Car: name = None speed = 0 brand = None def specs(self): print '{} speed is {}Km/h and it\'s brand is {}'.format(self.name, self.speed, self.brand) أنشأنا أعلاه صنفا بسيطا باسم Car مع ثلاثة مُتغيّرات name لاسم السّيارة، speed لسرعة السّيارة و brand لاسم العلامة التّجاريّة، كلمة None تُشير إلى أنّ المُتغيّر مُعرّف لكنّه لا يحمل أية قيمة. بعدها عرّفنا تابعا specs (اختصار لـ specifications) لطباعة مواصفات السّيارة. لننشئ كائنات مُتعدّدة من الصّنف الذّي كتبناه للتو. # Toyota Corolla corolla = Car() corolla.name = 'Toyota Corolla' corolla.speed = 200 corolla.brand = 'Toyota' # Ford Focus focus = Car() focus.name = 'Ford Focus' focus.speed = 220 focus.brand = 'Ford' # Honda Civic civic = Car() civic.name = 'Honda Civic' civic.speed = 210 civic.brand = 'Honda' بعد أن أنشأنا الصّنف والكائنات المندرجة تحت هذا الصّنف، وقمنا بتعيين قيم المُتغيّرات التي تُعتبر مواصفات كلّ سيارة، يُمكننا أخيرا طباعة مواصفات كل سيارة (كائن) وذلك باستدعاء التابع specs. corolla.specs() focus.specs() civic.specs() وكما تتوقّع، المُخرج سيكون كالتّالي: Toyota Corolla speed is 200Km/h and it's brand is Toyota Ford Focus speed is 220Km/h and it's brand is Ford Honda Civic speed is 210Km/h and it's brand is Honda إرجاع الصنف داخل تابع يُمكنك أن تقوم بكتابة تابع داخل صنف، بحيث يقوم التابع بتنفيذ شيفرة ما ثمّ إعادة الصّنف نفسه (ما نُشير إليه بالكلمة self) وبهذا ستتمكّن من استدعاء التابع أكثر من مرّة في سطر واحد، تأمل ما يلي: class Car: def start(self): print 'Starting engine…' return self ما فعلناه في الشّيفرة أعلاه ليس بالشيء الكثير، أولا نقوم بتعريف الصّنف ثمّ بعد ذلك نُعرّف التّابع start المسؤول عن تشغيل السّيارة، وبعدها نطبع جملة تُفيد بأنّ مُحرّك السّيارة قيد التّشغيل، بعد تنفيذ الشيفرة سيُرجع التّابع الصّنف نفسه. ما سيُمكّننا من استدعاء التّابع أكثر من مرّة في سطر واحد كما يلي: corolla = Car() corolla.start().start().start() المُخرج سيكون كالتّالي: Starting engine… Starting engine… Starting engine… كما تُلاحظ لقد نُفّذت الشّيفرة الموجودة داخل التّابع start ثلاث مرّات. قد تتساءل عن أهميّة الأمر، وأتّفق معك في أنّ الأمر يُمكن أن لا يكون مُفيدا. لكنّه يكون مُفيدا إذا أردت أن تقوم بتنفيذ تابعين بشكل تسلسلي، لنقل بأنّ لدينا تابعا آخر باسم move ولنفرض بأنّه سيكون مسؤولا عن تحريك السّيارة، سيكون من الأفضل لو استطعنا أن نستدعي تابع الحركة مُباشرة بعد استدعاء تابع التّشغيل. بحيث يبدو الاستعمال كالتّالي: corolla.start().move() يُمكننا أن نقوم بالأمر ببساطة، وفي الحقيقة لن نحتاج إلا لإضافة التابع move لما كتبناه سابقا. class Car: def start(self): print 'Starting engine…' return self def move(self): print 'The car is moving…' بهذه الطّريقة سنتمكن من تنفيذ الشيفرة دون مشاكل: Car().start().move() المُخرج: Starting engine… Starting engine… The car is moving… لكن لاحظ هذه المرّة بأنّنا لم نُرجع كلمة self في التّابع move ما يعني بأنّنا لن نتمكن من القيام باستدعاء التابع أكثر من مرّة: Car().start().move().move() إذا حاولت أن تُنفّذ الأمر السّابق ستحصل على خطأ كالتّالي: AttributeError: 'NoneType' object has no attribute 'move' وهذا راجع لكوننا لم نُرجع الصنف في آخر التّابع move. الوراثة مفهوم الوراثة في البرمجة كائنيّة التوجه لا يختلف كثيرا عن مفهوم الوراثة في العالم الواقعي، الصّنف الابن يرث جميع المتغيرات والتوابع من الصّنف الأب، يُمكن التّفكير في الأمر على أنّه نسخ. أي أنّ الصّنف الابن مُجرّد نُسخة طبق الأصل من الصّنف الأصلي، الفرق هنا هو أنّك تستطيع أن تُضيف المزيد من الخصائص والتوابع للصّنف الابن. وطريقة إنشاء صنف يرث من صنف آخر هي بوضع الصّنف الأب بين قوسين عند إنشاء الصّنف الابن. انظُر المثال التّالي: class Parent: a = 1 b = 2 class Child(Parent): c = 3 في المثال أعلاه، قُمنا بإنشاء صنف باسم Parent مع مُتغيّرين a و b، وبعدها أنشأنا صنفا Child الذي يرث خصائص الصّنف السابق، وبالتّالي فسنتمكّن من الوصول إلى مُتغيّرات الصّنف Parent من خلال الصّنف Child، ما يعني بأنّ الشيفرة التّالية ستعمل دون مشاكل: child = Child() print child.a print child.b print child.c المُخرج: 1 2 3 كما تُلاحظ فإنّنا قد استطعنا الوصول إلى المتغيّرين a و b رغم أنّهما لم يُعرّفا مُباشرة داخل الصّنف Child. يُمكن أن نجعل صنفا يرث توابع من صنف آخر بنفس الطّريقة: class Person: name = 'Person' def say_hello(self): name = self.name print 'Hello my name is {}'.format(name) class Abdelhadi(Person): name = 'Abdelhadi' استدعاء التّابع say_hello من كائن من الصّنف Abdelhadi. me = Abdelhadi() me.say_hello() المُخرج: Hello my name is Abdelhadi في الشيفرة أعلاه، قُمنا أولا بتعريف صنف "شخص" باسم Person ثمّ عرّفنا المُتغيّر name داخل الصّنف، وأعطيناه قيمة افتراضيّة، بعدها عرّفنا التّابع say_hello ليطبع جُملة التّرحيب. متغيرات الصنف المبنية مسبقا توجد مُتغيّرات مبنية مُسبقا في لغة بايثون، وتُسمى هذه المُتغيّرات بمُتغيّرات الصّنف، وما نعنيه بأنّها مبنية مُسبقا هو أنّك لا تحتاج إلى تعريفها، وتمتاز هذه المُتغيّرات بأنّها مُحاطة بتسطيرين سُفليّين Underscores وإليك بعضا من هذه المُتغيّرات: __doc__ سلسلة توثيق الصنف (Documentation string) وهو ما يُكتب مُباشرة بعد تعريف الصنف، ويحتوي في الغالب معلومات حول وظيفة الصّنف. القيمة الافتراضية لهذا المتغير هي None ألق نظرة على الصّنف التالي: class Person: ''' This class does nothing ''' pass لاحظ بأنّ سلسلة التوثيق مُحاطة بثلاث علامات تنصيص إذا أردنا الوصول إلى هذا التوثيق فكلّ ما علينا فعله هو استخدام الصّفة __doc__: print Person.__doc__ المُخرج: This class does nothing __module__ الوحدة التي عُرّفَ فيها الصّنف، وقيمتها الافتراضية هي'__main__'. print Person.__module__ المخرج: __main__ __dict__ هذا المُتغيّر عبارة عن قاموس يحمل ما سبق كقيم بدئية، كما يحمل أسماء المتغيّرات التي تُنشؤها أنت وقيمها وكذلك أسماء التوابع، لاحظ المثال التالي (نفّذه مُباشرة على مُفسّر بايثون لتُلاحظ النّتيجة): >>> class Math: ... x = 1 ... y = 2 ... def x_plus_y(self): ... return self.x + self.y ... >>> Math.__dict__ {'y': 2, 'x': 1, '__module__': '__main__', '__doc__': None, 'x_plus_y': <function x_plus_y at 0x7f186e76a6e0>} كما تُلاحظ مُخرجات القاموس تحتوي على كل من قيم المُتغيرات الخاصّة بالصّنف سواء التي عرّفناها أو المبنية مُسبقا، لاحظ كذلك العنصر الأخير من القاموس: 'x_plus_y': <function x_plus_y at 0x7f186e76a6e0>} هذا العنصر يحمل مفتاحا باسم التابع x_plus_y الذي عرّفناه، وقيمة هذه المفتاح تُشير إلى أنّه دالة. __name__ اسم الصّنف، يُمكنك تطبيقه على أي صنف للحصول على اسمه، انظر المثال التالي: >>> a = Math >>> a.__name__ 'Math' كما تُلاحظ فقد حصلنا على اسم الصنف Math. الوصول إلى الصنف الخاص بكائن ما المُتغيّرات التي ذكرناها سابقا خاصّة بالصّنف فقط ولا يُمكن الوصول إليها من كائن من هذا الصّنف. تأمّل الصّنف التالي: class Person: name = 'Abdelhadi' لننشئ الآن كائنا من هذا الصّنف: me = Person() يُمكننا الآن الوصول إلى قيمة المُتغيّر name كالتالي: >>> me.name 'Abdelhadi' لكننّا لن نستطيع الوصول إلى مُتغيّرات الصّنف من الكائن، بل من الصّنف فقط: person_class = Person person_class.__name__ # الشيفرة صحيحة person_object = Person() person_object.__name__ # الشيفرة خاطئة لأنّنا نحاول الوصول إلى مُتغيّر غير مُعرّف داخل الكائن ستحصل على خطأ من نوع AttributeError عند تنفيذ السّطر الأخير: AttributeError: Person instance has no attribute '__name__' الحل الأمثل هو أن نصل إلى الصّنف انطلاقا من الكائن، وبعدها سنتمكّن من الوصول إلى مُتغيّرات/صفات الصّنف دون مشاكل، وطريقة الوصول إلى صنف كائن هي بإلحاقه بكلمة __class__، انظر المثال التّالي (سنعتمد على الصنف الذي أنشأناه أعلاه): person_object = Person() person_object_class = person_object.__class__ # إسناد صنف الكائن لمتغيّر print person_object_class.__name__ # اسم الصّنف print person_object_class.__dict__ # قاموس يحتوي على بيانات الصّنف بدأنا بإنشاء كائن person_object ثمّ استخدام جُملة __class__ للوصول إلى الصّنف. المُخرج: Person {'__module__': '__main__', 'name': 'Abdelhadi', '__doc__': None} يُمكنك كذلك القيام بالأمر داخل تابع في الصّنف: class Person: def say_hello(self): print 'Hi I am a {}'.format(self.__class__.__name__) person_obj = Person() person_obj.say_hello() لاحظ استخدام self.__class__.__name__ للوصول إلى اسم الصّنف في التّابع say_hello. المُخرج: Hi I am a Person التوابع الخاصة يُمكن استخدام بعض التّوابع الخاصّة لتنفيذ شيفرة عند القيام بإجراء معيّن، وإليك قائمة بأهم هذه التّوابع (لاحظ بأنّها مُحاطة بتسطيرين سُفليّين Underscores). التابع init: تُنفَّذُ الشيفرة التي بداخله عند إنشاء كائن من الصّنف، ويُسمّى أيضا بتابع البناء Contractor . التابع del: تُنفّذ شيفرته عند حذف كائن أو عند استدعاء دالة الخروج exit. التابع repr: تُنفّذ الشيفرة عند استدعاء الكائن، ويُستخدم هذا التابع لإرجاع معلومات حول الكائن في الوضع التّفاعلي (من مُفسّر لغة بايثون مُباشرة) مثال: # استدعاء __init__ person_obj = Person() # استدعاء __repr__ person_obj repr(person_obj) # حذف الكائن واستدعاء __del__ del(person_obj) التابع init هذا مثال بسيط على إنشاء واستدعاء هذا التّابع: class Person: def __init__(self): print 'A new object was created' me = Person() مُخرج البرنامج سيكون جُملة تُفيد بأنّ كائنا قد أنشئ، لاحظ أنّ الجملة A new object was created قد طُبعت رغم عدم استدعاء التّابع init بشكل صريح، ويُمكن الحصول على نفس المُخرج باستدعاء التّابع كالتّالي: me.__init__() حسنا تعلّمنا الآن بأنّنا نستطيع تنفيذ الشيفرة الموجودة بداخل التّابع __init__ بمُجرّد إنشاء كائن من الصّنف، ولكن ماذا عن المُعاملات؟ يُمكن تمرير المُعاملات للتّابع __init__ عند إنشاء صنف كالتّالي: me = Person('Abdelhadi') وفي التّابع سيكون الأمر كالتّالي: def __init__(self, name): name = self.name print 'Hello My name is {}'.format(name) الشيفرة الكاملة: class Person: def __init__(self, name): self.name = name print 'Hello My name is {}'.format(name) me = Person('Abdelhadi') المُخرج: Hello My name is Abdelhadi التابع repr يُستعمل هذا التّابع لإرجاع معلومات قابلة للطّباعة، ويُستخدم كثيرا في الوضع التّفاعلي (مُفسّر بايثون)، ويجب أن تكون القيمة المُرجعَةُ عبارة عن سلسلة نصيّة. وإليك مثالا لكيفيّة استخدامه: class Math: x = 1 y = 2 def __repr__(self): x = self.x y = self.y return 'x: {}, y: {}'.format(x, y) x_y = Math() print x_y المُخرج: x: 1, y: 2 يُمكننا الاستفادة من هذا التّابع للوصول إلى اسم الصنف عند الوراثة: # تعريف الصّنف الرّئيسي class Person: def __repr__(self): return 'Hi I am a {}'.format(self.__class__.__name__) # الوراثة class Writer(Person): pass class Student(Person): pass # إنشاء الكائنات omar = Student() abdelhadi = Writer() # طباعة قيّم التّابع repr print omar print abdelhadi المُخرج: Hi I am a Student Hi I am a Writer لاحظ بأنّ الصّنفين Writer و Student لا يحتويان على أية شيفرة، ومع ذلك فقد نُفّذ التّابع repr واستجاب بطريقة مُختلفة مع كلّ كائن. التابع del تُنَفّذُ الشّيفرة الموجودة بداخل هذا التّابع عند حذف كائن باستعمال الدّالة del وهي دالة تُستعمل لإنجاز ما يُسمى بعمليّة جمع القُمامة Garbage Collecting والهدف الرئيسي من هذه العمليّة هو تحرير الذاكرة، ولحذف كائن يكفي أن تقوم باستدعاء الدّالة del مع تمرير الكائن كمُعامل: class Math: x = 1 y = 2 numbers = Math() numbers.x # ==> 1 numbers.y # ==> 2 # حذف الكائن del(numbers) # خطأ numbers.x # ==> NameError: name 'numbers' is not defined بعد أن حذفنا الكائن numbers لم يعد بإمكاننا الوصول إلى قيمة المُتغيّر x، وأسفر الأمر عن خطأ من نوع NameError. يُستدعى التابع del مُباشرة بعد حذف الكائن، ما يعني بأنّنا نستطيع أن نطبع جملة تُفيد بأنّ الكائن قد حُذف: class Math: x = 1 y = 2 def __del__(self): print 'Object deleted!' numbers = Math() numbers.x # ==> 1 del(numbers) المُخرج: Object deleted! تُنفَّذ شيفرة __del__ كذلك عند استدعاء الدالة exit للخروج من البرنامج: class Math: x = 1 y = 2 def exit_program(self): exit() def __del__(self): print 'Object deleted!' numbers = Math() numbers.exit_program() مُخرَج البرنامج أعلاه سيكون نفس الشيء رغم عدم حذف الكائن. والسبب راجع لاستخدام الدّالة exit داخل التّابع exit_program. تمارين تمرين 1 عد إلى الدّروس السّابقة وحاول تحويل برنامج تسجيل الدّخول إلى برنامج سهل القراءة والاستخدام باستعمال البرمجة كائنية التوجه، فمثلا يُمكن أن يستعمل من قبل مُبرمجين آخرين بالطّريقة التّالية: user = User() user.signup('username', 'password', 'password_confirmation') user.login('username', 'password') user.say_hello() user.logout() تمرين 2 أنشئ برنامجا يطبع جميع التوابع والمُتغيّرات الموجودة داخل الصّنف الذي أنشأته كحل للتّمرين الأول.
- 4 تعليقات
-
- 3
-
- البرمجة كائنية التوجه
- بايثون
- (و 5 أكثر)
-
يشرح هذا الجزء من سلسلة تعليم CSS كيف تعمل CSS في المتصفّحات وما الهدف من وجود DOM. سنتعلّم أيضًا كيف نُحلّل مستندًا بسيطًا. فهرس السلسلة: مدخل إلى أوراق الأنماط المتتالية (CSS). آلية عمل تعليمات CSS داخل المتصفحات. (هذا الدرس) المحددات (Selectors) في CSS. كيفية كتابة تعليمات CSS يسهل قراءتها. تنسيق نصوص صفحات الويب باستخدام CSS. التعامل مع الألوان في CSS. إضافة محتوى إلى صفحة ويب باستخدام CSS. تنسيق القوائم (Lists) في CSS. تعرف على الصناديق (Boxes) في CSS. رصف العناصر (Layout) في CSS. الجداول (Tables) في CSS. التعامل مع أجهزة العرض المختلفة والمطبوعات في CSS. كيفية عمل CSS عندما يعرض المتصفّح المستند، فإنّه يجمع محتواه مع معلومات التنسيق، ويُعالج المستند على مرحلتين: يحوّل المتصفّح لغة الرّماز وCSS إلى DOM (اختصارًا لـDocument Object Model). يُمثّل DOM المستند في ذاكرة الحاسوب، ويجمع محتوى المستند مع تنسيقه. يعرض المتصفّح محتويات DOM. تستخدم لغات الرّماز عناصر (elements) لتعريف هيكل المستند. يُحدّد العنصر بوسم (tag)، وهو نص يبدأ بالرّمز < وينتهي بالرّمز >. معظم العناصر لها زوجان من الوسوم، وسم في البداية وآخر في النهاية. لكتابة وسم البداية أدرج اسم العنصر بين <>، أما وسم النّهاية فيكتب بإضافة / بعد < وقبل اسم العنصر. قد يكون لبعض العناصر وسم بادئ فقط، أو وسم وحيد فيه تأتي / بعد اسم العنصر، وذلك تبعًا للغة الرّماز. قد يكون العنصر أيضًا حاويًا على عناصر آخرى ضمن وسْمَيه الأول والأخير. عليك التأكد من إغلاق الوسوم ضمن العنصر الحاوي. لـDOM بنية تشبه الشجرة، حيث يصبح كل عنصر أو خاصّة أو سلسلة من النصوص في لغة الرماز عقدة (node) في البنية الشجريّة. تُحدّد العُقد بعلاقتها بالعقد الأخرى، فبعض العناصر هي آباء (parent nodes) لعناصر آخرى (children nodes)، وقد يكون للأبناء إخوة (sibling nodes). يُعينك فهم DOM على تصميم وتنقيح وصيانة CSS، لأنّه يمثّل نقطة التقاء CSS مع محتوى المستند. مثال يُنشئ الوسم <p> ونظيره </p> عنصرًا حاويًا: <p> <strong>C</strong>ascading <strong>S</strong>tyle <strong>S</strong>heets </p> في هيكل DOM، تُعتير العقدة P أبًا، أبناؤه هي العقد STRONG والعقد النصيّة، وبالمثل فإنّ العقد STRONG آباء للعقد النّصيّة. ├─STRONG │ └─"C" ├─"ascading" ├─STRONG │ └─"S" ├─"tyle" ├─STRONG │ └─"S" └─"heets" تدريب: تحليل DOM باستخدام أداة فحص DOM لتحليل هيكل DOM، نحتاج إلى برنامج خاصّ، بإمكانك استخدام إضافة DOM Inspector من Mozilla لتحقيق ذلك. ما عليك إلا تثبيت الإضافة (المزيد من التفاصيل أدناه). استخدم متصفح Mozilla لفتح مستند HTML الذي أنشأته. من قائمة المتصفّح اختر: أدوات > DOM Inspector أو أدوات > تطوير الويب > DOM Inspector. تفاصيل أكثر إن لم يحوِ متصفح Mozilla عندك على أداة فحص DOM، فبإمكانك تثبيت الإضافة من موقع الإضافات وإعادة تشغيل المتصفح ثم العودة لهذا الدّرس. إن لم ترغب بتثبيت هذه الإضافة (أو لم تكن تستعمل متصفح من Mozilla)، بإمكانك استخدام Web X-Ray Goggles كما هو مشروح في القسم التالي. بإمكانك أيضًا تجاوز هذا القسم والمتابعة. في أداة فحص DOM، وسّع عقد المستند بالنقر على الأسهم. ملاحظة: قد تؤدي المسافات في ملف HTML إلى عرض عقد نصية فارغة، يمكنك تجاهلها. يبدو جزء من النتيجة مشابهًا لما يلي، تبعًا للعقد الّتي وسّعتها: │ ▼╴P │ │ │ ▼╴STRONG │ │ └#text │ ├╴#text │ ►╴STRONG │ │ عندما تختار أيّة عقدة، بإمكانك استخدام اللوحة على يمين DOM Inspector للاطّلاع على معلومات إضافية عنها، فمثلًا: يعرض DOM Inspector النص في اللوحة على اليمين عندما تحتار عقدة نصيّة. عندما تختار عقدة تمثّل عنصرًا، تُحلّل الأداة هذه العقدة وتقدّم معلومات كثيرة في اللّوحة على اليمين، يكون من ضمنها معلومات التنسيق. تمرين انقر على عقدة من نوع STRONG في أداة فحص DOM، ثم استخدم اللوحة على اليمين لإيجاد الموضع الّذي يُعيّن فيه لون العقدة إلى أحمر، والموضع الّذي يُجعل فيه خطّها أعرض من النصّ العاديّ. شاهد الحل في القائمة فوق اللوحة على اليمين، اختر CSS Rules، سترى عنصرين مُدرجين، أحدهما هو المصدر الداخلي الّذي ي يُعرّف font-width على أنّها bolder، والآخر آتٍ من ورقة الأنماط الّتي تُحرّرها والّذي يُعرّف color على أنّه red. استخدام Web X-Ray Goggles تعرض الأداة Web X-Ray Goggles معلومات أقل من DOM Inspector، ولكنّها أبسط في التثبيت والاستعمال: اذهب إلى الصّفحة الرئيسيّة للأداة. اسحب رابط العلامة المرجعيّة إلى شريط المتصفّح. افتح مستند HTML الّذي أنشأته. فعّل الأداة بالنقر على العلامة المرجعيّة في الشّريط. اسحب مؤشّر الفأرة فوق العناصر المختلفة في المستند. تتالي الأنماط ووراثتها سنتعلّم الآن كيف تتفاعل الأنماط المتتالية، وكيف ترث العناصر الأنماط عن آبائها، وكيف تضيف الأنماط إلى ورقة الأنماط مستفيدًا من فكرة الوراثة بحيث تغيّر تنسيق عدة أجزاء من المستند مرّة واحدة. قد يُحدّد النّمط النّهائي للعنصر في عدّة مواضع، تتفاعل فيما بينها بطريقة مُعقّدة، وهذا التّفاعل المعقّد يُعطي CSS قدراتها المُميّزة، ولكنّه قد يُعقّد الأمور ويجعل تتبّع الأخطاء عمليّة صعبة. تجتمع المصادر الثلاثة الأساسية لمعلومات التنسيق مشكّلة ما نسمّيه تعاقب الأنماط (cascade)، وهذه الأنماط هي: التنسيقات المبدئيّة الّتي يحدّدها المتصفّح للعناصر في لغة الرّماز. التنسيقات الّتي يحدّدها المستخدم الّذي يقرأ المستند. التنسيقات المرتبطة بالمستند الّتي يحدّدها مؤلّفه، والّتي قد تعيّن في ثلاثة مواضع: في ملفّ خارجيّ: وهذا الموضوع الأساسيّ لهذه الدّروس. في تعريف في بداية المستند: للتنسيقات الخاصّة بصفحة واحدة فقط. في عنصر معيّن ضمن متن المستند: وهي الطريقة الأقل سهولة في الصيانة، ويمكن استخدامها على سبيل التّجربة. تُغيّر أنماط المستخدم الأنماط الّتي يعيّنها المتصفّح، ثمّ تغيّر أنماط مؤلّف المستند تلك الّتي عيّنها المستخدم. في هذه السّلسلة أنت مؤلّف المستند، ولن نتعامل إلّا مع هذا النّوع من الأنماط. مثال عندما تقرأ هذا المستند في متصفّح، يأتي جزء من التنسيق الّذي تراه من الإعدادات المبدئيّة لـHTML في المتصفّح. وقد يأتي جزء آخر من الإعدادات المخصّصة للمتصفّح، إذ يمكن تخصيصها في مربع التفضيلات في Firefox مثلًا، أو يمكن تعيينها في ملف userContent.css في ملفّ المتصفّح الشّخصيّ. ويأتي الجزء الأخير من التنسيق من خادوم موقع الأكاديميّة. عندما تفتح المستند الّذي تتدرّب عليه في المتصفّح، تكون العناصر <strong> أعرض خطًّا من بقيّة النّصّ، هذا التنسيق يأتي من إعدادات المتصفّح المبدئيّة. كما أن العناصر نفسها ذات لون أحمر، وهذا التنسيق يأتي من ورقة الأنماط الّتي كتبناها بأنفسنا. ترث عناصر <strong> أيضًا معظم تنسيقها عن عناصر <p> لأنّها أبناؤها، وبالمثل ترث العناصر <p> معظم تنسيق عنصر <body>. يكون للتنسيقات الّتي يحدّدها مؤلّف المستند الأولويّة في تعاقب الأنماط على تلك الّتي يحدّدها القارئ، وتكون الأولويّة الأقل لتنسيقات المتصفّح المبدئيّة. بالنّسبة للتنسيقات الموروثة، يكون للتنسيقات المعرّفة على العقدة ذاتها الأولويّة على تلك الّتي ترثها عن آبائها. هناك قواعد أخرى لهذه الأولويّات، سنتعرّف عليها لاحقًا. تفاصيل أكثر توفّر CSS أيضًا طريقة للقارئ لحجب التنسيق الّذي يحدّده مؤلّف المستند باستخدام الكلمة !important. وهذا يعني أنّه لا يمكن توقّع الشّكل الّذي سيكون عليه المستند لكلّ قارئ. إذا أردت معرفة المزيد عن موضوع التعاقب والوراثة، انظر فقرة Assigning property values, Cascading and inheritance في تعريف CSS. تمرين: استخدام الوراثة حرّر ملفّ CSS الّذي تتدرّب عليه. أضف هذا السّطر بنسخه ولصقه. لا يهمّ إن كان فوق أو تحت السّطر الّذي أضفته من قبل. إلّا أن إضافته فوقه أكثر منطقيّة لأن العنصر <p> أب للعنصر <strong> في مستندك. p {color: blue; text-decoration: underline;} احفظ الملفّ وحدّث متصفّحك لتشاهد تأثير ذلك على المستند. يؤدّي ذلك إلى تسطير النّص في كامل الفقرة، بما في ذلك الحروف الأولى من كلّ كلمة، وهذا يعني أن العناصر <strong> ورثت عن أبيها نمط تسطير النّصّ. ولكّن هذه العناصر ما تزال حمراء اللّون، لأنّ اللون الأحمر هو نمطها الخاص، ولذا يكون له الأولويّة على اللّون الأزرق لأبيها العنصر <p>. قبل <p> <strong>C</strong>ascading <strong>S</strong>tyle <strong>S</strong>heets </p> strong {color:red} بعد <p> <strong>C</strong>ascading <strong>S</strong>tyle <strong>S</strong>heets </p> p {color:blue; text-decoration:underline} strong {color:red} تمرين عدّل ورقة الأنماط بحيث تكون الحروف الأولى فقط مُسطَّرة: شاهد الحل انقل التصريح الذي يُعنى بتسطير النّص من قاعدة <p> إلى <strong>، كما يلي: p {color: blue; } strong {color: orange; text-decoration: underline;} ما التالي؟ سنتعرّف في الدّرس التالي كيف نستهدف العناصر بطريقة أكثر انتقائيّة. ترجمة -وبتصرف- للمقال How CSS works من سلسلة Getting started with CSS على شبكة مطوّري Mozilla.
-
تحدثنا في الدرسين السابقين عن المبادئ الأوليّة لتطبيق مفاهيم البرمجة الكائنيّة التوجّه في سي شارب. سننهي في هذا الدرس تطبيق هذه المبادئ، حيث سنتناول موضوع الوراثة Inheritance والتعدديّة الشكليّة Polymorphism، كما سنتعرّف على محدّد الوصول protected الذي يُستخدم في الوراثة. الوراثة Inheritance سبق وأن قدّمنا للوراثة، واتفقنا على أنّها من أهمّ المفاهيم التي يمكن أن تدعمها لغات البرمجة كائنيّة التوجّه. في الحقيقة يرث أيّ صنف موجود في إطار عمل دوت نت أو أيّ صنف تنشئه بنفسك بشكل مباشر أو غير مباشر من الصنف Object حتى ولو لم نخبر مترجم سي شارب بذلك، حيث سيعمل المترجم على الوراثة منه بشكل ضمنيّ. هذا الصنف ذو دلالة عامّة، وهو غير مفيد كثيرًا كاستخدام بحدّ ذاته. يحتوي الصنف Object على عدد قليل من التوابع كأعضاء ضمنه مثل Equals و GetHashCode و GetType و ToString. التابع الأكثر استخدامًا هو التابع ToString، وهو يُستخدَم عادةً للحصول على التمثيل النصيّ لأيّ كائن. لكي نفهم الوراثة بشكل عمليّ لا بدّ لنا من مثال تمهيديّ. سننشئ لهذا الغرض صنف أب سأسمّيه Father. سيكون هذا الصنف هو الأساس الذي نرث منه. لهذا الصنف الشكل التالي: class Father { public Father() { Console.WriteLine("Father: In Constructor"); } public void MyMethod() { Console.WriteLine("Father: In MyMethod"); } } يحتوي هذا الصنف على التابع MyMethod الذي يحوي الكلمة void قبل اسم التابع مباشرةً. تعني هذه الكلمة أنّ التابع MyMethod لن يُرجع أي قيمة للشيفرة التي استدعته. كما يحتوي الصنف Father على البانية عديمة الوسائط Father. وضعت عبارتي Writeline في كلّ تابع من باب التوضيح. الآن سنعرّف صنفًا جديدًا لنسمّه Child يرث من الصنف Father على الشكل التالي: class Child : Father { } قد يبدو الصنف Child فارغًا إلّا أنّه ليس كذلك. لاحظ من السطر الأوّل لتصريح هذا الصنف كيف وضعنا النقطتان الرأسيّتان (:) ومن ثمّ اسم الصنف Father. يخبر ذلك مترجم سي شارب أنّنا نريد من الصنف Child أن يرث من الصنف Father. في الحقيقة جميع الأعضاء المعرّفة ضمن الصنف Father ستصبح موجودة تلقائيًّا ضمن الصنف Child، بل ويمكن إضافة المزيد من الأعضاء إلى الصنف Child بحسب الحاجة. أنشئ مشروعًا جديدًا وسمّه Lesson08_01، ضع الصنفين السابقين بجوار الصنف Program ضمن فضاء الاسم Lesson08_01 ثم اكتب الشيفرة التالية ضمن التابع Main: Child c = new Child(); c.MyMethod(); نفّذ البرنامج لتحصل على الخرج التالي: Father: In Constructor Father: In MyMethod من الواضح أنّ التنفيذ سيدخل إلى بانية الصنف Father (تذكّر أنّ البانية هي أوّل تابع يُستدعى عند إنشاء الكائن) وإلى التابع MyMethod وكلاهما موجودان ضمن الصنف الأب Father. لنضيف بعض التعديلات على الصنف Child. عدّل الصنف Child ليصبح كما يلي: class Child : Father { public Child() { Console.WriteLine("Child: In Constructor"); } } لاحظ أنّنا قد أضفنا بانية عديمة الوسائط للصنف Child وبداخلها التابع WriteLine لطباعة جملة توضيحيّة. أعد تنفيذ البرنامج السابق لتحصل على الخرج التالي: Father: In Constructor Child: In Constructor Father: In MyMethod الخرج السابق منطقيّ تمامًا. عند إنشاء كائن من الصنف Child باستخدام العبارة: Child c = new Child(); فإنّ بانية الصنف Child سُتستدعى نتيجة لذلك، وبما أنّ الصنف Child يرث من الصنف Father لذلك فإنّ بانية الصنف Father هي من ستُنفّذ أولًا ومن ثمّ بانية الصنف Child. أمّا عند استدعاء التابع MyMethod من الكائن الموجود ضمن المتغيّر c فسنحصل على رسالة الخرج الثالثة كما هو متوقّع. لنجرّب الآن شيئًا آخر. ماذا لو أردنا استبدال محتوى التابع MyMethod بمحتوى خاص بالابن، بمعنى آخر نريد "تجاوز" تعريف التابع MyMethod الموجود في الصنف الأب Father إلى تعريف آخر للتابع MyMethod ولكنّه خاص بالصنف Child. أضف التابع التالي إلى الصنف Child: public new void MyMethod() { Console.WriteLine("Child: In MyMethod"); } لاحظ وجود الكلمة المحجوزة new بعد مُحدّد الوصول public. وظيفة هذه الكلمة في هذا المكان هي إخفاء التابع MyMethod الموجود في الصنف Father واستبداله بالتابع MyMethod الموجود في الصنف Child. الآن بعد تنفيذ البرنامج ستحصل على الخرج التالي: Father: In Constructor Child: In Constructor Child: In MyMethod تمّ المطلوب، لقد أُخفي التابع MyMethod الموجود ضمن الصنف الأب Father لصالح التابع MyMethod الموجود ضمن الصنف الابن Child. يجب أن يبدو البرنامج Lesson08_01 بعد التعديلات الأخيرة شبيهًا بما يلي: 1 using System; 2 3 namespace Lesson08_01 4 { 5 class Father 6 { 7 public Father() 8 { 9 Console.WriteLine("Father: In Constructor"); 10 } 11 12 public void MyMethod() 13 { 14 Console.WriteLine("Father: In MyMethod"); 15 } 16 } 17 18 class Child : Father 19 { 20 public Child() 21 { 22 Console.WriteLine("Child: In Constructor"); 23 } 24 25 public new void MyMethod() 26 { 27 Console.WriteLine("Child: In MyMethod"); 28 } 29 } 30 31 class Program 32 { 33 static void Main(string[] args) 34 { 35 Child c = new Child(); 36 37 c.MyMethod(); 38 39 } 40 } 41 } محدد الوصول protected يُستخدم محدّد الوصول protected في الوراثة. فعندما نُعرّف أحد أعضاء الصنف الأب باستخدام protected فهذا يعني أنّه لا يمكن الوصول إليه مطلقًا إلّا من خلال أعضاء الصنف الأب نفسه، أو من خلال أعضاء الصنف الابن (أو الأحفاد). 1 using System; 2 3 namespace Lesson08_02 4 { 5 class Car 6 { 7 protected string manufacturer; 8 9 public Car() 10 { 11 this.manufacturer = "Car"; 12 } 13 14 public string Manufacturer 15 { 16 Get 17 { 18 return this.manufacturer; 19 } 20 } 21 } 22 23 class Toyota : Car 24 { 25 public Toyota() 26 { 27 this.manufacturer = "Toyota"; 28 } 29 } 30 31 class Program 32 { 33 static void Main(string[] args) 34 { 35 Toyota toyota = new Toyota(); 36 37 Console.WriteLine(toyota.Manufacturer); 38 } 39 } 40 } عند تنفيذ البرنامج ستحصل على الكلمة Toyota في الخرج. السبب في ذلك أنّ بانية الصنف Toyota تصل إلى الحقل manufacturer في السطر 27، رغم أنّه مصرّح عنه في الصنف الأب Car، وذلك لأنّه ذو محدّد وصول protected. من الواضح أنّ الأعضاء المصرّح عنها باستخدام محدّد الوصول private في الأصناف الآباء تبقى مرئيّةً فقط ضمن أعضاء الصنف الأب فحسب. التعددية الشكلية Polymorphism سبق وأن تحدّثنا عن التعدديّة الشكليّة، وكيف أنّها مفهوم أساسيّ في البرمجة كائنيّة التوجّه. يتمحور مفهوم التعدديّة الشكليّة حول أنّه يحق للصنف الابن إعادة صياغة تابع (أو خاصيّة) موجود في صنف أب بصورةٍ تناسبه أكثر. لقد طبّقنا هذا المفهوم قبل قليل وذلك عندما "تجاوز" التابع MyMethod في الصنف الابن Child، التابع MyMethod الموجود في الصنف الأب Father، فأصبح التابع الموجود في الابن يُعبّر عن نفسه بشكل أكثر تخصّصًا. ولكن هذه ليست هي الطريقة المثلى لتنفيذ فكرة التعدديّة الشكلية، تزوّدنا سي شارب في الواقع بأسلوب أفضل بكثير لتحقيق هذا المفهوم. هل تذكر مثال الضفدع Frog والسمكة Fish والطائر Bird وسلوكيّة الانتقال Move التي يرثونها من الصنف Animal؟ تناولنا هذا المثال البسيط في درس سابق. وقد ذكرنا أنّ الصنف Animal هو الصنف الأب للأصناف Frog و Fish و Bird وهو يحتوي على التابع Move الذي يُعبّر عن سلوكيّة الانتقال. وبما أنّ كلًّا من الأصناف الأبناء الثلاثة تُعبّر بشكل مختلف عن عمليّة الانتقال، لذلك فنحن أمام التعدديّة الشكليّة. يحتوي البرنامج Lesson08_03 على صنف أب Animal يحوي تابعًا وحيدًا اسمه Moveـ موسوم بالكلمة المحجوزة virtual التي تجعل منه تابعًا ظاهريًّا يسمح للتوابع الأخرى بتجاوزه. بالإضافة إلى وجود ثلاثة أصناف أبناء للصنف Animal وهي Frog و Fish و Bird. يحتوي كل صنف من الأصناف الأبناء على التابع Move مع وسم خاص هو override. تسمح هذه الكلمة للتابع في الصنف الابن أن "يتجاوز" تعريف نفس التابع في الصنف الأب (موسوم بالكلمة virtual). أعني بكلمة "تجاوز" إعادة تعريف التابع بالشكل الذي يناسب الصنف الابن. فعند الحديث عن الانتقال، فالذي يناسب الضفدع Frog هو القفز، والذي يناسب السمكة Fish هو السباحة، والذي يناسب الطائر Bird بالطبع هو الطيران. وبالمناسبة فإنّ التابع ToString الموجود في الصنف Object هو تابع ظاهريّ (موسوم بالكلمة virtual) ليسمح لأي صنف آخر بتجاوزه. إليك الآن البرنامج Lesson08_03: 1 using System; 2 3 namespace Lesson08_03 4 { 5 class Animal 6 { 7 public virtual void Move() 8 { 9 Console.WriteLine("Animal: Move General Method"); 10 } 11 } 12 13 class Frog : Animal 14 { 15 public override void Move() 16 { 17 Console.WriteLine("Frog - Move: jumping 20 cm"); 18 } 19 } 20 21 class Bird : Animal 22 { 23 public override void Move() 24 { 25 Console.WriteLine("Brid - Move: flying 10 m"); 26 } 27 28 } 29 30 class Fish : Animal 31 { 32 public override void Move() 33 { 34 Console.WriteLine("Fish - Move: swimming 1 m"); 35 } 36 } 37 class Program 38 { 39 static void Main(string[] args) 40 { 41 Frog frog = new Frog(); 42 Fish fish = new Fish(); 43 Bird bird = new Bird(); 44 45 frog.Move(); 46 fish.Move(); 47 bird.Move(); 48 } 49 } 50 } نفّذ البرنامج السابق لتحصل على الخرج التالي: Frog - Move: jumping 20 cm Fish - Move: swimming 1 m Brid - Move: flying 10 m لاحظ كيف يُعبّر كلّ كائن من الأصناف الأبناء عن التابع Move بالشكل الذي يناسبه. وواضح أنّ التابع Move الموجود في الصنف الأب Animal لا يُستدعى مطلقًا. ولكن في بعض الحالات قد نرغب أن يُستدعى التابع المُتجاوَز الموجود في الصنف الأب لإنجاز بعض المهام ومن ثمّ نتابع العمل ضمن التابع المُتجاوِز. يمكننا ذلك ببساطة من خلال استخدام الكلمة المحجوزة base التي تُشير إلى الصنف الأب الذي يرث منه الابن. لاستدعاء التابع Move الموجود في الصنف الأب Animal وذلك من خلال التابع Move الموجود في الصنف Frog أضف العبارة التالية بعد السطر 16 مباشرةً قبل أي عبارة أخرى، ليصبح هذا التابع على الشكل: public override void Move() { base.Move(); Console.WriteLine("Frog - Move: jumping 20 cm"); } أعد تنفيذ البرنامج لتحصل على الخرج التالي: Animal: Move General Method Frog - Move: jumping 20 cm Fish - Move: swimming 1 m Brid - Move: flying 10 m انظر كيف استُدعي التابع Move الموجود في الصنف الأب Animal ومن ثمّ استُدعي التابع Move الموجود في الصنف الابن Frog. التحويل بين الأنواع سنتناول في هذه الفقرة سلوكًا قد يبدو غريبًا بعض الشيء، ولكنّه مهم وأساسيّ وسيصادفك في معظم البرامج التي تكتبها باستخدام سي شارب. أعد البرنامج Lesson08_03 إلى حالته الأصلية (أي أزل العبارة ()base.Move). امسح محتويات التابع Main واستبدلها بالشيفرة التالية: Animal animal = new Frog(); animal.Move(); العبارة الأولى غريبة قليلًا أليس كذلك؟ في الحقيقة الوضع طبيعي تمامًا، فبما أنّ الصنف Animal هو صنف أب للصنف Frog لذلك فيستطيع أيّ متغيّر مصرّح عنه على أنّه من النوع Animal (في مثالنا هذا هو المتغيّر animal) أن يخزّن مرجع إلى كائن من الصنف Frog (تذكّر أنّ التعبير ()new Frog يولّد مرجع لكائن من الصنف Frog). نفّذ البرنامج الآن لتحصل على الخرج التالي: Frog - Move: jumping 20 cm يبدو أنّ برنامجنا ذكيّ كفاية لكي يعرف أنّ الكائن الذي يشير إليه المتغيّر animal هو كائن من الصنف Frog. في الحقيقة يحصل هنا تحويل ضمني بين الكائنات، ولكن إذا فعلنا العكس، أي أسندنا مرجع لكائن من الصنف Animal إلى متغيّر من النوع Frog فسنحصل على خطأ أثناء ترجمة البرنامج. امسح محتويات التابع Main واستبدلها بالشيفرة التالية: Animal animal = new Frog(); Frog frog = animal; نحاول في السطر الثاني أن نُسند المتغيّر animal من النوع Animal إلى المتغيّر frog من النوع Frog، فنحصل على الخطأ التالي عند محاولة تنفيذ البرنامج: Cannot implicitly convert type 'Lesson08_03.Animal' to 'Lesson08_03.Frog'. An explicit conversion exists (are you missing a cast?) يخبرنا هذا الخطأ أنّه لا يمكن التحويل بشكل ضمنيّ من النوع Animal إلى النوع Frog ويقترح علينا استخدام عامل التحويل بين الأنواع casting (هل تذكره؟). رغم أنّ المتغيّر animal يحمل مرجع إل كائن من الصنف Frog في حقيقة الأمر (انظر السطر الأوّل من الشيفرة السابقة) إلّا أنّنا عند محاولتنا إسناد المتغيّر animal إلى المتغيّر frog حصلنا على خطأ. السبب في ذلك هو أنّه لا يحدث تحويل ضمنيّ بين الأنواع implicit conversion وإنّما يتطلّب الأمر إجراء تحويل صريح باستخدام عامل التحويل بين الأنواع. إذا استبدلت السطر الثاني من الشيفرة السابقة بالسطر التالي، ستكون الأمور على ما يرام: Frog frog = (Frog)animal; لاحظ كيف وضعنا عامل التحويل (Frog) أمام المتغيّر animal. سيضمن ذلك حدوث التحويل المطلوب دون أيّ مشاكل. لكي تريح نفسك من التفكير متى يحدث التحويل الضمنيّ ومتى يجب استخدام التحويل الصريح، تذكّر منّي القاعدة التالية: "في حياتنا اليوميّة، كثيرًا ما يحتوي الأب ابنه، ولكنّ العكس ليس صحيحًا". أعلم أنّ لهذه القاعدة شواذ في واقعنا، ولكنّها في البرمجة لا تخيب! فالمتغيّر من النوع الأب يستطيع استقبال أي مرجع لكائن من صنف ابن، ولكنّ العكس ليس صحيح ما لم نستخدم التحويل الصريح بين الأنواع. تحدث ظاهرة التحويل بين الأنواع بالنسبة للأنواع المضمّنة built-in أيضًا. فهناك تحويلات تحدث ضمنيًّا، وأخرى تحدث بتدخّل من المبرمج باستخدام عامل التحويل بين الأنواع، ولكن مع فرق جوهريّ. ففي هذه الحالة ليس بالضرورة أن يكون بين الأنواع التي تجري عمليّة التحويل فيما بينها أي علاقة وراثة. فمثلًا يمكن التحويل ضمنيًّا بين متغيّر من النوع int إلى آخر من النوع double: int i = 6; double d = i; أمّا إذا حاولنا فعل العكس: double d = 6; int i = d; فسنحصل على نفس الخطأ السابق الذي يخبرنا بوجوب استخدام التحويل الصريح بين الأنواع. يمكن حل هذه المشكلة ببساطة باستخدام عامل التحويل (int) ووضعه أمام المتغيّر d في السطر الثاني: double d = 6; int i = (int)d; نخبر المترجم هنا أنّنا نريد التحويل فعليًّا من double إلى int. ستحتاج إلى مثل هذه التقنيّة دومًا إذا كانت عمليّة التحويل ستؤدّي إلى ضياع في البيانات. فالتحويل من double إلى int سيؤدّي إلى ضياع القيمة على يمين الفاصلة العشريّة لأنّ المتغيّرات من النوع int لا تقبلها. وكذلك الأمر عند التحويل من float إلى double لأنّ المتغيّرات من النوع float ذات دقّة أقل من المتغيّرات من النوع double، وهكذا. تمارين داعمة تمرين 1 عدّل البرنامج Lesson08_03 ليعمل كل صنف من الأصناف Frog و Bird و Fish على تجاوز التابع ToString (الموجود في الصنف الأب Object). بحيث عند استدعاء التابع ToString من كائن من الصنف Frog نحصل على النص "I am Frog"، وهكذا بالنسبة للصنفين الباقيين كلّ حسب اسمه. تمرين 2 استفد من البرنامج Lesson08_02 في إنشاء صنف جديد اسمه Corolla يرث من الصنف Toyota. وأضف إلى الصنف الجديد الخاصيّة ProductionYear من النوع int. بعد ذلك أنشئ كائنًا من الصنف Corolla وحاول إسناد قيم لهذه الخاصيّة، وقرائتها منها. الخلاصة تعرّفنا في هذا الدرس على كيفيّة تطبيق الوراثة في سي شارب، كما تعرّفنا على مبادئ التعدديّة الشكليّة Polymorphism وأهميّتها وكيفيّة استثمارها في هذه اللغة. وتعاملنا أيضًا مع التحويل بين الأنواع ورأينا كيف يمكن لمتغيّر من صنف أب أن يحمل مراجع لكائنات من أصناف أبناء. تُستَخدم هذه الأساليب على نحو واسع جدًّا في مكتبة الأصناف الأساسيّة، وستحتاجها في العديد من التطبيقات التي تُنشئها.
- 3 تعليقات
-
- 2
-
- polymorphism
- inheritance
- (و 7 أكثر)