أنماط عدم القابلية للتغيير
جزء من قسم 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: في ملفك الرئيسي، استعرض خاصية عدم التغيير (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()
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس 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