اذهب إلى المحتوى

مختصر البرمجة كائنية التوجه OOP وتطبيقها في بايثون


أحمد عصام النجار

تُعَدّ البرمجة كائنية التوجه Object Oriented Programming -أو OOP اختصارًا- نمطًا من أنماط البرمجة التي يُكتَب فيها البرنامج على صورة كائنات تحتوي على خاصيات ومهام -أي دوال- فيمكن مثلًا التفكير في السيارة على أساس كائن، إذ يكون لون السيارة ولون إطاراتها واسم الشركة المصنعة للسيارة خاصيات لها؛ أما مهمة حركة السيارة ومهمة إنقاص سرعة السيارة ومهمة زيادة سرعة السيارة، فهي دوال.

كما يمكن التفكير أيضًا في الأقلام على أنها كائنات، يكون لون القلم واسم الشركة المصنعة له خاصيات؛ أما مهمة الكتابة فهي دالة، كذلك الأشخاص يمكن التفكير فيهم من وجهة نظر البرمجة الكائنية على أساس كائنات لها خاصيات ودوال، إذ يكون اسم الشخص ولون بشرته وطول قامته خاصيات؛ أما مهمة المشي ومهمة الجري ومهمة الأكل فهي دوال، وكذلك مهمة النوم والجلوس والعمل، …إلخ فهي كلها دوال موجودة في الكائن.

ليست كل لغات البرمجة تدعم نمط البرمجة كائنية التوجه، إذ لا يتوفر هذا النمط في لغات مثل C، ومع ذلك فإنّ معظم لغات البرمجة المستخدَمة في هذا العقد تدعم البرمجة الكائنية، لكن العمل بذلك النمط ليس فرضًا أثناء عملك باللغات التي تدعم ذلك النمط مثل بايثون؛ إذ يوجد على سبيل المثال نمط آخر يدعى بالبرمجة الإجرائية Procedural Programing والتي تنفِّذ البرنامج على صورة كتل من الشيفرات تمثِّل كل واحدة منها خطوةً من خطوات تنفيذ البرنامج، وهو ما استخدمناه بالفعل في الأمثلة.

مفهوم الصنف class والكائن object

إنّ أنواع البيانات البدائية الموجودة بصورة مدمجة في بايثون هي نصوص وأعداد وقوائم، …إلخ، ولكن توفِّر البرمجة الكائنية بناء هيكل بيانات جديد خاص ببرنامجك، إذ تُنشأ الكائنات باستخدام صنف Class يحَّدد له مسبقًا الخاصيات والدوال، ويمكن التفكير مثلًا في الشخص أحمد على أنه كائن من الصنف إنسان، ولذلك يجب عند إنشاء أي كائن تحديد صنفه أولًا، كما يمكن عدّ ذلك الصنف نوعًا جديدًا من هياكل البيانات المصمم من قبلك خصيصًا لبرنامجك، إذ تُخزَّن تلك الكائنات في النهاية في متغيرات، فيصبح بذلك نوع البيانات المخزن في المتغير هو صنف الكائن المخزن به.

وبناءً على ذلك فإنَ الأصناف Classes لا تؤدي حقًا أيّ مهمة، فهي في الواقع نوع جديد من هيكلة البيانات الذي يعرِّفه المبرمج لتحديد الخاصيات والدوال الموجودة في كائنات تلك الأصناف، لكنها بدورها لا تحمل قيمة لأي خاصية ولا تنفِّذ أيّ دالة، فهي أشبه بمخطط فقط أو فكرة مجردة.

سيحمل الصنف إنسان في مثالنا السابق خاصيات مثل الاسم والعمر ولون البشرة ولون الشعر ولكنه لا يحمل قيمةّ للاسم مثل أحمد أو محمد، ولا يحمل قيمة للعمر مثل 14 أو 40 عامًا، ولا غيرها من الخاصيات وإنما يعرِّف الصنف تلك الخاصيات فقط لكي تُسنَد قيم لتلك الخاصيات عند إنشاء كائن من ذلك الصنف، وكذلك الأمر في الدوال، إذ يحمل الصنف نفسه شيفرة تلك الدوال، لكنه ينفِّذها بذاته إلا عند استدعاء تلك الدوال لتنفيذها من قِبَل كائنات الصنف.

أما الكائنات Objects أو تُدعى في بعض الأحيان نسخ Instances، فهي ليست أفكارًا أو مخططات، وإنما هي نسخة واقعية تحمل قيمًا في خاصياتها وتحمل دوالًا جاهزةً للاستخدام أخذتها نسخةً من الصنف، إذ يكون الكائن في حالة الصنف إنسان شخصًا حقيقيًا، يحمل قيمًا للخصائص التي يوفرها الصنف ويستطيع تنفيذ المهام، لذلك فإنه غالبًا عندما يُنشَأ كائن جديد، فإنه تُسنَد قيم لخاصياته الأساسية أثناء الإنشاء.

إنشاء صنف في بايثون

لإنشاء صنف جديد في بايثون، نستطيع استخدام الكلمة المفتاحية class متبوعةً باسم الصنف الذي من المفضَّل أن يبدأ بحرف كبير وليس شرطًا، ثم نبدأ كتلةً تحتوي على الخاصيات والدوال بالشكل التالي:

class Human:
    pass

عرَّفنا في المثال السابق صنفًا جديدًا باسم Human، ولا تحمل كتلته أيّ تعليمات حاليًا لذلك أضفنا الكلمة المفتاحية pass التي ذكرناها سابقًا بأنها تُستَعمل في حالة عدم وجود أيّ تعليمات في كتلة الشيفرة في حلقات التكرار وقواعد اتخاذ القرارات، وكذلك تستخدَم في أيّ كتلة شيفرة فارغة في أيّ قاعدة من قواعد اللغة.

كما أوردنا فإنّ الأصناف تحتوي على خاصيات ودوال، وبالتالي تكون أولى الخطوات عند تعريف صنف جديد هي تحديد الخاصيات، إذ يمكننا في ذلك المثال إضافة خاصيات مثل الاسم والعمر بتعريف متغيرات عادية في كتلة الصنف كما في المثال التالي:

class Human:
    name = 'Adam'
    age = 30

نلاحظ أننا كتبنا قيمًا لتلك الخاصيات على الرغم من قولنا السابق بأن الأصناف لا تحمل أيّ قيم حقيقية، وذلك لا يتعارض مع ذاك لأن تلك القيم لا يحملها الصنف ذاته، وإنما تحملها كائنات ذلك الصنف افتراضيًا في حال لم تُحدَّد قيمة لكل خاصية من تلك الخاصيات للكائن الجديد، لكن لكي يتسنى لنا تغيير تلك القيم عند إنشاء كائن جديد من ذلك الصنف، فإنه من الضروري استخدام دالة __init__ والتي هي إحدى الدوال الرئيسة في أيّ صنف؛ لأنها تُنفَّذ افتراضيًا عند إنشاء أيّ كائن جديد من الصنف.

يأخذنا ذلك للحديث عن الركن الثاني من أركان كتابة الأصناف وهو الدوال، فقد علمنا مسبقًا أن تعريف الخاصيات في الأصناف هي تمامًا مثل تعريف المتغيرات في أيّ جزء من أجزاء البرنامج؛ أما تعريف الدوال في الأصناف، فهو شبيه بقدر كبير لتعريفه في أيّ جزء آخر لكن مع فارق بسيط، وذلك أنه افتراضيًا عند تعريف دالة جديدة بداخل صنف ما، فستُمرَّر نسخة من الكائن على أساس أول معامل في الدالة؛ ولذلك سيكون لدينا دائمًا معاملًا موجودًا بالفعل في الدالة حتى ولو لم تتطلب الدالة أية معاملات على الإطلاق.

يلعب ذلك المعامِل في الحقيقة دورًا مركزيًا في أيّ دالة، إذ يمكِّنك من استدعاء أيّ دالة أخرى بداخل الكائن -أي الصنف- وأيضًا من استدعاء وتغيير قيم أيّ خاصية من الخاصيات في الكائن، وبناءً على ذلك فإننا سنستخدِم الدالة __init__ لتمرير قيم خاصيات الكائن عند إنشائه، والتي تُنفَّذ افتراضيًا عند الإنشاء لكي يتم تمرير قيم الخاصيات، ثم إعادة تعيينها في الكائن بأول معامل افتراضي تحدثنا عنه، وسنكتب في المثال التالي تلك الدالة، بحيث نكتب اسمًا للمعامل الأول والذي يفضَّل أن يكون self، ثم نبدأ في إضافة معامِلات لكل خاصية من خاصيات الكائن لتُعيَّن فيه.

class Human:
    name = 'Adam'
    age = 30
    def __init__(self, name, age):
        self.name = name
        self.age = age

برغم أننا أردنا تمرير قيمتين فقط للخاصيتين الموجودتين في التصنيف، إلا أنّ الدالة كما ذكرنا لها معامل ثالث يُحرَّر افتراضيًا على أساس أول معامل في الدالة، وذلك المعامل هو الكائن نفسه، إذ يعني الكائن هنا النسخة التي يُعمَل عليها من الصنف، وبالتالي سيتيح ذلك المعامِل لنا الوصول إلى خاصيات ودوال الصنف، كما سنستطيع استخدام القيم المُمرَّرة من باقي معاملات الدالة __init__ وإسنادها إلى خاصيات الكائن.

إنشاء الكائنات والتعامل معها

عند إنشاء كائن جديد من صنف تمرَّر معامِلات دالة الإنشاء __init__، إذ تُنفَّذ تلك الدالة كما ذكرنا عند إنشاء كائن جديد، كما يُسنَد الكائن الجديد في متغير، إذ أنه يُعَد نوعًا جديدًا من أنواع البيانات الذي عرَّفته أنت بنفسك عند إنشاء صنف جديد.

ahmed = Human('Ahmed', 23)
print (ahmed.name); print (ahmed.age)
>> Ahmed
>> 23

حسنًا، ما حدث لا يزيد عن إنشاء متغير جديد يحمل الاسم ahmed من الصنف الذي كتبناه مسبقًا Human، والذي تحتاج دالة __init__ فيه إلى معاملين هما الاسم والعمر واللذين مرِّرا بين الأقواس بعد اسم الصنف في المثال السابق.

نستطيع الآن بعد تنفيذ دالة __init__ التنبؤ بكون خاصية الاسم تحمل الاسم الذي مررناه، وخاصية العمر تحمل العدد الذي مررناه أيضًا، إذ أسندنا في دالة __init__ تلك المعاملات بالفعل إلى خاصيات الكائن، وبناءً على ذلك، فعند محاولة طباعة خاصية الاسم أو خاصية العمر من الكائن، فسنفترض أن تلك الخاصيات تحمل القيم التي مررناها في أول الأمر، ما لم تُستدعى دالة أخرى أو تُغيَّر خاصية من الخاصيات بصورة مباشرة أو غير مباشرة، فالأمر لا يحتاج إلى دالة بصورة ضرورية حتى تُعدَّل خاصية من خاصيات الكائن، إذ أنه يمكننا التلاعب بقيم الخاصيات بصورة مباشرة من خلال المتغير الذي يحمل الكائن كما في المثال التالي:

ahmed = Human('Ahmed', 23)
ahmed.age += 1
print (ahmed.age)
>> 24

استخدمنا في المثال السابق أحد عوامل الإسناد لتغيير قيمة خاصية من خاصيات الكائن حتى بعد تعريفه وإسناده في دالة __init__؛ ولذلك فنحن لا نحتاج دائمًا إلى دالة كي يتسنى لنا تغيير قيمة خاصية من الخاصيات؛ أما الدوال فهي كما أوردنا تُعرَّف تمامًا مثل دالة __init__ وتُستدعى عند الحاجة من خلال المتغير الذي يحمل الكائن، إذ يمكننا مثلًا تعريف دالة في الصنف تطبع كلمة المشي أو أمر من هذا القبيل للتجربة.

class Human:
    name = 'Adam'
    def __init__(self, name):
        self.name = name
    def walk(self):
        print (self.name + ' is walking now ..')
ahmed = Human('Ahmed')
ahmed.walk()
>> Ahmed is walking now ..

نلاحظ أنه على الرغم من أنّ الدالة walk لا تطلب أيّ معامل، إلا أنه يمرَّر أول معامل افتراضيًا أسميناه self، والذي نستطيع من خلاله استدعاء أي دالة أو خاصية من الكائن، وذلك ما فعلناه لطباعة اسم الكائن عند طباعة الجملة.

وراثة الأصناف وإنشاء أصناف فرعية

يمكننا إنشاء أصناف فرعية من أصناف منشأة بالفعل، إذ يمكننا إنشاء صنف جديد يُدعى Programmer يرث بصورة تلقائية خواص ودوال الصنف Human، فالأمر أشبه بإنشاء صنف للمبرمجين الذين هم في الأساس من صنف الإنسان، كذلك الأمر في أيّ صنف آخر، إذ نستطيع مثلًا إنشاء صنف للقطط يرث صنف الحيوانات، وإنشاء صنف للسيارات الرياضية يرث صنف السيارات عممرمًا، إذ تُعَدّ فائدة إنشاء أصناف فرعية هي إنشاء صنف يرث جميع الخواص والدوال من أبيه وكما أنّ ذلك لا يتعارض مع كوننا نستطيع إعادة كتابة بضع دوال أو بضع خواص في الصنف الفرعي الجديد، وتلك العملية في مجملها تُدعى بالوراثة Inheritance.

لنكتب مثالًا يحتوي على صنف Programmer الذي يرث الصنف Human مع إضافة خاصية اللغة language التي تحمل بدورها اسم لغة البرمجة المتخصص فيها المبرمج، ثم سنعدِّل على دالة __init__ لاستقبال قيمة للخاصية الجديدة وهي اللغة، وبذلك نكون قد أنشأنا صنفًا فرعيًا وراثيًا، وأضفنا خاصيةً له، ثم عدّلنا على دالة من دوال الصنف الأب:

class Human:
    name = 'Adam'
    def __init__(self, name):
        self.name = name
    def walk(self):
        print (self.name + ' is walking now ..')
class Programmer(Human):
    language = 'PHP'
    def __init__(self, name, language):
        self.name = name; self.language = language
ahmed = Programmer('Ahmed', 'Python')
print (ahmed.language); ahmed.walk()
>> Python
>> Ahmed is walking now ..

توجد الكثير والكثير من التفاصيل الأخرى الخاصة بالبرمجة كائنية التوجه في بايثون، إلا أن ذلك القدر يكفي لفهم الأفكار الأساسية في الأمر برمته، وذلك لأن التوسع في البرمجة كائنية التوجه لن يكون ذا فائدة كبيرة لأننا لن نستخدِم ذلك النمط في أمثلة نماذج تعلم الآلة في الكتاب، لكننا مع ذلك نوصي وبشدة بالتوسع في أمر البرمجة كائنية التوجه، إذ تستخدِمها كل التطبيقات الحديثة، فهي أمر لا بد منه عند التخصص في أيّ عمل يخص كتابة البرمجيات لذا نوصي بقراءة المقالات المقترحة في قسم اقرأ أيضًا.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...