:: كل المقالات ::

Tue, 13 Dec 2016

البرمجة الكائنية في بيرل ( الجزء الثاني ) " الطريقة القديمة "

بسم الله الرحمن الرحيم
في هذا الجزء سنحاول ان نستعرض النموذج القديم في بيرل و نتعرف على طريقة كتابة البرمجة الكائنية بشكلها القديم . هذه الايام نادرا ما نحتاج الى الكتابة بالطريقة القديمة و لكن المعرفة - و لو الاجمالية - ستكون مفيدة جدا و ذلك لنقاط نذكر منها :
- قد نحتاج الى قراءة او تعديل اكواد مكتوب بالطريقة القديمة خاصة ان بيرل لغة قديمة و هناك الكثير من المشاريع و البرامج القديمة التي لا تزال قيد الاستخدام.
- معرفة الطريقة القديمة تعطينا معرفة اجمالية بأساسيات البرمجة الكائنية في بيرل و بذلك يكون لدينا عمق اكبر في فهم الطريقة الجديدة .
- معرفة الطريقة القديمة تعطينا بعدا جديدا في معرفة كيفية تطبيق النماذج الكائنية في لغات البرمجة فالاشخاص الذين تعلموا البرمجة الكائنية من خلال جافا او بايثون او سمولتولك قد تكون نظرتهم الى البرمجة الكائنية فقط من منظار ما يقدمه نموذج هذه اللغات لذلك معرفة طريقة بيرل المغايرة - او طريقة جافا سكربت كمثال اخر- يعطي سعة في الاطلاع و كأنه دراسة مقارنة.
- الطريقة القديمة تقدم نموذجا شبه متكامل من خلال التوليف بين مزايا بسيطة موجودة في اصل اللغة، من خلالها ندرك ان النموذج الكائني قد لا يحتاج الى ذلك التعقيد .
- النموذج القديم الى الان يستخدمه البعض خاصة في المشاريع البسيطة و المتوسطة لسرعته و لكونه مضمنا في لغة البرمجة و بذلك يقل عدد الاعتماديات الخارجية للبرنامج .
طبعا لن نحاول ان نستقصي كل ما يتعلق بهذا النموذج فهذا يحتاج الى كتب مفصلة و يمكن الرجوع الى كتاب الدكتور دامين كانوي المعنون بـ البرمجة الكائنية في بيرل (1) ، و لكن سنحاول ان نستعرض الاساسيات التي تكلمنا عنها بشكل نظري في الجزء الاول .
قلنا سابقا ان البرمجة الكائنية هي عملية محاكاة للواقع من خلال النظر الى الاجزاء البرمجية على انها كائنات تشبه الكائنات التي نراها في الواقع الخارجي . هذا كلام جميل من الناحية النظرية و لكن كمبرمجين نريد شيئا ملموسا اكثر ، فنقول ان الكائن هو عبارة عن بيانات مرفق معها العمليات التي يمكن ان تجري عليها او بعبارة اخرى كما احب ان اسميها بيانات ذكية ! لنأخذ مثالا لتتضح الصورة :
عادة عندما يعلموننا لغات البرمجة يبداون بتعليمنا البرمجة الاجرائية و في البرمجة الاجرائية هناك بيانات data structures و هناك اجراءات procedures . لنقل ان لدي شكل من اشكال البيانات لنقل قيمة نصية :
"this is a data "
و لنقل انني اريد ان اعرف كم طول النص (عدد الحروف فيه ) ففي البرمجة الاجرائية سأقوم بانشاء اجراء مفصل فيه خطوات معرفة طول القيم و سأسميه ()length ثم اقوم بتمرير القيم النصية كلما احتجت ان اعرف طولها بهذا الشكل :
length("this is a data")
نلاحظ ان هناك ثنائية فالبيانات شيء و الاجراءات شيء اخر و لكن في البرمجة الكائنية نقوم بتوحيد البيانات مع الاجراءات ان صح التعبير فتكون البيانات ذكية بحيث يمكنني ان اسالها مباشرة و هي تجيب عن اسئلتي ، فلو اردت ان اعرف طول النص فيمكنني ان اسال الكائن مباشرة :
myString.length()
الان لندخل في الشرح التطبيقي لطريقة كتابة كلاسات بالطريقة القديمة :
اول خطوة هي الاعلان عن كلاس جديد و ذلك باستخدام الكلمة المفتاحية package :
package Person;
تجدر الاشارة هنا ان لغات مثل جافا تفرض ان يكون لكل كلاس ملفا خاصا به و لا يشاركه فيه غيره و لكن في بيرل لا يوجد شيء من هذا القبيل - الا اذا استخدمنا امر use parent كما سيأتي- ، فيمكننا ان نعلن عن عدة كلاسات في ملف مصدري واحد . و عادة نحفظ الملف المصدر لكلاسات بيرل بامتداد .pm . جميل ، بعد الاعلان عن اسم الكلاس يمكننا البدء في كتابة تفاصيل الكلاس. لنشرع في كتابة الكونستركتر و هو عبارة عن ميثود "فعل" يقوم بانشاء كائن جديد و هذا الميثود في جافا يجب ان يكون باسم new و لكن في بيرل مرة اخرى لا يوجد الزام بهذا الامر و يمكن ان نسمي هذا الميثود اي اسم اخر. مهمة هذا الميثود هو ان ينشيء كائنا جديدا من الكلاس المعني بالاضافة الى اسناد القيم الى الخصائص . نعلن عن الميثود بالكلمة المفتاحية sub :
sub new {
ثم نقوم باستقبال نوع الكائن من خلال الامر shift
my $class = shift;
ثم نشرع بتحديد الخصائص و كيفية استقبالها للقيم (نلاحظ اننا لم نعلن عن تلك الخصائص سلفا كما في جافا و كما سيأتي في النموذج الجديد !)
my $self = {
name => shift,
national_id => shift
};
هنا نقطة مهمة حيث اننا في الكود اعلاه حددنا اننا سنقوم بتخزين الخصائص على شكل هاش (قاموس ) و هذا هو الغالب عند مبرمجي بيرل و لكن في بيرل يمكن تغير ذلك الى اي نوع اخر مثلا قائمة array . الان يمكننا ان نستدعي دالة bless و هي التي تقوم بجمع البيانات و الاجراءات و تحويلها الى كائنات .
bless $self, $class;
اخيرا نرجع الكائن :
return $self;
} #end of new()
الان هذا يعتبر كلاسا كاملا و لكن نحتاج الى ان ننهي تعريف الكلاسات في النموذج القديم بقيمة صحيحة:
1;
الى الان لدينا كلاس و هذا الكلاس لا يحتوي الا على ميثود واحد اساسي يقوم باسناد القيم الى الخصائص و يرجع كائنا جديدا . اما طريقة انشاء كائن جديد من هذا الكلاس فبهذه الطريقة :
my $person = Person->new("Ali Yassen", 110009823);
نلاحظ ان في بيرل 5 و على خلاف اغلب لغات البرمجة معامل تنفيذ الميثودز هو السهم -> و ذلك لان في بيرل معامل النقطة " . " محجوز لامر اخر الا و هو دمج ( وصل ) قيمتين . تجدر الاشارة الى ان في بيرل 6 تم استبدال هذا المعامل بمعامل النقطة .
لو اردنا ان نعرف اسم الكلاس الذي ينتمي اليه اي كائن فبيرل توفر دالة مهمتها فقط ان ترجع نوع الكائن، هذه الدالة اسمها blessed و نمرر اليها اسم المتغير :
use Scalar::Util qw(blessed);
print blessed($person);
حسنا لنعد الى الكلاس مرة اخرى و لنضف اليه ما يعرف بالميثودات المساعدة و هي عبارة عن ميثودات تقوم فقط باسترجاع قيم الخصائص gettersاو الاسناد اليها setters و هي بذلك تساعد على الكبسلة بدلا من الوصول المباشر الى قيم الخصائص. و هذا الامر مهم جدا خاصة في بيرل اذ اننا كما اشرنا اعلاه نستطيع ان نمثل البيانات باشكال مختلفة فعندما نعزلها عن الوصول المباشر سيكون بامكاننا لاحقا ان ننتقل الى تمثيل مختلف للبيانات و ان نجري اي تعديلات اخرى بدون التأثير على المبرمجين الذي يعتمدون على اكوادنا .
sub get_name {
my $self = shift;
return $self->{name};
}

sub get_national_id {
my $self = shift;
return $self->{national_id};
}
لا شيء مميز في الاكواد اعلاه مجرد ارجاع لقيم الخصائص . اما الـ setters :
sub set_name {
my ($self , $name ) = (shift, shift);
if (defined $name) {
$self->{name} = $name;
return 1; }
}
هنا فقط قمنا باسناد القيمة الممررة الى خانة name و قمنا بارجاع قيمة 1 في حال لو اردنا الية للتشيك على نجاح العملية . اما الـ national_id فلا نحتاج الى انشاء setter لها لانها قيمة ثابتة فهل سمعتم عن شخص غير رقمه الوطني ؟! الان عرفنا طريقة انشاء الميثودز و نستطيع بنفس الطريقة ان ننشيء ما نشاء من الميثودز مثلا الميثود المشهور toString و هو ميثود يستخدم لانشاء نسخة مكتوبة تسهل قراءتها تشمل تفاصيل الكائن .
لنواصل الكلام و لكن هذه المرة عن التوارث فقد قلنا سابقا ان في بيرل يمكن الوراثة من اكثر من كلاس و ان كان هذا الامر غير مستحسن و ينصح باستخدام الرولز roles بدلا عن ذلك . لنقل اننا نريد ان ننشيء كلاسا جديدا اسمه student هذا كلاس الطالب يعتبر نوعا من انواع كلاس person و يرث كل ما عند الكلاس الاب من خصائص و افعال و للحصول على هذه العلاقة و التي تعرف بعلاقة is-a نستخدم هذا الامر :
package Student;
use parent 'Person';
لنفترض الان اننا نريد ان نضيف قيمة جديدة الى خصائص الطالب مثلا عمر الطالب و لكن الكونستركتر الذي ورثناه من كلاس الاب لا يحتوي الا على الاسم و الرقم الوطني ؟ في هذه الحالة نقوم بما يعرف بالميثود اوفررايد override و هي بعبارة مبسطة عملية تعديل الميثودات الموروثة من الاب و ذلك من خلال الامر SUPER. اذا لنقم باعادة ترتيب الكونستركتر لكي يضيف خانة للعمر :
sub new {
my $class = shift;
my $self = $class->SUPER::new(shift,shift);
$self->{age} = shift;
bless $self, $class;
return $self;
}
ثم نقوم بانشاء ميثودات مساعدة جديدة لـ age فقط اذ اننا ورثنا المساعدات الاخرى من كلاس الاب . هنا ايضا سأضيف ميثودا مساعدا لكلاس الطالب حيث سأسميه to_string مهمته فقط ان يرجع قيمة كتبية للكائن :
sub to_string {
my $self = shift;
print "Student name is : " . $self->get_name . "\n" .
"Student national id is : " . $self->get_national_id . "\n" .
"Student age is : " . $self->get_age . "\n"
}
الان عندما ننشيء كائنا جديدا من نوع طالب بالامر المعتاد :
my $student = Student->new("Ali", 110009 , 23);
و عندما نستخدم الميثود المساعد :
print $student->to_string;
فالنتيجة :
Student name is : Ali
Student national id is : 110009
Student age is : 23
الان بقي ان نذكر شيئا عن ما يعرف بـ "التركيب " composition و هذا المصطلح بكل بساطة يعني ان يكون احد الكائنات مكونا من كائن اخر او اكثر ، مثلا في مثالنا ممكن ان نضيف في خانة الخصائص للطالب خانة جديدة باسم bank account بحيث ان رقم الحساب هذا انما هو كائن في حد ذاته و ليس مجرد قيمة رقمية ، فمثلا قد يكون فيه رقم الحساب البنكي و اسم البنك و معلومات الدخول ...الخ. للوصول الى هذه النتيجة فقط نضيف خانة بإسم bank_account في الكونستركتر الخاص بالطلاب و عندما ننشيء كائنا من نوع طالب نمرر له كائن الحساب البنكي مثلا هكذا :
my $student = Student->new("Ali", 110009 , 23, $bank_account_object);
هل لاحظتم اننا تكلمنا عن الميثود اوفر رايد و لم نتكلم عن الميثود اوفر لود Overload و هو كما قلنا سابقا عملية انشاء ميثودات بنفس الاسم و لكن تختلف من ناحية عدد المتغيرات التي تقبلها مثلا :
method()
method(String astring)
mehtod(String astring, Int myNumber) ...etc
لم نتكلم عن هذا الجانب لان بيرل ديناميكية و يمكنها ان تتعامل مع اعداد مختلفة من المتغيرات باستخدام ميثود واحد بحيث هذا الميثود يختار الاوامر المناسبة على حسب عدد المتغيرات الممررة .
على كل حال لنكتفي بهذا القدر و الا سيطول بنا المقام و لن نستوفي الموضوع و لكن هذه تقريبا خلاصة المطلب و اساس النموذج القديم .

دمتم في الرضا ،
---
(1) Object Oriented Perl: A Comprehensive Guide to Concepts and Programming Techniques