لا بد أنَّك لاحظت كيف تُطرح دائمًا الأسئلة التقليدية المملة نفسها في مقابلات العمل مرارًا وتكرارًا؟ أنا متأكد من أنَّك تعرف ما أعنيه. على سبيل المثال: أين ترى نفسك بعد خمس سنوات؟ أو الأسوأ من ذلك: ما الذي تعدُّه أكبر نقاط ضعفك؟ أنا أعدُّ الإجابة على هذا السؤال بالذات نقطة ضعف كبيرة! على أية حال، ليست هذه وجهة نظري.

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

أريد اليوم أن أتحدث عن نوع مماثل من الأسئلة في عالم البرمجة:

ما هي المبادئ الرئيسة للبرمجة الكائنية التوجه؟

لقد كنت على طرفي هذا السؤال (السائل والمسؤول)، إنَّه أحد المواضيع الأساسية التي تُطرح في كثير من مقابلات العمل بحيث لا يمكنك السماح لنفسك بعدم معرفة الإجابة عنه.

ويتعين على المطورين المبتدئين الإجابة عن السؤال السابق عادةً؛ لأنه يُعدُّ طريقةً سهلة للمحاور لمعرفة  ثلاثة أشياء:

  1. هل استعد المرشح لهذه المقابلة؟
    سوف تحصل على تقييم جيد إذا أجبت إجابة صحيحة على الفور، فهي تظهر أنَّك جاد بشأن الحصول على الوظيفة.
  2. هل اجتاز المرشح المرحلة التعليمية؟
    يُظهر فهم مبادئ البرمجة الكائنية التوجه (OOP) أنَّك تجاوزت مرحلة النسخ واللصق من الدروس، فأنت ترى بالفعل أشياء من منظور متقدم.
  3. هل فَهْم المرشح عميق أم ضحل؟
    إنَّ مستوى الكفاءة في الإجابة على هذا السؤال يساوي في أغلب الأحيان مستوى كفاءتك في الإجابة على معظم الموضوعات الأخرى.. صدقني.

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

الفرق بين functions وmethods:

 الـ methods عبارة عن نوع خاص من functions، فتكون مخصصة للاستخدام على أنواع معينة من البيانات مثل الـ methods  الخاصة بالـمضفوفات arrays مثل: arrayName.pop() js تستخدم لحذف آخر عنصر في الـ array ؛ إذ إن الـ method السابقة خاصة بلغة javascript (اختصارًا js). أيضًا فإن أي function يتم تعريفه داخل object يسمى method إذ يُستخدم على الـ properties  الخاصة بالـ object فقط. أما الـ functions فيمكن استخدامها في أي مكان ضمن الكود وليس فقط على عناصر معينة.  

الفرق بين object وclass:

الـ class عبارة عن “blue print” يمكن من خلاله إنشاء العديد من الـ objects لها نفس النوع ولكن الخصائص الداخلية properties الخاصة بها مختلفة.

التغليف Encapsulation

لنفترض أنَّ لدينا برنامج يحوي عددًا قليلًا من الـ objects objects المختلفة التي تتواصل مع بعضها البعض وفقًا لقواعد محددة مسبقًا في البرنامج.

ويتحقق التغليف encapsulation عندما يحافظ كل كائن على حالته خصوصًا داخل فئة class، ولا يكون للـ objects الأخرى وصول مباشر إلى هذه الحالة. 

وعوضًا عن ذلك، يمكنهم استدعاء قائمة بالدوال العامة public functions فحسب، وهي تُسمى  methods.

لذا، فإنَّ الكائن object يتحكم بحالته الخاصة عن طريق methods، ولا يمكن لأية class أخرى الوصول إليه ما لم يُسمح لها بذلك صراحةً.

وإذا كنت تريد التواصل مع object، فيجب عليك استخدام methods المتاحة لذلك، ولكن؛ (افتراضيًّا) لا يمكنك تغيير الحالة الخاصة بالـ object.

ولنفترض أنَّنا نبني لعبة Sims صغيرة (تشبه لعبة The Sims وهي لعبة محاكاة افتراضية).

لدينا مجموعة من الناس وهناك قطة يمكنهم التواصل مع بعضهم البعض. 

والآن لنطبق مبدأ التغليف encapsulation، لذلك سنضع الأمور كلها المتعلقة بالـ”القطة” في class اسمها Cat.

مثال توضيحي:

يمكنك إطعام القطة باستخدام الدالة العامة feed، ولكن؛ لا يمكنك تغيير مدى جوع القطة تغييرًا مباشرًا.

هنا حالة القطة يمكن تمثيلها بالمتغيرات الخاصة private variables الآتية :mood ،hungry و energy . 

بالإضافة إلى أنَّ Cat class لديها دالة خاصة بها تدعى،meow يمكن أن تستدعيها وقتما تشاء، ولا يمكن لل classes الأخرى استدعائها (يمكن للقطة أن تموء وقتما تشاء ولا يمكن للclasses الاخرى إجبارها على المواء ما لم يُسمح لها صراحةً عبر الدوال العامة public methods).

ما يمكن لل classes الأخرى فعله محددٌ في الدوال العامة public methods الآتية: sleep ،play، feed.

كل واحد منهم يستطيع تعديل الحالة الخاصة للقطة بطريقة محددة مسبقًا، ويمكن لبعضهم استدعاء الدالة meow. وبهذه الطريقة، يُربط بين الحالة الخاصة للـ object والدوال العامة public methods التي تُستخدم لتغيير حالته.

التجريد Abstraction

يمكن عد التجريد امتدادًا طبيعيًّا للتغليف encapsulation.

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

التجريد abstraction مفهومٌ يهدف إلى تبسيط هذه المشكلة.

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

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

وكمثال للتوضيح: آلة صنع القهوة تجري كثيرًا من العمليات  داخلها وتصدر أصواتًا غريبة من تحت غطائها، وكل ما عليك فعله هو وضع القهوة والضغط على الزر.

مثال آخر على مبدأ التجريد abstraction هو استخدام هاتفك الخلوي:

الهواتف الخلوية معقدة. لكن استخدامها بسيط.
الهواتف الخلوية معقدة. لكن استخدامها بسيط.

يمكنك التفاعل مع هاتفك باستخدام بضعة أزرار فحسب، فما الذي يحدث داخله؟ ليس بالضرورة أن تعرف، فتفاصيل التنفيذ مخفية. 

وكل ما عليك معرفته مجموعة محددة وقصيرة من الإجراءات الواجب اتباعها.

ونادرًا ما تؤثر التغييرات البرمجية في التجريد abstraction الذي تستخدمه، وعلى سبيل المثال؛ تحديث نظام التشغيل.

التوريث Inheritance:

حسنًا، لقد رأينا كيف يمكن للتغليف encapsulation و التجريد abstraction أن يساعدنا في تطوير قواعد برمجية كبيرة والحفاظ عليها.

ولكن؛ هل تعرف ما المشكلة الشائعة الأخرى في البرمجة الكائنية التوجه؟

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

فكيف نعيد استخدام الصفات المنطقية المتشابهة وإنشاء صفات منطقية فريدة في class منفصل؟

هنالك طريقة واحدة لتحقيق ذلك وهي باستخدام خاصية التوريث inheritance، وتعني أن تنشئ child) class) عن طريق اشتقاقها من parent) class)، وبهذه الطريقة، يتشكل ما يسمى بالتسلسل الهرمي.

ويمكن لل child) class) استخدام حقول parent) class) وأساليبها جميعًا (الجزء المشترك فيما بينهما)، ويمكنها تنفيذ الجزء الفريد الخاص بها من methods و properties.

وكمثال على ذلك:

المعلم الخصوصي هو نوع من المعلمين، وأي معلم هو نوع من الأشخاص.

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

وبهذه الطريقة، يمكن إضافة ما هو ضروري لكل class حسب عملها فحسب، وإعادة استخدام الخصائص المشتركة مع parent) class) التي اشتُقت منها.

تعدد الأشكال Polymorphism

لقد وصلنا إلى الكلمة الأكثر تعقيدًا: Polymorphism تعني”عديدًا من الأشكال” في اللغة اليونانية.

لقد تعرفنا سابقًا إلى مبدأ التوريث inheritance الذي ساعدنا في حل مشكلات عديدة، ولكن؛ إليك المشكلة الآتية.

لنفترض أنَّ لدينا parent) class) وبعض child) classes) التي ترث بعض الصفات منها.

وقد نرغب في استخدام مجموعة تحتوي على مزيج من النوعين السابقين أحيانًا، أو لدينا  method خاصة ب parent) class) ، ولكنَّنا نرغب في استخدامها على child) classes) أيضًا. يمكن حل ذلك باستخدام تعدد الأشكال Polymorphism.

ويمكن فعل ذلك عن طريق تحديد واجهة رئيسة parent interface لإعادة استخدامها بحيث تُحدد مجموعة من methods الشائعة في داخلها ثم تنفذ كل child) classes) نسختها الخاصة من هذه ال methods.

في أي وقت تُستدعى فيه دالة عامة موجودة في parent) class)، فإنَّ اللغة البرمجية المُستخدمة ستستخدم  الكود البرمجي المناسب للعنصر الذي قام بعملية الاستدعاء.

ألقِ نظرة على المثال الآتي حيث تُستخدم واجهة رئيسة مشتركة  لحساب مساحة الشكل الهندسي ومحيطه:

يمكن الآن استخدام المثلث والدائرة والمستطيل في نفس المجموعة

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

والآن، إذا حاولت هذه القائمة حساب مساحة أحد الأشكال، فسيُبحَث عن الدالة الخاصة بذلك الشكل وتنفيذها.

فإذا كان الشكل مثلثًا، ستُستدعى الدالة calculate surface الخاصة بالمثلث، أما إذا كانت دائرة فستُستدعى calculate surface للدائرة وهكذا..

إذا كان لديك دالة تعمل على إيجاد محيط  شكل هندسي، فلن تحتاج إلى تعريفها ثلاث مرات؛ للمثلث والدائرة والمستطيل.

ويمكنك تعريفها مرة واحدة فحسب، وجعل المتغير argument الخاص بها أحدَ الأشكال الهندسية سواء مررت مثلثًا أو دائرة أو مستطيلًا، فلا يهم ما نوع الشكل طالما أنَّها تقوم بتطبيق الدالة calculate perimeter.

آمل أن يكون هذا المقال قد ساعدك في فهم هذه المبادئ، ويمكنك استخدام هذه التفسيرات نفسها إذا سُئلتَ عنها في مقابلات العمل مباشرةً.

How to explain object-oriented programming concepts to a 6-year-old

  • ترجمة: ياسر طبيلة
  • مراجعة: أحمد نزار
  • تدقيق لغوي: غنوة عميش