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

أنماط عدم القابلية للتغيير

جزء من قسم Object Oriented Programming في رحلة C# على Coddy — الدرس 34 من 70.

تصل خاصية عدم القابلية للتغيير (Immutability) بمبدأ التغليف (encapsulation) إلى أقصى حدوده المنطقية؛ فبمجرد إنشاء الكائن، لا يمكن تغييره أبداً. وهذا يقضي على فئات كاملة من الأخطاء البرمجية لأنك لن تضطر أبداً للقلق بشأن تعديل بياناتك بشكل غير متوقع.

أبسط طريقة لإنشاء فئة غير قابلة للتغيير (immutable class) هي استخدام حقول readonly مع منشئ (constructor) يضبط كافة القيم، ولا يوفر أي دوال تعيين (setters):

public class Point
{
    public readonly int X;
    public readonly int Y;
    
    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }
}

بمجرد إنشاء Point، لا يمكن تغيير إحداثياته. ولكن ماذا لو كنت بحاجة إلى نسخة معدلة؟ بدلاً من تغيير الكائن الحالي، تقوم بإرجاع كائن جديد:

public Point Move(int deltaX, int deltaY)
{
    return new Point(X + deltaX, Y + deltaY);
}

تبقى النقطة الأصلية دون تغيير بينما تحصل على نقطة جديدة بقيم محدثة. يُستخدم هذا النمط في جميع أنحاء .NET - فالسلاسل النصية (strings)، على سبيل المثال، غير قابلة للتغيير. فعندما تستدعي ToUpper()، تحصل على سلسلة نصية جديدة بدلاً من تعديل السلسلة الأصلية.

تعتبر الكائنات غير القابلة للتغيير (Immutable objects) آمنة بطبيعتها بالنسبة للخيوط (thread-safe) نظراً لعدم إمكانية تعديلها بواسطة أي كود بعد إنشائها. كما أنها أسهل في الفهم والتحليل لأنك تعلم أن حالتها لن تتغير أبداً بشكل غير متوقع.

challenge icon

التحدي

سهل

لنقم ببناء نظام مالي غير قابل للتغيير (immutable) يوضح كيف يمكن تصميم الكائنات بحيث لا تتغير أبداً بعد إنشائها. ستقوم بإنشاء فئة Money حيث تعيد كل عملية كائناً جديداً بدلاً من تعديل الكائن الأصلي - وهو نمط يجعل كودك أكثر أماناً وأسهل في التتبع.

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

  • Money.cs: أنشئ فئة Money غير قابلة للتغيير داخل مساحة الأسماء Finance. يجب أن تحتوي فئة المال الخاصة بك على:
    • حقلين readonly: Amount (decimal) و Currency (string)
    • منشئ (constructor) يضبط كلتا القيمتين - بمجرد إنشائه، لا يمكن لكائن Money أن يتغير أبداً
    • طريقة Add(decimal value) تعيد كائن Money جديد بالمبلغ المتزايد (نفس العملة)
    • طريقة Subtract(decimal value) تعيد كائن Money جديد بالمبلغ المنخفض (نفس العملة)
    • طريقة Convert(string newCurrency, decimal rate) تعيد كائن Money جديد بالمبلغ مضروباً في السعر والعملة الجديدة
    • طريقة GetDisplay() تعيد سلسلة نصية بتنسيق "{Amount} {Currency}"
    تذكر: لا ينبغي لأي من هذه الطرق تعديل الكائن الأصلي - فجميعها تعيد نسخ Money جديدة تماماً!
  • Program.cs: في ملفك الرئيسي، استعرض خاصية عدم التغيير (immutability) بشكل عملي. أنشئ كائن Money أولياً، ثم قم بإجراء عمليات عليه مع الاحتفاظ بمراجع لكل من الكائن الأصلي والنتائج. سيثبت هذا أن الكائن الأصلي لا يتغير أبداً حتى بعد عمليات متعددة.

ستتلقى أربعة مدخلات:

  • المبلغ الأولي (decimal)
  • العملة الأولية (string)
  • المبلغ المراد إضافته (decimal)
  • معدل التحويل إلى EUR (decimal)

اطبع المخرجات بهذا التنسيق:

Original: {GetDisplay()}
After add: {GetDisplay()}
Original unchanged: {GetDisplay()}
Converted: {GetDisplay()}

على سبيل المثال، إذا كانت المدخلات هي 100.00، و USD، و 50.00، و 0.85، فيجب أن تكون المخرجات:

Original: 100.00 USD
After add: 150.00 USD
Original unchanged: 100.00 USD
Converted: 127.50 EUR

لاحظ كيف لا يزال كائن Money الأصلي يظهر 100.00 USD حتى بعد استدعاء Add() - هذه هي خاصية عدم التغيير! أنشأت الطريقة Add() كائناً جديداً تماماً بقيمة 150.00 USD، تاركة الكائن الأصلي دون تغيير. يتم إجراء التحويل على نتيجة عملية الإضافة.

جرّب بنفسك

using System;
using Finance;

class Program
{
    public static void Main(string[] args)
    {
        // قراءة المدخلات
        decimal initialAmount = Convert.ToDecimal(Console.ReadLine());
        string initialCurrency = Console.ReadLine();
        decimal amountToAdd = Convert.ToDecimal(Console.ReadLine());
        decimal conversionRate = Convert.ToDecimal(Console.ReadLine());

        // TODO: إنشاء كائن Money أولي بالمبلغ والعملة الأوليين

        // TODO: طباعة المبلغ الأصلي باستخدام GetDisplay()

        // TODO: استدعاء Add() على المبلغ الأصلي وتخزين النتيجة في متغير جديد

        // TODO: طباعة النتيجة بعد الإضافة باستخدام GetDisplay()

        // TODO: طباعة المبلغ الأصلي مرة أخرى لإثبات أنه لم يتغير

        // TODO: تحويل النتيجة المضافة إلى EUR باستخدام معدل التحويل conversion rate

        // TODO: طباعة المبلغ المحول باستخدام GetDisplay()
    }
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming