هناك الكثير من الدوال أو الثوابت نقوم بكتابتها ووضعها في كلاس ومن ثم نجد أن هناك كلاسات اخرى بحاجه لها، احد الحلول الخاطئة تماماً وهو نسخ ولصق تلك الدوال في اي كلاس كان في حاجه له، بالطبع الحل الصحيح وهو نقل هذه الدوال أو الثوابت الى كلاس عام يسهل للجميع الوصول اليه، تسمى هذه الكلاسات التي تحتوي على الدوال بالإسم Utility Classes بينما التي تحتوي على متغيرات فقط تسمى Constant Classes.
لذلك كثير من الأحيان قد تحتاج لعمل هذا الكلاس وفيه مجموعه من الدوال والمتغيرات الstatic فقط ( كما توجد في الباكج java.lang.Math و java.util.Arrays ) ولن تحتاح لعمل كائن من الكلاس، فقط أن تريد ضم مجموعه من الدوال المتعلقه بشيء ما في كلاس معين. مثلاً دوال تتعامل مع عرض التاريخ أو مع المجلدات.
هذه المقالة جزء من سلسلة كيف تحترف لغة الجافا وهي موجهة لأي مطور OOP ويستخدم لغة الجافا
- الإسلوب الصحيح لكتابه Utility Classes (المقال الحالي)
- هل مللت من دالة البناء Constructor ؟
- استخدام ال Builder Pattern في دالة البناء
- لا تستخدم Public الا وقت الحاجة
- كيف تصمم الPackages جيداً في تطبيقات جافا
- جافا دائماً Passing by Value
- نظرة حول الدالة Equals
- خارطة طريق لتعلم الجافا
عمل دالة بناء خاصة Private Constructor
اذاً نريد أن نمنع الكلاينت من عمل كائن من هذا الكلاس، كيف السبيل لذلك؟ كما نعلم في أساسيات ال OOP في حال قمنا بعمل الكلاس ولم نقم بتزويده بداله بناء Constructor فيمكن أن يقوم المستخدم بعمل كائن منه وذلك بسبب أن المترجم سوف يزود الكلاس بدالة بناء افتراضية default constructor اذا لم يجد أي دالة بناء، أحد الحلول لمنع انشاء الكائن من الكلاس هي بجعل الكلاس abstract وبالتالي لا يستطيع أحد القيام بعمل كائن منه ولكن هذا لا يحبذ لأن الكلاس قد يتم الوراثه منه ويتم عمل الكائن بسهوله اضافه وهي الاهم أن المستخدم يخطئ ويظن ان الكلاس مصمم للوراثه منه.
الطريقة الافضل لمنع الكلاينت من انشاء كائن منه عن طريق تزويد الكلاس بدالة بناء private constructor، المثال التالي يبين ذلك:
// Noninstantiable utility class public final class UtilityClass { // Suppress default constructor for noninstantiability private UtilityClass() { throw new AssertionError(); } ... // Remainder omitted }
لاحظ أننا قمنا بعمل throw لexception فقط حتى تضمن أنه لا يمكن عمل كائن من الكلاس سواء من داخله وخارجه وأنه سيحدث خطأ يوقف سير البرنامج اذا قام أحد بطريقة ما بانشاء الكائن ،،هكذا أصبح لدينا كلاس Utility لا يمكن انشاء كائن منه وفي نفس الوقت غير قابل للوراثه والسبب أن دالة البناء private والكلاس الابن يحتاج لهذه الدالة لكي يستطيع انشاء الكائن، لذلك لا يمكن الوراثه من ذلك الكلاس.
كأسلوب Convention في التسمية يفضل ان تتبع اسلوب واحد في تسمية ال Utility ، مثلاً أن تكتب DateHelper (ينتهي ب Helper) بالنسبة لي فأستخدم اللاحقه Util فهي أسهل في الفهم، مثلاً الكود التالي من أحدى ال FileUtil لأحد المشاريع -تم ازالة كثير من الدوال ووضع ال import العامة التي تنتهي بعلامه *- :
package informatic-ar.com.util; /** * * @author wajdyessam */ import static informatic-ar.com.util.PreconditionsChecker.checkNull; import static informatic-ar.com.util.PreconditionsChecker.checkNotEmptyString; import java.io.*; import java.util.*; /* * Noninstantiable utility class */ public final class FileUtil { /* * Suppress default constructor for noninstantiability */ private FileUtil() { throw new AssertionError(); } /** * Generic Method to write any serializable object to file * @param <T> object type, must be implement Serializable interface * @param object the object to be written to the file * @param file the file path * @throws IOException if there are errors in the written process */ public static <T extends Serializable> void writeObject (T object, File file) throws IOException { ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream(file)); out.writeObject(object); out.close(); } /** * Generic Method to read any serializable object from file * @param <T> the type of object, must be implement serializable interface * @param file the file path * @return the object to be written * @throws IOException * @throws ClassNotFoundException */ public static <T extends Serializable> T readObject (File file) throws IOException,ClassNotFoundException { ObjectInputStream in = new ObjectInputStream(new FileInputStream(file)); T object = (T) in.readObject(); in.close(); return object; } /** * create new folder if folderPath is not exists * @param folderPath */ public static void createFolder(String folderPath) { folderPath = checkNull("filepath can't be null ", folderPath); folderPath = checkNotEmptyString("file path must be not empty", folderPath); createFolder(new File(folderPath)); } /** * create new folder if folder is not exists * @param folder is path to folder */ public static void createFolder(File folder) { folder = checkNull("folder must be not null", folder); if ( ! folder.exists()) folder.mkdir(); } /** * execute command line utility and read the output stream from it * @param path is the path for command line utility * @return the result of output stream as list of string * @throws IOException if the utility is not found * @throws NullPointerException if the path contain null data or empty string */ public static List<String> readProgramOutputStream (String path) throws IOException { path = checkNull("path can't be null", path); path = checkNotEmptyString("path must have a value", path); Process process = Runtime.getRuntime().exec(path); BufferedReader input = new BufferedReader( new InputStreamReader(process.getInputStream()) ); String line = null ; List<String> result = new ArrayList<String>(); while ( (line = input.readLine() ) != null ) { result.add( line ); } input.close(); return ( result ); } public static boolean removeDirectory (String path) { return removeDirectory(new File(path)); } public static boolean removeDirectory (File dirPath) { if ( dirPath.isDirectory() ) { File[] files = dirPath.listFiles() ; for(File file: files ) { if ( file.isDirectory() ) removeDirectory(file); else { boolean status = file.delete() ; if ( ! status ) return false ; } } } return dirPath.delete() ; } }
الثوابت Constant Interface
هذا ما يتعلق بكتابه الUtility Class بالنسبة للدوال ،، نأتي الأن لموضوع الثوابت ، وفي الحقيقة نجد أن هناك تصميم غير جيد منتشر منذ القدم لدى بعض مبرمجي الجافا وهو تعريف الثوابت في interface ومن ثم جعل الكلاسات التي تريد هذه الثوابت بعمل implement لهذا ال interface (هذا يسمى ب Constant Interface) ولا تحبذ هذه الطريقة بسبب الثوابت يفترض أن تكون جزءاً من عمل الكلاس Implementation ولكن بهذا الشكل سوف تكون جزءاً من الأشياء المتاحة في الكلاس Exported API، تخيل مثلاً انك في النسخه القادمة تريد ازالة ثابت منها فلن تستطيع حتى تحافظ على ال Compatibility في النسخ السابقة ، أيضاً لو كان هناك ابناء لهذا الكلاس فجيمعهم سوف يحمل هذه الثوابت معه حتى ولو لم يحتاجها.
لذلك الحل الأفضل للثوابت هي وضعها في Utility Class بدالة بناء private ، وبالنسبة لي افضل أن ينتهي اسمها ب Constants للدلالة على الثوابت بداخلها، على سبيل المثال:
package informatic-ar.com.constants; /** * Containing variable names related to the current JRE * implementation also hold some look and feel names * * also hold the application name and version * * @author wajdyessam */ public final class SystemConstant { // application name public final static String APPLICATION_NAME = "My Application Name: "; // application version public final static String APPLICATION_VERSION = "2"; // application and version public final static String APPLICATION_NAME_VERSION = APPLICATION_NAME + APPLICATION_VERSION; // auther name public final static String AUTHER = "WAJDY"; // current user name public final static String USER_NAME = System.getProperty("user.name"); // user home directory public final static String USER_HOME = System.getProperty("user.home"); // user working directory public final static String USER_DIRECTORY = System.getProperty("user.dir"); // operating system version public final static String OS_NAME = System.getProperty("os.name"); // operating system version public final static String OS_VERSION = System.getProperty("os.version"); // java run time version public final static String JRE_VERSION = System.getProperty("java.version"); // java home path public final static String JRE_HOME = System.getProperty("java.home"); /** look and feel constant */ public final static String DUSK_LOOK_AND_FEEL = "org.jvnet.substance.skin.SubstanceDustLookAndFeel"; public final static String RAVEN_LOOK_AND_FEEL = "org.jvnet.substance.skin.SubstanceRavenGraphiteLookAndFeel"; public final static String BUSINESS_LOOK_AND_FEEL = "org.jvnet.substance.skin.SubstanceBusinessLookAndFeel"; public final static String WINDOWS_LOOK_AND_FEEL = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel"; }
خلاصة
تأكد من وضع private constructor لكل من ال Utility Class و ال Constant Class حتى تضمن عدم وراثتها وانشاء كائن منها، ولا تستخدم interface لحمل الثوابت فالClass أفضل منها.
لماذا في الحالة الاولى لم تجعل الFileUtil
public final class FileUtil
??
نعم يفضل جعل اي كلاس لا يدخل في عملية وراثه ان يكون final، ولكن بما أن ال private constructor تحمي ايضاً من انشاء الكائن من اي كلاس ابن ، لذلك يمكن ان تكون بنفس معنى final في هذه الحالة..
على العموم للتعود على هذه ال practice والتي نحتاجها خصوصاً عندما نقوم بعمل Immutable سأقوم بوضع ال final على هذه ال utility .
تحياتي اخي محمد :).
بسم الله الرحمن الرحيم
جزاكم الله كل خير- بارك الله فيكم