مشروع: نمذجة نطاق عمل
مشروع: نمذجة نطاق عمل
أفضل طريقة لترسيخ فهمك للـ enums والـ records والأنواع المغلقة هي استخدامها معًا على مشكلة حقيقية. في هذا الدرس نُنمذج نطاق الطلبات في تطبيق تجارة إلكترونية: للطلب دورة حياة محدّدة، وعناصره كائنات قيمة غير قابلة للتغيير، وكل نتيجة دفع تنتمي إلى مجموعة ثابتة من الحالات. في نهاية الدرس ستمتلك نطاقًا صغيرًا صحيحًا بطبيعة بنائه — الحالات غير الصالحة غير قابلة للتمثيل حرفيًا.
نظرة عامة على النطاق
- OrderStatus — enum يُحرّك دورة حياة الطلب (PENDING ← CONFIRMED ← SHIPPED ← DELIVERED، أو CANCELLED).
- OrderItem — record يمثّل سطرًا واحدًا في الطلب (اسم المنتج، الكمية، سعر الوحدة).
- Order — record يجمع قائمة من
OrderItemوحالةOrderStatus. - PaymentResult — واجهة مغلقة بثلاثة تطبيقات:
SuccessوDeclinedوError. - OrderProcessor — صنف يربط القطع معًا، مستخدمًا مطابقة الأنماط للتعامل مع
PaymentResult.
Success معرّف المعاملة، وتحمل Declined رمز الرفض، وتحمل Error رسالة الخطأ. لا يمكن الخلط بينها في وقت الترجمة.
الخطوة 1 — Enum دورة حياة الطلب
يجعل الـ enum دورة الحياة صريحة. نضيف أيضًا دالة مساعدة canTransitionTo لتوضع قاعدة العمل في مكان واحد بدلًا من تشتيتها في سلاسل if.
لأن الـ switch على enum شامل (يتحقق منه المُترجم)، فإن إضافة حالة جديدة لاحقًا ستُسبّب خطأ في الترجمة هنا — تمامًا حيث تنتمي منطق العمل.
الخطوة 2 — Record عنصر الطلب
سطر الطلب قيمة خالصة: نفس اسم المنتج ونفس الكمية ونفس السعر يعني نفس العنصر. يمنحنا الـ record تلك المساواة، وعدم القابلية للتغيير، وإعلانًا مختصرًا مجانًا.
BigDecimal للمبالغ المالية. لا يستطيع double أو float تمثيل معظم الكسور العشرية بدقة. حسابات الأسعار بالفاصلة العائمة ستنتج في النهاية نتائج كـ 9.999999999 بدلًا من 10.00 — استخدم BigDecimal في أي نطاق مالي.
الخطوة 3 — Record الطلب
يغلّف record الـ Order قائمة العناصر والحالة الحالية. يُشتق الإجمالي من عناصره ويوفّر دالة للتقدّم في دورة الحياة.
لاحظ أن withStatus تُعيد Order جديدة بدلًا من تعديل الموجودة. تُعزّز الـ records اللاتغييرية؛ كل تغيير في الحالة يُنتج قيمة جديدة.
الخطوة 4 — الواجهة المغلقة PaymentResult
نتائج الدفع مجموعة مغلقة. تتيح الواجهة المغلقة للمُترجم معرفة كل الحالات الممكنة، وكل حالة تحمل بيانات مختلفة.
PaymentResult.Success مما يُقرأ كجملة مفهومة: "نتيجة الدفع هي نجاح."
الخطوة 5 — معالج الطلبات
المعالج يُقيّد العميل وبناءً على نتيجة الدفع يُقدّم الطلب أو يُلغيه. تتولّى مطابقة الأنماط في switch كل متغيّر دون الحاجة إلى cast.
الخطوة 6 — تجميع القطع
ما الذي يجعل هذا التصميم جيدًا؟
- الحالات غير الصالحة غير قابلة للتمثيل. لا يمكن إنشاء
OrderItemبكمية سالبة؛ ترفضOrderقوائم العناصر الفارغة؛ وترفضcanTransitionToانتقالات الحالة غير المشروعة. - المُترجم يتحقق من الشمولية. إن أضفت متغيّرًا جديدًا لـ
PaymentResult، لن يُكملswitchفيOrderProcessorالترجمة حتى تُعالجه. - البيانات والسلوك معًا. تعيش
lineTotal()فيOrderItem؛ وتعيشtotal()فيOrder؛ وتعيش قواعد الانتقال فيOrderStatus. - اللاتغييرية في كل مكان. كل تغيير في الحالة يُنتج كائنًا جديدًا — لا أخطاء تعديل خفية.
Order هنا لأننا نتعامل مع الطلبات كلقطات غير قابلة للتغيير. إن احتجت كيانات JPA بحقول قابلة للتعديل، استخدم صنفًا عاديًا. امزج الاثنين: استخدم الـ records لكائنات القيمة (المال، العناوين، النتائج) والصنوف العادية للكيانات القابلة للتعديل.
الخلاصة
في هذا الدرس الختامي جمعت الميزات الثلاث — الـ enums والـ records والأنواع المغلقة — في نموذج نطاق متماسك. النتيجة معبّرة ومختصرة وصعبة الاستخدام الخاطئ: يُطبّق نظام الأنواع قواعد العمل بحيث لا تحتاج إلى كتابة فحوصات null دفاعية ومقارنات سلاسل الحالة في كل مكان من قاعدة الكود. هذا هو العائد من تعلّم هذه الميزات الحديثة في Java.