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

التغليف ومُحدِّدات الوصول

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

التغليف ومُحدِّدات الوصول

في الدروس السابقة تعلّمت كيفية تعريف الحقول والتوابع داخل الأصناف، وكيف تُعِدّ المُنشئات الحالة الأولية. كنت تكتب حقولًا public دون التفكير كثيرًا في ذلك. يشرح هذا الدرس لماذا يُشكّل ذلك مشكلة في الغالب، وكيف يحلّها مبدأ التغليف — أحد الأعمدة الأربعة للبرمجة الكائنية.

مشكلة الحقول العامة (public)

تخيّل صنف BankAccount حيث الرصيد حقل عام:

public class BankAccount { public double balance; // يمكن لأي كود لمسه }

لأن balance عام، يستطيع أي كود في أي مكان بالبرنامج فعل هذا:

BankAccount account = new BankAccount(); account.balance = -99999.99; // مسموح قانونيًا — لكنه خطأ تمامًا

لا ينبغي لحساب بنكي أن يسمح أبدًا بكتابة رصيد سلبي من الخارج. الحقل لا يملك أي حماية؛ والصنف لا يستطيع فرض أي قواعد على بياناته الخاصة. هذه هي المشكلة التي صُمِّم التغليف لمنعها.

مُحدِّدات الوصول الأربعة

يوفّر Java أربعة مستويات للرؤية، من الأوسع إلى الأضيق:

  • public — يُمكن الوصول إليه من أي مكان: نفس الصنف، نفس الحزمة، الأصناف الفرعية، وكل كود آخر.
  • protected — يُمكن الوصول إليه من نفس الحزمة ومن الأصناف الفرعية حتى في حزم أخرى. مفيد عند التصميم للوراثة.
  • حزمي (الافتراضي) — بدون كلمة مفتاحية. يُمكن الوصول إليه فقط من داخل نفس الحزمة. يُطبَّق عند حذف مُحدِّد الوصول.
  • private — يُمكن الوصول إليه فقط من داخل الصنف الذي يُعرِّفه. لا شيء خارجه — حتى الصنف الفرعي — يستطيع رؤيته مباشرة.
قاعدة عملية: استخدم private للحقول بشكل افتراضي. خفِّف المُحدِّد فقط عند وجود سبب واضح — لا تبدأ بـ public وتأمل ألا يُسيء أحد استخدامه.

التغليف: إخفاء الحالة، كشف السلوك

يعني التغليف دمج الحالة (الحقول) والسلوك (التوابع) معًا، وإخفاء الحالة الداخلية خلف واجهة مُتحكَّم فيها. الكود الخارجي يستدعي التوابع؛ والصنف وحده هو الذي يقرأ حقوله الخاصة أو يكتبها مباشرة.

إليك إعادة كتابة BankAccount مع التغليف:

public class BankAccount { private double balance; // مخفي عن العالم الخارجي public BankAccount(double initialBalance) { if (initialBalance < 0) { throw new IllegalArgumentException("Initial balance cannot be negative."); } this.balance = initialBalance; } public void deposit(double amount) { if (amount <= 0) { throw new IllegalArgumentException("Deposit amount must be positive."); } balance += amount; } public boolean withdraw(double amount) { if (amount <= 0 || amount > balance) { return false; // رفض الطلبات غير الصالحة } balance -= amount; return true; } public double getBalance() { return balance; // عرض للقراءة فقط؛ المستدعون يرون القيمة لكن لا يُغيّرونها } }

الآن قارن ما يستطيع الكود الخارجي فعله:

BankAccount account = new BankAccount(500.0); account.deposit(200.0); account.withdraw(100.0); System.out.println(account.getBalance()); // 600.0 // account.balance = -99999.99; // خطأ في الترجمة — 'balance' خاص

الصنف الآن في سيطرة كاملة على بياناته الخاصة. يفرض المُترجم القواعد بنفسه.

أين يُطبَّق كل مُحدِّد

يمكن وضع مُحدِّدات الوصول على الحقول والتوابع والمُنشئات وعلى الأصناف (مع بعض القيود). ملخّص عملي:

  • الحقول: في الغالب private. اكشف القيم عبر التوابع عند الحاجة.
  • التوابع التي تُشكّل الواجهة العامة: public.
  • التوابع المساعدة المستخدمة داخل الصنف فقط: private.
  • التوابع المشتركة داخل الحزمة لكن ليس خارجها: حزمية (افتراضية).
  • التوابع المُصمَّمة لتُكتَب فوقها في الأصناف الفرعية: protected (سيُغطّى بالتفصيل في درس الوراثة).
ابدأ بالأضيق، وسِّع عند الحاجة فقط. من السهل تحويل تابع private إلى public لاحقًا. لكن حين تجعل شيئًا public ويعتمد عليه كود آخر، فإن إزالة ذلك الوصول يُعدّ تغييرًا كاسرًا. إخفاء تفاصيل التنفيذ يمنحك حرية تغييرها لاحقًا دون كسر المستدعين.

الوصول الحزمي (الافتراضي) في الممارسة

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

class OrderValidator { // صنف حزمي — تفصيل داخلي boolean isValid(Order order) { // تابع حزمي return order != null && order.getTotal() > 0; } }

مثال متكامل: حساس درجة الحرارة

إليك مثال مكتفٍ بذاته يستخدم عدة مُحدِّدات معًا:

public class TemperatureSensor { private static final double MIN_CELSIUS = -273.15; // الصفر المطلق private double currentTemperature; private String unit; public TemperatureSensor(double initialTemp, String unit) { if (initialTemp < MIN_CELSIUS) { throw new IllegalArgumentException("Temperature below absolute zero."); } this.currentTemperature = initialTemp; this.unit = unit; } public void recordReading(double temp) { if (temp < MIN_CELSIUS) { throw new IllegalArgumentException("Invalid temperature reading."); } this.currentTemperature = temp; } public double getCurrentTemperature() { return currentTemperature; } private double toCelsius(double value) { // تابع مساعد خاص — منطق التحويل الداخلي، ليس جزءًا من الواجهة العامة return unit.equals("F") ? (value - 32) * 5.0 / 9.0 : value; } public double getCelsius() { return toCelsius(currentTemperature); } }
  • MIN_CELSIUS خاص — ثابت داخلي؛ المستدعون لا يحتاجون معرفة وجوده.
  • currentTemperature وunit خاصّان — حالة مخفية عن الخارج.
  • recordReading وgetCelsius عامّان — الواجهة المُتحكَّم فيها التي يستخدمها المستدعون.
  • toCelsius خاص — تابع مساعد يُنظّف التنفيذ لكنه لا شأن لأحد سواه به.
لا تكشف التوابع المساعدة الداخلية فقط لتسهيل الاختبار. إن كان تابع خاص يصعب اختباره، فهذا يعني عادةً أنه ينتمي إلى صنف خاص به. تغيير تابع من private إلى public لمجرد الاختبار يُسرِّب تفاصيل التنفيذ ويُضعف التغليف.

الخلاصة

يحمي التغليف الحالة الداخلية للكائن بجعل الحقول private وتوفير وصول مُتحكَّم فيه عبر توابع public. تتيح لك مُحدِّدات الوصول الأربعة في Java — private، والحزمي، وprotected، وpublic — التحكم الدقيق فيما يستطيع رؤيته كل جزء من الكود. ابدأ دائمًا بأضيق وصول وسِّعه فقط عند وجود حاجة واضحة. في الدرس التالي ستحوّل هذا المبدأ إلى نمط قابل للتكرار باستخدام الـ getters والـ setters والتحقق من صحة الحقول.