اذهب إلى المحتوى

تصميم البرامج في جافا


رضوى العربي

قُدرتك على اِستيعاب طريقة عمل البرامج، وفهمها هو أمر جيد بلا شك، ولكنه يختلف تمامًا عن تَصميمها بحيث تُنفِّذ مُهِمّة معينة. لقد ناقشنا بالقسم ٣.٢ كيفية تَطْوير الخوارزميات بطريقة مَنهجية باستخدام أسلوب التصميم المُتدرج (stepwise refinement) والشيفرة الوهمية (pseudocode).

التصميم المُتدرج هو عملية تَقَع ضِمْن استراتيجيات التصميم من أعلى لأسفل (top-down)، أيّ لابُدّ عمومًا من وجود نقطة معينة تَتَوقَّف عندها عن عملية التحسِّين التدريجي لخوارزمية الشيفرة الوهمية (pseudocode algorithm)، وتُحوِّل عندها تلك الخوارزمية مباشرة إلى شيفرة برنامج فعليّة. بدون البرامج الفرعية (subroutines)، ستُمثِل تلك النقطة مستوًى بدائيًا جدًا من العمليات، كتَعْليمَات الإِسْناد (assignment statements)، وعمليات الدَخْل والخَرْج، أما في حالة تَوفُّر البرامج الفرعية المسئولة عن إنجاز بعض المَهَامّ (tasks) المُحدَّدة، فسيَكُون بإمكانك التَوقُّف عن التحسِّين بمجرد تعبيرك عن الخوارزمية بنفس المصطلحات التي تَستخدِمها البرامج الفرعية.

يَعنِي ذلك أننا قد أضفنا عنصر، هو بالأساس يَتَّبِع أسلوب التصميم من أسفل لأعلى (bottom-up)، إلى استراتيجيات التصميم من أعلى لأسفل (top-down approach) الخاصة بالتصميم المُتدرج. بفَرْض وجود مشكلة معينة، تستطيع الآن البدء بكتابة بعض البرامج الفرعية (subroutines) المسئولة عن إنجاز بعض المَهَامّ المُتَعلِّقة بموضوع البرنامج، بحيث تُصبِح تلك البرامج بمثابة صندوق من الأدوات الجاهزة (toolbox) يُمكِنك دَمْجها إلى الخوارزمية أثناء تَطْويرها، أو حتى قد تستطيع شراء صندوق أدوات برمجي جاهز (software toolbox)، كُتب بواسطة شخص آخر، بحيث يَتَضمَّن ذلك الصندوق مجموعة من البرامج الفرعية، التي ستَكُون بمثابة صندوقًا أسودًا (black boxes)، يُمكِنك تَوظِّيفه ضِمْن المشروع الخاص بك.

بإمكانك حتى استخدام البرامج الفرعية مباشرة ضِمْن أساليب التصميم من أعلى لأسفل (top-down approach) الأكثر صرامة، بمعنى أنه وبينما تُحسِّن الخوارزمية بالطريقة المُعتادة، يُمكِنك أن تُعبر عن مُهِمّة فرعية (sub-task) معينة ضِمْن الخوارزمية بصورة استدعاء لبرنامج فرعي. يُصبِح تَطْوير ذلك البرنامج الفرعي عندها مشكلة مُستقلة تستطيع العمل عليها بشكل منفصل. لا يَتَعدَى اتباع تلك الطريقة أكثر من مُجرَّد تَقسِّيم للمشكلة الرئيسية إلى مشكلات صغيرة مُنفصلة، أيّ أنها ما تزال تَقَع ضِمْن استراتيجيات التصميم من أعلى لأسفل؛ لأن تحليلك للمشكلة هو الذي وَجَّهك لكتابة البرامج الفرعية. في المقابل، تَبدأ استراتيجيات التصميم من أسفل لأعلى (bottom-up approach) بكتابة البرامج الفرعية المُتَعلِّقة بموضوع البرنامج، أو الحصول عليها بطريقة ما، بحيث تَعَمَل تلك البرامج الفرعية بمثابة أساس أو قاعدة تُستخدَم لبناء حل للمشكلة الأساسية.

الشروط المسبقة (precondition) والشروط اللاحقة (postcondition)

تَعََمَل البرامج الفرعية (subroutines) عمومًا كلَبِنات أساسية (building blocks) ضِمْن البرنامج الأساسي، لذا لابُدّ أن تَكُون طريقة تَفْاعُلها مع ذلك البرنامج واضحة. تُحدِّد المواصفة الاصطلاحية (contract) لأيّ برنامج فرعي عمومًا طريقة التَفْاعُل تلك، وهو ما ناقشناه بالقسم ٤.١، ويُمكِن كتابتها باِستخدَام ما يُعرَف باسم الشروط المُسَبَّقة (precondition) واللاحقة (postcondition).

لابُدّ أن تَكُون الشروط المُسَبَّقة (precondition) لأي برنامج فرعي (subroutine) مُتحقِّقة عند استدعائه. على سبيل المثال، إحدى الشروط المُسَبَّقة (precondition) للدالة Math.sqrt(x)‎ المبنية مُسْبَّقًا (built-in function) يَتمثَل في ضرورة أن تَكُون القيمة المُمرَّرة للمُعامِل (parameter)‏ x أكبر من أو تُساوِي الصفر؛ لأنه لا يُمكِن بطبيعة الحال حِسَاب الجذر التربيعي لعدد سالب. عمومًا، يُمثِل الشَّرْط المُسَبَّق (precondition) بمواصفة اصطلاحية معينة إلزامًا على مُستدعِي (caller) البرنامج الفرعي، أيّ أنه في حالة استدعائك لبرنامج فرعي معين دون تَوْفِية شَّرْطه المُسَبَّق، فما من سبب يَدْفَعك لتَوقع إِنجازه للمُهِمّة بشكل ملائم، فلرُبما يَنهار البرنامج أو يَكتفِي بإعادة نتائج غير صحيحة؛ لأنك ببساطة لم تَلتزِم بجانبك من الاتفاق، ولهذا لا تَلوّمن إلا نفسك.

في المقابل، الشروط اللاحقة (postcondition) هي بمثابة الجانب الآخر من المواصفة الاصطلاحية (contract)، حيث تُمثِل إلزامًا على البرنامج الفرعي ذاته، أي بفَرْض اِستيفاء الشروط المُسَبَّقة (preconditions) لبرنامج فرعي معين، وعدم احتوائه على أية أخطاء برمجية (bugs)، فلابُدّ من تَحقُّق شروطه اللاحقة بعد الاستدعاء. على سبيل المثال، الشَّرْط اللاحق للدالة Math.sqrt(x)‎ يَتمثَل في ضرورة تَساوِي كُلًا من مربع القيمة المُعادة من تلك الدالة (function) وقيمة المُعامِل (parameter) المُمرَّرة عند استدعاء البرنامج الفرعي، وهو ما سيَكُون صحيحًا فقط في حالة استيفاء شَّرْطها المُسَبَّق بخصوص كَوْن قيمة المُعامِل أكبر من أو تُساوِي الصفر. مثال آخر هو البرنامج الفرعي System.out.print(x)‎ المَبنِي مُسْبَّقًا، والذي يَتَمثَل شَّرْطه اللاحق (postcondition) بطباعة قيمة مُعامِله (parameter) المُمرَّرة على الشاشة.

عادة ما تَضَع الشروط المُسَبَّقة (preconditions) لأي برنامج فرعي قيودًا على قيم مُعاملاته، مثل البرنامج الفرعي Math.sqrt(x)‎، لكن لا يَقْتصِر دورها في الواقع على ذلك. قد تُشيِر تلك الشروط أيضًا إلى المُتَغيِّرات العامة (global variables) المُستخدَمة بالبرنامج الفرعي، أو قد تُحدِّد الحالة (state) التي ينبغي أن يَكُون عليها البرنامج عند استدعاء البرنامج الفرعي، وهو ما يَكُون مفيدًا إذا كانت عملية استدعاء ذلك البرنامج صالحة فقط بأوقات معينة.

من الناحية الآخرى، يُحدِّد الشَّرْط اللاحق (postcondition) لأي برنامج فرعي مُهِمّته المُوكَلة إليه. فمثلًا، ينبغي أن يَتضمَّن الشَّرْط اللاحق لأي دالة (function) القيمة المُعادة منها.

تُوصَف البرامج الفرعية أحيانًا باِستخدَام تعليقات (comments)، والتي ينبغي أن تُحدِّد شروط ذلك البرنامج المُسَبَّقة (preconditions) واللاحقة (postconditions). عندما تَستخدِم برنامجًا فرعيًا (subroutine) مكتوب مُسْبَّقًا، ستُخبرك تلك الشروط بكيفية اِستخدَام البرنامج بالإضافة إلى تِبيان الغرض منه. في المقابل، عندما تَكْتُب برنامجًا فرعيًا، ستَمنَحك تلك الشروط توصيفًا دقيقًا عما هو مُتوقَّع من ذلك البرنامج. سنَتَعرَّض خلال القسمين الفرعيين التاليين لمثال بسيط، سنَستخدِم فيه التعليقات لهذا الغرض، وستَكُون مَكْتوبة بصياغة تعليقات Javadoc مع عَنونة كُلًا من الشروط المُسَبَّقة واللاحقة. يرى كثير من علماء الحاسوب ضرورة إضافة وُسوم تَوْثيق جديدة ‎@precondition و ‎@postcondition إلى نظام Javadoc لعَنونة الشروط المُسَبَّقة واللاحقة بشكل صريح، ولكن لَمْ يَحدُث ذلك حتى الآن.

مثال عن عملية التصميم

سنُصمِّم الآن برنامجًا بالاعتماد على البرامج الفرعية بصورة أساسية، بمعنى أننا سنَستخدِم بعض البرامج الفرعية (subroutines) التي سُبق كتابتها كلَبِنة أساسية (building block)، كما أننا سنُصمِّم بعض البرامج الفرعية الجديدة التي قد نحتاجها لإكمال المشروع. فيما يَتعلَّق بالبرامج المَكْتوبة مُسْبَّقًا، سنَستخدِم واجهة برمجة تطبيقات (API) تَحتوِي على صنفين (classes) -كان الكاتب قد كَتَبَهما-، الصَنْف الأول هو Mosaic.java والذي بدوره يَعتمِد على الصنف الثاني MosaicCanvas.java. لاحظ ضرورة تَوفِّير كُلًا من الصنفين Mosaic و MosaicCanvas أثناء تَصْرِيف (compile) البرنامج وتَشْغِيله، مما يَعنِي وجود الملفين Mosaic.java و MosaicCanvas.java -أو ملفات الصَنْفين بَعْد التَصْرِيف- بنفس مجلد الصَنْف المُعرِّف للبرنامج.

يَسمَح لك الصَنْف Mosaic بالتَعْامُل مع نافذة (window) مُكوَّنة من مجموعة من المستطيلات الصغيرة المُلوَّنة، والمُرَتَّبة بصورة صفوف وأعمدة، حيث يَتضمَّن أكثر من عضو برنامج فرعي ساكن (static member subroutines) يُمكِن اِستخدَامها لأغراض فَتْح النافذة، وغَلْقها، بالإضافة إلى التَلاعب بمُحتوياتها. يُوفِّر الصَنْف بطبيعة الحال صندوق أدوات (toolbox) أو واجهة برمجة تطبيقات (API) تَتضمَّن تلك المجموعة من البرامج (routines)، والتي نسْتَعْرِض بالأسفل بعضًا منها، مُوثَّقة باِستخدَام تعليقات Javadoc. تَذَكَّر أن تعليق Javadoc يَسبِق العنصر المَعنِي بذلك التعليق.

/**
 * ‫افتح نافذة mosaic على الشاشة
 * ‫ينبغي أن يُستدعى هذا التابع قبل أي تابع آخر ضمن الصنف Mosaic
 * سينتهي البرنامج عندما يغلق المستخدم النافذة
 * 
 * ‫الشرط المسبق: المعاملات rows و cols و h و w هي أعداد صحيحة موجبة
 *
 * ‫‫الشرط اللاحق: تفتح نافذة على الشاشة والتي يمكنها عرض صفوف وأعمدة
 * ‫من المستطيلات الملونة بحيث يكون عرض المستطيل يساوي w وطوله يساوي h
 * ‫كما أن عدد الصفوف هو قيمة المعامل الأول الممررة بينما عدد الأعمدة
 * هو قيمة المعامل الثاني. مبدئيًا، تكون جميع المستطيلات سوداء
 *
 * ‫ملحوظة: الصفوف مرقمة من 0 وحتى rows - 1 بينما الأعمدة مرقمة 
 * ‫من 0 وحتى cols - 1 
 */
public static void open(int rows, int cols, int h, int w)


    /**
     * ضبط لون أحد مربعات النافذة
     * 
     * ‫الشرط المسبق: لابد أن كون المعاملين row و col ضمن النطاق المسموح
     * ‫به لرقمي الصف والعمود، كما لابد أن تقع كلا من المعاملات r و g و b ‫بين العددين 0 و 255
     * الشرط اللاحق: سيضبط لون المربع المخصص عن طريق رقمي الصف والعمود
     * الممررين إلى اللون المحدد عبر المعاملات الثلاثة‫ r و g و b
     * بحيث تعطي هذه المعاملات قيم اللون الأحمر والأخضر والأخضر
     * بنظام‫ RGB للمربع، قمثلًا يعطي المعامل r قيمة اللون الأحمر بحيث
     * تشير القيمة 0 إلى انعدام اللون الأحمر بينما تشير القيمة 255 إلى أكبر قدر ممكن من اللون
     */
    public static void setColor(int row, int col, int r, int g, int b)


    /**
     * ‫جلب قيمة اللون الأحمر بنظام RGB لأحد المربعات
     * 
     * ‫الشرط المسبق: لابد أن يقع كُلا من المعاملين row و col ضمن النطاق المسموح به
     * ‫الشرط اللاحق: إعادة قيمة اللون الأحمر بنظام RGB للمربع المخصص
     * ‫كعدد صحيح يقع بين العددين 0 و 255
     */
    public static int getRed(int row, int col)


    /**
     * ‫تعمل بنفس طريقة الدالة  getRed
     */
    public static int getGreen(int row, int col)


    /**
     * ‫تعمل بنفس طريقة الدالة  getRed
     */
    public static int getBlue(int row, int col)


    /**
     * لإبطاء تنفيذ البرنامج عبر الانتظار قليلًا 
     * 
     * الشرط المسبق: لابد أن يكون المعامل‫ milliseconds عددا موجبا
     * ‫الشرط اللاحق: سيتوقف البرنامج مؤقتًا لمدة تساوي الزمن الممرر
     * بوحدة المللي ثانية
     */
    public static void delay(int milliseconds)

تَذَكَّر أن البرامج الفرعية -بالأعلى- هي أعضاء (members) ضِمْن الصَنْف Mosaic، ولهذا ينبغي أن تَتضمَّن أسماء تلك البرامج (routine) اسم الصَنْف ذاته عند اِستدعائها بمكان يَقَع خارج الصنف Mosaic. على سبيل المثال، اِستخدِم الاسم Mosaic.isOpen()‎ بدلًا من الاسم isOpen.

لا تُحدِّد تعليقات البرامج الفرعية -بالأعلى- ما سيَحدُث في حالة عدم استيفاء شروطها المُسَبَّقة (preconditions). على الرغم من أن البرامج الفرعية (subroutine)، في العموم، غير مُلزَمة فعليًا بما هو مَكْتوب ضِمْن مواصفاتها الاصطلاحية (contract)، فسيَكُون من الجيد مَعرِفة ما سيَحدُث في مثل تلك الحالات. على سبيل المثال، يُبلِّغ البرنامجين الفرعيين setColor()‎ أو getRed()‎ عن حُدوث اعتراض من النوع IllegalArgumentException في حالة عدم استيفاء شَّرْطهما المُسَبَّق: "ضرورة وقوع كلًا من row و col ضِمْن النِطاق المَسموح به لرَقمي الصف والعمود على الترتيب." تَسمَح مَعرِفتك لمثل تلك الحقيقة بكتابة برامج يُمكِنها التقاط (catch) ذلك الاعتراض (exception)، ومُعالجته، ولهذا سيَكُون من المفيد تَوْثيق (document) تلك المعلومة من خلال إضافة الوَسْم التوثيقي (doc tag)‏ ‎@throws‎ إلى تعليق Javadoc. تَتَبقَّى أسئلة آخرى عديدة تَتَعلَّق بكيفية تَصرُّف البرنامج الفرعي ضِمْن حالات معينة. على سبيل المثال، ماذا سيَحدُث إذا استدعينا البرنامج الفرعي Mosaic.open()‎ بينما هنالك نافذة مفتوحة بالفعل على الشاشة؟ في تلك الحالة، سيتجاهل البرنامج الفرعي عملية الاستدعاء الثانية. في الواقع، يَصعُب عادة إعداد تَوْثيق كامل على تلك الشاكلة، وأحيانًا ستحتاج إلى مُجرَّد تجربة حالة معينة لترى بنفسك كيفية تَصرُّفها، أو قد تَضطرّ أحيانًا للإطلاع على كامل الشيفرة المصدرية (source code) في حالة تَوفُّرها.

تَتلخَّص فكرة البرنامج الذي سنقوم بتَطويره بكتابة تحريكة (animation) تَعتمِد على الصنف Mosaic. ببساطة، سنَملْئ نافذة بمربعات مُلوَّنة عشوائيًا، وبعدها، سنُغيِّر تلك الألوان عشوائيًا ضِمْن حَلْقة تَكْرار (loop) تستمر طالما كانت النافذة مفتوحة. قد تَعنِي عبارة "تَغْيِير الألوان عشوائيًا" الكثير من الأشياء المختلفة. المقصود هنا هو إِحداث ما يُشبِه "تشويش (disturbance)" يتحرك عبر النافذة، بحيث يُغيِّر ذلك التشويش من لون أيّ مربع يُواجهه. تُوضِح الصورة التالية ما قد تبدو عليه النافذة بلحظة معينة:

001Mosaic_Window.png

مع اعتمادنا على البرامج الفرعية (routines) ضِمْن الصنف Mosaic، نستطيع كتابة التَصوُّر المَبدئي للبرنامج كالتالي:

// ‫افتح نافذة Mosaic
Open a Mosaic window
// اِملئ النافذة بألوان عشوائية
Fill window with random colors
// تجول وغير لون المربعات عشوائيًا
Move around, changing squares at random

تبدو الخطوة "اِملئ النافذة بألوان عشوائية" كمُهِمّة (task) مُترابطة يُمكِن فصلها بصورة مستقلة، ولهذا سنَكتُب برنامجًا فرعيًا (subroutine) يَتولَى مسئولية تَّنْفيذ تلك المُهِمّة. في المقابل، يُمكِن إضافة مزيد من التفاصيل للخطوة الثالثة بحيث تُكتَب على عدة خطوات: اِبدأ من منتصف النافذة، ثُمَّ اِستمر بالتحرك إلى مربعات جديدة، وغَيّر لون تلك المربعات. ينبغي الاستمرار بتَّنْفيذ تلك الخطوات طالما كانت النافذة مفتوحة. يُمكِننا إعادة كتابة الخوارزمية (algorithm) لتُصبِح كالتالي:

// ‫افتح نافذة Mosaic
Open a Mosaic window
// اِملئ النافذة بألوان عشوائية
Fill window with random colors
// اضبط المَوضع الحالي إلى المربع بمنتصف النافذة
Set the current position to the middle square in the window
// طالما كانت النافذة مفتوحة
As long as the mosaic window is open:
   // غير لون المربع بالمَوضع الحالي عشوائيًا
   Randomly change color of the square at the current position
   // حرك الموضع الحالي لأعلى أو لأسفل أو لليسار أو لليمين عشوائيًا
   Move current position up, down, left, or right, at random

سنَستخدِم مُتَغيِّرين من النوع الصحيح int هما currentRow و currentColumn؛ لتَمثيِل "المَوْضِع الحالي (current position)"، سيَحمِلان رقمي الصف (row number) والعمود (column number) للمربع الحالي الذي يُطبَق عليه التشويش (disturbance). لما كانت نافذة mosaic مُكوَّنة من ١٦ صف و ٢٠ عمود من المربعات، يُمكِننا تهيئة قيمة "المَوضِع الحالي" المَبدئية إلى منتصف النافذة عن طريق ضَبْط المُتَغيِّرين currentRow و currentColumn إلى القيمتين ٨ و ١٠ على الترتيب. سنَلجأ لاستخدام البرنامج الفرعي Mosaic.open()‎ لفَتْح النافذة، كما سنَكتُب برنامجين فرعيين (subroutines) إضافيين لإِنجاز مُهِمّتين ضِمْن حَلْقة التَكْرار while بحيث يَظلّ البرنامج main()‎ بسيطًا. نستطيع الآن تَحْوِيل الخوارزمية (algorithm) إلى الشيفرة التالية بلغة الجافا:

Mosaic.open(16,20,25,25)
fillWithRandomColors();
currentRow = 8;       // الصف الواقع بمنتصف النافذة
currentColumn = 10;   // العمود الواقع بمنتصف النافذة
while ( true ) { // ينتهي البرنامج عند غلق النافذة
    changeToRandomColor(currentRow, currentColumn);
    randomMove();      
}

سنُجرِي أيضًا تَعْديلًا إضافيًا، وهو إِبطاء التحريكة (animation) قليلًا، ولذلك سنُضِيف السطر Mosaic.delay(10);‎ إلى حَلْقة التَكْرار while.

انتهينا الآن من إِعداد البرنامج main()‎، لكننا نَحتاج لكتابة البرامج الفرعية fillWithRandomColors()‎ و changeToRandomColor(int,int)‎ و randomMove()‎ لنُكمِل البرنامج. تُعدّ عملية كتابة كل برنامج منها بمَثابة مُهِمّة صغيرة مُنفصلة. سنبدأ بالبرنامج الفرعي fillWithRandomColors()‎ والمَسئول عن ضمان تَحقُّق الشَّرْط اللاحق (postcondition) التالي: "سيَتغيَّر لون كل مربع بالنافذة عشوائيًا." يُمكِننا كتابة خوارزمية (algorithm) لإِنجاز تلك المُهِمّة بأسلوب الشيفرة الوهمية (pseudocode) كالتالي:

// لكل صف
For each row:
   // لكل عمود
   For each column:
         // غير لون المربع بذلك الصف والعمود إلى لون عشوائي
      set the square in that row and column to a random color

يُمكِن تَّنْفيذ (implement) السَطْرين "لكل صف"، و"لكل عمود" -بالأعلى- باِستخدَام حَلْقة التَكْرار for. أما بخصُوص السطر الأخير، فلقد قَررنا بالفعل كتابة البرنامج الفرعي changeToRandomColor(int,int)‎ المَسئول عن ضَبْط اللون. لاحِظ أن إِمكانية إعادة اِستخدَام برنامج فرعي معين بعدة مَواضِع يُعدّ أحد أهم مَكاسِب اِستخدَام البرامج الفرعية. يُمكِننا الآن كتابة البرنامج الفرعي fillWithRandomColors()‎ بلغة الجافا كالتالي:

static void fillWithRandomColors() {
    int row, column;
    for ( row = 0; row < 16; row++ )
        for ( column = 0; column < 20; column++ )
            changeToRandomColor(row,column);
}

سننتقل الآن إلى البرنامج الفرعي التالي changeToRandomColor(int,int)‎. يُوفِّر الصَنْف Mosaic بالفعل التابع (method)‏ Mosaic.setColor()‎، والمُستخدَم لتَغْيِير لون المربع.لمّا كنا نُريد لونًا عشوائيًا، فسنحتاج إلى اختيار قيم عشوائية لكل من r و g و b، والتي ينبغي أن تَكُون أعدادًا صحيحة (integers) مُتراوحة بين العددين ٠ و ٢٥٥، وفقًا للشَّرْط المُسَبَّق (precondition) للبرنامج الفرعي Mosaic.setColor()‎، ولهذا سنَستخدِم المُعادلة (int)(256*Math.random()‎) لاختيار مثل تلك الأعداد. بالتالي، يُمكِننا كتابة البرنامج الفرعي المُستخدَم لتَغْيِير اللون عشوائيًا كالتالي:

static void changeToRandomColor(int rowNum, int colNum) {
    int red = (int)(256*Math.random());
    int green = (int)(256*Math.random());  
    int blue = (int)(256*Math.random());
    Mosaic.setColor(rowNum,colNum,red,green,blue);  
}

وأخيرًا، يُمكِننا الانتقال إلى البرنامج الفرعي randomMove()‎، والمَسئول عن تحريك التشويش (disturbance) عشوائيًا، لأعلى، أو لأسفل، أو يسارًا، أو يمينًا. سنَستخدِم عددًا عشوائيًا يَتراوح بين القيمتين ٠ و ٣؛ لإجراء الاختيار ما بين الاتجاهات الأربعة، فمثلًا، عندما تَكُون قيمة العدد مُساوِية للصفر، سيَتحرَك التشويش باتجاه معين، أما إذا كانت قيمته مُساوِية للواحد، فسيَتحرَك باتجاه آخر، وهكذا. لمّا كنا نَستخدِم المُتَغيِّرين currentRow و currentColumn لتحديد المَوضِع الحالي للتشويش، فإن تحريك ذلك المَوضِع لأعلى يَعنِي بالضرورة إِنقاص قيمة المُتَغيِّر currentRow بمقدار الواحد. يَترك ذلك استفهامًا عما يُفْترَض حُدوثه عندما يَصِل المُتَغيِّر currentRow إلى القيمة -١؛ خُصوصًا وأن ذلك سيَتَسبَّب بإخفاء التشويش خارج النافذة، وهو ما سينتهك الشَّرْط المُسَبَّق (precondition) لكثير من البرامج الفرعية ضِمْن الصنف Mosaic، ولهذا سنُحرِك التشويش إلى الحافة المُضادة من النافذة من خلال ضَبْط قيمة المُتَغيِّر currentRow إلى ١٥ (تَذَكَّر أن النافذة مُكوَّنة من ١٦ صف مُرقَّمَين من ٠ وحتى ١٥). بدلًا من القفز إلى الحافة الآخرى، نستطيع أيضًا تَجاهُل تلك الحالة. يُمكِننا معالجة تحريك التشويش (disturbance) للاتجاهات الثلاثة الأخرى بنفس الطريقة، بحيث نستعين بتَعْليمَة switch لتَحْديد الاتجاه ذاته. اُنظر شيفرة البرنامج الفرعي randomMove()‎:

int directionNum;
directionNum = (int)(4*Math.random());
switch (directionNum) {
    case 0:  // تحرك لأعلى
        currentRow--;
        if (currentRow < 0)   // إذا أصبح الموضع الحالي خارج النافذة
            currentRow = 15;   // قم بتحريكه للحافة المضادة
        break;
    case 1:  // تحرك لليمين
        currentColumn++;
        if (currentColumn >= 20)
            currentColumn = 0;
        break; 
    case 2:  // تحرك لأسفل
        currentRow++;
        if (currentRow >= 16)
            currentRow = 0;
        break;
    case 3:  // تحرك لليسار
        currentColumn--;
        if (currentColumn < 0)
            currentColumn = 19;
        break; 
}

البرنامج

انتهينا الآن من كتابة جميع البرامج الفرعية، وتَبقَّى لنا تَجْميعها معًا بحيث نَحصُل على البرنامج كاملًا، بالإضافة إلى كتابة تعليقات Javadoc للصنف ذاته ولبرامجه الفرعية. لاحظ أننا قد عَرَّفنا المُتَغيِّرين currentRow و currentColumn كأعضاء ساكنة (static members) ضِمْن الصنف، وليس كمُتَغيِّرات محليّة (local)؛ وذلك لكَوْنهما مُستخدَمين ضِمْن أكثر من مُجرَّد برنامج فرعي (subroutines) واحد. تَتَوفَّر نسخة من الشيفرة المصدرية للبرنامج بالملف RandomMosaicWalk.java، وانتبه لكَوْنه يَعتمِد على كُلًا من الملفين Mosaic.java و MosaicCanvas.java.

/**
 * يفتح هذا البرنامج نافذة مليئة بالمربعات الملونة عشوائيًا
 * بحيث يتحرك نوع من "التشويش" عشوائيًا عبر الشاشة ويغير من 
 * لون أي مربع يواجهه بشكل عشوائي. يستمر البرنامج في العمل 
 * طالما كانت النافذة مفتوحة
 */
public class RandomMosaicWalk {

    static int currentRow;    // رقم الصف المعرض للتشويش
    static int currentColumn; // رقم العمود المعرض للتشويش

    /**
     * ‫يُنشيء برنامج main النافذة ويملؤها بألوان عشوائية
     * ثم يحرك التشويش بصورة عشوائية عبر النافذة طالما كانت مفتوحة
     */
    public static void main(String[] args) {
        Mosaic.open(16,20,25,25);
        fillWithRandomColors();
        currentRow = 8;   // ابدأ بمنتصف النافذة
        currentColumn = 10;
        while (true) {
            changeToRandomColor(currentRow, currentColumn);
            randomMove();
            Mosaic.delay(10);  // احذف هذا السطر لزيادة سرعة التحريكة
        }
    }  // نهاية main

    /**
     * يملأ النافذة بمربعات ملونة عشوائيا
     *
     * الشرط المسبق: لابد أن تكون النافذة مفتوحة
     * الشرط اللاحق: سيصبح كل مربع النافذة ملون بصورة عشوائية
     */
    static void fillWithRandomColors() {
        int row, column;
        for ( row=0; row < 16; row++ ) {
            for ( column=0; column < 20; column++ ) {
                changeToRandomColor(row, column);  
            }
        }
    }  // نهاية fillWithRandomColors

    /**
     * يغير من لون مربع معين بالنافذة عشوائيًا
     *
     * الشرط المسبق: لابد أن يقع رقمي الصف والعمود الممررين ضمن
     * النطاق المسموح به لأرقام الصف والعمود
     * الشرط اللاحق: سيتغير لون المربع المخصص بواسطة رقمي الصف والعمود
     *  
     * @param rowNum رقم الصف للمربع بحيث يبدأ عد الصفوف من الأعلى
     * @param colNum رقم الصف للمربع بحيث يبدأ عد الأعمدة من اليسار
     */
    static void changeToRandomColor(int rowNum, int colNum) {
        // اختر قيم عشوائية تتراوح بين 0 و 255
        // لقيم الألوان الثلاثة (الأحمر، والأزرق، والأخضر‫) ‫بنظام الألوان RGB

        int red = (int)(256*Math.random());    
        int green = (int)(256*Math.random());  
        int blue = (int)(256*Math.random());   
        Mosaic.setColor(rowNum,colNum,red,green,blue);  
    }  // ‫نهاية changeToRandomColor

    /**
     * يحرك التشويش عبر النافذة
     * 
     * الشرط المسبق: لابد أن تكون المتغيرات العامة‫ currentRow و 
     * ‫currentColumn ضمن النطاق المسموح به لرقمي الصف والعمود
     * الشرط اللاحق: يتغير رقمي الصف والعمود إلى أحد المواضع المجاورة 
     * سواء للأعلى أو للأسفل أو لليسار أو لليمين
     */
    static void randomMove() {
        int directionNum; 

        directionNum = (int)(4*Math.random());
        switch (directionNum) {
            case 0:  // move up 
                currentRow--;
                if (currentRow < 0)
                    currentRow = 15;
                break;
            case 1:  // move right
                currentColumn++;
                if (currentColumn >= 20)
                    currentColumn = 0;
                break; 
            case 2:  // move down
                currentRow ++;
                if (currentRow >= 16)
                    currentRow = 0;
                break;
            case 3:  // move left  
                currentColumn--;
                if (currentColumn < 0)
                    currentColumn = 19;
                break; 
        }
    }  // ‫نهاية randomMove

} // ‫نهاية الصنف RandomMosaicWalk

ترجمة -بتصرّف- للقسم Section 7: More on Program Design من فصل Chapter 4: Programming in the Large I: Subroutines من كتاب Introduction to Programming Using Java.


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...