البحث في الموقع
المحتوى عن 'constructor'.
-
كان الغرض من البرمجة الكائنية (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 أكثر)
-
في سلسلة من عدة أجزاء سنناقش موضوعًا نظريًّا يُعتبر من أساسيّات هندسة البرامج، وهو أنماط التصميم (Design Patterns)، وسنعتمد لغة JavaScript في نقاشنا لتصاعد شعبيّتها ومرونتها التي تسمح لنا ببناء مشاريعنا وفق أنماط متنوّعة مما سيُسهّل علينا شرح موضوع السّلسلة نمط المُشيِّد (Constructor)يشيع استخدام المُشيّدات في اللغات الكائنيّة التّوجّه، حيث تُستخدم لإنشاء نُسخ (instances) من الأصناف (classes)، ومع أنّ JavaScript ليست لغةً كائنيّة التّوجّه بالمعنى التّقليديّ، إلّا أنّها تسمح بإنشاء نُسخ عن كائنات باستخدام بالمُشيّدات، ويمكن لأيّ دالّة أن تُستخدم كمُشيّد، وذلك بأن نُسبقها بالكلمة new، ولتوضيح هذا النّمط سنقوم بإنشاء مُنبّه (كالّذي تضبطه للاستيقاظ في هاتفك) يمكن ضبطه إلى تاريخ ووقت معيّنين ثمّ تفعيله أو تعطيله حسب الرّغبة: function Alarm(when) { this.setAt = when; this.enable = function() { var startAfter = new Date(this.setAt) - new Date; console.log("Alarm will wake you up after " + startAfter/1000 + " seconds"); this.timeout = setTimeout(function() { console.log("Wake up!"); }, startAfter); } this.disable = function() { if (this.timeout) { clearTimeout(this.timeout); delete this.timeout; console.log("Alarm diabled"); } } } var a = new Alarm("2015-03-19 5:58 PM"); a.enable() // Alarm will wake you up after 8.982 seconds // After a few seconds: // Wake up!في المثال السّابق نُسمّي الدّالة Alarm() مُشيّدًا (constructor)، والكائن a نُسخة (instance). لاحظ أنّ Alarm في المثال السّابق ليست سوى دالّة (function)، فهي ليست صنفًا كما في لغات أخرى مثل Java وC++، إذ تُعتبر الدّوال في JavaScript مكوّنًا من الدّرجة الأولى وتُعامل كما يُعامل أيّ كائن، وهكذا يمكن استخدامها كمشيّد لكائن آخر ممّا يسمح بمحاكاة مفهوم الأصناف الّذي لم يُضَف إلّا مؤخّرًا في JavaScript. من عيوب المِثال السّابق إسناد الدّوال الّتي ستعمل عمل الوظائف (methods) إلى النُسخة ذاتها عند إنشائها، وهذا يعني تكرار محتوى الدّوال في الذّاكرة مع كلّ نسخة جديدة من الكائن Alarm، بينما يمكننا توفير هذا الاستهلاك غير المُبرّر للذّاكرة بإسناد الدّوال إلى النّموذج البدئيّ للكائن (أي إلى Alarm.prototype) مما يسمح بمشاركتها بين كل نسخ الكائن، لتقوم الآلة الافتراضيّة بتنفيذ النّصّ البرمجيّ للدّالّة ذاتها بسياق النّسخة (instance context) الّتي استدعت الدّالة، أي إنّ this تُشير ضمن الدّالة عند تنفيذها إلى النُسخةَ المنشأة وليس الصّنف؛ بالطّبع ليس من المرغوب تطبيق الفكرة ذاتها على المُتغيّرات الأخرى مثل setAt، لأنّه من البديهيّ أن تختلف قيمتها بين نسخة وأخرى. لنُعد كتابة المثال السّابق بصورة أفضل: function Alarm(when) { this.setAt = when; } Alarm.prototype.enable = function() { var startAfter = new Date(this.setAt) - new Date; console.log("Alarm will wake you up after " + startAfter/1000 + " seconds"); this.timeout = setTimeout(function() { console.log("Wake up!"); }, startAfter); } Alarm.prototype.disable = function() { if (this.timeout) { clearTimeout(this.timeout); delete this.timeout; console.log("Alarm diabled"); } } var a = new Alarm("2015-03-19 6:21 PM"); a.enable(); // Alarm will wake you up after 30.243 seconds // After 30 seconds... // Wake up!هذا الأسلوب في إنشاء الأصناف شائع جدًّا، وهو يتطلّب فهمًا دقيقًا لآليّة الوراثة في JavaScript؛ إذ يُبنى كلّ كائنٍ فيها على كائن آخر يُسمّى النّموذج البدئيّ (prototype)، ويقوم هذا الكائن الأخير على كائن ثالث أعلى منه في السّلسلة هو نموذجه البدئيّ، وهكذا حتّى نصل إلى null الّذي ليس له نموذج بدئيّ بحسب تعريف اللّغة. في مثالنا السّابق الكائن Alarm.prototype هو النّموذج البدئيّ للكائن a، وهذا يعني أنّ كل الخواصّ المُسندة إلى Alarm.prototype وما فوقه ستكون مُتاحة للكائن a، ولو كتابنا برنامجًا مُشابهًا بـJava لقُلنا إنّ Alarm صنفٌ وإنّ a نُسخة عن هذا الصّنف (instance). عندما نحاول الوصول إلى الخاصّة a.setAt، فإنّ مُفسِّر JavaScript يبدأ بالبحث عن هذه الخاصّة من أدنى سلسلة الوراثة، أي من الكائن a ذاته، فإنّ وجدها قرأها وأعاد قيمتها، وإلّا تابع البحث صعودًا إلى النّموذج البدئيّ وهكذا... وطبيعة JavaScript هذه هي ما سمح لنا بإسناد الوظيفتين enable وdisable إلى Alarm.prototype مطمئنّين إلى أنّها ستكون مُتاحة عند قراءة a.enable() وa.disable(). يمكن التأكّد من النّموذج البدئيّ للكائن a كما يلي: Object.getPrototypeOf(a) == Alarm.prototype; // trueالأصناف في ECMAScript 6يُقدّم الإصدار الأحدث من JavaScript مفهوم الأصناف (classes) بصورته التّقليديّة المعروفة في اللّغات الأخرى، إلّا أنّه ليس سوى أسلوب آخر لصياغة النّماذج البدئيّة (أو ما يُسمّى syntactic sugar)، وهذا يعني أنّه نموذج الوراثة في JavaScript لم يتغيّر. يمكننا إعادة كتابة المثال السّابق بصياغة الأصناف في ES6 كما يلي: class Alarm { constructor(when) { this.startAt = when; } enable() { var startAfter = new Date(this.setAt) - new Date; console.log("Alarm will wake you up after " + startAfter/1000 + " seconds"); this.timeout = setTimeout(function() { console.log("Wake up!"); }, startAfter); } disable() { if (this.timeout) { clearTimeout(this.timeout); delete this.timeout; console.log("Alarm diabled"); } } }وستُسند الدّوال enable() وdisable() إلى Alarm.prototype تمامًا كما في المثال الذي سبقه. يُذكر أنّ استخدام new ليست الطّريقة الوحيدة لتشييد الكائنات، إذ يمكن استخدام الوظيفة Object.create() لتُعطي النّتيجة ذاتها: var a = Object.create(Alarm.prototype); Object.getPrototypeOf(a) == Alarm.prototype; // trueإسناد الخواصّ إلى الكائناتJavaScript لغة ديناميكية، وهذا يعني أنّه يمكن إضافة وحذف الخواصّ من الكائنات وتعديل نماذجها البدئيّة أثناء التّنفيذ، وهذا ما يمنحها القسم الأكبر من مرونتها ويجعلها مناسبة للاستخدام في بيئة مُعقّدة مثل بيئة الويب، وليس من الغرابة أن توفّر اللّغة وسائل متعدّدة لإسناد الخصائص إلى الكائنات لتلبية الحاجات المتنوّعة لتطبيقات الويب. ماذا لو أردنا تغيير قيمة المنبّه في مثالنا السّابق بعد تفعيله؟ لربّما ترادونا للوهلة الأولى إمكانيّة تغيير قيمة الخاصّة setAt بالطّريقة التّقليدية: a.setAt = "2016-03-03 03:03 PM"; // or a["setAt"] = "2016-03-03 03:03 PM";لكنّ نتيجة هذا الفعل لن تكون كما يُتوقّع، فلو عدنا للمثال السابق وتمعّنا في خواصّه، للاحظنا عيبًا في كيفيّة عمل المُنبّه، إذ إنّ الخاصّة setAt مكشوفة ويمكن تغيير قيمتها في أيّ وقت، حتى بعد تفعيل المُنبّه، إلّا أنّ تغييرها بعدئذٍ لن يغيّر اللّحظة الحقيقيّة الّتي سينطلق فيها المنبّه كما يتضّح لنا عند قراءة النّصّ البرمجيّ، ولذا فنحن هنا أمام حلّين: إمّا منع تغيير قيمة الخاصّة setAt وجعلها للقراءة فقط بعد إنشاء المُنبّه، أو إيقاف المنبّه وإعادة ضبطه في كلّ مرّة تُغيّر فيها قيمة الخاصّة setAt، وكلا الحلّين متاحان إذا كنّا على علم بأساليب إسناد الخصائص في JavaScript. توفّر اللّغة الوظيفة Object.defineProperty() الّتي تسمح بتعريف خواصّ لكائن ما مع إمكانيّة التّحكم بتفاصيل هذه الخاصّة، ومن هذه التّفاصيل: هل الخاصّة قابلة للكتابة؟ (writable)هل يجب المرور على هذه الخاصّة عند سرد خواصّ الكائن؟ (enumerable)ما الذي يحدث عند إسناد قيمة للخاصّة؟ (set)ما الذي يحدث عند قراءة قيمة الخاصّة؟ (get)وبهذا يمكننا بسهولة منع تغيير قيمة الخاصّة setAt بعد إسنادها: function Alarm(when) { Object.defineProperty(this, "setAt", { value: when, writable: false }) } Alarm.prototype.enable = function() { var startAfter = new Date(this.setAt) - new Date; console.log("Alarm will wake you up after " + startAfter/1000 + " seconds"); this.timeout = setTimeout(function() { console.log("Wake up!"); }, startAfter); } Alarm.prototype.disable = function() { if (this.timeout) { clearTimeout(this.timeout); delete this.timeout; console.log("Alarm diabled"); } } var a = new Alarm("2015-03-19 7:51 PM"); a.setAt = new Date("2016-03-19"); console.log(a.setAt); // "2015-03-19 7:51 PM"لاحظ أنّ قيمة setAt لم تتغيّر. هذا حلّ جيّد، لكن سيكون من الأفضل السّماح للمُستخدم بتعديل قيمة المنبّه، وعندها سنلجأ لإيقاف المنّبه وإعادة ضبطه كما يلي: function Alarm(when) { var _hidden_value = new Date(when); Object.defineProperty(this, "setAt", { set: function(newValue) { _hidden_value = new Date(newValue); if (this.timeout) { this.disable(); console.log("Alarm changed to " + newValue); console.log("You need to re-enable the alarm for changes to take effect"); } }, get: function() { return _hidden_value; } }) } Alarm.prototype.enable = function() { var startAfter = new Date(this.setAt) - new Date; console.log("Alarm will wake you up after " + startAfter/1000 + " seconds"); this.timeout = setTimeout(function() { console.log("Wake up!"); }, startAfter); } Alarm.prototype.disable = function() { if (this.timeout) { clearTimeout(this.timeout); delete this.timeout; console.log("Alarm diabled"); } } var a = new Alarm("2016-03-03 03:03 PM") a.enable(); // Alarm will wake you up after 30221933.66 seconds a.setAt = "2015-03-19 8:05 PM"; // Alarm changed to 2015-03-19 8:05 PM // You need to re-enable the alarm for changes to take effect a.enable() // Alarm will wake you up after 20.225 seconds // After 20 seconds... // Wake up!لاحظ أنّنا سنحتاج إلى مُتغيّر سرِّيِّ (_hidden_value) نُخزّن فيه القيمة الفعليّة لوقت التّنبيه. متى أستخدم هذا النّمط؟نمط المُشيّد لا يحتكر بنية مشروعك عند استخدامه؛ معنى هذا أنّه لا شيء يمنعك من استخدام نمط المُشيّد مع أي نمط آخر عند الحاجة لذلك، فيمكن (بل يشيع كثيرًا) استخدام المُشيّدات ضمن الوحدات (modules) ثمّ تصديرها لاستخدامها من موضع آخر في المشروع، ومثال ذلك أشياء مثل EventEmitter وStreams في Node.js. كما يمكن بناء أنماط أخرى سنتعرّف عليها لاحقًا على أساس المُشيِّدات مثل نمط الكائن المُتفرّد (Singleton) ونمط المُراقِب (Observer pattern). المصادر: شبكة مُطوّري موزيلّا: Inheritance and the prototype chainكتاب JavaScript Design Patterns لمؤلّفه Addy Osmani
- 1 تعليق
-
- 5
-
- constructor
- design patterns
-
(و 1 أكثر)
موسوم في: