بيانات الـ Instance مقابل البيانات الـ Static
جزء من قسم Object Oriented Programming في رحلة C# على Coddy — الدرس 15 من 70.
الآن بعد أن فهمت الأعضاء الساكنة (static members)، دعنا نوضح متى نستخدم بيانات النسخة (instance data) مقابل البيانات الساكنة (static data). هذا القرار يشكل كيفية قيام الفئة (class) الخاصة بك بتخزين ومشاركة المعلومات.
بيانات النسخة تنتمي إلى كل كائن بشكل فردي. يحصل كل كائن على نسخته الخاصة، والتغييرات التي تطرأ على كائن واحد لا تؤثر على الكائنات الأخرى:
public class BankAccount
{
public string Owner { get; set; } // نسخة - فريدة لكل حساب
public decimal Balance { get; set; } // نسخة - فريدة لكل حساب
}كل BankAccount له صاحب ورصيد خاص به. هذا منطقي لأن الحسابات المختلفة تحتفظ بمبالغ مختلفة لأشخاص مختلفين.
تنتمي البيانات الساكنة إلى الفئة نفسها وتتم مشاركتها عبر جميع المثيلات:
public class BankAccount
{
public static decimal InterestRate = 0.05m; // ثابت - نفسه للجميع
public static int TotalAccounts = 0; // ثابت - يتتبع جميع الحسابات
public string Owner { get; set; }
public decimal Balance { get; set; }
}ينطبق سعر الفائدة بالتساوي على كل حساب، لذا فمن المنطقي أن يكون ثابتاً (static). يتتبع عداد إجمالي الحسابات معلومات على مستوى الفئة (class)، وليس بيانات الحساب الفردية.
قاعدة بسيطة: إذا كانت البيانات تصف object معيناً، فاجعلها instance. أما إذا كانت تصف الـ class ككل أو يجب أن تكون مشتركة، فاجعلها static. يؤدي الخلط بينهما بشكل غير صحيح إلى حدوث bugs حيث تتغير البيانات بشكل غير متوقع عبر الـ objects أو لا يمكن الوصول إليها عند الحاجة.
التحدي
سهللنقم ببناء متتبع لجلسة اللعبة يوضح الفرق بين البيانات التي تنتمي للاعبين الفرديين مقابل البيانات المشتركة عبر اللعبة بأكملها.
ستقوم بإنشاء ملفين لتنظيم الكود الخاص بك:
Player.cs: قم بتعريف فئةPlayerداخل مساحة الأسماءGameSession. لكل لاعبUsername(string) وScore(int) خاصين به - هذه البيانات فريدة لكل لاعب. يجب أن تتبع الفئة أيضاً المعلومات التي تنطبق على جميع اللاعبين: حقل ثابتGameName(string) يخزن اسم اللعبة التي يلعبها الجميع، وحقل ثابتTotalPlayers(int) يحسب عدد اللاعبين الذين انضموا. قم بتضمين منشئ (constructor) يأخذ اسم المستخدم والنتيجة الابتدائية، ويقوم تلقائياً بزيادة إجمالي عدد اللاعبين. أضف طريقة ثابتة (static method) باسمSetGameName(string name)لتهيئة اسم اللعبة.Program.cs: في ملفك الرئيسي، قم بإعداد اللعبة وإنشاء اللاعبين بناءً على المدخلات. قم أولاً بتعيين اسم اللعبة، ثم أنشئ لاعبين واعرض معلوماتهما الفردية جنباً إلى جنب مع بيانات اللعبة المشتركة.
ستتلقى خمسة مدخلات:
- اسم اللعبة
- اسم المستخدم للاعب الأول
- النتيجة الابتدائية للاعب الأول
- اسم المستخدم للاعب الثاني
- النتيجة الابتدائية للاعب الثاني
اطبع المخرجات بهذا التنسيق:
Game: {GameName}
Player: {Username}, Score: {Score}
Player: {Username}, Score: {Score}
Total Players: {TotalPlayers}على سبيل المثال، إذا كانت المدخلات هي BattleArena، و DragonSlayer، و 1500، و ShadowNinja، و 2200، فيجب أن تكون المخرجات:
Game: BattleArena
Player: DragonSlayer, Score: 1500
Player: ShadowNinja, Score: 2200
Total Players: 2فكر جيداً في البيانات التي يجب أن تكون قائمة على المثيل (instance-based) (مختلفة لكل لاعب) والبيانات التي يجب أن تكون ثابتة (static) (مشتركة بين جميع اللاعبين). يصف اسم اللعبة وإجمالي عدد اللاعبين الجلسة ككل، بينما يعد اسم المستخدم والنتيجة فريدين لكل فرد.
ورقة مرجعية
استخدم بيانات النسخة (instance data) للخصائص الفريدة لكل كائن:
public class BankAccount
{
public string Owner { get; set; } // كل حساب له مالكه الخاص
public decimal Balance { get; set; } // كل حساب له رصيده الخاص
}استخدم البيانات الثابتة (static data) للخصائص المشتركة عبر جميع نسخ الفئة (class):
public class BankAccount
{
public static decimal InterestRate = 0.05m; // نفس القيمة لجميع الحسابات
public static int TotalAccounts = 0; // يتتبع جميع الحسابات
public string Owner { get; set; }
public decimal Balance { get; set; }
}قاعدة القرار: إذا كانت البيانات تصف كائناً معيناً، فاستخدم بيانات النسخة. أما إذا كانت تصف الفئة ككل أو يجب مشاركتها، فاستخدم البيانات الثابتة (static data).
جرّب بنفسك
using System;
namespace GameSession
{
class Program
{
public static void Main(string[] args)
{
// قراءة المدخلات
string gameName = Console.ReadLine();
string username1 = Console.ReadLine();
int score1 = Convert.ToInt32(Console.ReadLine());
string username2 = Console.ReadLine();
int score2 = Convert.ToInt32(Console.ReadLine());
// TODO: تعيين اسم اللعبة باستخدام الـ static method
// TODO: إنشاء كائنين Player باستخدام بيانات المدخلات
// TODO: طباعة المخرجات بالتنسيق المطلوب
// Game: {GameName}
// Player: {Username}, Score: {Score}
// Player: {Username}, Score: {Score}
// Total Players: {TotalPlayers}
}
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس 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 Injection3بنية الكلاسات
بيانات الـ Instance مقابل البيانات الـ Staticالكلمات المفتاحية 'readonly' و 'const'الحقول الخلفية (Backing Fields)مراجعة - مدير الحساب البنكي