أنماط عدم القابلية للتغيير
جزء من قسم 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) بطبيعتها، حيث لا يمكن لأي كود تعديلها بعد إنشائها. كما أنها أسهل في الفهم والتحليل لأنك تعلم أن حالتها لن تتغير أبداً بشكل غير متوقع.
التحدي
سهللنقم ببناء نظام مالي غير قابل للتغيير (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()
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات OOP
الملفات الخارجيةNamespaces والتوجيهاتمقدمة في Classes و Objectsالكلمة المفتاحية 'this'Methods والمعاملاتFields مقابل PropertiesConstructorsObject Initializersمراجعة - آلة حاسبة بسيطة4الوراثة
صيغة الوراثة الأساسية (:)الكلمة المفتاحية 'base'الكلمات المفتاحية Virtual و Overrideالأصناف المختومة (Sealed Classes)الفئة الأساسية 'object'مراجعة - هيكلية الموظفين7ميزات متقدمة
تحميل العوامل (Operator Overloading)المفهرسات (Indexers (this[]))إعادة تعريف ()ToStringدوال التوسعة (Extension Methods)ملخص - Custom List2الخصائص والأعضاء الساكنة (Static Members)
الخصائص المطبقة تلقائيًا (Auto-Implemented Properties)خصائص القراءة فقط والكتابة فقطالحقول والدوال الساكنة (Static)الفئات الساكنة (Static Classes)الأعضاء ذات البنية التعبيرية (Expression-Bodied Members)5تعدد الأشكال والواجهات (Polymorphism & Interfaces)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالواجهة مقابل الفئة المجردة (Abstract Class)الواجهات المتعددةالواجهات الصريحةUpcasting و Downcastingملخص - حاسبة الأشكال8مفاهيم البرمجة كائنية التوجه (OOP) المتقدمة
التركيب بدلاً من الوراثةالـ Generics (الأصناف والدوال)الـ Delegates والأحداثالـ Attributes والـ Reflectionواجهة IDisposable وجملة usingأساسيات Dependency Injection