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

البحث في الموقع

المحتوى عن 'أنماط التصميم'.

  • ابحث بالكلمات المفتاحية

    أضف وسومًا وافصل بينها بفواصل ","
  • ابحث باسم الكاتب

نوع المحتوى


التصنيفات

  • الإدارة والقيادة
  • التخطيط وسير العمل
  • التمويل
  • فريق العمل
  • دراسة حالات
  • التعامل مع العملاء
  • التعهيد الخارجي
  • السلوك التنظيمي في المؤسسات
  • عالم الأعمال
  • التجارة والتجارة الإلكترونية
  • نصائح وإرشادات
  • مقالات ريادة أعمال عامة

التصنيفات

  • مقالات برمجة عامة
  • مقالات برمجة متقدمة
  • PHP
    • Laravel
    • ووردبريس
  • جافاسكربت
    • لغة TypeScript
    • Node.js
    • React
    • Vue.js
    • Angular
    • jQuery
    • Cordova
  • HTML
  • CSS
    • Sass
    • إطار عمل Bootstrap
  • SQL
  • لغة C#‎
    • ‎.NET
    • منصة Xamarin
  • لغة C++‎
  • لغة C
  • بايثون
    • Flask
    • Django
  • لغة روبي
    • إطار العمل Ruby on Rails
  • لغة Go
  • لغة جافا
  • لغة Kotlin
  • لغة Rust
  • برمجة أندرويد
  • لغة R
  • الذكاء الاصطناعي
  • صناعة الألعاب
  • سير العمل
    • Git
  • الأنظمة والأنظمة المدمجة

التصنيفات

  • تصميم تجربة المستخدم UX
  • تصميم واجهة المستخدم UI
  • الرسوميات
    • إنكسكيب
    • أدوبي إليستريتور
  • التصميم الجرافيكي
    • أدوبي فوتوشوب
    • أدوبي إن ديزاين
    • جيمب GIMP
    • كريتا Krita
  • التصميم ثلاثي الأبعاد
    • 3Ds Max
    • Blender
  • نصائح وإرشادات
  • مقالات تصميم عامة

التصنيفات

  • مقالات DevOps عامة
  • خوادم
    • الويب HTTP
    • البريد الإلكتروني
    • قواعد البيانات
    • DNS
    • Samba
  • الحوسبة السحابية
    • Docker
  • إدارة الإعدادات والنشر
    • Chef
    • Puppet
    • Ansible
  • لينكس
    • ريدهات (Red Hat)
  • خواديم ويندوز
  • FreeBSD
  • حماية
    • الجدران النارية
    • VPN
    • SSH
  • شبكات
    • سيسكو (Cisco)

التصنيفات

  • التسويق بالأداء
    • أدوات تحليل الزوار
  • تهيئة محركات البحث SEO
  • الشبكات الاجتماعية
  • التسويق بالبريد الالكتروني
  • التسويق الضمني
  • استسراع النمو
  • المبيعات
  • تجارب ونصائح
  • مبادئ علم التسويق

التصنيفات

  • مقالات عمل حر عامة
  • إدارة مالية
  • الإنتاجية
  • تجارب
  • مشاريع جانبية
  • التعامل مع العملاء
  • الحفاظ على الصحة
  • التسويق الذاتي
  • العمل الحر المهني
    • العمل بالترجمة
    • العمل كمساعد افتراضي
    • العمل بكتابة المحتوى

التصنيفات

  • الإنتاجية وسير العمل
    • مايكروسوفت أوفيس
    • ليبر أوفيس
    • جوجل درايف
    • شيربوينت
    • Evernote
    • Trello
  • تطبيقات الويب
    • ووردبريس
    • ماجنتو
    • بريستاشوب
    • أوبن كارت
    • دروبال
  • الترجمة بمساعدة الحاسوب
    • omegaT
    • memoQ
    • Trados
    • Memsource
  • برامج تخطيط موارد المؤسسات ERP
    • تطبيقات أودو odoo
  • أنظمة تشغيل الحواسيب والهواتف
    • ويندوز
    • لينكس
  • مقالات عامة

التصنيفات

  • آخر التحديثات

أسئلة وأجوبة

  • الأقسام
    • أسئلة البرمجة
    • أسئلة ريادة الأعمال
    • أسئلة العمل الحر
    • أسئلة التسويق والمبيعات
    • أسئلة التصميم
    • أسئلة DevOps
    • أسئلة البرامج والتطبيقات

التصنيفات

  • كتب ريادة الأعمال
  • كتب العمل الحر
  • كتب تسويق ومبيعات
  • كتب برمجة
  • كتب تصميم
  • كتب DevOps

ابحث في

ابحث عن


تاريخ الإنشاء

  • بداية

    نهاية


آخر تحديث

  • بداية

    نهاية


رشح النتائج حسب

تاريخ الانضمام

  • بداية

    نهاية


المجموعة


النبذة الشخصية

تم العثور على 3 نتائج

  1. سنجمع في هذا المقال الصياغة الصالحة في الأنماط وسنتحدث عن مكان استخدام كل واحد منها. مطابقة القيم المجردة Literals يمكننا مطابقة الأنماط مباشرةً مع القيم المجرّدة كما رأينا سابقًا في الفصل التعدادات enums في لغة رست. تمنحنا الشيفرة البرمجية التالية بعض الأمثلة: let x = 1; match x { 1 => println!("one"), 2 => println!("two"), 3 => println!("three"), _ => println!("anything"), } تطبع هذه الشيفرة one لأن القيمة في x هي 1. تُعد هذه الصياغة مفيدة عندما تريد من الشيفرة أن تنفّذ عملًا ما عندما تحصل على قيمة معينة واحدة. مطابقة المتغيرات المسماة Named Variables المتغيرات المُسمّاة هي أنماط غير قابلة للجدل تطابق أي قيمة، وقد استخدمناها مرات عديدة سابقًا، ولكن هناك تعقيدات عند استخدامها في تعابير match. ينشئ تعبير match نطاقًا scope جديدًا، وبالتالي ستُخفي المتغيرات المُصرّح عنها على أنها جزء من النمط داخل match المتغيرات التي تحمل الاسم ذاته خارج هيكل match كما هو الحال في جميع المتغيرات. صرّحنا ضمن الشيفرة 11 عن متغير مُسمى x قيمته Some(5)‎ ومتغير y قيمته 10، ثم أنشأنا تعبير match على القيمة x. ألقِ نظرةً على الأنماط في أذرع المطابقة و !println وحاول اكتشاف ماذا ستطبع الشيفرة قبل تنفيذ هذه الشيفرة أو متابعة القراءة. اسم الملف: src/main.rs let x = Some(5); let y = 10; match x { Some(50) => println!("Got 50"), Some(y) => println!("Matched, y = {y}"), _ => println!("Default case, x = {:?}", x), } println!("at the end: x = {:?}, y = {y}", x); [الشيفرة 11: تعبير match مع ذراع يتسبب بظهور متغير خفي y] لنرى ما سيحصل عند تنفيذ تعبير match؛ إذ لا تتطابق القيمة المُعرّفة x مع ذراع المطابقة الأول في النمط لذا يستمر تنفيذ الشيفرة. يتسبب النمط الموجود في ذراع المطابقة الثاني بإنشاء متغير جديد باسم y وهو يطابق أي قيمة داخل قيمة Some، وذلك لأننا في نطاق جديد داخل تعبير match، هذا المتغير الجديد yهو ليس المتغير y ذاته الذي صرّحنا عنه في البداية بقيمة 10. يطابق الإسناد الجديد للمتغير y أي قيمة داخل Some وهي القيمة الموجودة في x، وبالتالي ترتبط y الجديدة بقيمة Some الداخلية في x، وتبلغ تلك القيمة 5، لذلك يُنَفَذ تعبير الذراع ويطبع Matched, y = 5. لن تُطابق الأنماط في ذراعي النمط الأولين إذا كانت القيمة في x هي None بدلًا من Some(5)‎، لذا ستُطابق القيم مع الشرطة السفلية underscore. لم نُضِف المتغير x في نمط ذراع الشرطة السفلية، لذلك يبقى x في التعبير هو المتغير x الخارجي الذي لم يُخفى، وتطبع match في هذه الحالة الافتراضية Default case, x = None. عندما ينتهي تعبير match ينتهي نطاقه أيضًا، وينتهي أيضًا نطاق المتغير y الداخلي. تطبع آخر تعليمة println!‎ ما يلي: at the end: x = Some(5), y = 10 يجب علينا استخدام درع مطابقة شرطي match guard conditional لإنشاء تعبير match يقارن قيم x و y الخارجية عوضًا عن تقديم متغير خفي، وسنتحدث عن ذلك لاحقّا في قسم "الشرطيات الإضافية مع دروع المطابقة". الأنماط المتعددة Multiple Patterns يمكننا مطابقة عدة أنماط في تعبير match باستخدام الصياغة | التي يمكن أن تكون النمط أو المعامل. نطابق في المثال التالي قيمة x مع أذرع المطابقة، بحيث يحتوي أول ذراع على الخَيَار "أو or"، بمعنى أنه إذا طابقت القيمة x أحد القيمتين في الذراع تُنفَّذ شيفرة الذراع كما يلي: let x = 1; match x { 1 | 2 => println!("one or two"), 3 => println!("three"), _ => println!("anything"), } تطبع الشيفرة السابقة ما يلي: one or two مطابقة مجالات القيم باستخدام الصيغة =.. تسمح صيغة =.. بمطابقة مجال شامل من القيم، إذ تُنفَّذ الشيفرة البرمجية الخاصة بالذراع عندما يطابق النمط أي قيمة في مجال ما كما في الشيفرة التالية: let x = 5; match x { 1..=5 => println!("one through five"), _ => println!("something else"), } تتطابق الذراع الأولى إذا كانت قيمة x هي 1 أو 2 أو 3 أو 4 أو 5، هذه الصياغة ملائمة لمطابقة قيم متعددة بدلًا من استخدام المعامل | للتعبير عن الفكرة ذاتها؛ إذا أردنا استخدام | فيجب تحديد 5 | 4 | 3 | 2 | 1، وستكون طريقة تحديد المجال أقصر خاصةً إذا أردنا مطابقة أي رقم بين 1 و 1,000. يتحقق المصرّف وقت التصريف من أن المجال غير فارغ لأن أنواع المجالات التي تستطيع رست تمييز ما إذا كانت فارغة أم لا هي char والقيم العددية، إذ يُسمح باستخدام المجالات فقط مع القيم العددية أو قيم char. إليك مثالًا يستخدم مجال من قيم char: let x = 'c'; match x { 'a'..='j' => println!("early ASCII letter"), 'k'..='z' => println!("late ASCII letter"), _ => println!("something else"), } تميز رست أن 'c' تقع داخل مجال النمط الأول وتطبع early ASCII letter. التفكيك Restructuring لتجزئة القيم يمكننا استخدام الأنماط لتفكيك الهياكل أو المعدّدات enums أو الصفوف tuples لاستخدام الأجزاء المختلفة من القيم، لنستعرض كل حالة من الحالات السابقة. تفكيك الهياكل تظهر الشيفرة 12 هيكل Point بحقلين x و y يُمكن تجزئتهما باستخدام نمط مع التعليمة let. اسم الملف:src/main.rs struct Point { x: i32, y: i32, } fn main() { let p = Point { x: 0, y: 7 }; let Point { x: a, y: b } = p; assert_eq!(0, a); assert_eq!(7, b); } [الشيفرة 12: تفكيك حقل هيكل إلى متغيرات منفصلة] تُنشئ الشيفرة السابقة المتغيرين a و b اللذين يطابقان قيمتي الحقلين x و y في الهيكل p، ويوضح هذا المثال أنه ليس من الضروري لأسماء المتغيرات في النمط أن تُطابق أسماء الحقول في الهيكل، ولكن من الشائع مطابقة أسماء المتغيرات مع أسماء الحقول لتذكر ارتباط المتغير بحقل معين. بما أن كتابة ما يلي مثلًا: let Point { x: x, y: y } = p;‎ تحتوي على الكثير من التكرار، لدى رست طريقةً مختصرة للأنماط التي تطابق حقول الهيكل؛ إذ عليك فقط أن تُضيف اسم حقل الهيكل مما يجعل المتغيرات المُنشأة من هذا النمط تحمل الاسم ذاته. تعمل الشيفرة 13 بطريقة عمل الشيفرة 12 ذاتها إلا أن المتغيرات المُنشأة في النمط let هي x و y بدلًا من a و b. اسم الملف: src/main.rs struct Point { x: i32, y: i32, } fn main() { let p = Point { x: 0, y: 7 }; let Point { x, y } = p; assert_eq!(0, x); assert_eq!(7, y); } [الشيفرة 13: تفكيك حقول هيكل باستخدام طريقة حقل الهيكل المختصرة] تُنشئ الشيفرة المتغيرين x و y اللذين يطابقان الحقلين x و y الخاصين بالهيكل p، والنتيجة هي احتواء المتغيرين x و y على القيم الموجودة في الهيكل p. يمكننا إجراء عملية التفكيك باستخدام القيم المجردة مثل جزء من نمط الهيكل بدلًا من إنشاء متغيرات لكل الحقول، ويسمح لنا ذلك باختبار بعض الحقول لقيم معينة أثناء إنشاء متغيرات لتفكيك حقول أخرى. لدينا في الشيفرة 14 تعبير match يقسم قيم Point إلى ثلاث حالات: نقاط تقع مباشرةً على محور x (الذي يُعدّ محققًا عندما y = 0)، ونقاط تقع على المحور y (أي x = 0)، ونقاط لا تقع على أي من المحورين. اسم الملف: src/main.rs fn main() { let p = Point { x: 0, y: 7 }; match p { Point { x, y: 0 } => println!("On the x axis at {x}"), Point { x: 0, y } => println!("On the y axis at {y}"), Point { x, y } => { println!("On neither axis: ({x}, {y})"); } } } [ الشيفرة 14: تفكيك ومطابقة القيم المجردة في نمط واحد] ستتطابق الذراع الأولى مع أي نقطة تقع على المحور x عن طريق تحديد أن الحقل y يقابل القيمة المجردة 0، ويُنشئ النمط متغير x يمكن استخدامه في شيفرة هذا الذراع؛ وبصورةٍ مشابهة، ستتطابق الذراع الثانية مع أي نقطة تقع على المحور y عن طريق تحديد أن الحقل x يقابل القيمة 0 ويُنشئ النمط متغير y للقيمة في الحقل y، ولا تحدد الذراع الثالثة أي قيمة مجرّدة لذا تُطابق أي قيمة Point أُخرى ويُنشأ متغيران لكل من الحقلين x و y. تطابِق القيمة p الذراع الثانية في هذا المثال بفضل احتواء x على 0 وتطبع هذه الشيفرة ما يلي: On the y axis at 7. تذكر أن تعبير match يتوقف عن التحقق من الأذرع عندما يجد أول نمط مطابق لذا حتى لو كانت Point { x: 0, y: 0}‎ موجودةً على المحورين x و y ستطبع الشيفرة فقط On the x axis at 0. تفكيك المعددات فكّكنا سابقًا المعدّدات (الشيفرة 5 في الفصل بنية match للتحكم بسير برامج لغة رست Rust) إلا أننا لم نتحدث صراحةً أن النمط لتفكيك المعدّد يوافق طريقة تخزين البيانات داخله. تستخدم الشيفرة 15 معدّدًا يدعى Message من الشيفرة 2 من الفصل التعدادات enums في لغة رست وتُكتب match مع أنماط تفكك كل من القيم الداخلية الخاصة بذلك المعدّد. اسم الملف: src/main.rs enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } fn main() { let msg = Message::ChangeColor(0, 160, 255); match msg { Message::Quit => { println!("The Quit variant has no data to destructure."); } Message::Move { x, y } => { println!("Move in the x direction {x} and in the y direction {y}"); } Message::Write(text) => { println!("Text message: {text}"); } Message::ChangeColor(r, g, b) => { println!("Change the color to red {r}, green {g}, and blue {b}",) } } } [الشيفرة 15: تفكيك متغايرات المعدّد التي تحتوي على أنواع مختلفة من القيم] تطبع الشيفرة السابقة ما يلي: Change the color to red 0, green 160, and blue 255 حاول تغيير قيمة msg لملاحظة تنفيذ الشيفرة البرمجية للأذرع الأُخرى. لا يمكننا تفكيك القيم أكثر من ذلك في متغيرات المعدّد التي لا تحتوي على أي بيانات مثل Message::Quit، إذ يمكننا فقط مطابقة القيمة المجرّدة Message::Quit ولا يوجد أي متغيرات في النمط. يمكن استخدام أنماط مشابهة للنمط الذي نحدده لمطابقة الهياكل من أجل متغايرات المعدّدات التي تشبه الهيكل مثل Message::Move، إذ نضع أقواس معقوصة بعد اسم المتغير وبعدها نضع الحقول مع المتغيرات لتجزئتها واستخدامها في شيفرة الذراع، واستخدمنا هنا الطريقة المُختصرة كما فعلنا في الشيفرة 13. يشابه النمط الذي نستخدمه لمتغيرات المعدّدات التي تشبه الصفوف مثل Message::Write الذي يحتوي على صف مع عنصر واحد و Message::ChangeColor الذي يحتوي صف مع ثلاثة عناصر للنمط الذي نحدده لمطابقة الصفوف، ويجب أن يطابق عدد المتغيرات في النمط عدد العناصر في المتغير المُراد مطابقته. تفكيك الهياكل والمعددات المتداخلة كانت أمثلتنا حتى الآن مقتصرةً على مطابقة الهياكل structs والمعدّدات enums بعمق طبقة واحدة، إلا أن المطابقة تعمل على العناصر المتداخلة أيضًا، فعلى سبيل المثال يمكن إعادة بناء الشيفرة 15 لتدعم ألوان RGB و HSV في رسالة ChangeColor كما تبين الشيفرة 16. enum Color { Rgb(i32, i32, i32), Hsv(i32, i32, i32), } enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(Color), } fn main() { let msg = Message::ChangeColor(Color::Hsv(0, 160, 255)); match msg { Message::ChangeColor(Color::Rgb(r, g, b)) => { println!("Change color to red {r}, green {g}, and blue {b}"); } Message::ChangeColor(Color::Hsv(h, s, v)) => { println!("Change color to hue {h}, saturation {s}, value {v}") } _ => (), } } [الشيفرة 16: مطابقة معدّدات متداخلة] يتطابق النمط في الذراع الأولى في تعبير match مع متغاير معدد Message::ChangeColor، الذي يحتوي على متغاير معدد Color::Rgb، ويرتبط النمط مع قيم i32 الداخلية الثلاث، بينما يتطابق نمط الذراع الثانية مع متغاير معدد Message::ChangeColor ويطابق المعدّد الداخلي Color::Hsv، ويمكن تحديد هذه الشروط المعقدة في تعبير match واحد حتى لو كان هناك معدّدان. تفكيك الهياكل والصفوف يمكننا مزج ومطابقة وتضمين الأنماط المفككة بطرق معقدة أكثر، ويبين المثال التالي تفكيك معقد، إذ نضمّن هياكل وصفوف داخل صف ونفكك جميع القيم الأولية primitive: let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 }); تسمح لنا الشيفرة السابقة بتجزئة الأنواع المعقدة إلى الأجزاء المكونة لها لاستخدام القيم التي نريدها على نحوٍ منفصل. تُعد التجزئة مع الأنماط طريقةً مناسبة لاستخدام أجزاء من القيم مثل قيمة من كل حقل في هيكل منفصلين عن بعضهم. تجاهل القيم في نمط من المفيد أحيانًا تجاهل القيم في نمط ما كما هو الحال في ذراع match الأخير، للحصول على مطابقة مع الكل catchall التي لا تفعل أي شيء بدورها سوى تعريف كل القيم الممكنة المتبقية. هناك عدة طرق لتجاهل قيم كاملة أو جزء من قيم في نمط، منها: استخدام نمط _ (الذي تطرقنا له سابقًا)، أو استخدام نمط _ مع نمط أخر، أو استخدام اسم يبدأ بشرطة سفلية، أو استخدام .. لتجاهل باقي أجزاء القيمة، وسنتعرف أكثر عن مكان وسبب استخدام كل نوع من هذه الأنماط. تجاهل قيمة كاملة باستخدام _ استخدمنا الشرطة السفلية مثل نمط محرف بدل wildcard يُطابق أي قيمة ولكن لا يرتبط بها، هذا مفيد لذراع أخير في تعبير match ولكن يمكن استخدامه أيضًا في أي نمط مثل معاملات الدالة كما تبين الشيفرة 17. اسم الملف: src/main.rs fn foo(_: i32, y: i32) { println!("This code only uses the y parameter: {}", y); } fn main() { foo(3, 4); } [الشيفرة 17: استخدام _ في بصمة الدالة] تتجاهل الشيفرة السابقة القيمة 3 المُمَررة مثل معامل أول تمامًا وتطبع: This code only uses the y parameter: 4 ستحتاج في معظم الحالات لتغيير الوسيط عندما لا توجد حاجة لمعامل دالة معين وذلك كي لا يُضمَّن المعامل غير المُستخدم، ويُعد تجاهل معامل الدالة مفيدًا في حالة تطبيق سمة trait بحاجة لنوع وسيط معين ولكن لا يحتاج متن الدالة في التطبيق إلى أحد الأنماط. يمكنك تفادي الحصول على تنبيه من المصرّف عن معاملات الدالة غير المُستخدمة كما تفعل لو استعضت عنها باسم آخر. تجاهل أجزاء من القيمة باستخدام _ متداخلة يمكن استخدام _ داخل نمط آخر لتجاهل جزء من القيمة، إذ من الممكن مثلًا اختبار جزء فقط من القيمة وألا تكون هناك حاجة لباقي الأجزاء في الشيفرة المرافقة التي نريد تنفيذها. تمثّل الشيفرة 18 الشيفرة المسؤولة عن تنظيم إعدادات القيم، إذ لا تسمح متطلبات العمل للمستخدم الكتابة فوق تعديل لإعداد موجود سابقًا ولكن يمكن إزالة ضبط الإعدادات واعطائه قيمة إذا كان غير مضبوط بعد. let mut setting_value = Some(5); let new_setting_value = Some(10); match (setting_value, new_setting_value) { (Some(_), Some(_)) => { println!("Can't overwrite an existing customized value"); } _ => { setting_value = new_setting_value; } } println!("setting is {:?}", setting_value); [الشيفرة 18: استخدام شرطة سفلية داخل الأنماط التي تطابق متغايرات Some عندما لا توجد حاجة لاستخدام القيمة داخل Some] تطبع الشيفرة ما يلي: Can't overwrite an existing customized value وبعدها: setting is Some(5)‎ لا نحتاج لمطابقة أو استخدام القيمة في كِلا متغايري Some في ذراع المطابقة الأولى، لكننا بحاجة لاختبار الحالة عندما يكون متغايرا Some هما setting_value و new_setting_value، وفي تلك الحالة نطبع سبب عدم تغيير setting_value ولا تتغيّر؛ ومن أجل باقي الحالات (إذا كانت setting_value أو new_setting_value هي None) مُعبرة بالنمط _ في الذراع الثانية، سنسمح للقيمة new_setting_value بأن تصبح setting_value. يمكننا استخدام الشرطة السفلية في أماكن متعددة داخل نمط واحد لتجاهل قيمة معينة، وتبين الشيفرة 19 مثالًا عن تجاهل القيمتين الثانية والرابعة في صف مكون من خمس قيم. let numbers = (2, 4, 8, 16, 32); match numbers { (first, _, third, _, fifth) => { println!("Some numbers: {first}, {third}, {fifth}") } } [الشيفرة 19: تجاهل قيم متعددة في صف] تطبع الشيفرة Some numbers: 2, 8, 32 متجاهلةً القيمتين 4 و16. تجاهل المتغيرات غير المستخدمة بكتابة _ بداية اسمها تنبّهك رست إذا أنشأت متغيرًا ولم تستخدمه في أي مكان، إذ يمكن أن يكون عدم استخدام متغير خطأ برمجي، ولكن من المفيد إنشاء متغير لا تريد استخدامه حاليًا مثل عندما نريد كتابة نموذج أولي prototype أو عند بداية مشروع جديد، ففي هذه الحالات يمكن إخبار رست بعدم التنبيه عن المتغيرات غير المستخدمة بكتابة شرطة سفلية "_" قبل اسم المتغير. أنشأنا في الشيفرة 20 متغيرين غير مستخدمين ولكن عندما نصرّف الشيفرة هذه يجب أن نحصل على تنبيه بخصوص واحد منهما فقط. اسم الملف: src/main.rs fn main() { let _x = 5; let y = 10; } [الشيفرة 20: كتابة اسم المتغير مسبوقًا بشرطة سفلية لتجنب الحصول على تنبيه متغير غير مُستخدم] نحصل على تنبيه عن عدم استخدام المتغير y ولكن لن نحصل على تنبيه لعدم استخدام المتغير ‎_x. لاحظ أن هناك اختلاف بسيط بين استخدام _ فقط أو استخدام اسم مسبوقًا بشرطة سفلية، إذ تُسنِد الصيغة ‎_x القيمة بالمتغير ولكن لا تُسند الصيغة _ أي قيمة إطلاقًا، ولتوضيح أهمية الفرق إليك الشيفرة 21 التي تعطينا الخطأ التالي. let s = Some(String::from("Hello!")); if let Some(_s) = s { println!("found a string"); } println!("{:?}", s); [الشيفرة 21: يُسند متغير غير مستخدم يبدأ بشرطة سفلية إلى قيمة، مما قد يمنحه ملكية القيمة] سنحصل على خطأ لأن قيمة s ستنتقل إلى s_ التي تمنع استخدام s مجددًا، بينما لا يُسند استخدام الشرطة السفلية لوحدها القيمة أبدًا. تُصرّف الشيفرة 22 التالية دون أي أخطاء لأن s لا تنتقل إلى _. let s = Some(String::from("Hello!")); if let Some(_) = s { println!("found a string"); } println!("{:?}", s); [ الشيفرة 22: استخدام الشرطة السفلية لا يُسند القيمة] تعمل الشيفرة بصورةٍ صحيحة لأن s لا تُسند لأي قيمة ولا يتغير مكانها. تجاهل الأجزاء المتبقية من القيمة باستخدام .. يمكننا استخدام الصيغة .. لاستخدام أجزاء معينة من القيمة وتجاهل الباقي وذلك مع القيم التي تحتوي على أجزاء متعددة، ودون الحاجة لاستخدام الشرطة السفلية لكل قيمة مُتَجَاهلة؛ إذ يتجاهل النمط .. أي جزء لم يُطابق من القيمة صراحةً في باقي النمط. لدينا في الشيفرة 23 هيكل Point يحتوي إحداثيات في الفضاء ثلاثي الأبعاد، ونريد في تعبير match أن نعمل فقط على إحداثيات x وتتجاهل القيم الموجودة في الحقلين y و z. struct Point { x: i32, y: i32, z: i32, } let origin = Point { x: 0, y: 0, z: 0 }; match origin { Point { x, .. } => println!("x is {}", x), } [الشيفرة 23: تجاهل كل الحقول في Point عدا الحقل x باستخدام ..] نضع القيمة x في قائمة وبعدها نضيف النمط ..، وتُعد هذه الطريقة أسرع من كتابة كل من y: _‎ و z: _‎ وتحديدًا عند العمل مع هياكل تحتوي على العديد من الحقول وتريد الحصول على حقل واحد أو اثنين. يمكن زيادة الصيغة .. إلى عدد كبير من القيم حسب الحاجة، وتظهر الشيفرة 24 كيفية استخدام .. مع صف. اسم الملف: src/main.rs fn main() { let numbers = (2, 4, 8, 16, 32); match numbers { (first, .., last) => { println!("Some numbers: {first}, {last}"); } } } [الشيفرة 24: مطابقة القيمتين الأولى والأخيرة في الصف وتجاهل باقي القيم] تتطابق القيمتين الأولى والأخيرة مع first و last في هذه الشيفرة، وتطابق .. القيمتين وتتجاهل كل شيء في المنتصف. يجب استخدام .. بوضوح، إذ تعطي رست رسالة خطأ إذا كانت القيمة المُراد مطابقتها والقيم المُتجاهلة غير واضحة. تبين الشيفرة 25 مثالًا لاستخدام .. غير واضح ونتيجة لذلك فإن الشيفرة لا تُصرَّف. اسم الملف: src/main.rs fn main() { let numbers = (2, 4, 8, 16, 32); match numbers { (.., second, ..) => { println!("Some numbers: {}", second) }, } } [الشيفرة 25: محاولة استخدام .. بطريقة غير واضحة] عندما نصرّف الشيفرة في هذا المثال نحصل على الخطأ التالي: $ cargo run Compiling patterns v0.1.0 (file:///projects/patterns) error: `..` can only be used once per tuple pattern --> src/main.rs:5:22 | 5 | (.., second, ..) => { | -- ^^ can only be used once per tuple pattern | | | previously used here error: could not compile `patterns` due to previous error من المستحيل أن تحدد رست عدد القيم التي تتجاهلها في الصف قبل مطابقة قيمة مع second وكم قيمة ستتجاهل بعدها، قد تعني هذه الشيفرة أننا نريد تجاهل 2 وإسناد second إلى 4 وبعدها تجاهل 8 و 16 و 32 أو قد تعني أيضًا تجاهل 2 و 4 وإسناد second إلى 8 وبعدها تجاهل 16 و 32 وهكذا. لا يعني اسم المتغير second أي شيء مميز لرست لذا نحصل على خطأ تصريفي لأن استخدام .. في مكانين يجعل النمط غير واضح. تعابير شرطية إضافية مع دروع المطابقة درع المطابقة هو شرط if إضافي يُحدّد بعد النمط في ذراع match ويجب أن يطابق الذراع حتى يجري اختياره، وتفيد دروع المقابلة للتعبير عن أفكار معقّدة لا يمكننا التعبير عنها بالنمط لوحده. يمكن أن يستخدم الشرط متغيرات مُنشأة في النمط، وتبين الشيفرة 26 تعليمة match تحتوي الذراع الأولى فيها على النمط Some(x)‎ وأيضًا درع مطابقة if x % 2 == 0 (الذي يكون صحيحًا إذا كان العدد زوجي). let num = Some(4); match num { Some(x) if x % 2 == 0 => println!("The number {} is even", x), Some(x) => println!("The number {} is odd", x), None => (), } [الشيفرة 26: إضافة درع مطابقة إلى نمط] ستطبع الشيفرة السابقة The number 4 is even، وتتطابق عندما تُقارن num مع النمط الموجود في الذراع الأولى، لأن Some(4)‎ تطابق Some(x)‎. بعد ذلك، يتحقق درع المطابقة إذا كان الباقي من عملية قسمة x على 2 يساوي 0 ولأن هذه الحالة محقّقة يقع الاختيار على الذراع الأولى. سيكون درع المطابقة في الذراع الأولى خاطئًا إذا كان num هو Some(5)‎، وذلك لأن باقي قسمة 5 على 2 هو 1 وهو لا يساوي 0، وتنتقل بعدها رست إلى الذراع الثانية التي ليس فيها درع مطابقة وبالتالي تطابق أي متغير Some. لا توجد طريقة للتعبير عن شرط if x % 2 == 0 داخل النمط، لذا يسمح لنا درع المطابقة بالتعبير عن هذا المنطق. سلبية هذا التعبير الإضافي هي أن المصرّف لا يتحقق من الشمولية عند تواجد تعابير درع مطابقة. ذكرنا في الشيفرة 11 أنه يمكننا استخدام دروع المطابقة لحل مشكلة إخفاء النمط pattern-shadowing. تذكر أننا أنشأنا متغيرًا جديدًا داخل النمط في التعبير match عوضًا عن استخدام المتغير خارج match، ويعني إنشاء هذا المتغير الجديد أنه لا يمكن اختبار القيم مع المتغير الخارجي. تبين الشيفرة 27 كيفية استخدام درع المطابقة لحل هذه المشكلة. اسم الملف: src/main.rs fn main() { let x = Some(5); let y = 10; match x { Some(50) => println!("Got 50"), Some(n) if n == y => println!("Matched, n = {n}"), _ => println!("Default case, x = {:?}", x), } println!("at the end: x = {:?}, y = {y}", x); } [الشيفرة 27: استخدام درع المطابقة لاختبار المساواة مع متغير خارجي] ستطبع الشيفرة الآن: Default case, x = Some(5)‎ لا يقدم النمط في الذراع الثاني متغير y جديد يخفي المتغير y الخارجي، وهذا يعني أنه يمكن استخدام y الخارجية في درع المطابقة، إذ نحدّد Some(n)‎ عوضًا عن تحديد النمط Some(y)‎ الذي كان سيخفي بدوره المتغير y الخارجي، وسينشئ ذلك متغيرًا جديدًا يدعى n لا يُخفي أي شيء لعدم وجود أي متغير بالاسم n خارج match. ليس درع المطابقة if n == y نمطًا وبالتالي لا يقدم أي متغيرات جديدة، إذ أن y هذه هي y الخارجية ذاتها وليست y مخفية جديدة، ويمكن البحث عن قيمة لديها نفس قيمة y الخارجية بمقارنة n مع y. يمكن استخدام المعامل "أو" | في درع المطابقة لتحديد الأنماط المتعددة، وسيُطبق شرط درع المطابقة على كل الأنماط. تبين الشيفرة 28 الأسبقية عند جمع نمط يستخدم | مع درع مطابقة، والقسم الأهم من هذا المثال هو درع المطابقة if y الذي يطبق على 4 و 5 و 6 على الرغم من أن if y تبدو أنها مطبقةٌ فقط على 6. let x = 4; let y = false; match x { 4 | 5 | 6 if y => println!("yes"), _ => println!("no"), } [ الشيفرة 28: جمع عدة أنماط مع درع مطابقة] ينصّ شرط المطابقة على أن الذراع تتطابق فقط إذا كانت قيمة x تساوي 4 أو 5 أو 6 وإذا كانت قيمة y هي true، وعندما تُنفذ الشيفرة يُطَابَق نمط الذراع الأول لأن x هي 4 ولكن درع المطابقة if y خاطئ، لذا لا يقع الاختيار على الذراع الأولى وتنتقل الشيفرة إلى الذراع الثانية التي تُطابَق ويطبع البرنامج no، والسبب وراء ذلك هو تطبيق شرط if لكل النمط 6 | 5 | 4، وليس فقط للقيمة الأخيرة 6، بمعنى أخر تتصرف أسبقية درع المطابقة مع النمط على النحو التالي: (4 | 5 | 6) if y => ... عوضًا عن: 4 | 5 | (6 if y) => ... سلوك الأسبقية واضح بعد تنفيذ الشيفرة، إذ ستُطابق الذراع وسيطبع البرنامج yes إذا كان درع المطابقة مطبقًا فقط على القيمة الأخيرة في قائمة القيم المحددة بالمعامل |. ارتباطات @ يسمح لنا معامل at @ بإنشاء متغيرات تحتوي قيمة واختبارها من أجل مطابقة نمط بنفس الوقت. نريد في الشيفرة 29 اختبار حقل id في Message::Hello إذا كان ضمن المجال 3‎..=7، ونريد أيضًا ربط القيمة إلى المتغير id_variable لكي نستخدمها في الشيفرة المرتبطة مع الذراع. يمكن تسمية هذا المتغير باسم الحقل id، لكننا استخدمنا اسمًا مختلفًا. enum Message { Hello { id: i32 }, } let msg = Message::Hello { id: 5 }; match msg { Message::Hello { id: id_variable @ 3..=7, } => println!("Found an id in range: {}", id_variable), Message::Hello { id: 10..=12 } => { println!("Found an id in another range") } Message::Hello { id } => println!("Found some other id: {}", id), } [الشيفرة 29: استخدام @ لربط القيمة في النمط مع اختبارها أيضًا] سيطبع المثال السابق ما يلي: Found an id in range: 5 نلتقط أي قيمة تطابق المجال ‎3..=7 بتحديد id_variable @‎ قبله، إضافةً إلى اختبار نمط تلك القيمة المطابقة. لا تحتوي الشيفرة في الذراع متغيرًا يحتوي على قيمة حقيقية في حقل id في الذراع الثانية، إذ لدينا فقط مجالًا محددًا في النمط. يمكن أن تكون قيمة الحقل id مساوية إلى 10 أو 11 أو 12 لكن لا تعرف الشيفرة التي في ذلك النمط أي قيمة هي منهم، ولا يستطيع نمط الشيفرة استخدام القيمة من حقل id لأننا لم نحفظ قيمة id في المتغير. ليس لدينا قيمةً متوفرةً لاستخدامها في شيفرة الذراع الأخيرة في المتغير المسمى id، إذ حددنا متغيرًا دون مجال، ويعود سبب ذلك إلى استخدام صيغة حقل الهيكل المختزلة، وعدم تطبيق أي اختبار على القيمة في حقل id` في هذا الذراع كما فعلنا في الذراعين الأوليين، فأي قيمة ستطابق هذا النمط. يسمح لنا استخدام @ باختبار القيمة وحفظها في متغير داخل نمط واحد. ترجمة -وبتصرف- لقسم من الفصل Patterns and Matching من كتاب The Rust Programming Language. اقرأ أيضًا المقال السابق: الأنماط Patterns واستخداماتها وقابليتها للدحض Refutability في لغة رست توثيق أنماط التصميم أنماط التصميم البرمجي Design patterns أنماط التصميم وتقنيات إعادة التصميم في Cpp
  2. تظهر الأنماط في العديد من الأماكن في رست، وقد استخدمتها سابقًا دون ملاحظتها غالبًا. سنتحدّث في هذه المقالة عن جميع الحالات التي يكون فيها استخدام الأنماط صالحًا، إضافةً إلى الحالات التي تكون فيها قابلة للدحض Refutable. أذرع تعبير match كما تحدثنا سابقًا في الفصل التعدادات enums في لغة رست، يمكننا استخدام الأنماط في أذرع arms تعبير match، ويُعرَّف التعبير match بالكلمة المفتاحية match، تليها قيمة للتطابق معها وواحد أو أكثر من أذرع المطابقة التي تتألف من نمط وتعبير يُنفذ إذا طابقت القيمة نمط الذراع على النحو التالي: match VALUE { PATTERN => EXPRESSION, PATTERN => EXPRESSION, PATTERN => EXPRESSION, } على سبيل المثال إليك تعبير match من الشيفرة 5 (من الفصل المذكور آنفًا) الذي يطابق القيمة Option<i32>‎ في المتغير x: match x { None => None, Some(i) => Some(i + 1), } الأنماط في تعبير match السابق، هما: None و Some(i)‎ على يسار كل سهم. أحد مُتطلبات تعبير match هو ضرورة كونه شاملًا لجميع الحالات، أي ينبغي أخذ جميع القيم المُحتملة بالحسبان في تعبير match. أحد الطرق لضمان تغطية شاملة الاحتمالات هي وجود نمط مطابقة مع الكل Catchall pattern للذراع الأخير، فلا يمكن مثلًا فشل تطابق اسم متغير لأي قيمة إطلاقًا وبذلك يغطي كل حالة متبقية. يطابق النمط المحدد _ أي قيمة، لكن لا يرتبط بمتغير، لذلك يُستخدم غالبًا في ذراع المطابقة الأخير. يمكن أن يكون النمط _ مفيدًا عندما نريد تجاهل أي قيمة غير محددة مثلًا، وسنتحدث لاحقًا بتفصيل أكثر عن النمط _ في قسم "تجاهل القيم في النمط". تعابير if let الشرطية تحدثنا في الفصل التعدادات enums في لغة رست عن كيفية استخدام تعابير if let بصورةٍ أساسية مثل طريقة مختصرة لكتابة مكافئ التعبير match، بحيث يطابق حالةً واحدةً فقط، ويمكن أن يكون التعبير if let مترافقًا مع else اختياريًا، بحيث يحتوي على شيفرة برمجية تُنفّذ في حال لم يتطابق النمط في if let. تبيّن الشيفرة 1 أنه من الممكن مزج ومطابقة تعابير if let و else if و else if let، لإعطاء مرونة أكثر من استخدام التعبير match الذي يقارن قيمةً واحدة مع الأنماط. إضافةً إلى ذلك، لا تتطلب رست أن تكون الأذرع في سلسلة if let أو else if أو else if let متعلقة ببعضها بعضًا. تحدد الشيفرة 1 لون الخلفية اعتمادّا على تحقّق عدد من الشروط، إذ أنشأنا في مثالنا هذا متغيرات مع قيم مضمّنة في الشيفرة بحيث يمكن لبرنامج حقيقي استقبالها مثل مدخلات من المستخدم. اسم الملف: src/main.rs fn main() { let favorite_color: Option<&str> = None; let is_tuesday = false; let age: Result<u8, _> = "34".parse(); if let Some(color) = favorite_color { println!("Using your favorite color, {color}, as the background"); } else if is_tuesday { println!("Tuesday is green day!"); } else if let Ok(age) = age { if age > 30 { println!("Using purple as the background color"); } else { println!("Using orange as the background color"); } } else { println!("Using blue as the background color"); } } [الشيفرة 1: استخدام if let و else if و else if let و else بنفس الوقت] يُستخدم اللون المفضّل للمستخدم لونًا للخلفية إذا حدده المستخدم، وفي حال لم يُحدد لونه المفضل وكان اليوم خميس فسيكون لون الخلفية أخضر؛ وإذا حدّد المستخدم عمره في سلسلة نصية string، يمكننا تحليلها إلى رقم بنجاح، ويكون اللون إما برتقاليًا أو بنفسجيًا اعتمادًا على قيمة هذا الرقم؛ وأخيرًا إذا لم تنطبق أي من هذه الشروط سيكون لون الخلفية أزرق. يسمح لنا هذا الهيكل الشرطي بدعم المتطلبات المعقدة، إذ نطبع في هذا المثال باستخدام القيم المضمّنة في الشيفرة ما يلي: Using purple as the background color يمكنك ملاحظة أن التعبير if let تسبب بحصولنا على متغير مخفي shadowed variable بالطريقة ذاتها التي تسببت بها أذرع التعبير match؛ إذ يُنشئ السطر if let Ok(age) = age متغيرًا مخفيًا جديد يدعى age يحتوي على القيمة داخل المتغاير Ok، وهذا يعني أنه يجب إضافة الشرط if age > 30 داخل الكتلة؛ إذ لا يمكننا جمع الشَرطين في if let ok(age) = age && age > 30، والقيمة الخفية age التي نريد مقارنتها مع 30 غير صالحة حتى يبدأ النطاق scope الجديد بالقوس المعقوص curly bracket. الجانب السلبي في استخدامنا لتعبير if let هو أن المصرّف لا يتحقق من شمولية الحالات مثلما يفعل تعبير match. إذا تخلّينا عن كتلة else الأخيرة وبالتالي فوّتنا معالجة بعض الحالات، لن ينبهنا المصرّف على احتمالية وجود خطأ منطقي. حلقات while let الشرطية تسمح الحلقة الشرطية while let بتنفيذ حلقة while طالما لا يزال النمط مُطابقًا على نحوٍ مشابه لبُنية if let. كتبنا في الشيفرة 2 حلقة while let تستخدم شعاعًا مثل مكدّس stack وتطبع القيم في الشعاع بالترتيب العكسي لإدخالها. fn main() { let mut stack = Vec::new(); stack.push(1); stack.push(2); stack.push(3); while let Some(top) = stack.pop() { println!("{}", top); } } [الشيفرة 2: استخدام حلقة while let لطباعة القيم طالما يُعيد استدعاء stack.pop()‎ المتغاير Some] يطبع المثال السابق القيمة 3 ثم 2 ثم 1، إذ يأخذ التابع pop آخر عنصر في الشعاع vector ويُعيد Some(value)‎، ويعيد القيمة None إذا كان الشعاع فارغًا. يستمر تنفيذ الحلقة while والشيفرة البرمجية داخل كتلتها طالما يُعيد استدعاء pop القيمة Some، وعندما يعيد استدعاء pop القيمة None تتوقف الحلقة، ويمكننا استخدام while let لإزالة pop off كل عنصر خارج المكدّس. حلقات for التكرارية القيمة التي تتبع الكلمة المفتاحية for في حلقة for هي النمط، فعلى سبيل المثال النمط في for x in y هو x. توضح الشيفرة 3 كيفية استخدام النمط في حلقة for لتفكيك destructure أو تجزئة الصف tuple إلى جزء من حلقة for. fn main() { let v = vec!['a', 'b', 'c']; for (index, value) in v.iter().enumerate() { println!("{} is at index {}", value, index); } } [الشيفرة 3: استخدام نمط في حلقة for لتفكيك صف] تطبع الشيفرة 3 ما يلي: $ cargo run Compiling patterns v0.1.0 (file:///projects/patterns) Finished dev [unoptimized + debuginfo] target(s) in 0.52s Running `target/debug/patterns` a is at index 0 b is at index 1 c is at index 2 نُعدل مكرّرًا iterator باستخدام التابع enumerate بحيث يعطينا قيمةً ودليلًا index لهذه القيمة ضمن صف tuple، بحيث تكون القيمة الأولى هي الصف ('‎0, 'a). عندما تطابق هذه القيمة النمط (index, value) ستكون index هي 0 وستكون value هي a، مما سيتسبب بطباعة السطر الأول من الخرج. تعليمات let تحدثنا سابقًا عن استخدام الأنماط مع match و if let فقط، إلا أننا استخدمنا الأنماط في أماكن أُخرى أيضًا مثل تعليمة let. ألقِ نظرةً على عملية إسناد المتغير التالية باستخدام let: #![allow(unused)] fn main() { let x = 5; } اِستخدمت الأنماط في كلّ مرة استخدمت فيها تعليمة let بالشكل السابق دون معرفتك لذلك. تكون تعليمة let بالشكل التالي: let PATTERN = EXPRESSION; يشكّل اسم المتغير في التعليمات المشابهة لتعليمة let x = 5;‎ -مع اسم المتغير في فتحة PATTERN- نوعًا بسيطًا من الأنماط. تقارن رست التعابير مع الأنماط وتُسند أي اسم تجده، أي تتألف التعليمة let x = 5‎;‎ من نمط هو x يعني "اربط ما يتطابق هنا مع المتغير x"، ولأن الاسم x يمثّل كامل النمط، فإن هذا النمط يعني "اربط كل شيء مع المتغير x مهما تكُن القيمة". لملاحظة مطابقة النمط في let بوضوح أكبر، جرّب الشيفرة 4 التي تستخدم نمطًا مع let لتفكيك الصف. let (x, y, z) = (1, 2, 3); [الشيفرة 4: استخدام نمط لتفكيك الصف وإنشاء ثلاثة متغيرات بخطوة واحدة] طابقنا الصف مع النمط هنا، إذ تُقارن رست القيمة (3, 2, 1) مع النمط (x, y, z) وترى أن القيمة تطابق النمط فعلًا، لذلك تربط رست 1 إلى xو 2 إلى y و 3 إلى z. يمكن عدّ نمط الصف هذا بمثابة تضمين ثلاثة أنماط متغيرات مفردة داخله. إذا كان عدد العناصر في النمط لا يطابق عدد العناصر في الصف، لن يُطابَق النوع بأكمله وسنحصل على خطأ تصريفي. تبيّن الشيفرة 5 على سبيل المثال محاولة تفكيك صف بثلاثة عناصر إلى متغيرين وهي محاولة فاشلة. let (x, y) = (1, 2, 3); [الشيفرة 5: بناء خاطئ لنمط لا تطابق متغيراته عدد العناصر الموجودة في الصف] ستتسبب محاولة تصريف الشيفرة السابقة بالخطأ التالي: $ cargo run Compiling patterns v0.1.0 (file:///projects/patterns) error[E0308]: mismatched types --> src/main.rs:2:9 | 2 | let (x, y) = (1, 2, 3); | ^^^^^^ --------- this expression has type `({integer}, {integer}, {integer})` | | | expected a tuple with 3 elements, found one with 2 elements | = note: expected tuple `({integer}, {integer}, {integer})` found tuple `(_, _)` For more information about this error, try `rustc --explain E0308`. error: could not compile `patterns` due to previous error يمكننا تجاهل قيمة أو أكثر في الصف لتصحيح الخطأ وذلك باستخدام _ أو .. كما سنرى لاحقًا في قسم "تجاهل القيم في النمط". إذا كانت المشكلة هي وجود عدة متغيرات في النمط فإن الحل يكمن بجعل الأنواع تتطابق عن طريق إزالة المتغيرات حتى يتساوى عدد المتغيرات وعدد العناصر في الصف. معاملات الدالة Function Parameters يمكن لمعاملات الدالة أن تمثّل أنماطًا. ينبغي أن تكون الشيفرة 6 مألوفةً بالنسبة لك، إذ نصرّح فيها عن دالة اسمها foo تقبل معاملًا واحدًا اسمه x من النوع i32. fn foo(x: i32) { // تُكتب الشيفرة البرمجية هنا } [الشيفرة 6: بصمة دالة function signature تستخدم الأنماط في معاملاتها] يشكّل الجزء x نمطًا. يمكننا مطابقة الصف في وسيط الدالة مع النمط كما فعلنا مع let. تجزِّء الشيفرة 7 القيم الموجودة في الصف عندما نمررها إلى الدالة. اسم الملف: src/main.rs fn print_coordinates(&(x, y): &(i32, i32)) { println!("Current location: ({}, {})", x, y); } fn main() { let point = (3, 5); print_coordinates(&point); } [الشيفرة 7: دالة تفكك الصف مع معاملاتها] تطبع الشيفرة السابقة: Current location: (3, 5)‎، إذ تطابق القيم ‎&(3, 5)‎ النمط ‎&(x, y)‎، لذا فإن قيمة x هي 3 وقيمة y هي 5. يمكننا أيضًا استخدام الأنماط في قوائم معاملات التغليف closure parameter lists بطريقة قوائم معاملات الدالة function parameter lists ذاتها، لأن المغلفات مشابهة للدالات كما رأينا سابقًا في الفصل المغلفات closures في لغة رست. رأينا بحلول هذه النقطة عدة طرق لاستخدام الأنماط، إلا أن الأنماط لا تعمل بالطريقة ذاتها في كل مكان تُستخدم فيه، إذ يجب أن تكون الأنماط غير قابلة للدحض في بعض الأماكن وفي بعضها الآخر كذلك، وسنتحدث عن هذين المفهومين تاليًا. قابلية الدحض refutability واحتمالية فشل مطابقة النمط تأتي الأنماط بشكلين: قابلة للدحض أو النقض refutable وغير قابلة للدحض irrefutable، إذ تُدعى الأنماط التي تطابق أي قيمة تمرر خلالها بالأنماط القابلة للدحض، ومثال على ذلك هو x في التعليمة let x = 5;‎ وذلك لأن المتغير x سيطابق أي شيء وبالتالي لا تفشل المطابقة؛ بينما تُدعى الأنماط التي تفشل في بعض القيم بالأنماط القابلة للدحض، ومثال على ذلك هو Some(x)‎ في التعبير if let Some(x) = a_value لأن النمط Some(x)‎ لن يُطابق إذا كانت القيمة في المتغير a_value هي None عوضًا عن Some. تقبل معاملات الدالة وتعليمات let وحلقات for فقط الأنماط غير القابلة للدحض لأن البرنامج لا يستطيع عمل أي شيء مفيد عندما لا تتطابق القيم. يقبل التعبيران if let و while let الأنماط القابلة للدحض وغير القابلة للدحض، إلا أنّ المصرّف يحذّر من استخدام الأنماط غير القابلة للدحض، لأنها -بحسب تعريفها- ليست معدّة لتتعامل مع فشل محتمل، إذ تتمثّل الوظيفة الشرطية بقدرتها على التصرف بصورةٍ مختلفة اعتمادًا على النجاح أو الفشل. عمومًا، لا يهم كثيرّا التمييز بين الأنماط القابلة للدحض وغير القابلة للدحض، ولكن يجب أن يكون مفهوم قابلية الدحض مألوفًا، وذلك لحل الأخطاء التي قد تحصل، إذ يجب في تلك الحالات تغيير إما النمط أو البنية construct المستخدمة مع النمط حسب السلوك المُراد من الشيفرة. لنتابع مثالّا لما قد يحصل عندما نجرب استخدام نمط قابل للدحض عندما تتطلب رست نمطّا غير قابل للدحض -والعكس صحيح- إذ تبيّن الشيفرة 8 تعليمة let إلا أن النمط الذي حددناه هو Some(x)‎ وهو نمط قابل للدحض، ولن تُصرَّف الشيفرة كما هو متوقع. let Some(x) = some_option_value; [الشيفرة 8: محاولة استخدام نمط قابل للدحض مع let] ستفشل مطابقة النمط في Some(x)‎ إذا كانت القيمة في some_option_value هي None أي أن النمط هو قابل للدحض، ولكن تقبل تعليمة let فقط الأنماط غير القابلة للدحض لأنه لا توجد قيمة صالحة تستطيع الشيفرة استخدامها مع قيمة None. تنبّهنا رست عند استخدام قيمة قابلة للدحض عندما يتطلب الأمر وجود قيمة غير قابلة للدحض وقت التصريف: $ cargo run Compiling patterns v0.1.0 (file:///projects/patterns) error[E0005]: refutable pattern in local binding: `None` not covered --> src/main.rs:3:9 | 3 | let Some(x) = some_option_value; | ^^^^^^^ pattern `None` not covered | = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant = note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html note: `Option<i32>` defined here --> /rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/option.rs:518:1 | = note: /rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/option.rs:522:5: not covered = note: the matched value is of type `Option<i32>` help: you might want to use `if let` to ignore the variant that isn't matched | 3 | let x = if let Some(x) = some_option_value { x } else { todo!() }; | ++++++++++ ++++++++++++++++++++++ help: alternatively, you might want to use let else to handle the variant that isn't matched | 3 | let Some(x) = some_option_value else { todo!() }; | ++++++++++++++++ For more information about this error, try `rustc --explain E0005`. error: could not compile `patterns` due to previous error تُعطينا رست الخطأ التصريفي السابق، وذلك بسبب عدم تغطيتنا لكل القيم الممكنة مع النمط Some(x)‎، ولن نستطيع فعل ذلك حتى لو أردنا. يمكننا إصلاح المشكلة في حال وجود نمط قابل للدحض يحل مكان نمط غير قابل للدحض عن طريق تغيير الشيفرة التي تستخدم النمط؛ فبدلًا من استخدام let نستخدم if let، وهكذا إذا لم يُطابق النمط تتخطى الشيفرة تلك الشيفرة الموجودة في القوسين المعقوصين وتستمر بذلك صلاحية الشيفرة. تبيّن الشيفرة 9 كيفية إصلاح الخطأ في الشيفرة 8. if let Some(x) = some_option_value { println!("{}", x); } [الشيفرة 9: استخدام if let وكتلة تحتوي على أنماط قابلة للدحض بدلًا من let] سُمح للشيفرة السابقة بالتصريف، فهذه الشيفرة صالحة، على الرغم من أنه لا يمكن استخدام نمط غير قابل للدحض دون رسالة خطأ. إذا أعطينا if let نمطًا يطابق دومًا مثل x كما في الشيفرة 10، سيمنحنا المصرّف تنبيهًا. fn main() { if let x = 5 { println!("{}", x); }; } [الشيفرة 10: محاولة استخدام نمط غير قابل للدحض مع if let] تشتكي رست من عدم منطقيّة استخدام if let مع نمط غير قابل للدحض: $ cargo run Compiling patterns v0.1.0 (file:///projects/patterns) warning: irrefutable `if let` pattern --> src/main.rs:2:8 | 2 | if let x = 5 { | ^^^^^^^^^ | = note: this pattern will always match, so the `if let` is useless = help: consider replacing the `if let` with a `let` = note: `#[warn(irrefutable_let_patterns)]` on by default warning: `patterns` (bin "patterns") generated 1 warning Finished dev [unoptimized + debuginfo] target(s) in 0.39s Running `target/debug/patterns` 5 يجب أن تستخدم مطابقة الأذرع match arms الأنماط القابلة للدحض لهذا السبب ما عدا الذراع الأخير، الذي يجب أن يطابق أي قيمة متبقية من النمط غير القابل للدحض. تسمح رست باستخدام نمط غير قابل للدحض في match باستخدام ذراع واحد فقط، ولكن الصياغة هذه ليست مفيدة ويمكن استبدالها بتعليمة let أبسط. بعدما عرفنا أماكن استخدام الأنماط والفرق بين الأنماط القابلة للدحض وغير القابلة للدحض، دعنا نكمل طريقة الصياغة syntax التي يمكن استخدامها لإنشاء الأنماط. ترجمة -وبتصرف- لقسم من الفصل Patterns and Matching من كتاب The Rust Programming Language. اقرأ أيضًا المقال السابق: تنفيذ نمط تصميمي Design Pattern كائني التوجه Object-Oriented في لغة رست أنماط التصميم البرمجي Design patterns أنماط التصميم وتقنيات إعادة التصميم في Cpp توثيق أنماط التصميم
  3. نمط الحالة state pattern هو نمط تصميم كائني التوجه Object-Oriented، والمغزى منه هو أننا نعرّف مجموعةً من الحالات التي يمكن للقيمة أن تمتلكها داخليًا، وتُمثَّل الحالات من خلال مجموعة من كائنات الحالة state objects، ويتغير سلوك القيمة بناءً على حالتها. سنعمل من خلال مثال لهيكل منشور مدونة يحتوي على حقل للاحتفاظ بحالته، والتي ستكون كائن حالة من مجموعة القيم "مسودة draft" أو " قيد المراجعة review" أو "منشور published". تتشارك كائنات الحالة بالوظيفة، ونستخدم الهياكل structs والسمات traits بدلًا من الكائنات objects والوراثة inheritance في لغة رست. كل كائن حالة مسؤول عن سلوكه الخاص وعن تحديد متى يجب عليه أن يتغير من حالة إلى أخرى. لا تعرف القيمة التي تخزّن كائن الحالة شيئًا عن السلوك المختلف للحالات أو متى تنتقل بينها. ميزة استخدام نمط الحالة هي أنه لن نحتاج إلى تغيير شيفرة القيمة البرمجية التي تحتفظ بالحالة أو الشيفرة البرمجية التي تستخدم القيمة عندما تتغير متطلبات العمل للبرنامج، إذ سنحتاج فقط إلى تعديل الشيفرة البرمجية داخل أحد كائنات الحالة لتغيير قواعدها أو ربما إضافة المزيد من كائنات الحالة. سننّفذ بدايةً نمط الحالة بطريقة تقليديّة كائنية التوجه، ثم سنستعمل نهجًا أكثر شيوعًا في رست. لننفّذ تدريجيًا طريقةً لتنظيم سير عمل منشور المدونة باستعمال نمط الحالة. ستكون النتيجة النهائية كما يلي: يبدأ منشور المدونة مثل مسودة فارغة. يُطلب مراجعة المنشور عند الانتهاء من المسودة. يُنشر المنشور عندما يُوافَق عليه. منشورات المدونة التي هي بحالة "منشور published" هي المنشورات الوحيدة التي تعيد محتوًى ليُطبع، بحيث لا يمكن نشر المنشورات غير الموافق عليها عن طريق الخطأ. يجب ألا يكون لأي تعديلات أخرى أُجريت على إحدى المنشورات أي تأثير، فعلى سبيل المثال إذا حاولنا الموافقة على مسودة منشور مدونة قبل أن نطلب المراجعة، فيجب أن يبقى المنشور مسودةً غير منشورة. تظهر الشيفرة 11 سير العمل هذا على هيئة شيفرة برمجية، وهذا مثال عن استعمال الواجهة البرمجية API التي سننفّذها في وحدة مكتبة مصرّفة library crate تسمى blog. لن تُصرَّف الشيفرة البرمجية التالية بنجاح، لأننا لم ننفّذ الوحدة المصرفة blog بعد. اسم الملف: src/main.rs use blog::Post; fn main() { let mut post = Post::new(); post.add_text("I ate a salad for lunch today"); assert_eq!("", post.content()); post.request_review(); assert_eq!("", post.content()); post.approve(); assert_eq!("I ate a salad for lunch today", post.content()); } [الشيفرة 11: الشيفرة التي تُظهر السلوك المرغوب الذي نريد لوحدتنا المصرفة blog أن تحتويه] نريد السماح للمستخدم بإنشاء مسودة منشور مدونة جديد باستعمال Post::new، كما نريد السماح بإضافة نص إلى منشور المدونة، إذ يجب ألّا نحصل على أي نص إذا حاولنا الحصول على محتوى المنشور فورًا قبل الموافقة وذلك لأن المنشور لا يزال في وضع المسودة. أضفنا assert_eq!‎ في الشيفرة البرمجية لأغراض توضيحية. قد يكون اختبار الوحدة unit test المناسب لهذا هو التأكيد على أن مسودة منشور مدونة تعرض سلسلةً نصيةً string فارغة من تابع content لكننا لن نكتب أي اختبارات لهذا المثال حاليًا. نريد بعد ذلك تمكين طلب مراجعة المنشور ونريد أن يُعيد content سلسلةً نصيةً فارغة أثناء انتظار المراجعة، ويُنشر المنشور عندما يتلقى الموافقة، مما يعني أنه سيُعاد نص المنشور عندما نستدعي content. لاحظ أن النوع الوحيد الذي نتفاعل معه من الوحدة المصرّفة هو النوع Post، إذ سيستعمل هذا النوع نمط الحالة وسيحتوي على قيمة واحدة من ثلاث قيم هي كائنات حالة تمثل الحالات المختلفة التي يمكن أن يكون المنشور فيها، ألا وهي مسودة، أو انتظار المراجعة، أو النشر. يجري التحكم بالتغيير من حالة إلى أخرى داخليًا ضمن نوع Post، إذ تتغير الحالات استجابةً للتوابع التي يستدعيها مستخدمو مكتبتنا في نسخة Post، لكن لا يتعين عليهم إدارة تغييرات الحالة مباشرةً، كما لا يمكن للمستخدمين ارتكاب خطأ في الحالات مثل نشر منشور قبل مراجعته. تعريف المنشور وإنشاء نسخة جديدة في حالة المسودة لنبدأ بتنفيذ المكتبة؛ نعلم أننا بحاجة إلى هيكل عام public struct يدعى Post ليخزّن محتوى المنشور، لذا سنبدأ بتعريف الهيكل ودالة عامة مرتبطة associated تدعى new لإنشاء نسخة من Post كما هو موضح في الشيفرة 12. سننشئ أيضًا سمةً خاصة تدعى State تعرّف السلوك الذي يجب أن تتمتع به جميع كائنات حالة Post. سيخزّن هيكل Post بعد ذلك كائن السمة <Box<dyn State داخل <Option<T في حقل خاص يسمى state ليخزّن كائن الحالة، وسترى سبب ضرورة <Option<T بعد قليل. اسم الملف: src/lib.rs pub struct Post { state: Option<Box<dyn State>>, content: String, } impl Post { pub fn new() -> Post { Post { state: Some(Box::new(Draft {})), content: String::new(), } } } trait State {} struct Draft {} impl State for Draft {} [الشيفرة 12: تعريف هيكل Post ودالة new التي تنشئ نسخة من Post وسمة State وهيكل Draft جدد] تعرّف السمة State السلوك المشترك بين حالات النشر المختلفة. كائنات الحالة هي Draft و PendingReview و Published وسوف تنفِّذ جميعها سمة State. لا تحتوي السمة في الوقت الحالي على أي توابع وسنبدأ بتعريف حالة Draft فقط لأن هذه هي الحالة التي نريد أن يبدأ المنشور فيها. عندما ننشئ Post جديد نعيّن حقل state الخاص به بقيمة Some التي تحتوي على Box، وتشير Box هنا إلى نسخة جديدة للهيكل Draft، أي أنه عندما ننشئ نسخةً جديدة من Post فإنها ستبدأ مثل مسودة. نظرًا لأن حقل state للهيكل Postهو خاص ولا توجد طريقةٌ لإنشاء Post في أي حالة أخرى. عيّننا سلسلة نصية String فارغة جديدة للحقل content في الدالة Post::new. تخزين نص محتوى المنشور كنا في الشيفرة 11 قادرين على استدعاء تابع يسمى add_text وتمريرstr& إليه ليُضاف لاحقًا على أنه محتوى نص منشور المدونة. ننفّذ هذا مثل تابع بدلًا من عرض حقل content على أنه pub حتى نتمكن لاحقًا من تنفيذ تابع يتحكم بكيفية قراءة بيانات حقل content. تابع add_text واضح جدًا، لذا سنضيف التنفيذ في الشيفرة 13 إلى كتلة impl Post. اسم الملف: src/lib.rs impl Post { // --snip-- pub fn add_text(&mut self, text: &str) { self.content.push_str(text); } } [الشيفرة 13: تنفيذ تابع add_text لإضافة نص لمنشور content] يأخذ تابع add_text مرجعًا متغيّرًا mutable إلى self لأننا نعدّل نسخة Post التي نستدعي add_text عليها، ثم نستدعي push_str على String في content ونمرّر الوسيط text لإضافتها إلى content المحفوظ. لا يعتمد هذا السلوك على حالة المنشور لذا فهو ليس جزءًا من نمط الحالة. لا يتفاعل تابع add_text مع حقل state إطلاقًا، لكنه جزءٌ من السلوك الذي نريد دعمه. ضمان أن محتوى مسودة المنشور فارغ ما زلنا بحاجة تابع content حتى بعد استدعائنا add_text وإضافة بعض المحتوى إلى منشورنا وذلك لإعادة شريحة سلسلة نصية string slice فارغة لأن المنشور لا يزال في حالة المسودة كما هو موضح في السطر 7 من الشيفرة 11. لننفّذ حاليًا تابع content بأبسط شيء يستوفي هذا المتطلب؛ وهو إعادة شريحة سلسلة نصية فارغة دائمًا، إلا أننا سنغير هذا لاحقًا بمجرد تقديم القدرة على تغيير حالة المنشور حتى يمكن نشره. حتى الآن يمكن أن تكون المنشورات في حالة المسودة فقط لذلك يجب أن يكون محتوى المنشور فارغًا دائمًا. تُظهر الشيفرة 14 تنفيذ الموضع المؤقت هذا. اسم الملف: src/lib.rs impl Post { // --snip-- pub fn content(&self) -> &str { "" } } [الشيفرة 14: إضافة تنفيذ موضع مؤقت للتابع content على Post يُعيد دائمًا شريحة سلسلة فارغة] يعمل الآن كل شيء كما خُطّط له مع إضافة تابع content في الشيفرة 11 حتى السطر 7. طلب مراجعة للمنشور يغير حالته نحتاج بعد ذلك إلى إضافة إمكانية طلب مراجعة منشور وتغيير حالته من Draft إلى PendingReview، وتوضّح الشيفرة 15 هذا الأمر. اسم الملف: src/lib.rs impl Post { // --snip-- pub fn request_review(&mut self) { if let Some(s) = self.state.take() { self.state = Some(s.request_review()) } } } trait State { fn request_review(self: Box<Self>) -> Box<dyn State>; } struct Draft {} impl State for Draft { fn request_review(self: Box<Self>) -> Box<dyn State> { Box::new(PendingReview {}) } } struct PendingReview {} impl State for PendingReview { fn request_review(self: Box<Self>) -> Box<dyn State> { self } } [الشيفرة 15: تنفيذ توابع request_review على Post وسمة State] نعطي Post تابعًا عام يدعى request_review، والذي سيأخذ مرجعًا متغيّرًا يشير إلى self، نستدعي بعد ذلك التابع request_review الداخلي على الحالة Post الحالية، إذ يستخدم request_review الثاني الحالة الحالية ويعيد حالةً جديدة. نضيف التابع request_review إلى السمة State؛ إذ ستحتاج جميع الأنواع التي تطبق السمة الآن إلى تنفيذ تابع request_review. لاحظ أن لدينا <self: Box<Self بدلًا من self، أو self&، أو mut self& بمثابة معامل أول للتابع. تعني هذه الصيغة أن التابع صالحٌ فقط عندما يُستدعى على Box يحتوي النوع. تأخذ هذه الطريقة بالصياغة ملكية <Box<Self مما يبطل الحالة القديمة بحيث يمكن أن تتحول قيمة حالة Post إلى حالة جديدة. يجب أن يأخذ تابع request_review ملكية قيمة الحالة القديمة لاستخدامها، وهذه هي فائدة استخدام Option في حقل state الخاص بالمنشور Post؛ إذ نستدعي تابع take لأخذ قيمة Some من حقل state وترك None في مكانها لأن رست لا تسمح لنا بامتلاك حقول غير مأهولة في الهياكل. يتيح لنا ذلك نقل قيمة state خارج Post بدلًا من استعارتها. ثم نعيّن قيمة state للمنشور بنتيجة هذه العملية. نحتاج إلى جعل state مساوية للقيمة None مؤقتًا بدلًا من تعيينها مباشرةً باستعمال شيفرة برمجية مثل: self.state = self.state.request_review(); للحصول على ملكية قيمة state، ويضمن لنا ذلك أن Post لا يمكنه استخدام قيمة state القديمة بعد أن حوّلناها إلى حالة جديدة. يُعيد التابع request_review الموجود في Draft نسخةً جديدةً موضوعة في صندوق لهيكل PendingReview جديد يمثل الحالة التي يكون فيها المنشور في انتظار المراجعة. يطبّق هيكل PendingReview أيضًا تابع request_review ولكنه لا يجري أي تحويلات، ويُعيد بدلًا من ذلك قيمًا لنفسه لأنه يجب أن يبقى المنشور على حالته إذا كانت PendingReview بعد طلبنا لمراجعته. يمكننا الآن البدء في رؤية مزايا نمط الحالة، فتابع request_review على Post هو نفسه بغض النظر عن قيمة state، فكل حالة مسؤولة عن قواعدها الخاصة. سنترك تابع content على Post كما هو ونعيد شريحة سلسلة نصية string slice فارغة. يمكننا الآن الحصول على Post في حالة PendingReview وكذلك في حالة Draft إلا أننا نريد السلوك ذاته في حالة PendingReview. تعمل الشيفرة 11 الآن بنجاح وصولًا للسطر 10. إضافة approve لتغيير سلوك التابع content سيكون تابع approve مشابهًا لتابع request_review، إذ سيضبط قيمة state على القيمة التي تقول الحالة الحالية أنها يجب أن تكون عليها عند الموافقة على تلك الحالة كما هو موضح في الشيفرة 16. اسم الملف: src/lib.rs impl Post { // --snip-- pub fn approve(&mut self) { if let Some(s) = self.state.take() { self.state = Some(s.approve()) } } } trait State { fn request_review(self: Box<Self>) -> Box<dyn State>; fn approve(self: Box<Self>) -> Box<dyn State>; } struct Draft {} impl State for Draft { // --snip-- fn approve(self: Box<Self>) -> Box<dyn State> { self } } struct PendingReview {} impl State for PendingReview { // --snip-- fn approve(self: Box<Self>) -> Box<dyn State> { Box::new(Published {}) } } struct Published {} impl State for Published { fn request_review(self: Box<Self>) -> Box<dyn State> { self } fn approve(self: Box<Self>) -> Box<dyn State> { self } } [الشيفرة 16: تنفيذ تابع approve على Post وسمة State] نضيف تابع approve إلى سمة State ونضيف هيكلًا جديدًا ينفّذ السمة State، والحالة Published. لن يكون للتابع approve على Draft عند استدعائه أي تأثير على غرار الطريقة التي يعمل بها request_review في PendingReview، وذلك لأن التابع approve سيعيد self، بينما يعيد التابع approve عندما نستدعيه على PendingReview نسخةً جديدةً ضمن صندوق boxed لهيكل Published. ينفّذ هيكل Publishedالسمة State وتعيد نفسها من أجل كل من تابع request_review وتابع approve لأن المنشور يجب أن يبقى في حالة Published في تلك الحالات. نحتاج الآن إلى تحديث تابع content على Post، إذ نريد أن تعتمد القيمة المُعادة من content على حالة Post الحالية، لذلك نعرّف Post مفوض للتابع content المعرّف على قيمة الحقل state الخاص به كما هو موضح في الشيفرة 17. اسم الملف: src/lib.rs impl Post { // --snip-- pub fn content(&self) -> &str { self.state.as_ref().unwrap().content(self) } // --snip-- } [الشيفرة 17: تحديث تابع content على Post للتفويض لتابع content على State] نظرًا لأن الهدف هو الاحتفاظ بكل هذه القواعد داخل الهياكل التي تنفّذ السمة State فإننا نستدعي تابع content على قيمة state ونمرر نسخة المنشور (في هذه الحالة self) مثل وسيط، ثم نعيد القيمة التي أُعيدَت من استعمال تابع content إلى قيمة state. نستدعي تابع as_ref على Option لأننا نريد مرجعًا للقيمة داخل Option بدلًا من الحصول على ملكية القيمة. بما أن state هو <<Option<Box<dyn State، تكون القيمة المُعادة عند استدعاء as_ref هي <<Option<&Box<dyn State. نحصل على خطأ إذا لم نستدعي as_ref لأننا لا نستطيع نقل state من self& المستعارة إلى معامل الدالة. نستدعي بعد ذلك التابع unwrap الذي نعلم أنه لن يهلع أبدًا لأننا نعلم أن التوابع الموجودة على Post تضمن أن state سيحتوي دائمًا على القيمة Some عند الانتهاء من هذه التوابع، وهذه إحدى الحالات التي تحدثنا عنها سابقًا في قسم "الحالات التي تعرف فيها معلومات أكثر من المصرف" من الفصل الاختيار ما بين الماكرو panic!‎ والنوع Result للتعامل مع الأخطاء في لغة Rust، وهي عندما نعلم أن قيمة None غير ممكنة أبدًا على الرغم من أن المصرف غير قادر على فهم ذلك. سيكون تأثير التحصيل القسري deref coercion في هذه المرحلة ساريًا على كل من & و Box عندما نستدعي content على <Box<dyn State&، لذلك يُستدعى تابع content في النهاية على النوع الذي ينفذ سمة State. هذا يعني أننا بحاجة إلى إضافة content إلى تعريف سمة State وهنا سنضع منطق المحتوى الذي سيُعاد اعتمادًا على الحالة التي لدينا كما هو موضح في الشيفرة 18. اسم الملف: src/lib.rs trait State { // --snip-- fn content<'a>(&self, post: &'a Post) -> &'a str { "" } } // --snip-- struct Published {} impl State for Published { // --snip-- fn content<'a>(&self, post: &'a Post) -> &'a str { &post.content } } [الشيفرة 18: إضافة تابع content إلى سمة State] نضيف تنفيذًا مبدئيًا للتابع content الذي يُعيد شريحة سلسلة نصية فارغة، ويعني هذا أننا لسنا بحاجة إلى تنفيذ content في هيكلي Draft و PendingReview، إذ سيُعيد هيكل Published تعريف التابع content ويعيد القيمة في post.content. لاحظ أننا نحتاج إلى توصيف لدورة الحياة lifetime على هذا التابع كما ناقشنا سابقًا في الفصل مقدمة إلى مفهوم الأنواع المعممة Generic Types في لغة Rust، إذ نأخذ هنا مرجعًا إلى post مثل وسيط ونعيد مرجعًا إلى جزء من post وبالتالي ترتبط دورة حياة المرجع المُعاد بدورة حياة وسيط post. تعمل الشيفرة 11 الآن كاملةً بعد أن طبّقنا نمط الحالة state pattern مع قواعد سير عمل منشور المدونة. المنطق المتعلق بالقواعد موجود في كائنات الحالة بدلًا من بعثرته في جميع أنحاء Post. لماذا لم نستخدم تعدادًا؟ لربما كنت تتساءل عن سبب عدم استخدامنا enum مع حالات المنشورات المختلفة الممكنة مثل متغايرات variants؛ هذا بالتأكيد حل ممكن، جرّبه وقارن النتائج النهائية لترى أيهما تفضل. أحد عيوب استعمال التعداد هو أن كل مكان يتحقق من قيمة التعداد سيحتاج إلى تعبير match أو ما شابه للتعامل مع كل متغاير ممكن، ويمكن أن يتطلب هذا الحل شيفرةً برمجيةً مكررة أكثر مقارنةً مع حل كائن السمة هذا. سلبيات استخدام نمط الحالة وضّحنا أن رست قادرة على تنفيذ نمط الحالة كائنية التوجه لتغليف أنواع مختلفة من السلوك التي يجب أن يتمتع بها المنشور في كل حالة. لا تعرف التوابع في Post شيئًا عن السلوكيات المختلفة. تسمح لنا الطريقة التي نظّمنا بها الشيفرة البرمجية -تنفيذ سمة State على الهيكل Published- أن ننظر في مكان واحد فقط لمعرفة الطرق المختلفة التي يمكن أن يتصرف بها المنشور المقبول للنشر. إذا أردنا إنشاء تنفيذ بديل لا يستخدم نمط الحالة فقد نستخدم بدلًا من ذلك تعبيرات match في التوابع على Post أو حتى في الشيفرة main التي تتحقق من حالة المنشور وتغيِّر السلوك في تلك الأماكن، هذا يعني أنه يتعين علينا البحث في عدة أماكن لفهم جميع الآثار المترتبة على المنشور في الحالة المنشورة، وسيؤدي هذا إلى زيادة عدد الحالات التي أضفناها، إذ سيحتاج كل تعبير من تعبيرات match هذه إلى ذراع أخرى. لا تحتاج توابع Post أو الأماكن التي نستخدم فيها Post إلى تعبيرات match مع نمط الحالة، وسنحتاج من أجل إضافة حالة جديدة إلى إضافة هيكل جديد فقط وتنفيذ توابع السمة على هذا الهيكل الواحد. من السهل توسيع التنفيذ باستعمال نمط الحالة لإضافة المزيد من الوظائف. لمعرفة بساطة الحفاظ على الشيفرة البرمجية التي تستخدم نمط الحالة، جرّب بعضًا من هذه الاقتراحات: أضف تابع reject الذي يغيّر شكل حالة المنشور من PendingReview إلى Draft. استدعِ approve مرّتين قبل أن تتغير الحالة إلى Published. اسمح للمستخدمين بإضافة محتوى النص فقط عندما يكون المنشور في حالة Draft. تلميح: اجعل كائن الحالة مسؤولًا عما قد يتغير بشأن المحتوى ولكن ليس مسؤولًا عن تعديل Post. يتمثل أحد الجوانب السلبية لنمط الحالة في أنه نظرًا لأن الحالات تنفّذ التحول بين الحالات، تكون بعض الحالات مقترنة ببعضها، وإذا أضفنا حالةً أخرى بين PendingReview و Published مثل Scheduled، سيتعين علينا تغيير الشيفرة البرمجية في PendingReview للانتقال إلى Scheduled بدلًا من ذلك. سيقلّ العمل المطلوب إذا لم تكن PendingReview بحاجة إلى التغيير مع إضافة حالة جديدة ولكن هذا يعني التبديل إلى نمط تصميم آخر. السلبية الأخرى هو أننا كرّرنا بعض المنطق، ويمكن إزالة بعض حالات التكرار من خلال إجراء عمليات تنفيذ مبدئية لتوابع request_review و approve على سمة State التي تعيد self، ومع ذلك فإن هذا من شأنه أن ينتهك سلامة الكائن لأن السمة لا تعرف فعلًا ما ستكون عليه self الحقيقية. نريد أن نكون قادرين على استعمال State مثل كائن سمة لذلك نحتاج إلى أن تكون توابعها آمنة من الكائنات. تتضمن التكرارات الأخرى عمليات تنفيذ مماثلة لتوابع request_review و approve على Post، يفوّض كلا التابعين تنفيذ التابع ذاته على القيمة في حقل state للقيمة Option وتعيين القيمة الجديدة لحقل state إلى النتيجة. إذا كان لدينا الكثير من التوابع في Post التي اتبعت هذا النمط، فقد نفكر في تعريف ماكرو لإزالة التكرار. لا نستفيد استفادة كاملة من نقاط قوة رست بقدر الإمكان عبر تنفيذ نمط الحالة تمامًا كما هو معرّف في اللغات البرمجية كائنية التوجه الأخرى. دعنا نلقي نظرةً على بعض التغييرات الممكن إجراؤها على الوحدة المصرفة blog، والتي من شأنها أن تجعل الحالات غير الصالحة والانتقالات transitions أخطاءً تظهر وقت التصريف. ترميز الحالات والسلوك مثل أنواع سنوضّح كيفية إعادة التفكير بنمط الحالة للحصول على مجموعة مختلفة من المقايضات، وذلك بدلًا من تغليف الحالات والانتقالات بحيث لا يكون لدى الشيفرة البرمجية الخارجية أي معرفة بها. نرمّز الحالات إلى أنواع مختلفة، وبالتالي سيمنع نظام فحص النوع في رست محاولات استخدام مسودات المنشورات، بحيث لا يُسمح إلا بالمنشورات المنشورة وذلك عن طريق إصدار خطأ في المصرّف. لننظر إلى الجزء الأول من دالة main في الشيفرة 11. اسم الملف: src/main.rs fn main() { let mut post = Post::new(); post.add_text("I ate a salad for lunch today"); assert_eq!("", post.content()); } ما زلنا نسمح بإنشاء منشورات جديدة في حالة المسودة باستخدام Post::new والقدرة على إضافة نص إلى محتوى المنشور، ولكن بدلًا من وجود تابع content في مسودة المنشور التي تعيد سلسلةً نصيةً فارغة، سنعمل على تعديلها بحيث لا تحتوي مسودة المنشورات على تابع content إطلاقًا؛ وستحصل بهذه الطريقة على خطأ في المصرف إذا حاولنا الحصول على محتوى مسودة منشور يخبرنا أن التابع غير موجود، ونتيجةً لذلك سيكون من المستحيل بالنسبة لنا عرض محتوى مسودة المنشور عن طريق الخطأ في مرحلة الإنتاج production لأن هذه الشيفرة البرمجية لن تصرف. تُظهر الشيفرة 19 تعريف هيكلي Post و DraftPost إضافةً إلى التوابع الخاصة بكل منهما. اسم الملف: src/lib.rs pub struct Post { content: String, } pub struct DraftPost { content: String, } impl Post { pub fn new() -> DraftPost { DraftPost { content: String::new(), } } pub fn content(&self) -> &str { &self.content } } impl DraftPost { pub fn add_text(&mut self, text: &str) { self.content.push_str(text); } } [الشيفرة 19: Post مع تابع content و DraftPost بدون تابع content] يحتوي كل من هيكلي Post و DraftPost على حقل content خاص يحتوي على النص الخاص بمنشور المدونة. لم يعد للهياكل حقل state لأننا ننقل ترميز الحالة إلى أنواع الهياكل، وسيمثل هيكل Post منشورًا قد نُشر وله تابع content يُعيد content. لا تزال لدينا دالة Post::new ولكن بدلًا من إعادة نسخة من Post، ستُعيد نسخةً من DraftPost، وذلك نظرًا لأن content خاص ولا وجود لأي دوال تُعيد Post، وبالتالي لا يمكن إنشاء نسخة عن Post حاليًا. يحتوي هيكل DraftPost على تابع add_text لذا يمكننا إضافة نص إلى content كما كان من قبل، لكن لاحظ أن DraftPost لا يحتوي على تابع content معرّف لذا يضمن البرنامج الآن بدء جميع المنشورات مثل مسودات منشورات وعدم إتاحة محتوى مسودات المنشورات للعرض. ستؤدي أي محاولة للتحايل على هذه القيود إلى حدوث خطأ في المصرّف. تنفيذ الانتقالات مثل تحولات إلى أنواع مختلفة كيف نحصل على منشور قد نُشر؟ نريد فرض القاعدة التي تنص على وجوب مراجعة مسودة المنشور والموافقة عليها قبل نشرها. ينبغي عدم عرض أي منشور في حالة "قيد المراجعة" أي محتوى. لنطبّق هذه القيود عن طريق إضافة هيكل آخر باسم PendingReviewPost وتعريف التابع request_review في DraftPost لإعادة PendingReviewPost وتعريف تابع approve على PendingReviewPost لإعادة Post كما هو موضح في الشيفرة 20. اسم الملف: src/lib.rs impl DraftPost { // --snip-- pub fn request_review(self) -> PendingReviewPost { PendingReviewPost { content: self.content, } } } pub struct PendingReviewPost { content: String, } impl PendingReviewPost { pub fn approve(self) -> Post { Post { content: self.content, } } } [الشيفرة 20: هيكل PendingReviewPost مُنشأ عن طريق استدعاء request_review على DraftPost وتابع approve الذي يرجع PendingReviewPost إلى Post منشور] يأخذ التابعان request_review و approve ملكية self وبالتالي تستخدم نُسَخ DraftPost و PendingReviewPost وتحوّلهما إلى PendingReviewPost و Post منشور published على التوالي، وبهذه الطريقة لن يكون لدينا أي نسخ متبقية من DraftPost بعد أن استدعينا request_review عليها وما إلى ذلك. لا يحتوي هيكل PendingReviewPost على تابع content معرف عليه لذلك تؤدي محاولة قراءة محتواها إلى حدوث خطأ في المصرّف كما هو الحال مع DraftPost، لأن الطريقة الوحيدة للحصول على نسخة Post قد جرى نشره وله تابع content معرّف هي استدعاء تابع approve على PendingReviewPost والطريقة الوحيدة للحصول على PendingReviewPost هي استدعاء تابع request_review على DraftPost، إذ رمّزنا الآن سير عمل منشور المدونة إلى نظام النوع. يتعين علينا أيضًا إجراء بعض التغييرات الصغيرة على main، إذ يُعيد التابعان request_review و approve حاليًا نسخًا جديدة بدلًا من تعديل الهيكل الذي استدعيت عليهما، لذلك نحتاج إلى إضافة المزيد من الإسنادات الخفية let post =‎ لحفظ الأمثلة المُعادة. لا يمكن أيضًا أن تكون لدينا تأكيدات بسلاسل نصية فارغة حول محتويات المسودة ومنشورات بانتظار المراجعة، فنحن لسنا بحاجتها. لا يمكننا تصريف الشيفرة البرمجية التي تحاول استعمال محتوى المنشورات في تلك الحالات بعد الآن. تظهر الشيفرة البرمجية الجديدة ضمن الدالة main في الشيفرة 21. اسم الملف: src/main.rs use blog::Post; fn main() { let mut post = Post::new(); post.add_text("I ate a salad for lunch today"); let post = post.request_review(); let post = post.approve(); assert_eq!("I ate a salad for lunch today", post.content()); } [الشيفرة 21: تعديل main لاستعمال التنفيذ الجديد لسير عمل منشور مدونة] تعني التغييرات التي احتجنا لإجرائها على main من أجل إعادة تعيين post أن هذا التنفيذ لم يعد يتبع نمط الحالة كائنية التوجه بعد الآن، إذ لم تعد كامل التحوّلات بين الحالات مغلّفة في تنفيذ Post، ومع ذلك فإن النقطة التي بصالحنا هنا هي أن الحالات غير الصالحة أصبحت الآن مستحيلة بسبب نظام النوع والتحقق من النوع الذي يحدث في وقت التصريف، إذ يضمن ذلك اكتشاف أخطاء معينة مثل عرض محتوى منشور لم يُنشر قبل الوصول لمرحلة الإنتاج. جرّب المهام المقترحة في بداية المقالة على وحدة blog المصرفة كما هي بعد الشيفرة 21 لمعرفة ما هو رأيك في تصميم هذا الإصدار من الشيفرة البرمجية. لاحظ أن بعض المهام قد تكون مكتملة فعلًا في هذا التصميم. رأينا أنه على الرغم من أن رست قادرة على تنفيذ أنماط تصميم كائنية التوجه، إلا أن أنماطًا أخرى مثل ترميز الحالة في نظام النوع متاحة أيضًا في رست. هذه الأنماط لها مقايضات مختلفة. يمكن أن تكون على دراية كبيرة بالأنماط كائنية التوجه لكن يمكن أن توفّر إعادة التفكير في المشكلة للاستفادة من ميزات رست عدّة فوائد مثل منع بعض الأخطاء في وقت التصريف. لن تكون الأنماط كائنية التوجه هي الحل الأفضل دائمًا في رست نظرًا لوجود ميزات معينة مثل الملكية التي لا تمتلكها اللغات كائنية التوجه. ترجمة -وبتصرف- لقسم من الفصل Object-Oriented Programming Features of Rust من كتاب The Rust Programming Language. اقرأ أيضًا المقال السابق: استخدام كائنات السمة Object Trait في لغة رست البرمجة كائنية التوجه OOP في لغة رست أنماط التصميم البرمجي Design patterns سلسلة أنماط التصميم
×
×
  • أضف...