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

البرمجة كائنية التوجه OOP في لغة رست Rust


Naser Dakhel

لا يوجد إجماع في مجتمع البرمجة حول الميزات التي يجب أن تكون موجودة في لغة البرمجة حتى تكون لغة كائنية التوجه، وتتأثر رست بالعديد من نماذج البرمجة 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.

اقرأ أيضًا


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

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

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



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

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

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

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   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.


×
×
  • أضف...