لا يوجد إجماع في مجتمع البرمجة حول الميزات التي يجب أن تكون موجودة في لغة البرمجة حتى تكون لغة كائنية التوجه، وتتأثر رست بالعديد من نماذج البرمجة programming paradigms بما في ذلك البرمجة كائنية التوجه، إذ اكتشفنا الميزات التي جاءت من البرمجة الوظيفية functional programming سابقًا بدءًا من الفصل المغلفات closures. يمكن القول أن اللغات كائنية التوجه تشترك ببعض الميزات المشتركة وهي الكائنات objects والتغليف encapsulation والوراثة inheritance، لنلقي نظرةً على ما تعنيه كل من هذه الميزات وما إذا كانت رست تدعمها.
الكائنات واحتوائها على بيانات وسلوك
يُعدّ الكتاب "أنماط التصميم: عناصر البرمجيات الموجهة للكائنات القابلة لإعادة الاستعمال Design Patterns: Elements of "Reusable Object-Oriented Software للمؤلفين إريك جاما Erich Gamma،وريتشارد هيلم Richard Helm، ورالف جونسون Ralph Johnson، وجون فليسيديس John Vlissides، التابع لدار النشر (Addison-Wesley Professional, 1994)، والذي يشار إليه بالعامية كتاب عصابة الأربعة The Gang of Four book، فهرسًا لأنماط التصميم كائنية التوجه، ويعرّف الكتاب البرمجة كائنية التوجّه بهذه الطريقة:
اقتباستتكون البرامج كائنية التوجه من كائنات، ويضمّ الكائن داخله كل من البيانات والإجراءات procedures التي تستخدم تلك البيانات، وتدعى الإجراءات عادًة بالتوابع methods أو العمليات operations.
بالنظر للتعريف السابق تكون رست لغة كائنية التوجه؛ إذ تحتوي الهياكل structs والتعدادات enums على البيانات، وتقدّم كتل impl
توابعًا على الهياكل والتعدادات، وعلى الرغم من أن الهياكل والتعدادات ذات التوابع لا تدعى بالكائنات إلا أنها تقدّم الوظيفة نفسها وذلك بحسب تعريف الكائنات في كتاب عصابة الأربعة.
ويمكنك الرجوع إلى توثيق أنماط التصميم العربي في موسوعة حسوب لمزيد من التفاصيل.
التغليف وإخفاءه لتفاصيل التنفيذ
هناك جانبٌ آخر مرتبط جدًا بالبرمجة كائنية التوجه وهو فكرة التغليف، والتي تعني أن تفاصيل تطبيق كائن ما لا يمكنها الوصول للشيفرة البرمجية من خلال هذا الكائن، لذا فإن الطريقة الوحيدة للتفاعل مع كائن ما هي من خلال واجهة برمجية عامة Public API خاصة به، وينبغي ألا تكون الشيفرة البرمجية التي يستخدمها الكائن قادرةً على الوصول إلى الأجزاء الداخلية للكائن وتغيير البيانات أو السلوك مباشرةً، وهذا يمكّن المبرمج من تغيير وإعادة تشكيل العناصر الداخلية للكائن دون الحاجة إلى تغيير الشيفرة البرمجية التي تستخدم الكائن.
ناقشنا كيفية التحكم في التغليف سابقًا بدءًا من الفصل الحزم packages والوحدات المصرفة crates، إذ يمكننا استخدام الكلمة المفتاحية pub
لتحديد أي من الوحدات modules والأنواع types والدوال functions والتوابع methods في الشيفرات البرمجية الخاصة بنا التي ينبغي أن تكون عامة، ويكون كل شيء آخر خاص افتراضيًا، فعلى سبيل المثال يمكننا تعريف هيكل AveragedCollection
يحتوي على حقل يضمّ شعاعًا vector بقيم i32
، كما يمكن للهيكل أيضًا أن يحتوي على حقل يضمّ متوسط القيم في الشعاع مما يعني أنه لا لزوم لحساب المتوسط عند الطلب كلما احتاجه أي أحد. بعبارة أخرى سيخزّن AveragedCollection
المتوسط الناتج. تحتوي الشيفرة 1 على تعريف لهيكل AveragedCollection
:
اسم الملف: src/lib.rs
pub struct AveragedCollection { list: Vec<i32>, average: f64, }
[الشيفرة 1: هيكل AveragedCollection
الذي يخزّن قائمة من الأعداد الصحيحة والمتوسط لعناصر التجميعة]
الهيكل مُشار إليه بالكلمة المفتاحية pub
، بحيث يمكن لشيفرة برمجية أخرى استخدام ذلك الهيكل، إلا أن الحقول الموجودة داخل الهيكل تبقى خاصة. هذا مهمٌ في هذه الحالة لأننا نريد التأكد من أنه كلما أُضيفت قيمة أو أُزيلت من الشيفرة يُحَدَّث المتوسط أيضًا، ونحقق ذلك من خلال تطبيق توابع add
و remove
و average
كما هو موضح في الشيفرة 2:
اسم الملف: src/lib.rs
impl AveragedCollection { pub fn add(&mut self, value: i32) { self.list.push(value); self.update_average(); } pub fn remove(&mut self) -> Option<i32> { let result = self.list.pop(); match result { Some(value) => { self.update_average(); Some(value) } None => None, } } pub fn average(&self) -> f64 { self.average } fn update_average(&mut self) { let total: i32 = self.list.iter().sum(); self.average = total as f64 / self.list.len() as f64; } }
[الشيفرة 2: تطبيق التوابع العامة add
وremove
وaverage
على AveragedCollection
]
تعدّ التوابع العامة add
و remove
و average
الوسائل الوحيدة للوصول إلى البيانات أو تعديلها في نسخ من AveragedCollection
. يُستدعى التابع الخاص update_average
عندما يضاف عنصر على list
باستخدام التابع add
أو يُزال باستخدام التابع remove
، وهو التابع الذي يحدّث حقل average
بدوره.
نترك حقول list
و average
خاصة لكي لا يبقى أي وسيلة للشيفرة برمجية الخارجية أن تضيف أو تزيل عناصر إلى أو من حقل list
مباشرةً، وإلّا، يمكن للحقل average
ألّا يتوافق مع القيم عندما تتغير list
. يعيد تابع average
القيمة في حقل average
مما يسمح للشيفرة البرمجية الخارجية أن تقرأ average
دون أن تعدل عليها.
بما أننا غلفنا تفاصيل تنفيذ الهيكل AveragedCollection
، يمكننا بسهولة مستقبلًا تغيير بعض التفاصيل مثل هيكل البيانات، فعلى سبيل المثال، يمكننا استعمال <HashSet<i32
بدلًا من <Vec<i32
لحقل list
. بما أن بصمات التوابع العامة add
و remove
و average
بقيت على حالها، فلا ضرورة للتعديل على الشيفرة البرمجية التي تستخدم AveragedCollection
. ولكنّ هذا الأمر لن يكون محققًا إذا جعلنا list
عامة بدلًا من ذلك، إذ تملك <HashSet<i32
و <Vec<i32
توابعًا مختلفة لإضافة وإزالة العناصر بحيث ستحتاج إلى تغيير الشيفرة البرمجية الخارجية غالبًا في حال كانت تعدل على list
مباشرةً.
إذا كان التغليف جزءًا مطلوبًا للغة البرمجة حتى تصبح لغة كائنية التوجه، فإن رست تلبي هذا المطلب، إذ يتيح خيار استعمال pub
أو عدم استعماله لأجزاء مختلفة من الشيفرة، التغليف لتفاصيل التنفيذ.
الوراثة واستخدامها مثل نظام نوع ومشاركة الشيفرة البرمجية
الوراثة هي آلية يمكن بواسطتها للكائن أن يرث عناصر من تعريف كائن آخر وبالتالي يكتسب بيانات الكائن الأصل وسلوكه دون الحاجة إلى تعريفها مرةً أخرى.
إذا كانت الوراثة متطلبًا للغة برمجية حتى تكون اللغة كائنية التوجه فإن رست ليست بلغة كائنية التوجه، إذ لا توجد طريقة لتعريف هيكل يرث حقول وتطبيقات تابع الهيكل الأصلي دون استخدام ماكرو، ومع ذلك إذا كنت معتادًا على وجود الوراثة في اللغة البرمجية التي تتعامل معها، فيمكنك استخدام حلول أخرى في رست بحسب سبب حاجتك للوراثة.
قد تحتاج للتوريث لسببين رئيسيين؛ أحدهما لإعادة استعمال الشيفرة البرمجية، ويمكنك في هذه الحالة تنفيذ سلوك معين لنوع واحد، ويمكّنُك التوريث بدوره من إعادة استعمال هذا التنفيذ لنوع مختلف. يمكنك استخدام هذه الوسيلة بطريقة محدودة في شيفرة رست باستخدام تطبيقات تابع السمة الافتراضية التي رأيتها في الشيفرة 14 من فصل السمات Traits عندما أضفنا تنفيذًا افتراضيًا لتابع summarize
على السمة Summary
. سيُتاح لأي نوع يطبق السمة Summary
التابع summarize
دون أي شيفرة برمجية إضافية، وهذا مشابه للصنف الأب parent class الذي يحتوي على تنفيذ لتابع وصنف ابن يرث الصف الأب ويحتوي على تنفيذ التابع. يمكننا أيضًا تجاوز التطبيق الافتراضي لتابع summarize
عندما نطبق السمة Summary
التي تشبه الصنف الابن الذي يُعيد تعريف تابع موروث من صنف أب.
تتعلق الحاجة الأخرى لاستخدام التوريث بنظام النوع بتمكين استعمال نوع فرعي في نفس الأماكن مثل النوع الأصل. يسمى هذا أيضًا التعددية الشكلية polymorphism مما يعني أنه يمكنك استبدال كائنات متعددة ببعضها في وقت التنفيذ إذا كانت تشترك في خصائص معينة.
التعددية الشكلية Polymorphism
ينظر الكثير من الناس إلى التعددية الشكلية polymorphism بكونها مشابهة للوراثة لكنها فعليًا مفهوم أوسع، إذ تشير إلى الشيفرة البرمجية التي يمكنها العمل مع بيانات ذات أنواع مختلفة، بينما تكون هذه الأنواع بالنسبة للوراثة أصنافًا فرعيةً subclass عمومًا.
يستخدم رست أنواع معممة generics بدلًا من هذا لتجريد الأنواع المختلفة الممكنة وحدود السمات trait bounds، وذلك لفرض قيود على ما يجب أن توفره هذه الأنواع، إذ يسمى ذلك أحيانًا بالتعددية الشكلية المحدودة المقيّدة bounded parametric polymorphism.
فقد التوريث مكانته مؤخرًا مثل حل برمجي تصميمي في العديد من لغات البرمجة لأنه غالبًا ما يكون عرضةً لخطر مشاركة شيفرة برمجية زيادةً عن اللزوم. لا يجب أن تشترك الأصناف الابن دائمًا في جميع خصائص صنفها الأب ولكنها ستفعل ذلك مع التوريث، ومن شأن ذلك جعل تصميم البرنامج أقل مرونة. يُضيف ذلك أيضًا إمكانية استدعاء توابع على الأصناف الابن التي لا معنى لها أو التي تتسبب بأخطاء لأن التوابع لا تنطبق على الأصناف الابن. إضافةً إلى ذلك تسمح بعض اللغات فقط بالوراثة الفردية single inheritance (بمعنى أنه يمكن للصنف الابن أن يرث فقط من صنف واحد) مما يقيد مرونة تصميم البرنامج أكثر.
تتخذ رست لهذه الأسباب طريقةً مختلفة في استعمال كائنات السمة بدلاً من الوراثة، وسنلقي نظرةَ على كيفية تمكين كائنات السمة من تعدد الأشكال في رست.
ترجمة -وبتصرف- لقسم من الفصل Object-Oriented Programming Features of Rust من كتاب The Rust Programming Language.
اقرأ أيضًا
- المقال التالي: استخدام كائنات السمة Object Trait في لغة رست
- المقال السابق: تزامن الحالة المشتركة Shared-State Concurrency في لغة رست وتوسيع التزامن مع Send و Sync
- البرمجة كائنية التوجه
- ما هي التعددية الشكلية polymorphism؟
- كيفية تطبيق التعددية الشكلية (Polymorphism) على الأصناف في بايثون
- مقدمة إلى البرمجة الوظيفية Functional Programming
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.