أركان البرمجة الشيئية الأربعة: دليل شامل للمطورين
تُعد لغة JavaScript لغة متعددة الأنماط (multi-paradigm)، مما يعني أنه يمكن كتابة الكود الخاص بها باتباع نماذج برمجية مختلفة. النمط البرمجي هو في الأساس مجموعة من القواعد التي تتبعها عند كتابة الكود لمساعدتك في حل مشكلة معينة. هذا هو جوهر أركان البرمجة الشيئية الأربعة: إنها مبادئ تصميم برمجيات تهدف إلى مساعدتك في كتابة كود شيئي نظيف وفعال.
تتمثل الأركان الأربعة للبرمجة الشيئية (Object-Oriented Programming - OOP) في:
- التجريد (
Abstraction) - التغليف (
Encapsulation) - الوراثة (
Inheritance) - تعدد الأشكال (
Polymorphism)
دعونا نتعمق في كل منها على حدة.
التجريد (Abstraction) في البرمجة الشيئية
يعني تجريد شيء ما إخفاء تفاصيل التنفيذ الداخلية. قد يكون هذا داخل نموذج أولي (prototype) أو دالة (function). عندما تستدعي الدالة، لا تحتاج إلى فهم ما تفعله بالضبط. إذا كان عليك فهم كل دالة في قاعدة كود كبيرة، فلن تتمكن أبدًا من كتابة أي شيء؛ سيستغرق الأمر شهورًا لإنهاء قراءتها كلها. يمكنك إنشاء قاعدة كود قابلة لإعادة الاستخدام، سهلة الفهم، وسهلة التغيير عن طريق تجريد تفاصيل معينة.
دعني أقدم لك مثالاً:
function hitAPI ( type ) {
if (type instanceof InitialLoad) {
// Implementation example
} else if (type instanceof NavBar) {
// Implementation example
} else {
// Implementation example
}
}
هل ترى في المثال كيف يتعين عليك تنفيذ ما تحتاجه بالضبط لحالة استخدامك المخصصة؟ كل واجهة برمجة تطبيقات (API) جديدة تحتاج إلى استدعائها تتطلب كتلة if جديدة وكودها المخصص. هذا ليس مجردًا لأنك تحتاج إلى القلق بشأن التنفيذ لكل نوع جديد تضيفه. إنه ليس قابلاً لإعادة الاستخدام، ويمثل كابوسًا للصيانة.
ماذا عن شيء مثل هذا؟
hitApi( 'www.kealanparr.com' , HTTPMethod.Get)
الآن يمكنك ببساطة تمرير عنوان URL إلى دالتك ونوع طريقة HTTP التي تريد استخدامها، وقد انتهيت. لا داعي للقلق بشأن كيفية عمل الدالة؛ لقد تم التعامل معها. هذا يساعد بشكل كبير في إعادة استخدام الكود ويجعل الكود الخاص بك أكثر قابلية للصيانة أيضًا.
هذا هو جوهر التجريد: إيجاد الأشياء المتشابهة في الكود الخاص بك وتوفير دالة عامة (generic function) أو كائن (object) لخدمة أماكن متعددة أو اهتمامات متعددة. إليك مثال أخير جيد على التجريد: تخيل أنك تقوم بإنشاء آلة لصنع القهوة لمستخدميك. يمكن أن يكون هناك نهجان:
كيفية الإنشاء باستخدام التجريد
- زر بعنوان “صنع القهوة” (
Make coffee)
كيفية الإنشاء بدون التجريد
- زر بعنوان “غلي الماء” (
Boil the water) - زر بعنوان “أضف الماء البارد إلى الغلاية” (
Add the cold water to the kettle) - زر بعنوان “أضف ملعقة واحدة من القهوة المطحونة إلى كوب نظيف” (
Add 1 spoon of ground coffee to a clean cup) - زر بعنوان “نظف أي أكواب متسخة” (
Clean any dirty cups) - وجميع الأزرار الأخرى…
إنه مثال بسيط للغاية، لكن النهج الأول يجرّد المنطق داخل الآلة. أما النهج الثاني فيجبر المستخدم على فهم كيفية صنع القهوة، وبالتالي يصنعها بنفسه. يوضح لنا الركن التالي طريقة واحدة يمكننا من خلالها تحقيق التجريد، وهي باستخدام التغليف.
التغليف (Encapsulation) في البرمجة الشيئية
تعريف التغليف هو “العملية التي يتم من خلالها إحاطة شيء ما داخل كبسولة أو كما لو كان داخل كبسولة”. إزالة الوصول إلى أجزاء من الكود الخاص بك وجعل الأشياء خاصة (private) هو بالضبط ما يدور حوله التغليف (غالبًا ما يشير الناس إليه على أنه إخفاء البيانات data hiding).
يعني التغليف أن كل كائن في الكود الخاص بك يجب أن يتحكم في حالته الخاصة (state). الحالة هي “لقطة” الكائن الحالي: المفاتيح (keys)، الدوال (methods) الموجودة على الكائن، الخصائص المنطقية (Boolean properties) وما إلى ذلك. إذا قمت بإعادة تعيين قيمة منطقية أو حذف مفتاح من الكائن، فإن كل هذه التغييرات تؤثر على حالتك. قم بتقييد الأجزاء التي يمكن للكود الخاص بك الوصول إليها. اجعل المزيد من الأشياء غير قابلة للوصول إذا لم تكن هناك حاجة إليها.
يتم تحقيق الخصائص الخاصة (Private properties) في JavaScript باستخدام الإغلاقات (closures). إليك مثال أدناه:
var Dog = ( function ( ) {
// Private
var play = function ( ) {
// play implementation
};
// Private
var breed = "Dalmatian"
// Public
var name = "Rex" ;
// Public
var makeNoise = function ( ) {
return 'Bark bark!' ;
};
return {
makeNoise : makeNoise,
name : name
};
})();
أول شيء فعلناه هو إنشاء دالة يتم استدعاؤها فورًا (تسمى تعبير دالة يتم استدعاؤها فورًا، أو Immediately Invoked Function Expression - IIFE باختصار). هذا أنشأ كائنًا يمكن لأي شخص الوصول إليه ولكنه أخفى بعض التفاصيل. لا يمكنك استدعاء play() ولا يمكنك الوصول إلى breed لأننا لم نكشف عنها في الكائن النهائي باستخدام return.
يسمى هذا النمط المحدد أعلاه نمط الوحدة الكاشفة (Revealing Module Pattern)، لكنه مجرد مثال على كيفية تحقيق التغليف. أرغب في التركيز أكثر على فكرة التغليف (لأنها أكثر أهمية من مجرد تعلم نمط واحد واعتبار التغليف مكتملًا تمامًا الآن). فكر أكثر في كيفية إخفاء بياناتك وكودك وفصلهما. إن التنميط (Modularising) ووجود مسؤوليات واضحة أمر أساسي في التوجه الكائني (Object Orientation).
لماذا يجب أن نفضل الخصوصية؟
لماذا لا نجعل كل شيء عامًا (global)؟
- ستصبح الكثير من أجزاء الكود غير ذات الصلة معتمدة/مرتبطة ببعضها البعض عبر المتغيرات العامة.
- من المحتمل أن تتجاوز (
override) المتغيرات إذا تم إعادة استخدام الاسم، مما قد يؤدي إلى أخطاء أو سلوك غير متوقع. - من المحتمل أن ينتهي بك الأمر بكود السباغيتي (
Spaghetti Code) – كود يصعب فهمه وتتبع ما يقرأ ويكتب في متغيراتك ويغير الحالة.
يمكن تطبيق التغليف عن طريق فصل أسطر الكود الطويلة إلى دوال أصغر منفصلة. افصل هذه الدوال إلى وحدات (modules). نخفي البيانات في مكان لا يحتاج أي شيء آخر للوصول إليه، ونكشف بوضوح ما هو ضروري. هذا هو التغليف باختصار: ربط بياناتك بشيء ما، سواء كان فئة (class)، كائن (object)، وحدة (module) أو دالة (function)، وبذل قصارى جهدك للحفاظ عليها خاصة قدر الإمكان.
الوراثة (Inheritance) في البرمجة الشيئية
تسمح الوراثة لكائن واحد باكتساب خصائص ودوال كائن آخر. في JavaScript، يتم ذلك عن طريق الوراثة النمطية (Prototypal Inheritance). الفائدة الرئيسية هنا هي قابلية إعادة الاستخدام (Reusability). نعلم أحيانًا أن أماكن متعددة تحتاج إلى القيام بنفس الشيء، وتحتاج إلى القيام بكل شيء بنفس الطريقة باستثناء جزء صغير واحد. هذه مشكلة يمكن للوراثة حلها.
كلما استخدمنا الوراثة، نحاول أن نجعل الأب (parent) والابن (child) يتمتعان بتماسك عالٍ (high cohesion). التماسك هو مدى ارتباط الكود الخاص بك. على سبيل المثال، هل نوع Bird يمتد من نوع DieselEngine؟ حافظ على وراثتك بسيطة الفهم وقابلة للتنبؤ. لا ترث من مكان غير ذي صلة تمامًا لأن هناك دالة أو خاصية واحدة تحتاجها. الوراثة لا تحل هذه المشكلة بشكل جيد.
عند استخدام الوراثة، يجب أن تحتاج إلى معظم الوظائف (لا تحتاج دائمًا إلى كل شيء على الإطلاق). لدى المطورين مبدأ يسمى مبدأ استبدال ليسكوف (Liskov Substitution Principle). ينص هذا المبدأ على أنه إذا كان بإمكانك استخدام فئة أب (لنسميها ParentType) في أي مكان تستخدم فيه ابنًا (لنسميه ChildType) – و ChildType يرث من ParentType – فإنك تجتاز الاختبار. السبب الرئيسي لفشلك في هذا الاختبار هو إذا كان ChildType يزيل أشياء من الأب. إذا أزال ChildType الدوال التي ورثها من الأب، فسيؤدي ذلك إلى أخطاء من نوع TypeError حيث تكون الأشياء غير معرفة بينما تتوقع ألا تكون كذلك.

تبدو الأسهم وكأنها تسير في الاتجاه الخاطئ، لكن Animal هو الأساس – الأب. سلسلة الوراثة (Inheritance chain) هو المصطلح المستخدم لوصف تدفق الوراثة من نموذج الكائن الأساسي (الذي يرث منه كل شيء آخر) إلى “نهاية” سلسلة الوراثة (النوع الأخير الذي يرث – Dog في المثال أعلاه).
ابذل قصارى جهدك للحفاظ على سلاسل الوراثة نظيفة ومعقولة. يمكنك بسهولة أن ينتهي بك الأمر بكتابة أنماط مضادة (anti-patterns) عند استخدام الوراثة (تسمى النمط المضاد للقاعدة الهشة Fragile base anti-pattern). يحدث هذا عندما تعتبر نماذجك الأولية الأساسية “هشة” لأنك تقوم بتغيير “آمن” للكائن الأساسي ثم تبدأ في كسر جميع أبنائك.
تعدد الأشكال (Polymorphism) في البرمجة الشيئية
يعني تعدد الأشكال “حالة الظهور بأشكال مختلفة”. هذا هو بالضبط ما يتعلق به الركن الرابع والأخير – الأنواع في نفس سلاسل الوراثة التي يمكنها القيام بأشياء مختلفة. إذا كنت قد استخدمت الوراثة بشكل صحيح، يمكنك الآن استخدام الآباء بشكل موثوق كما لو كانوا أبناءهم. عندما يتشارك نوعان في سلسلة وراثة، يمكن استخدامهما بالتبادل دون أخطاء أو تأكيدات في الكود الخاص بك.
من الرسم البياني الأخير، قد يكون لدينا نموذج أولي أساسي يسمى Animal والذي يحدد الدالة makeNoise(). ثم يمكن لكل نوع يمتد من هذا النموذج الأولي تجاوزها (override) للقيام بعمله المخصص. شيء من هذا القبيل:
// Let's set up an Animal and Dog example
function Animal ( ) {}
function Dog ( ) {}
Animal.prototype.makeNoise = function ( ) {
console .log( "Base noise" );
};
// Most animals we code up have 4. This can be overridden if needed
Animal.prototype.legs = 4 ;
Dog.prototype = new Animal();
Dog.prototype.makeNoise = function ( ) {
console .log( "Woof woof" );
};
var animal = new Animal();
var dog = new Dog();
animal.makeNoise(); // Base noise
dog.makeNoise(); // Woof woof- this was overridden
dog.legs; // 4! This was inherited
يمتد Dog من Animal ويمكنه الاستفادة من الخاصية الافتراضية legs. لكنه أيضًا قادر على تنفيذ نسخته الخاصة من إصدار الضوضاء. القوة الحقيقية لتعدد الأشكال تكمن في مشاركة السلوكيات، والسماح بالتجاوزات المخصصة (custom overrides).
الخلاصة التقنية
تُعد أركان البرمجة الشيئية الأربعة – التجريد، التغليف، الوراثة، وتعدد الأشكال – حجر الزاوية في تصميم البرمجيات الحديثة. إن فهم هذه المبادئ وتطبيقها بفعالية يمكّن المطورين من بناء أنظمة قوية، قابلة للتوسع، وسهلة الصيانة. التجريد يقلل من التعقيد، والتغليف يحمي سلامة البيانات، والوراثة تعزز إعادة الاستخدام، بينما يتيح تعدد الأشكال مرونة لا مثيل لها في التعامل مع الكائنات. الالتزام بهذه المبادئ لا يقتصر على تحسين جودة الكود فحسب، بل يعزز أيضًا التعاون بين فرق التطوير ويسرع من دورة حياة المنتج البرمجي.