Java / الأساسي / الدرس 6
الأصناف والكائنات والتغليف
نمذج كائناً صغيراً ذا حالة تحمي بانيته ودواله انتقالاته الصالحة، وميّز النسخ والأعضاء الساكنة والتحكم بالوصول وسلوك الواجهة.
في هذه الصفحة
أهداف الدرس
- حماية ثوابت الكائن بالباني والدوال.
- تمييز حالة النسخة من الحالة الساكنة.
- استخدام واجهة لوصف سلوك قابل للاستبدال.
امنح الكائن مسؤولية واضحة
يحدد الصنف نوعاً بحقول وبوانٍ وسلوك، والكائن نسخة منه. قد تحمل نسختان حالة مستقلة مع اشتراك تعريف الدوال. نمذج مسؤولية مثل مخزون لا يقبل إلا إضافات موجبة بدلاً من حقول عامة قابلة للكتابة. الثابت شرط يصح عند ظهور الكائن للخارج.
ينشئ الباني الحالة الصحيحة وتحافظ عليها الدوال العامة. لا يقتصر التحقق على الواجهة الرسومية لأن مستدعياً آخر قد ينشئ الكائن مباشرة. صعّب إنشاء الحالة غير الصالحة واختر فشلاً مفهوماً. لا يحقق getter التغليف إن أعاد مجموعة داخلية متغيرة يعبث بها المستدعي.
أنشئ الثابت وحافظ عليه
يقبل مثال العداد بداية غير سالبة وإضافات موجبة. تمنع الحالة الخاصة إسناداً خارجياً مباشراً ويعيد القارئ لقطة أولية. تمنع Math.addExact تجاوزاً صامتاً يفسد العدد. يطبع5 بعد إضافة3 إلى2، وتطبق شروط الباني قبل عودة كائن صالح.
يحمل الباني اسم الصنف دون نوع إرجاع؛ وكتابة void Counter تنشئ دالة عادية. تميز this الحقل من معامل باسمه. ركز الباني على الحالة المحلية الصحيحة وتجنب إدخالاً وإخراجاً مفاجئاً أو اتصالاً بعيداً أو تسريب this للخيوط قبل اكتمال التهيئة.
public class Main {
static final class Counter {
private int value;
Counter(int initial) { if(initial<0) throw new IllegalArgumentException("negative"); value=initial; }
void add(int n) { if(n<=0) throw new IllegalArgumentException("nonpositive"); value=Math.addExact(value,n); }
int value() { return value; }
}
public static void main(String[] args) throws Exception {
Counter c=new Counter(2);
c.add(3);
System.out.println(c.value());
}
}
5
يتحقق الإنشاء والتعديل من المجال ولا يستطيع المستدعي إسناد الحقل الخاص مباشرة.
افصل حالة الكائن وسلوك الصنف
ينتمي حقل النسخة لكائن، وينتمي static للصنف المحمل. العداد الساكن المتغير حالة مشتركة لا عداد جديد لكل طلب، وقد يسبب مشكلات عمر وتزامن. تفيد الدوال الساكنة لعملية بلا حالة نسخة كأداة عددية، ولا ينبغي تحويلها إلى مخزن عام خفي.
تمنع final إعادة إسناد المتغير ولا تجمد محتويات المرجوع إليه. قد يشير final List إلى قائمة متغيرة. اجعل حالة الكائن الثابت خاصة ومكتملة في الباني ولا تسرب الداخل المتغير، واضبط المراجع بنسخ أو تمثيل ثابت صراحة.
صف السلوك بواجهة
تصف الواجهة عقد سلوك تنفذه أصناف متعددة. يستطيع متغير نوعه الواجهة حمل كائن منفذ، ويوزع استدعاء الدالة المعاد تعريفها إلى سلوكه. يشير PriceRule إلى HalfPrice، لذلك ينتج تطبيق10 القيمة5. يختلف ذلك من التحميل الزائد الذي يختاره المترجم بالتوقيع والنوع المعلن.
استخدم @Override لكشف التهجئة والتواقيع غير المتوافقة. يجب أن يحترم الفرعي سلوك المدخل والناتج لا أن يترجم فقط. يجمع التركيب متعاونين دون فرض هرم وراثة. فضّل واجهة صغيرة لاستبدال حقيقي؛ فالواجهة لكل صنف تضيف مراسم لا جودة تلقائية.
public class Main {
interface PriceRule { int apply(int amount); }
static final class HalfPrice implements PriceRule {
@Override public int apply(int amount) { return amount/2; }
}
public static void main(String[] args) throws Exception {
PriceRule rule=new HalfPrice();
System.out.println(rule.apply(10));
}
}
5
النوع المعلن PriceRule والكائن الفعلي يوفر HalfPrice.apply، ويستخدم المثال مبلغاً صحيحاً زوجياً.
اختر الهوية ودلالة القيمة بقصد
تجيب الهوية هل يشير المرجعان للكائن نفسه، وتجيب مساواة القيمة هل تتكافأ الحالة المعنية وفق عقد equals. تحتاج مفاتيح الخرائط equals وhashCode متوافقين وحالة مساواة مستقرة أثناء التخزين. قد يصبح المفتاح المتغير غير قابل للعثور بعد تغير الحقول المؤثرة في التجزئة.
توفر records في Java17 حوامل بيانات موجزة ثابتة سطحياً ودوال ذات دلالة قيمة، ولا تجمد المكونات المتغيرة عميقاً. تقيد الأنواع المختومة التنفيذات المسموحة لمجال مغلق مقصود. الأداتان معياريتان في الأساس، لكن التغليف يتبع التصميم والتحقق والملكية الواضحة لا كلمة وحدها.
الأخطاء الشائعة وإصلاحها
non-static variable cannot be referenced from a static context
- لماذا يحدث؟
- استُخدمت حالة نسخة دون تحديد كائن.
- كيف تصلحه؟
- أنشئ النسخة أو استقبلها أو أعد تصميم العملية عديمة الحالة.
- كيف تتجنب تكراره؟
- وضح مسؤوليات النسخة والصنف.
method does not override or implement a method from a supertype
- لماذا يحدث؟
- توقيع إعادة التعريف غير مطابق.
- كيف تصلحه؟
- طابق العقد وأبق @Override.
- كيف تتجنب تكراره؟
- اجعل المترجم يحمي التغييرات.
An invariant was bypassed
- لماذا يحدث؟
- كُشف حقل أو كائن داخلي متغير.
- كيف تصلحه؟
- قيد التعديل وأعد نسخة أو عرضاً ثابتاً مناسباً.
- كيف تتجنب تكراره؟
- راجع الملكية في كل دالة عامة.
اختبر فهمك
1. هل تمنع final int[] values كتابة values[0]=9؟
اعرض الحل المشروح
لا؛ تمنع إسناد مصفوفة أخرى للمتغير وتبقى الحالية متغيرة. استخدم نسخة دفاعية أو سياسة ملكية إن وجب منع تغيير الداخل.
2. صمّم بانياً لحاوية سعتها موجبة.
اعرض الحل المشروح
ارفض capacity <= 0 قبل إظهار الكائن واحفظ السعة خاصة وحافظ على الإشغال بين صفر والسعة. اختبر الإنشاء غير الصالح وحدي الانتقال لا القراءات فقط.
أهم ما تعلمته
- تنشئ البواني الثوابت وتحفظها الدوال.
- لا تجمد مراجع final الكائنات عميقاً.
- تصف الواجهات استبدالاً سلوكياً لا صياغة فقط.