البحث في الموقع
المحتوى عن 'object'.
-
كان الغرض من البرمجة الكائنية (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 أكثر)
-
تعرّفنا فيما سبق من دروس هذه السلسلة على أساسيات لغة بايثون، من مُتغيّرات وحلقات تكرار إلى الدوال، وقد حان الوقتُ للدخول إلى أساسيات البرمجة كائنية التوجّه Object Oriented Programming وهي ببساطة طريقة أخرى للبرمجة بحيث تكون أجزاء الشّيفرة مجموعة داخل دوال تُسمّى التوابع methods والدوال تكون داخل صنف معيّن Class. عند إنشاء كائن object من هذا الصنف فإنّنا نستطيع أن نُنفّذ عليه مُختلف العمليات الموجودة داخل التوابع والتي بدورها توجد داخل الصنف. هناك تسميّات عربية أخرى لهذا النّوع من البرمجة، لذا لا تقلق إذا صادَفتَ أحد هذه التّسميات في أماكن أخرى، فكلّها تُشير إلى نفس المعنى: برمجة غرضيّة التوجه برمجة شيئية المنحى برمجة كائنيّة المنحى بنية الصنف Class الصنف ببساطة يحتوي على أجزاء مُختلفة من الشيفرة تماما مثل الدالة، الفرق هنا هو أنّ الصنف يحتوي على دوال كذلك، وهذه الدوال تُسمى التّوابع، ويحتوي كذلك على مُتغيّرات وتنقسم هذه الأخيرة إلى نوعين، مُتغيّر الصنف، والمُتغيّر العادي، الفرق بينهما هو أنّك تستطيع الوصول إلى مُتغيّر الصنف في أي مكان داخل الصنف (سواء داخل التوابع أو خارجها). إنشاء صنف لإنشاء صنف في لغة بايثون كلّ ما عليك فعله هو كتابة كلمة class وبعدها اسم الصنف ثمّ إلحاق نقطتين، بعدها اكتب الشيفرة بإزاحة أربع مسافات: >>> class My_class: ... pass أنشأنا أعلاه صنفًا بسيطا باسم My_class وهو لا يفعل أي شيء يُذكر (كلمة pass تُخبر بايثون بالمرور دون تنفيذ أي شيء). إذا كتبت اسم الصنف على مفسّر بايثون فستجد مُخرجا كالتّالي: >>> My_class <class __main__.My_class at 0x7fd0efc41460> لاحظ بأنّ الكلمة الأولى من المُخرج هي class أي أنّنا أصبحنا نمتلك صنفًا جديدًا، ما يتبع at هو المكان في الذاكرة الذي وُضع فيه الصنف ويتغيّر بين الحين والآخر. إنشاء كائن من صنف بعد أن أنشأنا الصنف سنتمكّن الآن من إنشاء كائن من هذا الصنف، والكائن مُجرّد اسم تماما كالمُتغيّر: my_object = My_class() الآن الكائن my_object هو من صنف My_class. تعريف المتغيرات داخل صنف يُمكننا أن نُعرّف مُتغيّرات في الصنف تماما كما نُعرّف المُتغيّرات بشكل عادي. class My_class: my_variable = 'This is my variable' للوصول إلى المُتغيّر ننشئ أولا كائنا من الصنف وبعدها نكتب اسم الكائن ثمّ نقطة ثمّ اسم المُتغيّر: my_object = My_class() print my_object.my_variable المُخرج: This is my variable يُمكن كذلك الحصول على النّتيجة ذاتها في سطر واحد: print My_class().my_variable إنشاء التوابع التوابع هي دوال خاصّة بالصنف، ويُمكننا إنشاء التابع بنفس الطّريقة التي نُنشئ بها الدالة، الإختلاف هنا هو أنّ جميع التوابع يجب أن تُعرّف مع مُعامل باسم self وذلك للإشارة إلى أنّ الدالة/التابع تابع للصنف، لننشئ تابعا داخل صنف الآن. class My_class: my_variable = 'This is my variable' def my_method(self): print 'This is my method' الآن إذا أنشأنا كائنا فإنّنا سنتمكّن من الوصول إلى التابع، وتذكّر بأنّ التابع تلحقه الأقواس: my_object = My_class() my_object.my_method() المُخرج: This is my method يُمكن كذلك الحصول على النّتيجة ذاتها في سطر واحد: My_class().my_method() كما تُلاحظ فقد نُفّذت الشيفرة الموجودة داخل التّابع my_method ويُمكننا كذلك أن نجعل التّابع يقبل المُعاملات، لكن تذكّر الحفاظ على الكلمة self كمُتغيّر أول. class My_class: my_variable = 'This is my variable' def my_method(self, my_parameter): print 'This is my method ; {} is my parameter'.format(my_parameter) يُمكنك استدعاء التّابع كالتّالي: my_object = My_class() my_object.my_method('Parameter1') my_object.my_method('Parameter2') المُخرج: This is my method ; Parameter1 is my parameter This is my method ; Parameter2 is my parameter في البرنامج السّابق، أنشأنا أولا صنفًا باسم My_class وقُمنا بتعريف مُتغيّر، ثمّ بتعريف تابع باسم my_method يقبل مُعاملين self و my_parameter، بالنّسبة لاستدعاء التّابع، فنحتاج فقط إلى تمرير المُعاملات الموجودة بعد المُعامل selfولا نحتاج إلى تعيين قيمة لهذا المُعامل. مُلاحظة: يُمكنك إعادة تسميّة المُعامل الأول كما تشاء، أي أنّ البرنامج التّالي سيعمل دون مشاكل. class My_class: def my_method(this, my_parameter): print '{} is my parameter'.format(my_parameter) ولكن رغم ذلك فالمُتعارف عليه بين مُبرمجي لغة بايثون هو استعمال self، وفي كثير من اللغات الأخرى تُستعمل this عوضا عن self، أما في برامِجك فمن المُفضّل الإبقاء على هذه التّسميّة المُتعارف عنها، وذلك لتكون شيفراته سهلة القراءة. الوصول إلى متغيرات الصنف داخل التوابع تأمّل الصنف التّالي: class Person: lastname = 'Dyouri' job = 'Writer, Developer' def say_hello(self): name = 'Abdelhadi' print 'Hello, My name is {}'.format(name) البرنامج أعلاه بسيط جدا، أولا نعرّف صنف باسم Person وبعدها نقوم بتعيين قيمتين للمُتغيّرين name و lastname، وبعدها عرّفنا تابعا باسم say_hello يطبع جملة Hello, My name is Abdelhadi. كلّ شيء جيد، لكن ماذا لو أردنا أن نصل إلى المُتغيّرات الأخرى الموجودة خارج التّابع، فلا يُمكننا مثلا أن نقوم بالأمر كالتّالي: class Person: lastname = 'Dyouri' job = 'Writer, Developer' def say_hello(self): name = 'Abdelhadi' print 'Hello, My name is {}'.format(name) print lastname print job ستحصل على الخطأ التّالي: global name 'lastname' is not defined لتفادي هذا الخطأ سنستعمل كلمة self قبل المُتغيّر. class Person: lastname = 'Dyouri' job = 'Writer, Developer' def say_hello(self): name = 'Abdelhadi' print 'Hello, My name is {}'.format(name) print 'My Last name is {} '.format(self.lastname) print 'I am a {}'.format(self.job) استدعاء التّابع: me = Person() me.say_hello() المُخرج: Hello, My name is Abdelhadi My Last name is Dyouri I am a Writer, Developer لاحظ بأنّنا قُمنا بالوصول إلى مُتغيّر lastname عن طريق استدعائه بـ self.lastname وكذا الحال مع المُتغيّر job، وهذه الطّريقة مُشابهة لاستخدام كلمة global الفرق هنا أنّ هذه الأخيرة تُمكن من الوصول إلى المُتغيّر في كامل البرنامج، أمّا كلمة self فتُشير إلى المُتغيّر المُعرّف في الصنف الحاليّة فقط. لتفهم أكثر كيفيّة عمل الكلمة self فقط تخيّل بأنّها تحمل نفس اسم الصنف، مثلا: class Person: lastname = 'Dyouri' job = 'Writer, Developer' def say_hello(self): name = 'Abdelhadi' print 'Hello, My name is {}'.format(name) print 'My Last name is {} '.format(Abd.lastname) print 'I am a {}'.format(Abd.job) لاحظ بأنّنا غيّرنا كلمة self إلى اسم الصنف واستمرّ عمل البرنامج دون مشاكل. وبنفس الطّريقة يُمكنك أن تستدعي تابعا داخل تابع آخر في نفس الصنف: class Person: def say_name(self): print 'Abdelhadi' def say_hello(self): print 'Hello My name is:' self.say_name() المُخرج: Hello My name is: Abdelhadi ما حدث هو أنّ التّابع say_hello قام بطباعة جملة :Hello My name is ثمّ قام باستدعاء التّابع say_name الذي قام بدوره بطباعة الاسم Abdelhadi. لماذا تستعمل البرمجة الكائنية، ومتى يجب علي استخدامها قد تُلاحظ بأنّ ما يُمكنك فعله بالبرمجة الكائنيّة يُمكن القيام به بالدوال والمُتغيّرات فقط. هذا صحيح، وهو أمر واضح، البرمجة الكائنيّة تُستعمل أساسا إذا كان البرنامج الذي تبنيه مُعقّدا مع العديد من الوظائف المُتعلّقة ببعضها (كمكتبة برمجيّة)، مثلا لنقل بأنّك تُطوّر برنامجا مسؤولا عن جلب بيانات من موقع مُعيّن، وبعدها التّعديل عليها، ثمّ إخراج مستند PDF يحتوي على هذه البيانات بشكل يُسهّل قراءتها، هنا ستحتاج إلى صنف لجلب البيانات والتّعديل عليها، وصنف أخرى لتحويل البيانات إلى نصّ مقروء واضح ثمّ إلى ملفّ PDF. إذا نشرت برنامجك مع صديقك وأراد أن يعمل على الجزء الثاني لإضافة وظيفة تُمكن المُستخدم من طباعة المُستند فلا يُعقل أن يضطر للمرور على كل ما يتعلّق بجلب البيانات فقط لأنّه يريد أن يضيف خاصيّة لا علاقة لها بجلب البيانات. استعمال البرمجة الكائنيّة في هذا المشروع سيسمح لك بالتّركيز على الجزء الأول، وسيسمح لصديقك بالتّركيز على تطوير الجزء الثاني. خلاصة الأمر هي أنّك لست مضطرا لاستعمال البرمجة الكائنيّة إلا إذا كان برنامجك طويلا يحتوي على وظائف تتعلّق ببعضها البعض (وظائف من نفس الصنف)، ونسبة استخدام الآخرين لشيفرتك عالية. تمارين التمرين 1 أنشئ صنفًا باسمك، وقم بتعريف مُتغيّرين lastname (الاسم العائلي) و age (العمر)، ثم أنشئ كائنا باسم me (أنا) وقم بطباعة اسمك العائلي وعمرك. التمرين 2 أنشئ صنفًا باسم Car (سيارة) وقم بتعريف مُتغيّرات لصفات السّيارة، مثلا brand لاسم الشّركة، release_date لتاريخ الإعلان عن السّيارة. التمرين 3 أضف توابع إلى الصنف Car التي أنشأتها في التّمرين الثّاني، يُمكن أن تكون التوابع عبارة عن عمليّات تقوم بها السّيارة مثلا move للحركة، stop للتوقّف، slow_down لتخفيض السّرعة، وقم بطباعة جمل تفيد بأنّ العمليّة قد نجحت. المفروض أن يتمكّن الآخرون من إنشاء كائنات خاصّة بهم بحيث تُستخدم بهذه الطّريقة: bmw = Car() bmw.move() bmw.slow_down() bmw.stop() خاتمة تعرّفنا في هذا الدّرس على بعض من أهم أساسيات البرمجة الكائنيّة التوجه في لغة بايثون وهذا الموضوع أطول من أن أشرحه في درس واحد لذلك سيكون الدّرس التّالي تكملة لما تعلّمناه في هذا الدّرس حول تعلم بايثون وسيغطي مفاهيم أخرى حول البرمجة كائنية التوجّه.
-
مقدمة تُعتبر خدمات تخزين البيانات المرنة والقابلة للتوسع حسب الحاجة، متطلب أساسي لأغلب التطبيقات والخدمات التي يتم تطويرها بالأدوات والتقنيات الحديثة. بغض النظر عن تخزين كميات قليلة أو كثيرة من الصور، الفيديوهات والنصوص الضخمة فإن مُطوري التطبيقات يحتاجون حلًا لتخزين واسترجاع المحتوى الخاص بالمستخدم، سجلات عمله، نسخه الاحتياطية وغيره من الأمور الأخرى. في ظل الأنظمة المنشورة المعقدة، وفي ظل الحاويات المختلفة والبيئات سريعة التغير والزوال، فإن زمن حفظ الملفات ببساطة في وحدة تخزين على خادوم واحد قد انتهى، حيث قام مزودو الخدمات السحابية بتطوير خدمات لتلبية حاجات التخزين في الأنظمة والتطبيقات الحديثة، وغالبًا تندرج خدمات التخزين السحابية تحت نوعين هما التخزين الكائني (Object Storage) والتخزين الكتلي (Block Storage). سنتناول هنا مميزات وسلبيات كل نوع. ما هو التخزين الكتلي؟ تعتبر خدمات التخزين الكتلي بسيطة ومألوفة نوعًا ما، حيث تُقدم هذه الخدمات خدمة التخزين العادية –كما في القرص الصلب– ولكن من خلال الشبكة. يقدم مزودو الخدمات السحابية الأدوات اللازمة التي تُتيح الحصول على جهاز تخزين كتلي بأي حجم وربطه بالآلة الافتراضية الخاصة بك، ومن هنا تستطيع التعامل مع الجهاز كقرص عادي، حيث تستطيع تهيئته، حفظ الملفات عليه، ربط أكثر من قرص بنظام RAID أو حتى إعداد قاعدة بيانات عليه للكتابة مباشرةً على الجهاز الكتلي. بالإضافة لما سبق، فإن أجهزة التخزين المرتبطة بشبكة غالبًا ما تتميز عن الأقراص الصلبة العادية بما يلي: تستطيع أخذ نسخة حية بسهولة للجهاز للأغراض الاحتياطية. من الممكن إعادة تغيير حجم جهاز التخزين الكتلي وتلبية الاحتياجات المتراكمة. تستطيع فك وربط الجهاز بين الآلات الوهمية بسهولة. ما ذكرناه من مميزات هي خصائص مرنة من الممكن أن تكون مفيدة غالبًا لأي تطبيق. فيما يلي نسرد بعضًا من مميزات وعيوب هذه التقنية. مميزات التخزين الكتلي: التخزين الكتلي شائع ومألوف، حيث أن المستخدمين والبرمجيات يفهمونه ويستطيعون التعامل مع ملفاته وأنظمته بشكل واسع. الأجهزة الكتلية مدعومة جيدًا. كل لغات البرمجة تستطيع قراءة وكتابة الملفات منها. صلاحيات وأذونات ملفات النظام في الأجهزة الكتلية مألوفة ومفهومة بشكل جيد. أجهزة التخزين الكتلي لديها وقت استجابة منخفض في عمليات الإدخال والإخراج، مما يجعلها مناسبة لقواعد البيانات. عيوب التخزين الكتلي: التخزين مرتبط بخادم واحد في نفس الوقت. تحتوي كتلة التخزين وملفات النظام على بيانات وصفية (metadata) قليلة عن المعلومات التي تُخزنها مثل تاريخ الإنشاء، المستخدم الذي تعود له البيانات وحجم هذه البيانات. أي معلومات إضافية عما تُخزنه وحدة التخزين الكتلي يجب أن يتم التعامل معها على مستوى التطبيق أو قاعدة البيانات مما يؤدي إلى زيادة الأعباء على المطور لمتابعة هذه المعلومات. يجب عليك أن تدفع مقابل حجم التخزين الذي حجزته حتى لو لم تقم بالاستفادة منه أو استخدامه. تستطيع أن تصل لخدمة التخزين الكتلي عبر خادومٍ واحد يعمل فقط. التخزين الكتلي يحتاج إلى اعدادات تنصيب وعمل أكثر (اختيار ملفات النظام، الأذونات، الإصدارات، النسخ الاحتياطية...وغيرها) مقارنة بالتخزين الكائني. بسبب سرعة عمليات الإدخال والإخراج في التخزين الكتلي ، فإنه يُعتبر خيارًا أفضل لتخزين البيانات في قواعد البيانات العادية. بالإضافة لذلك، فإن العديد من الأنظمة القديمة تتطلب وجود ملفات نظام عادية للتخزين مما يجعل التخزين الكتلي هو الحل في هذه الحالة. إذا لم يتوفر لدى مزود الخدمات السحابية خدمة التخزين الكتلي، تستطيع أن تقوم بتشغيل الخدمة الخاصة بك بنفسك باستخدام OpenStack Cinder، Ceph، أو خدمة iSCSI المُضمنة مع العديد من أجهزة NAS. ما هو التخزين الكائني؟ في عالم الحوسبة السحابية الحديث، يُعرف التخزين الكائني بأنه تخزين واسترجاع البيانات غير المُرتبة وفق بنية معينة (Unstructured) عبر استخدام الواجهة البرمجية ل HTTP. فبدلًا من تقسيم الملفات إلى كُتل متعددة وحفظها باستخدام نظام الملفات على وحدة التخزين، يتم التعامل مع الملفات ككائنات وتُحفظ على الشبكة. قد تكون هذه الكائنات عبارة عن صورة، سجلات، ملفات HTML أو أية كائنات ثنائية كبيرة بذاتها. تكون هذه الكائنات غير مرتبة وفق بنية معينة لأنها لا تحتاج لاتباع مُخطط أو تنسيق محدد. أصبح التخزين الكائني معروفًا بسبب قدرته الكبيرة على تبسيط تجربة المطور في بناء وتطوير خدمات التخزين، حيث تم تطوير مكتبات تدعم بناء الواجهات البرمجية (التي تحتوي طلبات (HTTP في أغلب لغات البرمجة. حفظ ملف ثنائي كبير أصبح أمرًا سهلًا باستخدام طلب HTTP PUT، وكذلك استرجاع الملف وبياناته الوصفية يتم عبر طلب GET عادي. علاوة على ذلك، فأغلب خدمات التخزين الكائني تستطيع مشاركة هذه الملفات مع المستخدمين الآخرين دون الحاجة لإعدادات خاصة على الخادم المُستضيف. بالإضافة لما سبق، لن تَدفع مقابل خدمة التخزين الكائني إلا مقابل المساحة التي تَستخدمها (بعض الخدمات تُدفع مقابل كل طلب HTTP أو حسب استهلاك النقل عبر النطاق) وهذا الأمر يُعد نعمةً لبعض المطورين الذين يحصلون على خدمة استضافة وتخزين عالمية مقابل تكلفة تتلاءم مع حجم الاستخدام. التخزين الكائني ليس الحل الجيد لكل الحالات. لنلقي نظرة على مميزات وعيوب هذه النوع. مميزات التخزين الكائني: واجهة برمجية بسيطة عبر HTTP تدعم أغلب المستخدمين بغض النظر عن نظام التشغيل المستخدم أو لغة البرمجة. تكلفة التخزين تُحدد مقابل ما يتم استخدامه من مساحة وليس ما يتم حجزه. لن تحتاج لحجز خادوم كامل للحصول على خدمة التخزين الكائني لملفاتك الساكنة. بعض مزودي خدمة التخزين الكائني تُقدم خاصية الدمج CDN المُضمنة، والتي تتيح تخبئة ملفاتك حتى يكون تنزيلها أسرع للمستخدمين. خاصية الأَصْدَرَة (Versioning) التي تتيح لك استرجاع النسخ القديمة من ملفاتك واستعادتها بعد التعديل عليها بشكل خاطئ. تتكيف خدمات التخزين الكائني مع احتياجات الاستخدام بدءًا من الاستخدام البسيط وحتى حالات الاستخدام المكثف دون أن يحتاج المطور لزيادة المصادر أو إعادة الهيكلة لمعالجة الحمل المطلوب. باستخدام خدمات التخزين الكائني لن تحتاج لمتابعة وإدارة وحدات التخزين أو أنظمة RAID لأنها من مسئولية مزود الخدمة. تستطيع حفظ بيانات وصفية بجانب البيانات الأصلية وذلك يُبسط لك هيكلة التطبيق الذي تعمل عليه. عيوب التخزين الكائني: لا تستطيع أن تستضيف قاعدة بيانات عادية على خدمة التخزين الكائني بسبب وقت الاستجابة العالي واللازم لقاعدة البيانات. باستخدام التخزين الكائني لا تستطيع التعديل على جزء من الكائن الذي تحفظه، حيث عليك قراءة أو كتابة الكائن كله في العملية الواحدة وهذا له آثار مترتبة على الأداء. فمثلًا، باستخدام نظام الملفات تستطيع بسهولة أن تُضيف سطر في نهاية ملف سجلات، ولكن في نظام التخزين الكائني يجب عليك قراءة الملف كاملا ثم التعديل عليه ثم إعادة كتابته وحفظه مرة أخرى، وهذا الأمر يجعل من التخزين الكائني خيارًا غير مثالي في حفظ البيانات التي تتغير بشكل مستمر. لا تستطع نُظم التشغيل تعيين كائن كمحرك أقراص بشكل سهل. يوجد بعض العملاء والمحولات التي تساعد في تنفيذ هذا الأمر، ولكن بشكل عام، استخدام الكائن وتصفحه ليس متاحًا بسهولة كما هو متاح لدى التنقل خلال المجلدات في نظام الملفات. بسبب هذه الخصائص، التخزين الكائني مفيد في استضافة الملفات الساكنة، حفظ محتوى المستخدم المتمثل مثلًا في الصور ولقطات الفيديو، حفظ ملفات النسخ الاحتياطي والسجلات. يوجد بعض حلول التخزين الكائني التي تتيح لك حفظ ملفاتك وبياناتك دون القلق على كيفية إدارة الأقراص أو خيارات التوسع. تستطيع مثلا أن تُجرب Minio، وهو خادوم مشهور مُطَوَّرْ باستخدام لغة Go ويقدم خدمة التخزين الكائني، أو تستطيع تجربة Ceph أو OpenStack Swift. خاتمة اختيار طريقة التخزين قد يكون قرارًا معقدًا للمطورين. ناقشنا مزايا وعيوب كلًا من التخزين الكتلي والتخزين الكائني. على الأرجح أن أي تطبيق معقد وذو كفاءة سيحتاج لاستخدام كلا النوعين في التخزين لتلبية كافة الاحتياجات اللازمة للعمل. ترجمة -وبتصرّف- للمقال Object Storage vs. Block Storage Services لصاحبه Brian Boucheron
-
هل تعاني من صعوبة في تطبيق مفاهيم البرمجة الكائنية مع لغة JavaScript؟ إذًا أنت في المكان الصحيح، ففي هذا المقال سنبدأ بمطلع لمفاهيم البرمجة الكائنية (أو الشيئية كما قد يُطلق عليها البعض)، ومن ثم مراجعة نموذج جافا سكريبت في الكائنات، وأخيرًا شرح مفاهيم البرمجة الكائنية في جافا سكريبت، لتحصل على إلمام واف والقصة الكاملة. مراجعة في جافاسكريبت JavaScript يُمكن العودة إلى المقال إعادة تقديم JavaScript لمن أساء فهمها هنا في أكاديمية حسوب للحصول على مراجعة لأساسيات لغة جافا سكريبت وأخذ فكرة عن المغيرات وأنواع الدوال وبقية الأساسيات. البرمجة الكائنية Object-oriented programming إن البرمجة الكائنية (OOP) ما هي إلا نمط برمجي يَستخدم التجريد في إنشاء نماذج/نسخ لتجسيد العالم الحقيقي، وتَستخدم البرمجة الكائنية في ذلك أساليب مُتعدّدة من هذا النمط، فهي تستخدم الوحدات module، وتعدديّة الأشكال polymorphism والتغليف encapsulation، وتجدر الإشارة إلى أن معظم لغات البرمجة تدعم مفهوم OOP أمثال اللغات البرمجية: جافا، بايثون، روبي، وطبعًا جافا سكريبت. يُعالج أو لنقل يَتصور مفهوم البرمجة الكائنية OOP البرنامج كتشكيلة من الأشياء/الكائنات المتعاونة/المترابطة بدلًا من يتصوّره كتشكيلة من الدوال (functions) أو كسرد من الأوامر. ففي مفهوم OOP، كل كائن/شيء له القدرة على استقبال الرسائل، ومعالجة البيانات، وإرسال الرسائل إلى باقي الكائنات، ويُمكن اعتبار أنه لكل كائن object كينونة خاصة به ودور/وظيفة مستقلة عن الكائن الآخر. تُعزز البرمجة الكائنية القدرة على صيانة الشيفرة البرمجية والمرونة في التطوير، وأثبتت جدارتها على نطاق واسع في هندسة البرمجيات الكبيرة، ولأن البرمجة الكائنية تُشدد على استخدام الوحدات module، فإن الشيفرة/الكود المكتوب بمفهوم البرمجة الكائنية هو أبسط في التطوير وأسهل في الفهم مستقبلًا (عند التنقيح والتعديل)، وكما يعزز مفهوم البرمجة الكائنية التحليل المباشر للشيفرة، وفهم الحالات الشائكة فهمًا أفضل من باقي الأساليب البرمجية الأخرى. مصطلحات البرمجة الكائنية المجال في البرمجة الكائنية Namespace ما هو إلا عبارة عن حاوي تسمح للمطوّر بتحزيم جميع الوظائف تحت اسم محدد وفريد. الصنف أو الفئة Class في البرمجة الكائنية يعتني الصنف بكل ما يتعلّق بميزات وخصائص الكائن، والصنف ما هو إلا قالب template تعريفي بخاصيات properties وبطُرق/وظائف methods الكائن object. الكائن Object في البرمجة الكائنية الكائن ما هو إلا حالة/أمثولة instance من صنف class. الخاصية property في البرمجة الكائنية ما هي إلا مميزات وخصائص الكائن، كاللون مثلًا. الطريقة أو الوظيفة Method في البرمجة الكائنية تعتني الطريقة أو الوظيفة كما يُسميها البعض بقدرات الكائن، مثل قدرة المشي مثلًا، وهي دور أو وظيفة مرتبطة مع صنف class. المشيد Constructor في البرمجة الكائنية ما هو إلا طريقة method تُستدعى في لحظة استهلال instantiate الكائن، وعادةً ما يكون له نفس اسم الصنف الذي يحتويه. الوراثة Inheritance في البرمجة الكائنية يُمكن للصنف أن يرث مميزات من صنف آخر. التغليف Encapsulation في البرمجة الكائنية طريقة في تحزيم البيانات data والطُرق methods التي تستخدم البيانات. التجريد Abstraction في البرمجة الكائنية يجب على الاقتران الحاصل من: الوراثة والطُرق methods والخاصيات properties لكائن معقد وشائك التمثيل برمجيًا أن يعكس الواقع المراد محاكاته في البرمجة الكائنية. تعددية الأشكال Polymorphism في البرمجة الكائنية تحمل كلمة Poly بحد ذاتها المعنى "متعدد" وتحمل الكلمة morphism المعنى "أشكال، ويُشير المفهوم ككل إلى أن أكثر من صنف قد يُعرّف نفس الطريقة method أو الخاصية property. البرمجة المعتمدة على النموذج الأولي Prototype البرمجة المعتمدة على النموذج الأوّلي (Prototype-based programming) ما هي إلا نموذج من البرمجة الكائنية OOP ولكنها لا تستخدم الأصناف classes، بل تقوم أولًا بإعداد سلوك أي صنف class ما ومن ثم تُعيد استخدامه، ويُطلق البعض على هذا النموذج: البرمجة بلا أصناف classless، أو البرمجة المَبْدَئِية المنحى prototype-oriented، أو البرمجة المعتمدة على الأمثولة instance-based). يعود أصل اللغة المعتمدة على النموذج الأولي إلى لغة Self، والتي طوّرها David Ungar وRandall Smith، ولكن أسلوب البرمجة بدون أصناف class-less توسّع ونال شهرة كبيرة في العقد الأخير، واُعتمد من قبل العديد من اللغات البرمجية أشهرهم جافا سكريبت. البرمجة الكائنية باستخدام جافا سكريبت المجال Namespace في جافا سكريبت المجال هو أشبه بمستوعب/بحاوية (container) تسمح للمطوّر في تحزيم وظائف تحت اسم فريد، أو اسم تطبيق محدد، ففي جافا سكريبت المجال هو مجرد كائن object كأي كائن آخر يحتوي على طُرق methods، وخاصيات properties، وحتى كائنات objects. ملاحظة هامة: من المهم جدًا الانتباه إلى أنه في جافا سكريبت، لا يوجد فرق بين الكائنات العادية والمجالات namespaces، وهذا يختلف عن اللغات الكائنية الأخرى، الأمر الذي قد يُربك المبرمجين المبتدئين في جافا سكريبت. إن إنشاء مجال namespace في جافا سكريبت بسيطٌ للغاية، فمن خلال إنشاء كائن عام/مشترك/شامل global، ستصبح جميع المُتغيّرات variables والطرق methods، والدوال functions خاصياتٍ لهذا الكائن، ويٌقلل استخدام المجالات namespaces أيضًا من احتمالية تضارب الأسماء في التطبيق، منذ أن كل كائن من كائنات التطبيق ما هي إلى خاصيات كائن شامل/عام معرّفة على مستوى التطبيق. سيُنشئ في الخطوة التالية كائنًا عامًا global وبالاسم MYAPP: // global namespace var MYAPP = MYAPP || {}; يُظهر المثال السابق، كيف تم التأكّد أولًا فيما إذا كان MYAPP معرفًا (سواء في نفس الملف أو في آخر)، ففي حال الإيجاب سيُستخدم الكائن العام MYAPP، وفي حال عدم تعريفه مُسبقًا سيُنشئ كائنًا خالٍ وبالاسم MYAPP والذي سيغلّف encapsulate الطرق methods والدوال functions والمتغيرات variables والكائنات objects. كما يُمكن أيضًا إنشاء مجال فرعي sub-namespaces: // sub namespace MYAPP.event = {}; يوضّح المثال التالي الصيغة المستخدمة في إنشاء مجال namespace وإضافة متغيرات ودوال: // Create container called MYAPP.commonMethod for common method and properties MYAPP.commonMethod = { regExForName: "", // define regex for name validation regExForPhone: "", // define regex for phone no validation validateName: function(name){ // Do something with name, you can access regExForName variable // using "this.regExForName" }, validatePhoneNo: function(phoneNo){ // do something with phone number } } // Object together with the method declarations MYAPP.event = { addListener: function(el, type, fn) { // code stuff }, removeListener: function(el, type, fn) { // code stuff }, getEvent: function(e) { // code stuff } // Can add another method and properties } // Syntax for Using addListener method: MYAPP.event.addListener("yourel", "type", callback); الكائنات الأساسية/القياسية المبنية داخل لغة جافا سكريبت Standard built-in objects تتضمن لغة جافا سكريبت العديد من الكائنات في تركيبتها، على سبيل المثال، يوجد كائنات مثل Math، Object، Array، String، ويُظهر المثال التالي كيفيّة استخدام الكائن Math للحصول على رقم عشوائي باستخدام أحد طُرق method هذا الكائن وهي الطريقة ()random. console.log(Math.random()); ملاحظة: يَفترض المثال السابق وجميع الأمثلة التالية في المقال وجود دالة function بالاسم ()console.log معرّفة تعريفًا عامًا (globally)، مع العلم أن هذه الدالة ليست جزء من اللغة نفسها، ولكنها دالة متوفّرة في العديد من متصفحات الإنترنت لأغراض تشخيص الشيفرة البرمجية debugging. يُمكن العودة إلى مرجع لغة جافا سكريبت: الكائنات الأصلية المعيارية للحصول على قائمة بالكائنات المبينة داخل لغة جافا سكريبت نفسها. كل كائن في جافا سكريبت هو حالة/أمثولة instance من الكائن Object ويَرث كافة خاصياته properties وطُرقه methods. الكائنات المخصصة Custom objects في جافا سكريبت الصنف/الفئة The class لغة جافا سكريبت لغة من النوع prototype-based ولا تحتوي على العبارة class كما هو حال باقي لغات البرمجة الكائنية، كما في روبي أو بايثون، ويُربك هذا الأمر المبرمجين المعتادين على اللغات التي تعتمد على هذه العبارة أو المفهوم، وتستخدم جافا سكريبت بدلًا من ذلك الدوال functions لمحاكات مفهوم الأصناف classes، وتعريف صنف هو بسهولة تعريف أي دالّة: var Person = function () {}; الكائن (أمثولة الصنف class instance) يتطلب إنشاء حالة/أمثولة instance جديدة من كائن obj استخدام العبارة new obj، وتعيين النتيجة إلى متغيّر بغرض الوصول إلى فيما بعد. عُرّف في الشيفرة السابقة صنف class بالاسم Person، وفي الشيفرة التالية، سيُنشئ حالتين/أمثولتين instances من هذا الصنف، الأولى بالاسم person1 والثانية بالاسم person2. var person1 = new Person(); var person2 = new Person(); المشيد The constructor يُستدعى المُشيّد constructor في لحظة الاستهلال instantiation (اللحظة التي يُنشئ فيها الكائن)، والمُشيّد ما هو إلا طريقة method من طُرق الصنف class، وفي جافا سكريبت تعمل الدالة على تشييد الكائن، ولذلك لا داعي إلى تعريف طريقة method من أجل عميلة التشييد، وكل إجراء مصرّح في الصنف class يُنفّذ في لحظة الاستهلال instantiation. يُستخدم المُشيّد في تعيين خاصيات properties الكائن، أو في استدعاء طُرق methods معينة لتحضير الكائن للاستخدام، وأما إضافة طُرق صنف وتعريفها يحدث باستخدام صيغة syntax مختلفة سنتطرّق إليها فيما بعد خلال المقال. تُظهر الشيفرة التالية كيف يُسجّل log (يُرسل رسالة نصية إلى طرفية المتصفح console) مُشيّد الصنف Person رسالة نصية حينما يُستهل instantiated. var Person = function () { console.log('instance created'); }; var person1 = new Person(); var person2 = new Person(); الخاصية The property (خاصية الكائن object attribute) الخاصيات properties ما هي إلا متغيرات محتوات في الصنف class، وكل حالة/أمثولة من الكائن تمتلك هذه الخاصيات، وتُعيّن الخاصيات في دالة مُشيّد الصنف بحيثُ تُنشئ مع كل حالة/أمثولة instance. إن الكلمة المفتاحية this، والتي تُشير إلى الكائن الحالي، تسمح للمطوّر بالعمل مع الخاصيات من ضمن الصنف، والوصول (قراءةً وكتابةً) إلى الخاصية property من خارج الصنف يكون من خلال الصيغة InstanceName.Property كما هو الأمر في لغة C++ (سي بلس بلس) وJava والعديد من اللغات الأخرى، ومن داخل الصنف تُستخدم الصيغة this.Property للحصول على قيمة الخاصية أو لتعيين قيمتها. في الشيفرة التالية، عُرّفت الخاصية firstName للصنف Person وفي لحظة الاستهلال instantiation: var Person = function (firstName) { this.firstName = firstName; console.log('Person instantiated'); }; var person1 = new Person('Alice'); var person2 = new Person('Bob'); // Show the firstName properties of the objects console.log('person1 is ' + person1.firstName); // logs "person1 is Alice" console.log('person2 is ' + person2.firstName); // logs "person2 is Bob" الطرق The methods الطرق methods ما هي إلا دوال (وتُعرّف كما تعرّف الدوال functions)، فيما عدا ذلك فهي تُشبه الخاصيات properties، واستدعاء طريقة method مشابه إلى الوصول إلى خاصيّة ما، ولكن مع إضافة () في نهاية اسم الطريقة، وربما مع مُعطيات arguments، ولتعريف طريقة، تُعيّن دالة إلى خاصيّة مُسمّات من خاصيّة الصنف prototype، ويُمكن فيما بعد استدعاء الطريقة على الكائن بنفس الاسم الذي عُيّن للدالة. في الشيفرة التالية، عُرّفت ومن ثم اُستخدِمت الطريقة ()sayHello للصنف Person. var Person = function (firstName) { this.firstName = firstName; }; Person.prototype.sayHello = function() { console.log("Hello, I'm " + this.firstName); }; var person1 = new Person("Alice"); var person2 = new Person("Bob"); // call the Person sayHello method. person1.sayHello(); // logs "Hello, I'm Alice" person2.sayHello(); // logs "Hello, I'm Bob" إن الطُرق methods في جافا سكريبت ما هي إلا دالة كائن عادية مرتبطة مع كائن كخاصية property، وهذا يعني أنه يُمكن استدعاء الطُرق خارج السياق، كما في المثال التالي: var Person = function (firstName) { this.firstName = firstName; }; Person.prototype.sayHello = function() { console.log("Hello, I'm " + this.firstName); }; var person1 = new Person("Alice"); var person2 = new Person("Bob"); var helloFunction = person1.sayHello; // logs "Hello, I'm Alice" person1.sayHello(); // logs "Hello, I'm Bob" person2.sayHello(); // logs "Hello, I'm undefined" (or fails // with a TypeError in strict mode) helloFunction(); // logs true console.log(helloFunction === person1.sayHello); // logs true console.log(helloFunction === Person.prototype.sayHello); // logs "Hello, I'm Alice" helloFunction.call(person1); كما يُظهر المثال السابق، جميع الإحالات المستخدمة في استدعاء الدالة sayHello تُشير إلى نفس الدالة سواءً الاستدعاء الحاصل مع person1 أو Person.prototype أو حتى في المتغيّر helloFunction وقيمة this خلال استدعاء الدالة يعتمد على الكيفية التي تُستدعى فيها، حيث تُشير الكلمة المفتاحية this إلى الكائن الحالي الذي تُستدعى عليه الطريقة method، بمعنى عندما تم استدعاء الطريقة ()sayHello على الكائن person1 فإن this تُشير إلى الكائن person1، وعند استدعاء sayHello على الكائن person2 فإن this تُشير إلى الكائن person2، ولكن إن تم الاستدعاء بطريقة مختلفة، فإن this ستُعيّن تعينًا مختلفًا، فاستدعاء this من المتغيّر (كما في ()helloFunction) سيُعيّن this إلى الكائن العام global (والذي سيكون window في متصفح الإنترنت)، ومنذ أن هذا الكائن (على الأغلب) لا يملك الخاصّيّة firstName، ستكون النتيجة كما هو الحال في المثال السابق “Hello, I’m undefined”، كما يمكن دائمًا تعيين this صراحةً باستخدام Function#call (أو Function#apply) وهو كما كان في نهاية المثال. الوراثة تُستخدم الوراثة في جافا سكريبت في إنشاء صنف class كمثيل مخصص لصنف أو أكثر (تدعم جافا سكريبت وراثة وحيدة فقط single inheritance)، ويُطلق على الصنف المخصص عادةً ابن (child)، ويطلق على الصنف الآخر عادةً الأب (parent)، وفي جافا سكريبت يتمّ ذلك من خلال إسناد حالة/أمثولة من الصنف الأب إلى الصنف الابن، ومن ثم تخصيصه، وفي متصفحات الإنترنت الحديثة يُمكن استخدام Object.create في تحقيق الوراثة inheritance أيضًا. ملاحظة: لا تتفقد جافا سكريبت مُشيّد صنف الابن prototype.constructor (راجع Object.prototype)، وعليه يجب التصريح عن ذلك يدويًا، لمزيد من التفصيل راجع السؤال التالي على Stackoverflow. عُرّف في الشيفرة التالية الصنف Student كصنف ابن للصنف Person، ومن ثم أُعيد تعريف الطريقة ()sayHello وأُضيفت الطريقة ()sayGoodBye علاوة على ذلك. // Define the Person constructor var Person = function(firstName) { this.firstName = firstName; }; // Add a couple of methods to Person.prototype Person.prototype.walk = function(){ console.log("I am walking!"); }; Person.prototype.sayHello = function(){ console.log("Hello, I'm " + this.firstName); }; // Define the Student constructor function Student(firstName, subject) { // Call the parent constructor, making sure (using Function#call) // that "this" is set correctly during the call Person.call(this, firstName); // Initialize our Student-specific properties this.subject = subject; }; // Create a Student.prototype object that inherits from Person.prototype. // Note: A common error here is to use "new Person()" to create the // Student.prototype. That's incorrect for several reasons, not least // that we don't have anything to give Person for the "firstName" // argument. The correct place to call Person is above, where we call // it from Student. Student.prototype = Object.create(Person.prototype); // See note below // Set the "constructor" property to refer to Student Student.prototype.constructor = Student; // Replace the "sayHello" method Student.prototype.sayHello = function(){ console.log("Hello, I'm " + this.firstName + ". I'm studying " + this.subject + "."); }; // Add a "sayGoodBye" method Student.prototype.sayGoodBye = function(){ console.log("Goodbye!"); }; // Example usage: var student1 = new Student("Janet", "Applied Physics"); student1.sayHello(); // "Hello, I'm Janet. I'm studying Applied Physics." student1.walk(); // "I am walking!" student1.sayGoodBye(); // "Goodbye!" // Check that instanceof works correctly console.log(student1 instanceof Person); // true console.log(student1 instanceof Student); // true فيما يخص السطر ;(Student.prototype = Object.create(Person.prototype في الإصدارات القديمة من جافا سكريبت والتي لا تدعم Object.create يمكن إما استخدام بعض الحيل في خداع المتصفحات –هذه الخدع معروفة إما بالاسم polyfill أو shim—أو استخدام دالة تحقق نفس النتيجة كما في المثال التالي: function createObject(proto) { function ctor() { } ctor.prototype = proto; return new ctor(); } // Usage: Student.prototype = createObject(Person.prototype); التأكّد من أن this تُشير إلى الكائن المطلوب بغض النظر عن كيف للكائن أن يُستهل يمكن أن يكون صعبًا، ومع ذلك يوجد صياغة أبسط من شأنها أن تسهّل الأمر. var Person = function(firstName) { if (this instanceof Person) { this.firstName = firstName; } else { return new Person(firstName); } } التغليف Encapsulation ليس بالضرورة أن يعلم الصنف Student كيف تمّ تنفيذ/تعريف الطريقة ()walk للصنف Person لكي يستطيع استخدام تلك الطريقة، ولا يحتاج الصنف Student إلى تعريف تلك الطريقة صراحةً إلا إذا كان المطلوب التعديل عليها، ويُطلق على هذا الإجراء مفهوم التغليف encapsulation، والذي فيه يَحزم كل صنف البيانات والطُرق methods داخل وحدة/كينونة وحيدة. إخفاء المعلومات سمة شائعة في باقي اللغات البرمجية وعادةً ما توجد كخاصيات/كطُرق إما بالاسم private أو protected، وعلى الرغم من أنه يُمكن مماثلة/محاكاة ذات الأمر في جافا سكريبت، إلا أن هذا الأمر ليس مطلبًا من متطلبات البرمجة الكائنية. التجريد Abstraction التجرير ما هو إلا ميكانيكية تسمح للمطوّر في تجسيد جانب من المشكلة التي يُعمل عليها، إما من خلال الوراثة inheritance (التخصيص specialization) أو التركيب composition، وتُحقق جافا سكريبت التخصيص من خلال الوراثة، والتركيب من خلال السماح لحالات/أمثولات الصنف لتكون قيمًا لخاصيات attributes الكائنات الأخرى. الصنف Function في جافا سكريبت يرث من الصنف Object (وهذا يوضّح التخصيص في هذا النموذج) والخاصية Function.prototype ما هي إلا حالة/أمثولة من الصنف Object (وهذا يوضّح جزئية التركيب composition). var foo = function () {}; // logs "foo is a Function: true" console.log('foo is a Function: ' + (foo instanceof Function)); // logs "foo.prototype is an Object: true" console.log('foo.prototype is an Object: ' + (foo.prototype instanceof Object)); تعددية الأشكال Polymorphism كما أن جميع الطُرق methods والخاصيات properties معرّفة ضمن الخاصية prototype، فيُمكن لبقية الأصناف أن تُعرِّف طُرقًا methods بنفس الاسم، وستكون الطُرق في نطاق الصنف الذي عُرفت به، إلا إذا كان الصنفان على علاقة من نوع أب وابن parent-child، بمعنى آخر أحد الصنفان يرث من الآخر خاتمة إن الأساليب التي تم التطرُّق إليها ليست الأساليب الوحيدة التي يمكن استخدامها في تطبيق مفاهيم البرمجة الكائنية في جافا سكريبت، والتي هي مرنة إلى حد كبير في هذا الصدد، ولم تلجئ إلى أي خدع في تطبيق هذه المفاهيم، ولم تقلّد أيضًا الأساليب والنظريات المستخدمة في اللغات الأخرى، وفي جعبة جافا سكريبت العديد من الأساليب الأخرى لتطبيق مفاهيم متقدمة في البرمجة الكائنية التوجّه، ولكن هذه الأساليب المتقدمة هي خارج إطار مقالة تمهيدية، ربما نتطرّق إليها لاحقًا في الأكاديمية. ترجمة وبتصرّف للمقال Introduction to Object-Oriented JavaScript.
- 1 تعليق
-
- 4
-
- javascript
- js
-
(و 3 أكثر)
موسوم في: