قلل درجة الوصول للClass أو الMember بأقصى ما تستطيع!
هناك نوعين من الكلاسات يمكن كتابتها في جافا، النوع الأول وهو الكلاس العادي الذي تكتبه عندما تنشى أي ملف جافا -ليس بداخل اي كلاس أخر- ونسميه عادة Top Level Class، النوع الأخر وهي الكلاسات التي تكون بداخل كلاس أخر وهي Nested Class.
هذه المقاله تحث على تقليل ال Public والذي هو أحد Access Level المتاحة في لغه الجافا، واذا نظرنا بشكل أوسع لل Access Level في اللغه واستخداماتها في الكلاسات (وحتى الInterface) التي من النوع Top Level فسوف نجد أننا أمام خياران فقط (public class أو package-private class) ولن تستطيع ان تقوم بعمل private class أو حتى protected لل Top Level.
هذه المقالة جزء من سلسلة كيف تحترف لغة الجافا وهي موجهة لأي مطور OOP ويستخدم لغة الجافا
- الإسلوب الصحيح لكتابه Utility Classes
- هل مللت من دالة البناء Constructor ؟
- استخدام ال Builder Pattern في دالة البناء
- لا تستخدم Public الا وقت الحاجة (المقال الحالي)
- كيف تصمم الPackages جيداً في تطبيقات جافا
- جافا دائماً Passing by Value
- نظرة حول الدالة Equals
- خارطة طريق لتعلم الجافا
لماذا لا يجب أن تستخدم Public
الأمر الشائع لدى كثير من المبرمجين هو جعل الكلاسات الTop Level دائماً public، وهذا لا يفضل ذلك الا عندما تحتاج لهذا، فلو قمت بعمله package-private سوف يكون جزء من الImplementation وليس من الExported API وهكذا تستطيع تعديله وتغييره أو حتى مسحه كاملاً في النسخ التالية subsequent release بدون التأثير على الأكواد الClient، ولكن اذا جعلته Public سوف يبقى معك للأبد ولن تستطيع حذفه لأنه سوف يؤثر على الClients.
تخيل أنك تعمل في مشروع كبير ومعك أكثر من مبرمج، اذا كنت تضع الكلاسات في باكج واحد وكلهم بأعلى Level (الpublic) فماذا لو أردت تغيير أو حذف شيء ما، سوف تقوم بذلك وتنتظر الى ان يصدر المترجم رسائل الخطأ لكل كلاس يعتمد عليه ذلك الخطأ، وقد تكون أحياناً لا تعلم درجة الاعتمادية الى درجة أنك لا تقوم بتغييره خوفاً من المجهول,, كل ذلك بسبب هذا المستوى العالى من السماحية… في المقابل لو قمت بتصميم الكلاسات بطريقة الباكجات التي تنظر لل Features ومن ثم في داخل كل باكج تقوم باعطاء اقل مستوى تستطيع هكذا عندما تقوم بتغيير او حذف الFeature فبمجرد مشاهدتك Level أقل من ال public فسوف تعلم أنك في أمان وتستطيع العمل داخل الباكج.. هذه ميزة رائعه وتسهل عليك التطوير في المشاريع الكبيرة,, لذلك منذ الآن اعتبر package-private صديقك الحميم.
أحياناً بعد أن تقوم بعمل الكلاس ال Top Level بدرجة وصول package-private قد تجد أنه لا يستخدمه الا كلاس واحد موجود في ذلك الpackage، ويمكنك في هذه الحالة ان تجعله private nested class لأن ذلك يقلل الوصول له حتى من الكلاسات الموجودة في نفس الpackage، ولكن المهم (وهو لب هذه المقاله) هو أن تقلل ايضاً الوصول للpublic class وذلك من خلال جعلها Package private.
لذلك اذا وجدت هذا الكلاس:
class Point { public double x; public double y; }
فقبل أن تهجم على صاحبه بأنه لا يراعي مفاهيم ال Encapsulation وأنه يجب توفير Setters و Getters بهذا الشكل:
class Point { private double x; private double y; public Point(double x, double y) { this.x = x; this.y = y; } public double getX() { return x; } public double getY() { return y; } public void setX(double x) { this.x = x; } public void setY(double y) { this.y = y; } }
هذا التصميم فعلاً صحيح في حاله كان الكلاس public ولكن في حالة كان package private او حتى nested class فلا يوجد ضير من ذلك لأنك الوحيد الذي سوف تستخدمه والكلاس أدى غرضه بدون مشاكل.
بالنسبة الكلاسات من النوع الأخر Nested Class (بالاضافة الى أي شيء members مثل member variables أو member methods) هناك أربعه احتمالات للAccess Level متاحة لهم، وهي بدءاً من الأقل:
- private وهنا سوف يكون ال member متاح فقط للTop Level الذي يحتوى على هذا الmember.
- Package-Private وهنا يكون ال member متاح لأي كلاس موجود في الPackage الذي يتواجد فيه الكلاس (أحياناً تسمى هذه Default Access وهي عندما لا تستخدم أي Access Modifier عند تعريفك للكلاس أو ال Member).
- Protected وهنا يكون ال member متاح الوصول له من الSubclasses وأيضاً من الكلاسات التي توجد في نفس ال package.
- Public وهنا يكون ال member متاح للجميع ، سواء الTop Level Class الذي يحتويه، أو الكلاسات في نفس الPackage ، أو الكلاسات التي في Packages أخرى.
بشكل عام يمكنك وضع جميع ال member بالنوع private، واذا احتاج كلاس اخر في نفس ال Package أن يصل ل Member في هذه الكلاس، فاستخدم الPackage private عن طريق مسح كلمة private من تعريف الmember ، لكن اذا كررت هذه العملية كثيراً فعليك بمراجعة تصميم للكلاسات وحاول استخراج تصميم افضل. على العموم هكذا باستخدام ال private أو package-private انت الأن لديك members تعتبر جزءاً من عمل الكلاس Class Implementations وليس جزءاً من الExported API.
بالنسبة للكلاسات ال public فعندما تغيير الوصول من package-private ل protected فأنت قد رفعت درجة الوصول بشكل كبير، لأن الprotected members يشابه ال public members ويعتبر جزءاً من ال Exported API وسوف يبقى معك للأبد. لذلك استخدام ال protected يجب أن يكون محدود.
بعض الأحيان لن تستطيع تقليل درجة الوصول لل members methods وهي في حالة الوراثه، حيث انك لا تستطيع استخدام Access Modifier أقل من ما هو موجود في الsuperclass (بالطبع هذا لضمان أنك تستطيع استخدام ال Subclass في أي مكان تستطيع فيه استخدام ال Superclass) واذا قمت بالتقليل سوف تحصل على رسالة خطأ من المترجم، مثال أخر وهو عندما تقوم بعمل implements لأي Interface فلن تستطيع جعل تلك الدوال التي قمت بعمل implement لها سوى public (وذلك لأن أي member في الinterface هو بشكل ضمني public).
ايضاً ال member fields يجب أن لا تكون public ، فاذا كان لديك متغير ليس ثابت non-final أو مؤشر ثابت لكائن متغير final reference to mutable object فعندما تقوم بعمل هذا الmember ب public فأنت تسمح بالجميع الوصول له وتغيير قيمته وبالتالي قد لا تستطيع عمل اي فحص على القيمه invariants checking ، ايضاً مثل هذه الكلاسات class with public mutable fields ليست أمنه Not Thread Safe.
بنفس الأمر ينطبق الكلام مع ال static fields لكن يمكنك عمل الثوابت من خلال public static final fields (بالطبع يكون ثابت ولا يتغير ، وياتباع المقاييس يجب تسميته بأحرف كبيرة Capital Letter وتفصل بين الكلمات بunderscores). ومن المهم أن تكون هذه ال fields اما primitive values أو reference to immutable objects. أما ال final field ويحتوي على reference to mutable Object له كل المساوىء كما هو الحال مع ال non-final field. بالرغم من أن ال reference لا يمكن تغييره لكن الكائن يمكن أن يتغيير بشكل غير متوقع.
ايضاً لاحظ ان المصفوفات التي تكون أكبر من 0 هي دائماً Mutable لذلك من الخطأ ان يحتوي الكلاس على public static final array field ، أو ان يحتوي على Accessors يقوم بارجاع هذه ال field لأن الكلاينت يمكنه ان يغير قيمة هذه المصفوفة.
مثال:
public static final Things[] value = {};
وهناك حلين لهذه المشكلة، الأول أن تجعل المصفوفة private وتقوم بعمل دالة public ترجع نسخه Immutable من هذه المصفوفة:
private static final Thing[] PRIVATE_VALUES = { ... }; public static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
الحل الثاني هو بجعل الصمفوفة private وارجاع مصفوفة جديدة بتلك القيم:
private static final Thing[] PRIVATE_VALUES = { ... }; public static final Thing[] values() { return PRIVATE_VALUES.clone(); }
قد تقول ما الأفضل من هذين الحلين ؟ لكن هذا يعتمد على طريقة استخدام الكلاينت للreturn type وكيف تود أنت مستخدم الكلاس ان يستخدمها الكلاينت. وخلاصه لهذه المقالة يجب أن تقلل ال Accessibility بقدر المستطاع، فقلل أولاً الPublic API بقدر المستطاع (سواء كانت كلاسات أو متغيرات) وابعدهم عن حصولهم على لقب Exported API ( باستنثاء الثوابت public static final fields) وتأكد حينها أن الثابت public static final fields عباره عن مؤشر لكائن Immutable.
هكذا باستخدام الباكج بال Feature مع أقل سماحية لل Classes بقى لدينا السؤال الأخير للوصول للكلاس الخارق: To Mutate or Not Mutate ؟
نراكم ان شاء الله في المقاله المقبلة عن Immutable VS Mutable ، وهو أحد أسس ال Functional Language
بسم الله الرحمن الرحيم
جزاكم الله كل خير -جارى القراءة والتنسيق لجمع المواضيع فى كتاب بأذن الله تعالى-استمر اخى الفاضل-بارك الله فيكم
بارك الله فيك يا أخي …..
موضوع أكثر من رائع . هذا من الاشياء الكثيرة الي تنقصني في البرمجة
لانو جرت العادة اعمل كل شيء public . لا ربما استعمل ذلك المتغير او الكلاس في وقت لاحق .
شكرا لك اخي