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

التكرار عبر كائنات Trait

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

الآن بعد أن أصبح بإمكانك تخزين أنواع مختلفة في Vec<Box<dyn Trait>>، فإن الخطوة التالية هي معالجة تلك المجموعة. يعمل التكرار عبر كائنات السمات (trait objects) تماماً مثل التكرار عبر أي متجه (vector) — حيث تقوم بالمرور عبر كل عنصر واستدعاء الطرق (methods) المحددة بواسطة السمة (trait).

trait Shape {
    fn area(&self) -> f64;
}

struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }

impl Shape for Circle {
    fn area(&self) -> f64 { 3.14159 * self.radius * self.radius }
}

impl Shape for Rectangle {
    fn area(&self) -> f64 { self.width * self.height }
}

fn main() {
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Circle { radius: 2.0 }),
        Box::new(Rectangle { width: 3.0, height: 4.0 }),
        Box::new(Circle { radius: 1.0 }),
    ];

    for shape in &shapes {
        println!("Area: {}", shape.area());
    }
}

عندما تقوم بالتكرار باستخدام for shape in &shapes، يكون كل shape عبارة عن مرجع لـ Box<dyn Shape>. يمكنك استدعاء أي طريقة من الـ Shape trait بشكل مباشر—يتعامل Rust مع الإرسال الديناميكي (dynamic dispatch) تلقائياً، مستدعياً التنفيذ الصحيح لكل نوع ملموس (concrete type).

هذا النمط قوي لتجميع النتائج. يمكنك جمع كل المساحات، أو تصفية الأشكال حسب الحجم، أو تحويل المجموعة. الفكرة الجوهرية هي أنه بمجرد أن تصبح العناصر في مجموعة كائنات سمة (trait object collection)، فإنك تتفاعل معها حصرياً من خلال واجهة السمة (trait's interface)، بغض النظر عن أنواعها الأساسية.

challenge icon

التحدي

سهل

لنقم ببناء حاسبة أسعار لعربة تسوق يمكنها استيعاب أنواع مختلفة من العناصر! ستقوم بإنشاء نظام حيث يمكن تخزين المنتجات والخدمات — ولكل منها منطق تسعير مختلف — معاً ومعالجتها لحساب السعر الإجمالي.

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

  • items.rs: قم بتعريف سمة (trait) عامة باسم Priceable مع طريقة price تأخذ &self وتعيد f64. ثم قم بإنشاء هيكلين (structs) عامين:
    • Product — مع حقول عامة name (String) و cost (f64). يجب أن تعيد طريقة price الخاصة به قيمة cost ببساطة.
    • Service — مع حقول عامة name (String) و hourly_rate (f64) و hours (f64). يجب أن تعيد طريقة price الخاصة به حاصل ضرب hourly_rate * hours.
  • main.rs: قم باستيراد وحدة العناصر (items module) الخاصة بك وأنشئ متجهاً من نوع Vec<Box<dyn Priceable>>. استخدم المدخلات لإنشاء Product واحد و Service واحد، وأضفهما إلى المتجه الخاص بك، ثم قم بالمرور عبر المجموعة لحساب وطباعة السعر الإجمالي لجميع العناصر.

تتجلى قوة كائنات السمات (trait objects) هنا — لا يحتاج كود التكرار الخاص بك إلى معرفة ما إذا كان يعالج Product أو Service. إنه ببساطة يستدعي price() على كل عنصر، ويقوم Rust بتوجيه التنفيذ إلى التطبيق الصحيح تلقائياً.

يجب أن يعرض مخرجك السعر الإجمالي مع منزلة عشرية واحدة:

Total: ${total}

على سبيل المثال، مع المدخلات Laptop و 999.99 و Consulting و 150.0 و 3.0:

Total: $1449.99

ومع المدخلات Book و 29.99 و Tutoring و 50.0 و 2.0:

Total: $129.99

ستتلقى خمسة مدخلات: اسم المنتج، تكلفة المنتج، اسم الخدمة، معدل الأجر بالساعة، وساعات العمل (قم بتحليل القيم الرقمية كـ f64).

ورقة مرجعية

لتكرار (iterate) عبر مجموعة من كائنات السمة (trait objects)، استخدم حلقة for قياسية مع مرجع (reference) للمتجه (vector):

for item in &collection {
    item.trait_method();
}

عند التكرار باستخدام for item in &vec، يكون كل item عبارة عن مرجع لـ Box<dyn Trait>. يمكنك استدعاء أي طريقة (method) من السمة مباشرةً—تتعامل Rust مع الإرسال الديناميكي (dynamic dispatch) تلقائيًا، وتستدعي التنفيذ الصحيح لكل نوع ملموس (concrete type).

مثال على التكرار عبر كائنات السمة لتجميع النتائج:

trait Shape {
    fn area(&self) -> f64;
}

struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }

impl Shape for Circle {
    fn area(&self) -> f64 { 3.14159 * self.radius * self.radius }
}

impl Shape for Rectangle {
    fn area(&self) -> f64 { self.width * self.height }
}

fn main() {
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Circle { radius: 2.0 }),
        Box::new(Rectangle { width: 3.0, height: 4.0 }),
    ];

    for shape in &shapes {
        println!("Area: {}", shape.area());
    }
}

هذا النمط مفيد لتجميع النتائج—يمكنك جمع القيم، أو التصفية حسب المعايير، أو تحويل المجموعة أثناء التفاعل مع العناصر حصريًا من خلال واجهة السمة (trait's interface).

جرّب بنفسك

mod items;

use items::{Priceable, Product, Service};

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

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

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

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

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

    // TODO: إنشاء Product باستخدام product_name و product_cost

    // TODO: إنشاء Service باستخدام service_name و hourly_rate و hours

    // TODO: إنشاء Vec<Box<dyn Priceable>> وإضافة كلا العنصرين إليه

    // TODO: التكرار عبر الـ vector وحساب السعر الإجمالي

    // TODO: طباعة المجموع مع منزلة عشرية واحدة
    // التنسيق: Total: ${total:.1}
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming