Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

متجهات من الـ Traits

جزء من قسم Object Oriented Programming في رحلة Rust على Coddy — الدرس 46 من 61.

لقد رأيت كيف يسمح Box<dyn Trait> لمتغير واحد بحمل أنواع مختلفة. تظهر القوة الحقيقية عندما تجمع بين هذا والمجموعات (collections) — تخزين أنواع مختلفة متعددة في متجه (vector) واحد.

يتطلب المتجه (vector) العادي أن تكون جميع العناصر من نفس النوع. لا يمكنك امتلاك Vec<Dog> يحتوي أيضاً على قطط. ولكن مع كائنات السمات (trait objects)، يمكنك إنشاء متجه يحمل أي شيء يقوم بتطبيق سمة مشتركة:

trait Speak {
    fn speak(&self) -> String;
}

struct Dog;
struct Cat;
struct Bird;

impl Speak for Dog {
    fn speak(&self) -> String { String::from("Woof!") }
}

impl Speak for Cat {
    fn speak(&self) -> String { String::from("Meow!") }
}

impl Speak for Bird {
    fn speak(&self) -> String { String::from("Tweet!") }
}

fn main() {
    let animals: Vec<Box<dyn Speak>> = vec![
        Box::new(Dog),
        Box::new(Cat),
        Box::new(Bird),
    ];
}

النوع Vec<Box<dyn Speak>> يعني "متجه من كائنات السمات المغلفة (boxed trait objects)". كل عنصر هو Box يشير إلى شيء ينفذ Speak—الأنواع الفعلية يمكن أن تختلف. تقوم بتغليف كل قيمة بـ Box::new() عند إضافتها إلى المتجه.

هذا النمط ضروري لبناء أنظمة مرنة حيث تحتاج إلى إدارة مجموعات من الكائنات المرتبطة ولكن المختلفة—مثل مكونات واجهة المستخدم (UI components)، أو كيانات الألعاب (game entities)، أو أنظمة الإضافات (plugin systems).

challenge icon

التحدي

سهل

لنقم ببناء نظام إشعارات يمكنه التعامل مع أنواع مختلفة من التنبيهات! ستقوم بإنشاء مجموعة تخزن أنواعًا مختلفة من الإشعارات — رسائل البريد الإلكتروني، ورسائل SMS، وإشعارات الدفع (push notifications) — جميعها في متجه (vector) واحد باستخدام كائنات السمة (trait objects).

ستقوم بتنظيم الكود الخاص بك عبر ملفين:

  • notifications.rs: قم بتعريف سمة (trait) عامة باسم Notify مع طريقة send تأخذ &self وتعيد String يصف الإشعار الذي يتم إرساله. ثم أنشئ ثلاث هياكل (structs) عامة:
    • Email — مع حقل عام recipient من نوع (String). يجب أن تعيد طريقة send الخاصة به "Email to: {recipient}"
    • Sms — مع حقل عام phone من نوع (String). يجب أن تعيد طريقة send الخاصة به "SMS to: {phone}"
    • Push — مع حقل عام device من نوع (String). يجب أن تعيد طريقة send الخاصة به "Push to: {device}"
  • main.rs: قم باستيراد وحدة الإشعارات الخاصة بك وأنشئ متجهًا من نوع Vec<Box<dyn Notify>> يحتوي على جميع أنواع الإشعارات الثلاثة. استخدم المدخلات المقدمة لإنشاء واحد من كل نوع إشعار، وأضفهم إلى المتجه الخاص بك بالترتيب (Email، Sms، Push)، ثم اطبع نتيجة استدعاء send() على العنصر الأول في المتجه.

المفهوم الأساسي هنا هو أنه على الرغم من أن Email و Sms و Push هي هياكل مختلفة تمامًا، إلا أنها يمكن أن تتواجد معًا في نفس المتجه لأنها تشترك في سمة Notify. يتم تغليف كل عنصر بـ Box::new() لإنشاء كائن السمة.

يجب أن يظهر مخرجك الإشعار الأول:

Email to: {recipient}

على سبيل المثال، مع المدخلات "alice@example.com" و "555-1234" و "iPhone-12":

Email to: alice@example.com

ستتلقى ثلاثة مدخلات: مستلم البريد الإلكتروني، ورقم الهاتف، واسم الجهاز.

ورقة مرجعية

يسمح Vec<Box<dyn Trait>> بتخزين أنواع مختلفة متعددة في متجه (vector) واحد، طالما أنها جميعاً تطبق نفس السمة (trait).

تتطلب المتجهات العادية أن تكون جميع العناصر من نفس النوع، ولكن كائنات السمات (trait objects) تُمكّن من إنشاء مجموعات غير متجانسة:

trait Speak {
    fn speak(&self) -> String;
}

struct Dog;
struct Cat;

impl Speak for Dog {
    fn speak(&self) -> String { String::from("Woof!") }
}

impl Speak for Cat {
    fn speak(&self) -> String { String::from("Meow!") }
}

fn main() {
    let animals: Vec<Box<dyn Speak>> = vec![
        Box::new(Dog),
        Box::new(Cat),
    ];
}

يتم تغليف كل عنصر بـ Box::new() عند إضافته إلى المتجه. يمكن أن تختلف الأنواع الفعلية طالما أنها تطبق السمة المشتركة.

جرّب بنفسك

mod notifications;

use notifications::{Notify, Email, Sms, Push};

fn main() {
    // قراءة المدخلات
    let mut recipient = String::new();
    std::io::stdin().read_line(&mut recipient).expect("Failed to read line");
    let recipient = recipient.trim().to_string();

    let mut phone = String::new();
    std::io::stdin().read_line(&mut phone).expect("Failed to read line");
    let phone = phone.trim().to_string();

    let mut device = String::new();
    std::io::stdin().read_line(&mut device).expect("Failed to read line");
    let device = device.trim().to_string();

    // TODO: لتمثيل أنواع إشعارات مختلفة Vec<Box<dyn Notify>> أنشئ
    // TODO: باستخدام المدخلات Push و Sms و Email أنشئ كائنات من
    // TODO: Box::new() أضفهم إلى المتجه بالترتيب (Email, Sms, Push) باستخدام
    // TODO: اطبع نتيجة استدعاء ()send على العنصر الأول
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming