أساسيات البرمجة الكائنيّة

دوال الوصول والتعيين والتحقق من المدخلات

15 دقيقة الدرس 5 من 14

دوال الوصول والتعيين والتحقق من المدخلات

في الدرس السابق جعلت الحقول private باستخدام التغليف. وهذا يطرح سؤالاً واضحاً: إذا كانت الحقول خاصة، كيف يقرأها الكود الخارجي أو يغيّرها؟ الجواب هو دوال الوصول (getters) ودوال التعيين (setters). والأهم من ذلك أن دوال التعيين تمنحك مكاناً واحداً لتطبيق القواعد، بحيث لا يمكن لـ BankAccount أبداً أن يحمل رصيداً سالباً، ولا لـ Person أن يحمل اسماً فارغاً.

ما هي دالة الوصول (Getter)؟

دالة الوصول هي دالة public تقرأ حقلاً خاصاً وتُعيد قيمته. يبدأ اسمها باتفاقية بـ get يليه اسم الحقل بصيغة PascalCase:

public class Person { private String name; private int age; // getter لاسم الشخص public String getName() { return name; } // getter للعمر public int getAge() { return age; } }

يكتب المستدعي person.getName() بدلاً من person.name. قد يبدو ذلك مجرد تكلّف إضافي، لكنه يُؤتي ثمارَه فور احتياجك لتغيير طريقة تخزين القيمة أو حسابها، إذ لن تغيّر إلا الدالة فحسب دون كل موضع استخدام.

دوال الوصول للقيم المنطقية تستخدم is. للحقول من نوع boolean، تستبدل الاتفاقية get بـ is: isActive()، isEmpty()، isVerified(). تعتمد كثير من الأدوات (بيئات التطوير، الأطر البرمجية، مكتبات JSON) على هذه الاتفاقية.

ما هي دالة التعيين (Setter)؟

دالة التعيين هي دالة public void تقبل قيمة جديدة وتُسندها إلى الحقل الخاص. يبدأ اسمها بـ set:

public class Person { private String name; private int age; public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } }

هذه الدوال في صورتها الحالية تقبل أي قيمة بشكل أعمى، وهو ما لا يختلف كثيراً عن الحقل العام. القوة الحقيقية تظهر حين تُضيف التحقق من المدخلات.

إضافة التحقق من المدخلات داخل دوال التعيين

لأن دالة التعيين هي المسار الوحيد للوصول إلى الحقل الخاص، فإن كل كود يغيّر الحقل يمر تلقائياً عبر تحققاتك. تكتب القاعدة مرة واحدة وتُطبّق دائماً:

public class Person { private String name; private int age; public void setName(String name) { if (name == null || name.isBlank()) { throw new IllegalArgumentException("الاسم لا يمكن أن يكون فارغاً."); } this.name = name.trim(); } public void setAge(int age) { if (age < 0 || age > 150) { throw new IllegalArgumentException("يجب أن يكون العمر بين 0 و150."); } this.age = age; } public String getName() { return name; } public int getAge() { return age; } }

الآن أي مستدعٍ يحاول person.setAge(-5) سيحصل فوراً على IllegalArgumentException، بدلاً من خطأ غامض في مكان آخر من البرنامج.

ارمِ الاستثناء مبكراً، وأخفِق بصوت عالٍ. رمي استثناء داخل دالة التعيين فور وصول بيانات خاطئة أسهل بكثير في التتبع من السماح للبيانات الفاسدة بتلويث الكائن وإحداث فشل بعد خمس استدعاءات لاحقة.

استدعاء دالة التعيين من المُنشئ

تعلمت سابقاً أن المُنشئات تُهيئ الحقول. خطأ شائع هو إسناد الحقول مباشرة داخل المُنشئ متجاوزاً التحقق. بدلاً من ذلك استدعِ دوال التعيين الخاصة بك حتى تنطبق القواعد نفسها:

public class Person { private String name; private int age; public Person(String name, int age) { setName(name); // يُعيد استخدام تحقق دالة التعيين setAge(age); } public void setName(String name) { if (name == null || name.isBlank()) { throw new IllegalArgumentException("الاسم لا يمكن أن يكون فارغاً."); } this.name = name.trim(); } public void setAge(int age) { if (age < 0 || age > 150) { throw new IllegalArgumentException("يجب أن يكون العمر بين 0 و150."); } this.age = age; } public String getName() { return name; } public int getAge() { return age; } }

بهذا التصميم، إنشاء new Person("", 25) يرمي استثناءً فوراً — لا يمكن أبداً بناء كائن في حالة غير صالحة.

الحقول للقراءة فقط

أحياناً يجب أن يُسند الحقل مرة واحدة عند الإنشاء ولا يتغير بعدها أبداً. في هذه الحالة أتح دالة وصول ولا تُتح دالة تعيين. يمكنك أيضاً تمييز الحقل بـ final ليُطبّق المُصرِّف ذلك:

public class BankAccount { private final String accountNumber; // يُسند مرة واحدة، لا يتغير private double balance; public BankAccount(String accountNumber, double initialBalance) { if (accountNumber == null || accountNumber.isBlank()) { throw new IllegalArgumentException("رقم الحساب مطلوب."); } this.accountNumber = accountNumber; setBalance(initialBalance); } // دالة وصول فقط — لا توجد دالة تعيين public String getAccountNumber() { return accountNumber; } public double getBalance() { return balance; } public void setBalance(double balance) { if (balance < 0) { throw new IllegalArgumentException("الرصيد لا يمكن أن يكون سالباً."); } this.balance = balance; } }

لأن accountNumber معلّم بـ final، يرفض المُصرِّف تصريفَ أي كود يحاول إعادة إسناده، حتى داخل الصنف نفسه.

لا تُتح دوال تعيين لكل شيء تلقائياً. كثير من بيئات التطوير تعرض اختصار "توليد الـ getters والـ setters". استخدمه بتفكير. إضافة دالة تعيين لكل حقل تُفسد الغرض من التغليف. اسأل نفسك: هل يجب أن يتغير هذا الحقل بعد الإنشاء؟ إن لم يكن كذلك، احذف دالة التعيين.

تجميع المفاهيم

إليك برنامج قصير يختبر صنف BankAccount أعلاه:

public class Main { public static void main(String[] args) { BankAccount account = new BankAccount("ACC-001", 500.00); System.out.println(account.getAccountNumber()); // ACC-001 System.out.println(account.getBalance()); // 500.0 account.setBalance(750.00); System.out.println(account.getBalance()); // 750.0 // هذا السطر سيرمي IllegalArgumentException وقت التشغيل: // account.setBalance(-100); } }

الخلاصة

دوال الوصول تُظهر قيم الحقول الخاصة دون إعطاء سيطرة مباشرة عليها. دوال التعيين هي نقطة الدخول الوحيدة للتغييرات، مما يعني أن التحقق يسكن في مكان واحد ويُطبَّق في كل مرة. استدعاء دوال التعيين من المُنشئات يضمن صحة الكائن منذ لحظة إنشائه. الحقول للقراءة فقط تستخدم دالة وصول دون دالة تعيين، وتمييزها بـ final يُحوّل الثبات إلى قيد يطبّقه المُصرِّف بنفسه.