التكرار عبر كائنات Trait
جزء من قسم Object Oriented Programming في رحلة Rust على Coddy — الدرس 47 من 61.
الآن بعد أن أصبح بإمكانك تخزين أنواع مختلفة في Vec<Box<dyn Trait>>، فإن الخطوة التالية هي معالجة تلك المجموعة. يعمل التكرار عبر كائنات السمة (trait objects) تماماً مثل التكرار عبر أي متجه (vector) - حيث تقوم بالمرور عبر كل عنصر واستدعاء الطرق (methods) المحددة بواسطة السمة.
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>. يمكنك استدعاء أي طريقة (method) من الـ Shape trait مباشرةً—تتعامل لغة Rust مع الإرسال الديناميكي (dynamic dispatch) تلقائياً، حيث تستدعي التنفيذ الصحيح لكل نوع ملموس (concrete type).
هذا النمط قوي لتجميع النتائج. يمكنك جمع كل المساحات، أو تصفية الأشكال حسب الحجم، أو تحويل المجموعة. الفكرة الجوهرية هي أنه بمجرد أن تصبح العناصر في مجموعة من الـ trait objects، فإنك تتفاعل معها حصرياً من خلال واجهة الـ trait، بغض النظر عن أنواعها الأساسية.
التحدي
سهللنقم ببناء حاسبة أسعار لعربة تسوق يمكنها استيعاب أنواع مختلفة من العناصر! ستقوم بإنشاء نظام حيث يمكن تخزين المنتجات والخدمات — ولكل منها منطق تسعير مختلف — معاً ومعالجتها لحساب السعر الإجمالي.
ستقوم بتنظيم الكود الخاص بك عبر ملفين:
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) الخاصة بك وأنشئ متجهاً (vector) من نوع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).
جرّب بنفسك
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}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1الأساليب والسلوك
مقدمة في كتل التنفيذمعامل Selfالأساليب القابلة للتعديلالدوال المرتبطةكتل تنفيذ متعددةتسلسل الأساليبمراجعة - إجراءات المستطيل4مشروع: حيوان أليف افتراضي
تعريف الحيوان الأليفإطعام الحيوان الأليف2التغليف والوحدات البرمجية
أساسيات الوحدات البرمجيةالكلمة المفتاحية Publicالحقول الخاصةدوال Gettersدوال Settersمراجعة - الخزانة الآمنة5الأنواع العامة (Generics)
الـ Structs العامةالـ Methods العامةأنواع عامة متعددةالـ Functions العامةمراجعة - Coordinate Point3التعدادات المتقدمة (Advanced Enums)
التعدادات مع البياناتالأساليب (Methods) في التعداداتمطابقة حالات البياناتمراجعة التعداد Optionملخص - التعداد Shape6تعريف الـ Traits
ما هي الـ Trait؟تنفيذ الـ Traitsالتنفيذات الافتراضيةتجاوز التنفيذات الافتراضيةالـ Traits مع المعاملاتملخص - مشغل الوسائط9كائنات Trait
الإرسال الديناميكي (Dynamic Dispatch)متجهات من الـ Traitsالتكرار عبر كائنات Traitمراجعة - مدير حديقة الحيوان