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: في ملفك الرئيسي، استعرض عدم القابلية للتغيير أثناء العمل. أنشئ كائن 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، تاركة الكائن الأصلي دون تغيير. يتم إجراء التحويل على نتيجة عملية الإضافة.

ورقة مرجعية

الثبات (Immutability) يعني أنه بمجرد إنشاء الكائن، لا يمكن تغييره أبداً. وهذا يزيل الأخطاء المتعلقة بتعديلات البيانات غير المتوقعة.

قم بإنشاء فئة (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;
    }
}

لتعديل كائن ثابت، قم بإرجاع مثيل جديد بدلاً من تغيير المثيل الحالي:

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

فوائد الكائنات الثابتة:

  • آمنة للخيوط (Thread-safe) بطبيعتها حيث لا يمكن لأي كود تعديلها بعد الإنشاء
  • أسهل في الفهم والتحليل لأن حالتها لا تتغير أبداً بشكل غير متوقع
  • تُستخدم في جميع أنحاء .NET (على سبيل المثال، السلاسل النصية strings ثابتة)

جرّب بنفسك

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 باستخدام سعر التحويل

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

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

جميع دروس Object Oriented Programming