لوحة المتصدرين
المحتوى الأكثر حصولًا على سمعة جيدة
المحتوى الأعلى تقييمًا في 10/24/21 في كل الموقع
-
2 نقاط
-
تَسمَح بعض اللغات البرمجية -بل غالبيتها في الواقع- بتعريف البرامج الفرعية (subroutines) بصورة مستقلة خارج أي صَنْف، لكن تختلف لغة الجافا عنهم في تلك النقطة، حيث لابُدّ أن يُعرَّف أيّ برنامج فرعي (subroutine) بلغة الجافا ضِمْن صَنْف (class). الهدف من الصَنْف عمومًا هو تجميع البرامج الفرعية والمُتَغيِّرات المُرتبطة معًا ضِمْن وحدة واحدة. نظرًا لأن البرامج المكتوبة بلغة الجافا عادة ما تَستخدِم عددًا ضخمًا من البرامج الفرعية المَكْتوبة بواسطة مبرمجين مختلفين، فإن احتمالية الخلط بين أسمائها يَكُون كبيرًا، لذا حَرَص مُصمِّمي الجافا على وَضع تقييد حازم على الطرق التي يُمكِن بها تسمية الأشياء، ومن هنا كانت ضرورة تعريف البرامج الفرعية وتجميعها ضِمْن أصناف (classes) لها اسم وبالتبعية تجميع تلك الأصناف ضِمْن حزم (packages) لها اسم أيضًا كما سنرى لاحقًا. تُميز لغة الجافا بشكل واضح بين البرامج الفرعية الساكنة (static) وغَيْر الساكنة (non-static). يُمكِن لأي صَنْف أن يَتضمَّن كِلا النوعين، ولكن كيفية اِستخدَامهما مختلفة تمامًا. تُعدّ البرامج الفرعية الساكنة (static subroutine) عمومًا أسهل في الفهم، فببساطة أي برنامج فرعي ساكن هو عضو (member) ينتمي للصنف ذاته المُعرَّف بداخله. في المقابل، تعريف البرامج الفرعية غَيْر الساكنة (non-static subroutine) موجود فقط ضِمْن الصَنْف حتى تَتَمكَّن كائنات (objects) ذلك الصنف -عند إنشائها- من اِستخدَام تلك البرامج الفرعية، وتُصبِح عندها تلك البرامج أعضاء بتلك الكائنات (objects). يُمكِن تطبيق ذلك الاختلاف على المُتَغيِّرات (variables) الساكنة وغَيْر الساكنة بنفس الكيفية، بل وعلى أي شيء آخر قد يَقع ضمن تعريف الأصناف (class definition). سنتناول في هذا الفصل كُلًا من البرامج الفرعية الساكنة والمُتَغيِّرات الساكنة فقط، وسننتقل بالفصل التالي إلى البرمجة كائنية التوجه (object-oriented programming) وعندها سنستطيع مناقشة الأعضاء غَيْر الساكنة. عادة ما يُطلَق مصطلح "التابع (method)" على البرامج الفرعية (subroutines) المُعرَّفة ضِمْن صَنْف، وهو الاسم الذي يُفضِّله غالبية مبرمجي الجافا مع أن المصطلحان عمومًا مترادفان خاصة فيما يَتعلَّق بلغة الجافا. ستَجِدْ أيضًا البعض يَستخدِم مصطلحات آخرى للإشارة إلى البرامج الفرعية مثل "الإجراء (procedure)" أو "الدالة (function)". يُفضِّل الكاتب الاستمرار باِستخدَام المصطلح الأعم "البرنامج الفرعي (subroutine)" ضِمْن هذا الفصل للإشارة إلى البرامج الفرعية الساكنة على الأقل، ولكنه سيبدأ في اِستخدَام مصطلح "التابع (method)" من حين لآخر، كما أنه سيَستخدِم مصطلح "الدالة (function)" فقط للإشارة إلى البرامج الفرعية التي تَحسِب قيمة وتُعيدها مع أن بعض اللغات البرمجية الآخرى تَستخدِم ذلك المصطلح للإشارة إلى البرامج الفرعية (subroutines) بمفهومها الأعم. تعريف البرامج الفرعية ينبغي أن يُعرَّف (define) أيّ برنامج فرعي (subroutine)، تَنوِي اِستخدَامه، بمكان ما بالبرنامج، بحيث يَتضمَّن ذلك التعريف (subroutine definition) كُلًا من الآتي: اسم البرنامج الفرعي (subroutine name)، والمعلومات اللازمة لاستدعائه (subroutine call)، وأخيرًا، الشيفرة الفعليّة المطلوب تَّنْفيذها مع كل عملية استدعاء. يُكتَب تعريف البرنامج الفرعي (subroutine definition) بالجافا بالصياغة التالية: <modifiers> <return-type> <subroutine-name> ( <parameter-list> ) { <statements> } قد تكون الصيغة بالأعلى مألوفة بالنسبة لك؛ فقد تَعرَّضنا بالفعل خلال الفصول السابقة لبعض البرامج الفرعية، مثل البرنامج main() المُعرَّف بأيّ برنامج، والبرنامج drawFrame() ببرامج التحريكة (animation) بالقسم ٣.٩. ومع ذلك، لاِستيعاب ما تَعنيه تلك الصيغة تفصيليًا، سنحتاج إلى مُناقشتها باستفاضة، وهو ما سنفعله بغالبية هذا الفصل. أولًا، تُكوِّن التَعْليمَات -الواقعة بين القوسين المعقوصين {}- مَتْن البرنامج الفرعي (subroutine body)، وتُمثِل الجزء التَّنْفيذي (implementation) أو الداخلي "للصندوق الأسود (black box)" كما ذَكَرنا بالقسم السابق. يُنفِّذ الحاسوب تلك التَعْليمَات عند إجراء عملية اِستدعاء لذلك البرنامج (أو التابع [method]). لاحظ أن تلك التَعْليمَات قد تَتكوَّن من أي تَعْليمَة قد تَعرَّضنا لها خلال الفصلين الثاني والثالث. ثانيًا، تُكتَب المُبدِّلات ببداية التعريف، وهي عبارة عن كلمات تُستخدَم لضَبْط بعض خاصيات البرنامج الفرعي، مثل ما إذا كان ساكنًا (static) أم غير ساكن (non-static). تُوفِّر لغة الجافا عمومًا ستة مُبدِّلات (modifiers) يُمكنك اِستخدَامها، ولقد تَعرَّضنا لاثنين منها فقط حتى الآن، وهي المُبدِّل الساكن static، والمُبدِّل العام public. ثالثًا، يُستخدَم النوع المُعاد لتَخْصِيص نوع القيمة المُعادة من البرنامج الفرعي. تحديدًا، إذا كان البرنامج الفرعي بالأساس هو عبارة عن دالة (function) تَحسِب قيمة معينة، عندها قد تَستخدِم أي نوع، مثل String أو int أو حتى أنواع المصفوفة مثل double[]. أما إذا كان البرنامج الفرعي ليس بدالة، أيّ لا يُعيد قيمة، فعندها تُستخدَم القيمة الخاصة void، والتي تُشير إلى عدم وجود قيمة مُعادة، أيّ أنها إِما أن تَكُون فارغة (empty) أو غَيْر موجودة. سنتناول الدوال (functions)، والأنواع المُعادة (return types) تفصيليًا بالقسم ٤.٤. وأخيرًا، قائمة المُعامِلات الخاصة بالتابع (method). تُعدّ المُعامِلات جزءًا من وَاجهة البرنامج الفرعي (subroutine interface)، وتُمثِل المعلومات المُمرَّرة (passed) إليه من الخارج، والتي قد يَستخدِمها لحِسَاب بعض العمليات الداخلية الخاصة به. فمثلًا، بفَرْض وجود الصَنْف Television، والذي يَتضمَّن التابع changeChannel(). في تلك الحالة، يُفْترَض أن يُطرَح سؤالًا تلقائيًا عن رقم القناة التي تُريد من البرنامج الفرعي الانتقال إليها، وهنا يأتي دور المُعامِلات (parameter)؛ حيث يُمكِن اِستخدَام مُعامِل للإجابة على مثل هذا السؤال، ولمّا كان رقم القناة هو عدد صحيح، فسيَكُون نوع ذلك المُعامِل (parameter type) هو int، ولهذا يُمكِنك التَّصْريح (declaration) عن التابع changeChannel() كالتالي: public void changeChannel(int channelNum) { ... } يُشير التَّصْريح بالأعلى إلى أن التابع changeChannel() لديه مُعامِل يَحمِل اسم channelNum من النوع العددي الصحيح int. لاحظ أن قيمة المُعامِل channelNum غير مُتوفِّرة حتى الآن، وإنما تَتوفَّر عند عملية الاستدعاء الفعليّ للتابع الفرعي، على سبيل المثال: changeChannel(17);. قد تتكوَّن قائمة المُعامِلات (parameter list) -بتعريف البرنامج الفرعي- من تَّصْريح عن مُعامِل (parameter declaration) واحد أو أكثر، وذلك على الصورة ، بحيث يَتكوَّن كل تَّصْريح من مُعامِل وحيد، كما يُفصَل بين كل تَّصْريح والذي يليه باستخدام فاصلة (comma). على سبيل المثال، في حالة أردت التَّصْريح عن مُعامِلين من النوع double، فستضطر إلى كتابة double x, double y وليس double x, y. لاحِظ أن قائمة المُعامِلات قد تَكُون فارغة أيضًا. سنتناول المُعامِلات (parameters) تفصيليًا بالقسم التالي. ها هي التعريفات الخاصة ببعض البرامج الفرعية (subroutine definitions)، ولكن بدون الجزء التَّنْفيذي (implementation) منها، أي بدون التَعْليمَات الفعليّة التي تُعرِّف ما تُنفِّذه تلك البرامج الفرعية: public static void playGame() { // المبدلات هي public و static // النوع المعاد هو void // اسم البرنامج الفرعي هو playGame // قائمة المعاملات فارغةً } int getNextN(int N) { // لا يوجد مبدلات // النوع المعاد هو int // اسم البرنامج الفرعي هو getNextN // قائمة المعاملات تتضمن معامل اسمه N من النوع int } static boolean lessThan(double x, double y) { // المبدلات هي static // النوع المعاد هو boolean // اسم البرنامج الفرعي هو lessThan // قائمة المعاملات تتضمن معاملين x و y كلاهما من النوع double } بالمثال الثاني بالأعلى، لمّا كان تعريف التابع getNextN لا يَتضمَّن المُبدل static، فإنه يُعدّ بصورة افتراضية تابعًا غَيْر ساكن (non-static method)، ولذلك لن نَفْحصه بهذا الفصل! اُستخدِم المُبدل public بالمثال الأول، والذي يُشير إلى إِمكانية استدعاء التابع (method) من أي مكان بالبرنامج، حتى من خارج الصَنْف (class) الذي عُرِّف فيه هذا التابع. في المقابل، يَتوفَّر المُبدل private، والذي يُشير إلى إمكانية استدعاء التابع من داخل نفس الصَنْف فقط. يُطلَق على كُلًا من المُبدلين public و private مُحدِّدات الوصول أو مُبدِّلات الوصول (access specifiers/access modifier). يَتوفَّر مُبدل وصول (access modifier) أخير، هو protected، والذي ستتضح أهميته عندما ننتقل إلى البرمجة كائنية التَوجه (object-oriented programming) بالفصل الخامس. في حالة عدم تخصيص مُحدِّد وصول لتابع معين، فإنه، وبصورة افتراضية، يُصبِح قابلًا للاستدعاء من أيّ مكان بالحزمة (package) التي تَتضمَّن صَنْفه، ولكن ليس من خارج تلك الحزمة. سنُناقش الحزم (packages) خلال هذا الفصل، تحديدًا بالقسم ٤.٦. يَتَّبِع البرنامج main()، المُعرَّف بأي برنامج، نفس قواعد الصيغة (syntax rules) المُعتادة لأي برنامج فرعي: public static void main(String[] args) { ... } بفَحْص التعريف بالأعلى، تَجد أن المُبدلات المُستخدَمة هي public و static، أما النوع المُعاد فهو void، أما اسم البرنامج الفرعي فهو main، وأخيرًا، قائمة المعاملات هي String[] args، أيّ أن نوع المُعامِل المُمرَّر هو نوع المصفوفة String[]. إذا كنت قد قرأت الفصول السابقة، فلديك بالفعل الخبرة الكافية لكتابة الجزء التَّنْفيذي من البرنامج الفرعي (implementation of a subroutine). في هذا الفصل، سنتعلَّم كتابة التَعرِيف بالكامل بما في ذلك جزء الواجهة (interface). استدعاء البرامج الفرعية يُعدّ تعريف البرنامج الفرعي (subroutine definition) بمثابة إعلام للحاسوب بوجود ذلك البرنامج الفرعي وبالوظيفة التي يُؤديها، لكن يُرجئ التَّنْفيذ الفعليّ للبرنامج الفرعي إلى حين استدعائه (call). يَنطبِق ذلك حتى على البرنامج (routine) main()، والذي يَستدعيه النظام (system) -لا أنت- عند تَّنْفيذه للبرنامج (program) ككل. تُستخدَم، مثلًا، تَعْليمَة استدعاء البرنامج الفرعي (subroutine call) التالية؛ لاستدعاء التابع playGame() المذكور بالأعلى: playGame(); يُمكن عمومًا كتابة تَعْليمَة الاستدعاء بالأعلى بأيّ مكان داخل نفس الصَنْف (class) الذي عَرَّف التابع playGame()، سواء كان ذلك بالتابع main()، أو بأيّ برنامج فرعي آخر. علاوة على ذلك، لمّا كان التابع playGame() مُعرَّف باِستخدَام المُبدل public، وهو ما يَعنِي كَوْنه تابعًا عامًا، فإنه من الممكن لأيّ صَنْف آخر استدعائه أيضًا، ولكن ينبغي أن تُعلِم الحاسوب، في تلك الحالة، باسم التابع كاملًا أثناء الاستدعاء، أيّ بذكر الصَنْف الذي ينتمي إليه ذلك التابع. ولأن التابع playGame() مُعرَّف باِستخدَام المُبدل static، وهو ما يَعنِي كَوْنه تابعًا ساكنًا، فإن اسمه الكامل يَتضمَّن اسم الصنف ذاته المُعرَّف بداخله. على سبيل المثال، إذا كان التابع playGame() مُعرَّف بالصَنْف Poker، تُستخدَم التَعْليمَة التالية لاستدعائه من خارج هذا الصَنْف: Poker.playGame(); يُعلِم اسم الصَنْف -بالأعلى- الحاسوب بأيّ صَنْف ينبغي له أن يَجِد التابع. بالإضافة إلى ذلك، يُساعدنا وجود اسم الصَنْف أثناء الاستدعاء على التمييز بين التابع Poker.playGame() وأي توابع اخرى لها نفس الاسم ومُعرَّفة بأصناف آخرى، مثل Roulette.playGame() أو Blackjack.playGame(). تُكتَب عمومًا تَعْليمَة استدعاء أي برنامج فرعي ساكن static (static subroutine call) بالجافا بالصيغة التالية إذا كان البرنامج الفرعي المُستدعَى مُعرَّفًا بنفس الصَنْف (class): <subroutine-name>(parameters); أو كالتالي إذا كان البرنامج الفرعي مُعرَّفًا بصَنْف آخر: <class-name>.<subroutine-name>(parameters); يَختلف ذلك عن التوابع غَيْر الساكنة (non-static methods) -سنتناولها لاحقًا-، والتي تنتمي إلى كائنات (objects) وليس أصناف (classes)، ولهذا يُستدعَى ذلك النوع من التوابع من خلال الكائنات (objects) ذاتها لا من خلال أسماء الأصناف. لاحظ أنه في حين يُمكِن لقائمة المُعامِلات (parameter list) أن تَكُون فارغة (empty)، كما هو الحال بالمثال playGame()، فإن كتابة الأقواس (parentheses) بتَعْليمَة الاستدعاء ما تزال ضرورية حتى مع كَوْن ما بينها فارغًا. أما في حالة تخصيص مُعامِل (parameter) أو أكثر بقائمة المُعامِلات (parameter list) بالتعريف الخاص ببرنامج فرعي ما (subroutine definition)، فينبغي عمومًا أن يَتطابَق عدد المُعامِلات المُمرَّرة أثناء استدعاء ذلك البرنامج الفرعي مع العَدَدَ المُخصَّص بذلك التعريف، كما لابُدّ بطبيعة الحال أن تَتطابَق أنواع تلك المُعامِلات المُمرَّرة بتَعْليمَة الاستدعاء مع نوعها المُناظِر بنفس ذلك التعريف. البرامج الفرعية بالبرامج سنُعطي الآن مثالًا عما قد يبدو عليه البرنامج (program) عند تَضمُّنه لبرنامج فرعي آخر غَيْر البرنامج main(). سنَكتُب تحديدًا برنامجًا للعبة تخمين، يَختار فيه الحاسوب عددًا عشوائيًا بين العددين ١ و ١٠٠، ثُمَّ يُحاول المُستخدِم تخمين ذلك العدد، ليُخبره الحاسوب بَعْدها عما إذا كان تخمينه أكبر أو أقل أو يُساوِي العَدَد الصحيح، وبحيث يَفوز المُستخدِم بالمباراة إذا تَمكَّن من تخمين العدد الصحيح خلال ٦ تخمينات كحد أقصى. أخيرًا، يَستطيع المُستخدِم اختيار الاستمرار بلعب مباراة إضافية بنهاية كل مباراة. لمّا كانت كل مباراة هي بالنهاية مُهِمّة مُترابطة مُفردة، كان بديهيًا كتابة برنامج فرعي playGame() بهدف لعب مباراة تخمين واحدة مع المُستخدِم. سيَستخدِم البرنامج main() حَلْقة تَكْرار (loop)، والتي ستَستدعِي ذلك البرنامج الفرعي مع كل مرة يختار فيها المُستخدِم الاستمرار بلعب مباراة إضافية. نحتاج الآن إلى تصميم البرنامج الفرعي playGame() وكتابته، وفي الواقع، يُصمَّم أي برنامج فرعي بنفس طريقة تَصْميم البرنامج main()، أيّ نبدأ بكتابة توصيف للخوارزمية (algorithm)، ثم نُطبِق التَصْميم المُتدرج (stepwise refinement). اُنظر الخوارزمية التالية لبرنامج لعبة التخمين مكتوبًا بالشيفرة الوهمية: // اختر عددًا عشوائيًا Pick a random number // طالما لم تنته المباراة while the game is not over: // اقرأ تخمين المستخدم Get the user's guess // اخبر المستخدم عما إذا كان تخمينه صحيحًا أم أكبر أم أقل Tell the user whether the guess is high, low, or correct. يُعدّ الاختبار "طالما لم تنته المباراة" معقدًا نوعًا ما؛ وذلك لأن المباراة قد تنتهي لسببين: إما لأن تخمين المُستخدِم كان صحيحًا أو لوصوله للحد الأقصى من عدد التخمينات المُمكنة، أيّ ٦. أحد أسهل الطرائق للقيام بذلك هو اِستخدَام حَلْقة تَكْرار لا نهائية while (true) تحتوي على تَعْليمَة break؛ بهدف إِنهاء الحَلْقة في الوقت المناسب. لاحِظ أننا سنحتاج إلى الاحتفاظ بعَدَد تخمينات المُستخدِم؛ حتى نَتمكَّن من إِنهاء المباراة في حالة وصول المُستخدِم للحد الأقصى من التخمينات. تُصبِح الخوارزمية كالتالي بعد إِجراء التعديلات: // أسند قيمة عشوائية بين 1 و 100 إلى المتغير computersNumber Let computersNumber be a random number between 1 and 100 // اضبط قيمة العداد المستخدم لعدّ عدد تخمينات المستخدم Let guessCount = 0 // استمر بتنفيذ الآتي while (true): // اقرأ تخمين المستخدم Get the user's guess // أزد قيمة العداد بمقدار الواحد Count the guess by adding 1 to guess count // إذا كان تخمين المستخدم صحيحًا if the user's guess equals computersNumber: // بلغ المستخدم بفوزه بالمباراة Tell the user he won // اخرج من الحلقة break out of the loop // إذا وصل العداد للحد الأقصى 6 if the number of guesses is 6: // بلغ المستخدم بخسارته للمباراة Tell the user he lost // اخرج من الحلقة break out of the loop // إذا كان كان تخمين المستخدم أقل من العدد if the user's guess is less than computersNumber: // بلغ المستخدم بكَوْن التخمين أقل من العدد Tell the user the guess was low // إذا كان كان تخمين المستخدم أعلى من العدد else if the user's guess is higher than computersNumber: // بلغ المستخدم بكَوْن التخمين أكبر من العدد Tell the user the guess was high يُستخدَم التعبير (int)(100 * Math.random()) + 1 لاختيار عدد عشوائي يقع بين العددين ١ و ١٠٠. اُنظر الشيفرة التالية بلغة الجافا، والتي تَتضمَّن تعريف البرنامج playGame() بعد التَّصْريح عن المُتَغيِّرات (variable declarations): static void playGame() { int computersNumber; // العدد العشوائي int usersGuess; // إحدى تخمينات المستخدم int guessCount; // عدد تخمينات المستخدم computersNumber = (int)(100 * Math.random()) + 1; guessCount = 0; System.out.println(); System.out.print("What is your first guess? "); while (true) { usersGuess = TextIO.getInt(); // اقرأ تخمين المستخدم guessCount++; if (usersGuess == computersNumber) { System.out.println("You got it in " + guessCount + " guesses! My number was " + computersNumber); break; // انتهت المباراة بفوز المستخدم } if (guessCount == 6) { System.out.println("You didn't get the number in 6 guesses."); System.out.println("You lose. My number was " + computersNumber); break; // انتهت المباراة بخسارة المستخدم } // بلغ المستخدم عما إذا كان تخمينه أكبر أم أصغر من العدد if (usersGuess < computersNumber) System.out.print("That's too low. Try again: "); else if (usersGuess > computersNumber) System.out.print("That's too high. Try again: "); } System.out.println(); } // end of playGame() الآن، وبعد انتهائنا من كتابة تعريف البرنامج الفرعي بالأعلى، فإننا سنحتاج إلى معرفة المكان الذي يُفْترَض أن نضع به هذا التعريف؟ ينبغي أن نضعه عمومًا ضِمْن نفس الصَنْف المُتضمِّن للبرنامج main()، ولكن ليس داخل البرنامج main ذاته؛ فمن غَيْر المسموح كتابة برنامج فرعي ضِمْن آخر (nested). لمّا كانت لغة الجافا لا تَشترِط أيّ ترتيب معين للبرامج الفرعية المُعرَّفة ضِمْن نفس الصَنْف، فيُمكِنك وضع تعريف playGame() قَبْل البرنامج main() أو بَعْده. لاحِظ أن البرنامج main() سيَستدعِي البرنامج الفرعي playGame()، وهو ما يَعنِي مُجرد تَضمُّنه لتَعْليمَة استدعاء (call statement) لذلك البرنامج الفرعي، لا تَضمُّنه لتعريفها الكامل (definition). يتبقَّى لنا الآن كتابة البرنامج main، وهو ما قد تراه أمرًا بغاية السهولة خاصة مع رؤيتنا لكثير من الأمثلة المشابهة مُسْبَّقًا. سيبدو البرنامج كاملًا كالتالي مع مُراعاة إِمكانية إضافة المزيد من التعليقات (comments): import textio.TextIO; public class GuessingGame { public static void main(String[] args) { System.out.println("Let's play a game. I'll pick a number between"); System.out.println("1 and 100, and you try to guess it."); boolean playAgain; do { playGame(); // اِستدعي البرنامج الفرعي لإجراء مباراة System.out.print("Would you like to play again? "); playAgain = TextIO.getlnBoolean(); } while (playAgain); System.out.println("Thanks for playing. Goodbye."); } // نهاية main static void playGame() { int computersNumber; // العدد العشوائي int usersGuess; // إحدى تخمينات المستخدم int guessCount; // عدد تخمينات المستخدم computersNumber = (int)(100 * Math.random()) + 1; guessCount = 0; System.out.println(); System.out.print("What is your first guess? "); while (true) { usersGuess = TextIO.getInt(); // اقرأ تخمين المستخدم guessCount++; if (usersGuess == computersNumber) { System.out.println("You got it in " + guessCount + " guesses! My number was " + computersNumber); break; // انتهت المباراة بفوز المستخدم } if (guessCount == 6) { System.out.println("You didn't get the number in 6 guesses."); System.out.println("You lose. My number was " + computersNumber); break; // انتهت المباراة بخسارة المستخدم } // بلغ المستخدم عما إذا كان تخمينه أكبر أم أصغر من العدد if (usersGuess < computersNumber) System.out.print("That's too low. Try again: "); else if (usersGuess > computersNumber) System.out.print("That's too high. Try again: "); } System.out.println(); } // نهاية البرنامج الفرعي playGame } // نهاية الصنف GuessingGame اِستغرِق الوقت الكافي لقراءة شيفرة البرنامج بالأعلى، وحَاول اِستيعاب طريقة عملها. رُبما تَكُون قد لاحَظت أن تَقسيم البرنامج إلى تابعين (methods) قد جَعل البرنامج عمومًا أسهل في القراءة، وربما حتى في الكتابة حتى مع كَوْنه بسيطًا، أما إذا لم تَلحَظ ذلك، فحاول إقناع نفسك به في الوقت الحالي. المتغيرات الأعضاء (Member Variables) قد تَتضمَّن الأصناف (classes) أعضاء (members) آخرى، غير البرامج الفرعية، كالمُتَغيِّرات (variables)، فيُمكِن لأي صَنْف التَّصْريح عن مُتَغيِّر ما (variable declaration)، ولا يُقصَد بذلك تلك المُتَغيِّرات المُعرَّفة داخل برنامج فرعي معين، والمَعروفة باسم المُتَغيِّرات المحليّة (local variable)، وإنما تلك المُعرَّفة بمكان يقع خارج أيّ برنامج فرعي ضِمْن الصَنْف. تُعرَف تلك المُتَغيِّرات باسم المُتَغيِّرات العامة (global variable) أو المُتَغيِّرات الأعضاء (member variables)؛ وذلك لكَوْنهم أعضاء (members) ضِمْن الصنف (class). مثلما هو الحال مع البرامج الفرعية، يُمكِن لأي مُتَغيِّر عضو (member variable) أن يُعرَّف بعدّه عضوًا ساكنًا (static) أو عضوًا غير ساكن (non-static). سنقتصر في هذا الفصل على الساكن منها. بدايةً، ينتمي أي مُتَغيِّر عضو، مُعرَّف باِستخدَام المُبدل static، إلى الصَنْف ذاته المُعرَّف بداخله -لا إلى كائنات ذلك الصَنْف-، فعندما يُحَمِّل مُفسر الجافا (Java interpreter) صَنْف معين، فإنه يُخصِّص مساحة بالذاكرة لكل مُتَغيِّر عضو ساكن ضِمْن ذلك الصَنْف. تُعدِّل أي تَعْليمَة إِسْناد (assignment) إلى واحد من تلك المُتَغيِّرات، وبغض النظر عن مكانها بالبرنامج، من محتوى نفس المساحة بالذاكرة، وكذلك يَلج (access) أي تعبير (expression) يَتضمَّن واحدًا من تلك المُتَغيِّرات، وبغض النظر عن مكانه بالبرنامج، إلى نفس المساحة بالذاكرة ويُعيد نفس القيمة، أيّ تتشارك البرامج الفرعية الساكنة المُعرَّفة بصَنْف ما قيم المُتَغيِّرات الأعضاء الساكنة المُعرَّفة ضِمْن نفس ذلك الصَنْف، فيُمكِن لبرنامج فرعي مُعين ضَبْط قيمة مُتَغيِّر عضو ساكن ما، بحيث يَستخدِمها برنامج فرعي آخر، وهو ما يَختلِف عن المُتَغيِّرات المحليّة المُعرَّفة (local variable) داخل أحد البرامج الفرعية؛ حيث تَتوفَّر فقط بينما يُنفَّذ ذلك البرنامج الفرعي، ثم لا يَعُدْ بالإمكان الوُلوج إليها (inaccessible) من خارج ذلك البرنامج الفرعي. تتشابه تَعْليمَة التَّصْريح (declaration) عن مُتَغيِّر عضو (member variable) مع تلك المَسئولة عن التَّصْريح عن أي مُتَغيِّر محليّ تقليدي باستثناء شيئين. الأول هو وقوع التَّصْريح عن المُتَغيِّر العضو بمكان خارج أي برنامج فرعي (مع ذلك ما يزال التَّصْريح ضِمْن الصَنْف نفسه)، والثاني هو إمكانية اِستخدَام المُبدلات (modifiers) مثل static و public و private ضِمْن التَّصْريح. يقتصر هذا الفصل على الأعضاء الساكنة فقط، ولهذا ستَتضمَّن أي تَعْليمَة تَّصْريح عن مُتَغيِّر عضو (member variable) المُبدل static، وربما قد يُستخدَم أيًا من المُبدلين public أو private. على سبيل المثال، انظر التالي: static String usersName; public static int numberOfPlayers; private static double velocity, time; إذا لم تَستخدِم المُبدل private ضِمْن تَعْليمَة التَّصْريح عن مُتَغيِّر عضو ساكن معين، فإنه يُعامَل افتراضيًا كعضو عام public، أي يُمكن الوُلوج إليه بأي مكان سواء من داخل الصَنْف المُعرَّف به أو من خارج ذلك الصنف. ولكن لاحِظ أنك ستحتاج إلى اِستخدَام مُعرِّف (identifier) مُركَّب على الصورة . عند محاولة الإشارة إليه من خارج الصَنْف. فمثلًا، يحتوي الصَنْف System على مُتَغيِّر عضو ساكن عام اسمه هو out، لذلك تستطيع الإشارة إلى ذلك المُتَغيِّر باِستخدَام System.out بأيّ صَنْف خاص بك. مثال آخر هو المُتَغيِّر العضو الساكن العام Math.PI بالصَنْف Math. مثال أخير، وبفَرْض أن لدينا الصَنْف Poker، والذي يُصَرِّح عن مُتَغيِّر عضو ساكن عام، وليَكُن numberOfPlayers، فإنه يُمكِن الإشارة إلى ذلك المُتَغيِّر داخل الصَنْف Poker ببساطة باِستخدَام numberOfPlayers، في المقابل، يُمكِن الإشارة إليه باِستخدَام Poker.numberOfPlayers من خارج الصَنْف. والآن، لنضيف عدة مُتَغيِّرات أعضاء ساكنة إلى الصَنْف GuessingGame الذي كتبناه مُسْبَّقًا بهذا القسم: أولًا، المُتَغيِّر العضو gamesPlayed بهدف الاحتفاظ بعَدَدَ المباريات التي لعبها المُستخدِم إجمالًا. ثانيًا، المُتَغيِّر العضو gamesWon بهدف الاحتفاظ بعَدَدَ المباريات التي كَسَبها المُستخدِم. يُصَرَّح عن تلك المُتغيرات كالتالي: static int gamesPlayed; static int gamesWon; ستَزداد قيمة المُتَغيِّر gamesPlayed بمقدار الواحد دائمًا مع كل عملية اِستدعاء للبرنامج playGame()، بينما ستَزداد قيمة المُتَغيِّر gamesWon بمقدار الواحد في حالة فوز المُستخدِم بالمباراة فقط، ثم تُطبَع قيمة المُتَغيِّرين بنهاية البرنامج main(). لمّا كان ضروريًا لكِلا البرنامجين الفرعيين playGame() و main() أن يَلجا إلى نفس قيمتي المُتَغيِّرين، فإنه يَستحِيل اِستخدَام المُتَغيِّرات المحليّة للقيام بنفس الشيء؛ ففي الواقع، يَقتصِر الولوج إلى قيمة مُتَغيِّر محليّ معين على برنامج فرعي وحيد، هو البرنامج الفرعي الذي عَرَّفه، وضِمْن نفس الاستدعاء؛ حيث تُسنَد قيمة جديدة للمُتَغيِّرات المحليّة (local variables) مع كل عملية استدعاء للبرنامج الفرعي الشامل لها، وهو ما يَختلِف عن المُتَغيِّرات العامة (global variables)، والتي تَحتفِظ بنفس قيمها بين كل استدعاء والاستدعاء الذي يَليه. تُهيَئ (initialized) المُتَغيِّرات الأعضاء تلقائيًا بقيم افتراضية بعد التَّصْريح عنها، وهو ما يَختلِف عن المُتَغيِّرات المحليّة ضِمْن البرامج الفرعية، والتي لابُدّ من إِسْناد قيمة لها صراحةً قَبْل اِستخدَامها. تلك القيم الافتراضية هي ذاتها القيم الافتراضية لعناصر المصفوفات، فتُسنَد القيمة صفر افتراضيًا إلى المُتَغيِّرات العددية، بينما تُسنَد القيمة false للمُتَغيِّرات من النوع boolean، في حين يُسنَد المحرف المقابل لقيمة ترميز اليونيكود (Unicode code) \u0000 للمُتَغيِّرات من النوع المحرفي char، أما القيمة الافتراضية المبدئية للكائنات (objects)، كالسَلاسِل النصية من النوع String، فهي القيمة الفارغة null. لمّا كان المُتَغيِّرين gamesPlayed و gamesWon من النوع int، فإنهما يُهيَئا أتوماتيكيًا إلى القيمة المبدئية صفر، وهو ما تَصادَف أن يَكُون القيمة المبدئية المناسبة لمُتَغيِّر يُنوَى اِستخدَامه كعَدَّاد (counter). مع ذلك، إذا لم تُناسبك القيمة المبدئية الافتراضية أو حتى إذا كنت تُريد مُجرد التَّصْريح عن نفس القيمة المبدئية لكن بصورة أكثر وضوحًا، فما يزال بإمكانك إِجراء عملية إِسْناد إلى أي من تلك المُتَغيِّرات ببداية البرنامج main(). اُنظر نسخة البرنامج GuessingGame.java بَعْد التعديل: import textio.TextIO; public class GuessingGame2 { static int gamesPlayed; // العدد الإجمالي للمباريات static int gamesWon; // عدد المباريات التي فاز فيها المستخدم public static void main(String[] args) { gamesPlayed = 0; gamesWon = 0; // لا فائدة فعلية من ذلك لأن الصفر هو القيمة الافتراضية System.out.println("Let's play a game. I'll pick a number between"); System.out.println("1 and 100, and you try to guess it."); boolean playAgain; do { playGame(); // استدع البرنامج الفرعي للعب مباراة System.out.print("Would you like to play again? "); playAgain = TextIO.getlnBoolean(); } while (playAgain); System.out.println(); System.out.println("You played " + gamesPlayed + " games,"); System.out.println("and you won " + gamesWon + " of those games."); System.out.println("Thanks for playing. Goodbye."); } // end of main() static void playGame() { int computersNumber; // العدد العشوائي int usersGuess; // إحدى تخمينات المستخدم int guessCount; // عدد تخمينات المستخدم gamesPlayed++; // أزد العدد الإجمالي للمباريات computersNumber = (int)(100 * Math.random()) + 1; guessCount = 0; System.out.println(); System.out.print("What is your first guess? "); while (true) { usersGuess = TextIO.getInt(); // اقرأ تخمين المستخدم guessCount++; if (usersGuess == computersNumber) { System.out.println("You got it in " + guessCount + " guesses! My number was " + computersNumber); gamesWon++; break; // انتهت المباراة بفوز المستخدم } if (guessCount == 6) { System.out.println("You didn't get the number in 6 guesses."); System.out.println("You lose. My number was " + computersNumber); break; // انتهت المباراة بخسارة المستخدم } // بلغ المستخدم عما إذا كان تخمينه أكبر أم أصغر من العدد if (usersGuess < computersNumber) System.out.print("That's too low. Try again: "); else if (usersGuess > computersNumber) System.out.print("That's too high. Try again: "); } System.out.println(); } // نهاية البرنامج الفرعي playGame } // نهاية الصنف GuessingGame2 بالمناسبة، لم يُستخدَم أي من المُبدلين public و private مع البرامج الفرعية أو المُتَغيِّرات الساكنة static بالأعلى، فما الذي يَعنيه ذلك؟ في الواقع، إذا لم يُخصَّص أي مُبدل وصول (access modifier) لمُتَغيِّر عام (global variable) أو لبرنامج فرعي (subroutine)، فإنه يُصبِح قابلًا للوصول بأيّ مكان يقع ضِمْن حزمة (package) الصَنْف الحاضن له، ولكن ليس بأي حزم آخرى. تقع الأصناف التي لا تُصَرِّح عن وجودها ضِمْن حزمة معينة بالحزمة الافتراضية (default package)، لذا تستطيع جميع الأصناف ضِمْن الحزمة الافتراضية -وهو ما يَشمَل أغلبية الأصناف بهذا الكتاب- الوصول إلى كِلا المُتَغيِّرين gamesPlayed و gamesWon وكذلك استدعاء البرنامج الفرعي playGame(). مع ذلك، يُعدّ اِستخدَام المُبدل private أثناء التَّصْريح عن كُلًا من المُتَغيِّرات الأعضاء والبرامج الفرعية عمومًا واحدًا من الممارسات الجيدة إلا إذا كان هناك سببًا يَدعوك لمخالفة ذلك، كما يُفضَّل تَجَنُّب اِستخدَام الحزمة الافتراضية (default package). ترجمة -بتصرّف- للقسم Section 2: Static Subroutines and Static Variables من فصل Chapter 4: Programming in the Large I: Subroutines من كتاب Introduction to Programming Using Java.1 نقطة
-
الإصدار 1.0.0
116548 تنزيل
سطع نجم لغة البرمجة بايثون في الآونة الأخيرة حتى بدأت تزاحم أقوى لغات البرمجة في الصدارة وذاك لمزايا هذه اللغة التي لا تنحصر أولها سهولة كتابة وقراءة شيفراتها حتى أصبحت الخيار الأول بين يدي المؤسسات الأكاديمية والتدريبية لتدريسها للطلاب الجدد الراغبين في الدخول إلى مجال علوم الحاسوب والبرمجة. أضف إلى ذلك أن بايثون لغةً متعدَّدة الأغراض والاستخدامات، لذا فهي دومًا الخيار الأول في شتى مجالات علوم الحاسوب الصاعدة مثل الذكاء الصنعي وتعلم الآلة وعلوم البيانات وغيرها، كما أنَّها مطلوبة بشدة في سوق العمل وتعتمدها كبرى الشركات التقنية. دورة تطوير التطبيقات باستخدام لغة Python احترف تطوير التطبيقات مع أكاديمية حسوب والتحق بسوق العمل فور انتهائك من الدورة اشترك الآن بني هذا العمل على كتاب «How to code in Python» لصاحبته ليزا تاغليفيري (Lisa Tagliaferri) وترجمه إلى العربية محمد بغات وعبد اللطيف ايمش، وحرره جميل بيلوني، ويأتي شارحًا المفاهيم البرمجية الأساسية بلغة بايثون، ونأمل في أكاديمية حسوب أن يكون إضافةً نافعةً للمكتبة العربيَّة وأن يفيد القارئ العربي في أن يكون منطلقًا للدخول إلى عالم البرمجة من أوسع أبوابه. رُبط هذا الكتاب مع توثيق لغة بايثون في موسوعة حسوب لتسهيل عملية الاطلاع على أي جزء من اللغة مباشرة وقراءة التفاصيل باللغة العربية. هذا الكتاب مرخص بموجب رخصة المشاع الإبداعي Creative Commons «نسب المُصنَّف - غير تجاري - الترخيص بالمثل 4.0». يمكنك قراءة فصول الكتاب على شكل مقالات من هذه الصفحة، «المرجع الشامل إلى تعلم لغة بايثون»، أو مباشرةً من الآتي: المقال الأول: دليل تعلم بايثون اعتبارات عملية للاختيار ما بين بايثون 2 و بايثون 3 المقال الثاني: تثبيت بايثون 3 وإعداد بيئتها البرمجية المقال الثالث: كيف تكتب أول برنامج لك المقال الرابع: كيفية استخدام سطر أوامر بايثون التفاعلي المقال الخامس: كيفية كتابة التعليقات المقال السادس: فهم أنواع البيانات المقال السابع: مدخل إلى التعامل مع السلاسل النصية المقال الثامن: كيفية تنسيق النصوص المقال التاسع: مقدمة إلى دوال التعامل مع السلاسل النصية المقال العاشر: آلية فهرسة السلاسل النصية وطريقة تقسيمها المقال الحادي عشر: كيفية التحويل بين أنواع البيانات المقال الثاني عشر: كيفية استخدام المتغيرات المقال الثالث عشر: كيفية استخدام آلية تنسيق السلاسل النصية المقال الرابع عشر: كيفية إجراء العمليات الحسابية المقال الخامس عشر: الدوال الرياضية المضمنة المقال السادس عشر: فهم العمليات المنطقية المقال السابع عشر: مدخل إلى القوائم المقال الثامن عشر: كيفية استخدام توابع القوائم المقال التاسع عشر: فهم كيفية استعمال List Comprehensions المقال العشرون: فهم نوع البيانات Tuples المقال الحادي والعشرين: فهم القواميس المقال الثاني والعشرين: كيفية استيراد الوحدات المقال الثالث والعشرين: كيفية كتابة الوحدات المقال الرابع والعشرين: كيفية كتابة التعليمات الشرطية المقال الخامس والعشرين: كيفية إنشاء حلقات تكرار while المقال السادس والعشرين: كيفية إنشاء حلقات تكرار for المقال السابع والعشرين: كيفية استخدام تعابير break وcontinue وpass عند التعامل مع حلقات التكرار المقال الثامن والعشرين: كيفية تعريف الدوال المقال التاسع والعشرين: كيفية استخدام *args و**kwargs المقال الثلاثين: كيفية إنشاء الأصناف وتعريف الكائنات المقال الحادي والثلاثين: فهم متغيرات الأصناف والنسخ المقال الثاني والثلاثين: وراثة الأصناف المقال الثالث والثلاثين: كيفية تطبيق التعددية الشكلية (Polymorphism) على الأصناف المقال الرابع والثلاثين: كيف تستخدم منقح بايثون المقال الخامس والثلاثين: كيفية تنقيح شيفرات بايثون من سطر الأوامر التفاعلي المقال السادس والثلاثين: كيف تستخدم التسجيل Logging المقال السابع والثلاثين: كيفية ترحيل شيفرة بايثون 2 إلى بايثون 31 نقطة -
لقد قمت بتمثيل بياناتي، لكن أريد أن أضيف للرسم البياني شبكة grid، كيف نقوم بذلك؟ هذا هو الكود: import matplotlib.pyplot as plt import numpy as np from matplotlib import colors from matplotlib.ticker import PercentFormatter # إنشاء بيانات عشوائية N_points = 984 n_bins = 15 # إنشاء توزيع x = np.random.randn(N_points) y = .8 ** x + np.random.randn(984) + 25 legend = ['distribution'] # إنشاء الهيستوغرام fig, axs = plt.subplots(1, 1, figsize =(10, 7), tight_layout = True) #ticks حذف العلامات axs.xaxis.set_ticks_position('none') axs.yaxis.set_ticks_position('none') # إضافة نص للشكل fig.text(0.9, 0.15, 'Histogram', fontsize = 13,color ='red',ha ='right', va ='top',alpha = 0.7) # plot حذف خطوط حاويةال for s in ['top', 'bottom', 'left', 'right']: axs.spines[s].set_visible(False) #وضع مسافة بين التسميات وبين المحاور axs.xaxis.set_tick_params(pad = 4) axs.yaxis.set_tick_params(pad = 8) # إنشاء الهستوغرام N, bins, patches = axs.hist(x, bins = n_bins) # تحديد الألوان fracs = ((N**(9)) / N.max()) norm = colors.Normalize(fracs.min(), fracs.max()) for thisfrac, thispatch in zip(fracs, patches): color = plt.cm.viridis(norm(thisfrac)) thispatch.set_facecolor(color) # تسمية للمحاور plt.xlabel("X-axis") plt.ylabel("y-axis") plt.legend(legend) # عنوان plt.title('Histogram') # عرض plt.show() وهذه هي النتيجة:1 نقطة
-
أريد تطبيق الدالة SimpleBlobDetector لكنها تتعامل مع الصور ذات المجال من 0 إلى 255 أي 8bit-image فقط، ولدي صورة رمادية ممثلة ب np.array وأريد تطبيق هذه الدالة عليها، فهل هناك طريقة لكي أقوم بالتحويل بشكل فعَال؟1 نقطة
-
لدي واجهة خلفية وقاعدة بيانات python + mysql حيث بعد التعديل أفقد البيانات ضمن القاعدة في وضع الإنتاج الملف docker-compose.yml version: "2" services: db: image: mysql:5.7 volumes: - ./db:/docker-entrypoint-initdb.d/:ro1 نقطة
-
1 نقطة
-
أريد جعل سكربت npm يعمل على صدفة محددة بغض النظر عن الصدفة الافتراضية. مثلاً أريد جعل npm script ينفذ بواسطة bash على نظام ويندوز1 نقطة
-
1 نقطة
-
أحاول رفع الموقع الخاص بي الى DigitalOcean عبر SSH و Rsync في Github Action متبعا هذا المقال https://zellwk.com/blog/github-actions-deploy/ لم افهم الخطوة الرابعة كيف يمكنني إضافة المفتاح الخاص إلى Github Actions Workflow ؟1 نقطة
-
في المقال الذي اتبعته لو ترجع لتلك الخطوة هناك ارشادات منه بعدم انشاء كلمة سر لانو الaction مبنية ما تقبل كلمة سر1 نقطة
-
نعم server يطلب كلمة السر عند الدخول ،كيف يمكن اضافة ذلك لaction1 نقطة
-
هل عندما تدخل لل server الخاص بك من putty أو منفذ الأوامر يطلب كلمة سر قبل الدخول أو يكتفي ب private key وأيضا هل ال port تركته 22 أم غيرته وأيضا الhost هل مازال root أم قمت بإنشاء مستخدم آخر كل هذا يجب معرفته ويكون خاص من سيرفر اجمع كل هذه المعلومات ثم تحقق هل ال script الخاص ب ال action يغذيها بكل هذه المعلومات ام لا1 نقطة
-
1 نقطة
-
لم أفهم هنا, اعتقد انني ايضا لم اضف كلمة السر عند انشائه كيف حللت هذه المشكلة؟1 نقطة
-
شخصيا المشاكل التي واجهتني عندما استخدمت الactions لأول مرة كان أولا اني أضفت كل شئ ونسيت أن اضف كلمة السر لأنني أنشأت ssh ومعه passpharse والمشكلة الأخرى التي اعاقتني كثيرا هي أنني عند انشاء كلمات السر في المستودع قمت بنسخ محتوى ال private key الداخلي بالنسبة للssh وكان من المفترض أن انسخ المحتوى كله يعني انت تأكد أن تفتح ملف id_rsa وتنسخ كل شئ ليس فقط المحتوى الداخلي وتضغه في متغيرات البيئة الخاصة بالمستودع دون أي تغيير1 نقطة
-
1 نقطة
-
يمكنك إضافة كل ما هو سري كمفتاح ssh و كلمة السر إذا تواجدت في المستودع عن طريق الذهاب إلى settings. الخاصة بالمستودع ثم ستجد tab secret هنالك يمكنك انشاء متغيرات البيئة التي تستخدمهم الaction فقط تأكد انك تسميات متغيرات البيئة تكون متوافقة تماما مع التسميات المستخدمة في ال action مثلا secrets. SSH_PRIVATE_KEY أنت تسميه في ال secrets الخاصة بالمستودع SSH_PRIVATE_KEY1 نقطة
-
الآمن السبراني هو عملية لحماية الأنظمة والأجهزة في مؤسسة ما بهدف منع الهجمات الإلكترونية عليها، حيث تحتوي هذه الأنظمة والأجهزة -المراد حمايتها- على معلومات سرية أو حساسة. وتهدف الهجمات الإلكترونية هذه إلى الوصول إلى هذه المعلومات بغية تسريبها أو تغيرها أو حتى تدميرها. وينقسم المن السيبراني إلى طبقات من الحماية تنتشر عبر أجهزة الكمبيوتر والشبكات والبرامج (وفي بعض الأحيان الأشخاص) التي يجب حمايتها والحفاظ عليها، ويتم الإتفاق وتقسيم هذه الطبقات حسب رغبة المؤسسة أو المنظمة وما تراه مناسبًا لها. بعض أنواع التهديد في الأمن السيبراني تصيد المعلومات Phishing Information ويتم في هذه العملية إرسال محتوى زائف مشابهة لمحتوى أصلي موثوق، مثل إرسال رسالة إلى بريد إلكتروني مزورة أو إرسال رابط موقع مشابهة لموقع معروف مثل Facebook على سبيل المثال، وهذا بغرض الحصول على معلومات حساسة وسرية، مثل كلمات المرور أو أرقام البطاقات الإتمانية أو أجوبة لأسئلة سرية .. إلخ، وبالطبع قد ينخدع عدد من المستخدمين في مثل هذا الإحتيال ظنًا منهم أنه هذا هو الموقع الحقيقي أو أن هذه عبارة عن رسالة رسمية موثوقة. البرامج الضارة Malware وهي برامج يتم إرسالة إلى الضحايا بأي شكل كان، وتهدف هذه البرامج إلى الوصول الكامل لجهاز الكمبيوتر والتحكم فيه عن بعد بطريقة غير مرئية، بهدف إستخراج معلومات سرية أو مراقبة صاحب الجهاز أو حتى الوصول إلى أجهزة أخرى في المؤسسة .. إلخ، وهذه البرامج الضارة يتم الكشف عنها في الغالب عن طريق طرق بسيطة مثل إستخدام برامج الحماية Anti-Virus أو من خلال طرق أكثر تعقيدًا مثل مراقبة نشاطات البرنامج وما يحاول القيام به في بيئة إفتراضية معزولة Isolated Virtual Environment .. إلخ. برامج الفدية الضارة Ransomware وهي برامج ضارة تحاول تشفير كل محتويات جهاز الكمبيوتر (أو نوع معين من الملفات)، بغرض من صاحب الجهاز من الوصول إلى هذه الملفات أو إستخدامها إلا بعد دفع مبلغ مالي (فدية) إلى صانع برنامج الفدية، وفي الغالب تتم هذه العملية عبر عملة مشفر يصعب تتبع صاحبها مثل البيتكوين أو إيثريوم Ethereum وغيرها. وفي كثير من الأحيان تفشل المنظمة (أو صاحب جهاز الكمبيوتر) من إعادة الملفات المشفرة وفك تشفيرها. متطلبات الأمن السيبراني يحتاج متخصص الأمن السيبراني إلى تعلم الكثير من المهارات التي تُمكنه من التعرف على التهديدات وكيفية مواجهتها، كما أنه بحاجة إلى مهارات التفكير بشكل تحليلي ونقدي وحل المشكلات والقدرة على العمل بشكل جيد سواء كفرد أو ضمن فريق. أيضًا المهارات التقنية مثل تعلم لغات البرمجة والخوارزميات وبنية البرامج والأنظمة وكيفية حمايتها من التهديدات تُعد مهارات أساسية لا غنى عنها. كما أنه بحاجة إلى القراءة عن أشهر الثغرات وكيفية عملها وإستغلالها وكيفية الحماية منها أيضًا.1 نقطة
-
1 نقطة
-
1 نقطة
-
كلا الخطأين يدلان على عدم إمكانية إرسال الطلب بهذا النوع من الطريقة, مثلا أن يكون لدينا ال endpoint التالية http://example.com/user وﻻ يكون بإمكان إرسال طلب عن طريق الhttp method delete فمن الممكن وقتها أن يأتيك أحدى الخطأين ولكن الفرق بينهما كالتالي 415 الطريقة غير مسموح بها method not allowed ـــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــ هذه الرسالة تعني أن تصميم الapi نفسه لا يدعم هذه الطريقة على هذا النوع من الموارد , مثلاً في الendpoint بالأعلى من الممكن أن يكون تصميم الapi ﻻ يدعم وجود خاصية حذف المُستخدم, ففي هذه الحالة يكوون الخطأ من العميل حيث أرسل طلب بطريقة غير موجودة في الapi من الأساس 501 الطريقة لم تُدعم بعد method not implemented ـــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــ في هذه الرسالة تكون الapi مُصممة لإستقبال هذه الطريقة على هذا النوع من الموارد ولكن لم يتم دعممه في الوقت الحالي ولكن من الممكن دعمه في المستقبل, ففي المثال السابق إن كان يوجد بالفعل دالة تستمع للطريقة delete على الuser ولكن لم يتم تفعيل تلك الخاصية بعد(خاصية حذف المُستخدم) سيكون الرد برسالة 501 حيث أن الخطأ هنا من الخادم وليس العميل حيث أن الخادم يستمع إلى تلك الطريقة delete للuser ولكن فقط لم يقم بدعم تلك الخاصية في الوقت الحالي(لم يقوم بعمل implementation للدالة المسؤلة عن تلك الوظيفة)1 نقطة
-
هل بإمكانك تنفيذ الأمر: php --version إذا لم تحصلي على إصدار الphp المثبت فتحتاجين إلى إضافة مسار php إلى متغيرات البيئة إبحثي عن edit the system environment variables من خلال شريط البحث ثم من خلال system variables حددي متغيري path و أضيفي مسار جديد الذي سيكون مسار الملف التنفيذي ل php سيكون بهذا الشكل: C:\xampp\php1 نقطة
-
نقوم باختيار جميع حقول جدول الشركات، وتجميع تعداد حقل الأسماء في جدول الموظفين وتنفيذ عملية الربط بين الجدولين حسب معرف الشركة كالتالي SELECT Companies.*, COUNT(employees.name) AS employees_count FROM Companies LEFT JOIN employees ON Companies.id = employees.company_id بعدها نقوم بتجميع أسطر الشركة الواحدة عبر المعرف الخاص بها (لتفادي التكرار الناتج عن عملية الربط) GROUP BY Companies.id ثم نقوم بترتيب الناتج حسب حقل عدد موظفي الشركة المنشئ سابقا ORDER BY employees_count DESC1 نقطة
-
يوجد خيارات كميزات إضافية لمخدم nginx: تحديد وزن المخدم (ثقله - قوة المعالجة له) مما يزيد عدد الطلبيات الموجهة له weight: http { upstream backend { server 192.0.0.1 weight=5; ^^^^^^^^^ } } يمكن وضع عنوان دومين domain عادي بدل وضع ip مثل: http { upstream backend { server backend5.example.com; ^^^^^^^^^^^^^^^^^^^^ } } يمكن تحديد مخدم إحتياطي backup، يعمل في حال فشل المخدمات البقية، أي لا يرسل له أي طلبية حتى توقفهم: http { upstream backend { server 192.0.0.1 backup; } } في حال تعطل أحد المخدمات ورغبتنا بعودته للعمل يمكننا اختباره بعد مرور مدة زمنية باستخدام الخاصية slow start: upstream backend { server backend1.example.com slow_start=30s; ^^^^^^^^^^^^^^ } في حال تعطل أحد المخدمات يمكننا تحديده ك down upstream backend { server backend6.example.com down; ^^^^^ } تحديد العدد الأعظمي من قنوات الاتصال، upstream backend { server backend1.example.com max_conns=3; ^^^^^^^^^^^ } وعمل رتل queue لعدد الاتصالات مع تحديد وقت انتهاء timeout upstream backend { server backend2.example.com; queue 100 timeout=70; ^^^^^^^^^^^^^^^^^^^^^ } يمكن الاطلاع علي كيفية ضبط الخادم وأساسياته من خلال:1 نقطة
-
يمكننا إلغاء التحقق من تطابق نوع الملف مع المحتوى الفعلي للصورة بإضافة: أرفقت 4 طرق للحل، كل منها لإصدار gradle مختلف، .. apps's build.gradle => android { … buildTypes { release { crunchPngs false // or true هذه } } } أو android { ... aaptOptions { cruncherEnabled = false } } أو android { ... aaptOptions { useNewCruncher false } .... } أو android { ... aaptOptions.useAaptPngCruncher = false }1 نقطة
-
يقول الخطأ أن هنالك قوس ناقص بمكان الخطأ expecting '}', found '' @ line 77 ماهو السطر 77? المشكلة فقط قوس ناقص في الملف build.gradle تأكدي من كتابته بطريقة سليمة1 نقطة
-
تستطيع عمومًا أن تقتطع مجموعة من التَعْليمَات (instructions) المسئولة عن إنجاز مُهِمّة واحدة مُحدَّدة، وتُضمِّنها معًا تحت اسم معين، ومِنْ ثَمَّ، تستطيع التَعامُل مع تلك المُهِمّة كوحدة واحدة مهما بَلغت درجة تعقيدها، يُطلق على تلك الوحدات اسم البرنامج الفرعي (subroutine). والآن، لَمْ يَعُدْ هناك أيّ داعي للقلق بشأن أيّ من تلك الخطوات التي قد يَكُون الحاسوب مضطرًا لتَّنْفيذها أثناء إنجازه لمُهِمّة معينة، وإنما تحتاج فقط لتَذَكُّر اسم البرنامج الفرعي (subroutine) الخاص بتلك المُهِمّة؛ لكي تتَمكَّنْ من استدعائه (call). تُعدّ البرامج الفرعية عمومًا أحد أهم الأدوات الأساسية لتبسيط المسائل المعقدة. في الواقع، البرامج الفرعية (subroutine) هي أشبه ما تَكُون بـ"صندوق أسود (black box)"؛ لا يُمكِنك رؤية ما بداخله، أو بتعبير أدق، لا ترغب برؤيته؛ لأنك إذا فعلت، فستكون مُضطرًا للتعامل مع ذلك الكم الهائل من التعقيد الذي يُفْترَض لذلك البرنامج الفرعي إخفائه بالأساس. على الجانب الآخر، لا يُمكِن أن يَقْتصِر ذلك الصندوق على عالمه الداخلي، وإلا سيكون عديم الفائدة، بل ينبغي له التَفاعُل مع العالم الخارجي، ولهذا فإنه يحتاج إلى ما يُشبه الواجهة (interface) التي تَسمَح بتَفاعُل ما بداخل الصندوق مع ما هو خارجه. على سبيل المثال، قد يُوفِّر صندوق أسود مادي عدة أزرار قابلة للضغط، أو قرص قابل للضبط، أو عدة فتحات لتمرير المعلومات جيْئةَ وذَهابًا. يحاول الصندوق عمومًا التخلص من التعقيد بإخفائه، ولذلك فإن القاعدة الأولى لأيّ صندوق أسود هي كالتالي: "ينبغي لواجهة (interface) أيّ صندوق أسود أن تَكُون مُبسَّطة، وسَهلة الفهم، ومُعرَّفة بصورة جيدة." هل توجد صناديق سوداء ضِمْن عالمنا الذي نعيش فيه؟ في الواقع، إنها تُحيط بك من كل جانب: تلفازك، وسيارتك، وهاتفك المحمول، وثلاجتك هي مجرد أمثلة قليلة. على سبيل المثال، تَتَكوَّن واجهة (interface) التلفاز من عدة عناصر هي مفتاحي التَشْغِيل والغَلْق، وجهاز التحكم -ولا تنس بالطبع وضع قابس الكهرباء-، والتي تستطيع من خلالها غَلْق التلفاز، وتَشْغِيله، أو تَغْيِير القناة، أو ضَبْط الصوت، وذلك بدون أيّ فهم لكيفية عَمَل تلك الأشياء. يَنْطَبِق نفس الشيء على هاتفك المحمول على الرغم من كَوْن واجهته أكثر تعقيدًا بعدّة مراحل. يُسمَى الجزء الداخلي من الصندوق الأسود بالتَّنْفيذ (implementation)، والذي قد يَتمثَل في صورة مجموعة الالكترونيات داخل عُدَّة التلفاز أو في صورة شيفرة البرنامج الفرعي الفعليّة المسئولة عن تَّنْفيذ مهمة البرنامج، وعليه تَكُون القاعدة الثانية لأيّ صندوق أسود كالتالي: "لا حاجة لمَعرِفة أيّ شيء عن الجزء التَّنْفيذي (implementation) الخاص بصندوق أسود لكي تَتَمكَّنْ من اِستخدَامه، وإنما يَكفيك مَعرِفة واجهته (interface)." تستطيع عمومًا إعادة كتابة الجزء التَّنْفيذي (implementation) لبرنامج فرعي معين طالما لم تتأثر واجهته بذلك التَغْيِير. على سبيل المثال، عندما تغَيَّرت عُدَّة التلفاز الداخلية من اعتمادها على الصمامات المُفْرغَة (vacuum tubes) إلى الترانزستور (Transistor)، لَمْ يَكُن هناك داعي لمَعرِفة أي شيء عن ذلك أو مَعرِفة حتى ما قد يَعنِيه، وبالمثل، يُمكِن إعادة كتابة الجزء الداخلي لأيّ برنامج فرعي؛ ربما بهدف كتابة شيفرة أكثر كفاءة، وذلك بدون إِحداث أي تَغْيِير على البرامج المُستخدِمة لذلك البرنامج الفرعي (subroutine). تَحَصُّلك على صندوق أسود يَعنِي بالضرورة أن شخصًا ما كان قد صمم تَّنْفيذه (implementation) وبناه، وفي الواقع، يَستفيد كُلًا من المُنفِّذ (implementor) والمُستخدِم من فكرة وجود صندوق أسود، فبالنهاية، يُمكِن اِستخدَام ذلك الصندوق بعَدََدَ لا محدود من المواضع المختلفة، والتي لا يحتاج المُنفِّذ (implementor) أن يَعلم عنها شيئًا، وإنما يَقْتصِر دوره على التأَكُّد من أن ذلك الصندوق يُنفِّذ مُهِمّته المُوكلة إليه تَّنْفيذًا صحيحًا، مع تَوْفِيره لواجهة (interface) سليمة تَسمَح للصندوق بالتَفاعُل مع العالم الخارجي. وعليه تَكُون القاعدة الثالثة لأيّ صندوق أسود كالتالي: "لا يحتاج مُنفِّذ صندوق أسود معين مَعرِفة أيّ شيء عن تلك الأنظمة الأكبر التي ربما قد تُوظِّف ذلك الصندوق داخلها." يُمكِن القول أن أيّ صندوق أسود يُقسِّم العالم إلى جزئين: الجزء الداخلي أو التَّنْفيذي (implementation) والجزء الخارجي، بحيث تَقع الواجهة (interface) على الحد الفاصل بينهما وتربطهما معًا. لاحِظ أن الواجهة (interface) ليست مجرد رابط مادي بين صندوق معين وعالمه الخارجي، وإنما تَتضمَّن أيضًا تَوصِيفًا (specification) لما يُنجزه الصندوق بالإضافة إلى تبيان واضح لطريقة اِستخدَام تلك الواجهة. فمثلًا، لا يكفي أن تقول أن التلفاز يُوفِّر مفتاح تَشْغِيل، وإنما لابُدّ من تَحْدِيد إمكانية اِستخدَام ذلك المفتاح لأغراض تَشْغِيل التلفاز وغَلْقه. دَعْنَا نُعيد صياغة ما تَعنيه الواجهة (interface) بمصطلحات علم الحاسوب (computer science). تَتَكوَّن واجهة أيّ برنامج فرعي (subroutine) من مُكوّنين رئيسيين، الأول هو تَوصِيف صياغي أو نَحوِي (syntactic specification)، والآخر تَوصِيف دلالي (semantic specification). يخبرك الأول بالكيفية التي يُسْتَدعى (call) بها ذلك البرنامج الفرعي، بينما يُحدِّد الثاني المُهِمّة (task) المُوكَلة لذلك البرنامج والتي يُفْترَض له إنجازها. لابدّ من مَعرِفة التوصيف الصياغي (syntactic specification) لبرنامج فرعي معين؛ وذلك لاستدعائه استدعاءً سليمًا، بينما لابُدّ من مَعرِفة توصيفه الدلالي (semantic specification)؛ وذلك لفهم الغرض منه، واِستخدَامه بطريقة فعالة. عادةً ما يُستخدَم مصطلح المواصفة الاصطلاحية للبرنامج الفرعي (contract of subroutine) للإشارة إلى مُكوّني الواجهة -الصياغي والدلالي- معًا. تقول المواصفة الاصطلاحية (contract) لأيّ برنامج فرعي: "ها هو ما ينبغي القيام به لإستدعائي، وها هو ما سأُنْجِزُه بالمقابل". ينبغي للتعليقات (comments) المكتوبة لبرنامج فرعي معين أن تُبرز مُواصفته الاصطلاحية بصورة واضحة، وهو في الواقع ما لا يَحدُث عمليًا، مما يُؤدي إلى انزعاج المبرمجين الذين قد يكونوا مُضطرّين لاِستخدَام ذلك البرنامج الفرعي. سأنتقل ببقية هذا الفصل من مجرد الحديث عن الفكرة العامة للصندوق الأسود والبرامج الفرعية إلى تفاصيل كتابة تلك البرامج بلغة الجافا وكيفية اِستخدَامها. ولكن حَاوِل دومًا اِستحضار تلك الأفكار والمبادئ العامة بذهنك؛ لأنها تُبرز الغرض الأساسي من وجود البرامج الفرعية، وتُرشدك لكيفية اِستخدَامها. سيَكُون ذلك أكثر وُضوحًا بوصولنا للقسم ٤.٧ حيث سنناقش البرامج الفرعية كأداة لتطوير البرامج. تَذكَّر دائمًا أن البرامج الفرعية (subroutines) ليست الصناديق السوداء الوحيدة بعالم البرمجة. على سبيل المثال، يُعدّ الصنف (class) صندوقًا أسودًا، يَتكوَّن من جزئين، أحدهما عام (public) يُمثِل واجهته (interface)، والآخر خاص (private) يُمثِل تَّنْفيذه (implementation) الخَفي. تَنْطَبِق مبادئ الصناديق السوداء عمومًا على كُلًا من الأصناف (classes) والبرامج الفرعية (subroutines). ترجمة -بتصرّف- للقسم Section 1: Black Boxes من فصل Chapter 4: Programming in the Large I: Subroutines من كتاب Introduction to Programming Using Java.1 نقطة
-
لقد تَعلَّمت، على مدار الفصلين السابقين، نوعية البرمجة المُستخدَمة أثناء كتابة برنامج فرعي وحيد (subroutine)، فيما أطلقنا عليه اسم "البرمجة في نطاق ضيق". سنُركز أكثر خلال الفصول المُتبقّية من الكتاب على بناء البرامج ضِمْن نطاق أوسع، ولكن ما يزال ما تَعلَّمته حتى الآن هو البذرة الأساسية والضرورية لكل ما ستَتَعرَّض له فيما بَعْد. سنرى، في هذا القسم، كيف يُمكِن تطبيق ما قد تَعلَّمته خلال الفصول السابقة ضِمْن سياق برمجة واجهات المُستخدِم الرسومية (graphical user interface)، والتي تُعرَف اختصارًا باسم GUI، وهو سِّياق مُختلف نوعًا ما عما اعتدته من برامج الطرفيّة النصية. ستَعتمِد برمجة الواجهات الرسومية (GUI) سواء التي سنَتَعرَّض لها خلال هذا القسم أو خلال بقية الكتاب على منصة JavaFX، والتي تَضُمّ مجموعة من الأصناف (classes) المُستخدَمة لكتابة هذه النوعية من البرامج، أيّ أن جميع الأصناف (classes) المذكورة بهذا القسم هي جزء من منصة JavaFX، وبالتالي يَنبغي أن تقوم باستيرادها (import) إلى البرنامج حتى تَتَمكَّن من اِستخدَامها. اُنظر القسم ٢.٦ لمزيد من المعلومات عن تَصرِّيف (compiling) البرامج المُستخدِمة لمنصة JavaFX وكيفية تَشْغِيلها. عند تَشْغِيل برامج واجهات المُستخدِم الرسومية (GUI)، ستُفتَح نافذة واحدة (window) أو أكثر على شاشة الحاسوب الخاصة بك. يُمكِنك، كمبرمج، التَحكُّم الكامل بما يَظهر على تلك النافذة، وكذلك بالكيفية التي يُمكِن للمُستخدِم التَفاعُل (interact) بها مع النافذة. سنَفْحَص خلال هذا القسم أمثلة بسيطة مثل طباعة بعض الأشكال البسيطة كالخطوط والمستطيلات على النافذة بدون أي تَفاعُل من المُستخدِم، فالنقطة المُهمّة، في الوقت الحالي، هو أن تتعرَّف على الطريقة التي تُستخدَم بها أساليب "البرمجة في نطاق ضيق" ضِمْن سياقات اخرى غَيْر برامج الطرفية المُعتمدة على النصوص، وسترى بنفسك أنه يمكن تطبيق نفس تلك الأساليب لكتابة أيّ برنامج فرعي (subroutine) وليس فقط البرنامج main. رسم الأشكال ستحتاج إلى أن تَكُون على دراية ببعض المفاهيم كالبكسل (pixels)، وأنظمة الإِحداثيَّات (coordinate systems)؛ كي يتَسَنَّى لك فهم الرسومات الحاسوبية (computer graphics)، ولذلك سنمر على بعض المفاهيم الأساسية سريعًا. تتكون عامةً شاشة الحاسوب (computer screen) من مربعات صغيرة تُسمى البكسل (pixels)، مُرَتَّبة بصورة صفوف وعواميد، بدقة تَصِل عادةً إلى ١٠٠ بكسل لكل بُوصَة (pixels per inch). تَحتوِي الكثير من الشاشات حاليًا على عدد أكبر بكثير من البكسلات المَلْموسة (physical pixels) لكل بُوصَة، لدرجة أنه مِنْ المُحتمَل لبكسل منصة JavaFX أن يُشير إلى بكسل مَلْموس (physical pixel) بمثل هذه الشاشات عالية الدقة (high-resolution)، ولكنه على الأرجح يُشيِر إلى بكسل مَنطقي (logical pixel)، والتي هي وَحدة قياس تُعادِل ٠.٠١ بوصة تقريبًا. لمّا كان باستطاعة الحاسوب التَحكُّم بلون البكسل، فإنه، في الواقع، يَرسِم (drawing) الأشكال عن طريق تَغْيِير ألوان البكسلات المُفردة (individual pixels). كل بكسل له زوج من الإِحداثيَّات (coordinates)، يُشار إليها عادة باسم الإِحداثيّ x والإِحداثيّ y، وتُستخدَم لتَحْدِيد المَوْضِع الأفقي (horizontal) والرأسي (vertical) للبكسل على الترتيب. عند الرسم بمساحة مستطيلية الشكل على الشاشة، تَكُون إِحداثيَّات البكسل بالرُكْن العُلوِي الأيسر (upper left corner) هي (٠،٠)، وبحيث تزداد قيمة الإِحداثيّ x من اليسار إلى اليمين، بينما تزداد قيمة الإِحداثيّ y من الأعلى إلى الأسفل. يُستخدَم البكسل لتَحْدِيد الأشكال وتَعرَيفها، فعلى سبيل المثال، يُعرَّف أيّ مستطيل من خلال الإِحداثيّ x والإِحداثيّ y بالرُكْن العُلوِي الأيسر للمستطيل، بالإضافة إلى كُلًا من عَرْضه (width)، وارتفاعه (height) بوَحدة البكسل. تَعرَض مساحة الرسم (drawing area) بالصورة التالية نطاق كلًا من الإِحداثيَّات x و y، ويُمثِل العَرْض والارتفاع بها حجم مساحة الرسم بوَحدة البكسل: بفَرْض أن مساحة الرسم (drawing area) -بالأعلى- مُكوَّنة من ٨٠٠*٥٠٠ بكسل، سيكون المستطيل، الواقع بالجزء العُلوِي الأيسر من الصورة، تقريبًا بعَرْض ٢٠٠ بكسل وارتفاع ١٥٠ بكسل، كما يَقع الرُكْن العُلوِي الأيسر (upper left corner) للمستطيل بالإِحداثيَّات (٥٠،٥٠) تقريبيًا. يَتمّ الرسم بلغة الجافا باِستخدَام كائن سِّياق رُسومي (graphics context) من النوع GraphicsContext. يَشتمِل هذا الكائن على بعض البرامج الفرعية (subroutines)، مثل برامج (routines) لرسم الأشكال البسيطة كالخطوط، والمستطيلات، والأشكال البيضاوية، والنصوص. (عندما يَظهر النص على الشاشة، يَرسِم الحاسوب حروف النص مثلما يَرسِم أيّ أشكال آخرى). بالإضافة إلى ذلك، يَشتمِل كائن السِّياق الرُسومي (graphics context) أيضًا على مجموعة من البيانات (data)، مثل نوع الخط المُختار حاليًا للرَسْم ولونه. (يُحدِّد نوع الخط كلًا من حجم وشكل الحروف). تَشتمِل بيانات كائن السِّياق أيضًا على سطح رسم (drawing surface)، وهو ما يَتمّ الرسم عليه، وفي حالتنا، سيكون سطح الرسم هو مساحة مُحتوَى النافذة بدون الحواف (border) وشريط العنوان (title bar)، ولكن تَتوفَّر أسطح رَسْم مختلفة يمكن الرَسْم عليها أيضًا. تُوفِّر منصة JavaFX طريقتين لرسم الأشكال: إِمّا بمَلْئ الشكل (filling) أو بتَحْدِيد حوافه (stroking). مَلْئ الشكل (filling) هو ضَبْط لون كل بكسل بداخله، أمَا تَحْدِيد حواف الشكل (stroking) فهو ضَبْط لون البكسلات الواقعة بحوافه (border)، وهو ما يُشبه عملية سَحب قلم على طول حواف الشكل، وفي هذه الحالة، تُعدّ صفات القلم -كحجمه (width/size) أو ما إذا كان يَستخدِم خط صلب (solid line) أو مُتقطِّع (dashed line)- خاصيات (properties) ضِمْن كائن السِّياق الرُسومي (graphics context). يُخصِّص كائن السِّياق الرُسومي أيضًا لونين مُنفصلين، أحدهما لمَلْئ الأشكال (filling)، والآخر لتَحْدِيد حوافها (stroking). لاحظ اقتصار بعض الأشكال -كالخطوط- على طريقة تَحْدِيد الحواف فقط. يُستخدَم مُتَغيِّر من النوع GraphicsContext لتَمثيِل السِّياق الرُسومي (graphics context)، ويَحمِل هذا المُتَغيِّر عادةً الاسم g. ليس هذا ضروريًا بالطبع، حيث يَتوقَف اسمه بالنهاية على المُبرمج. نَعرِض هنا بعض البرامج الفرعية (subroutines) المُتوفرة ضِمْن كائن السِّياق الرُسومي g. لاحظ أن كل قيم المُعامِلات العددية هي من النوع double: البرنامج الفرعي g.setFill(c): يَضبُط اللون المُستخدَم لمَلْئ الأشكال (filling)، حيث المُعامِل c هو كائن من الصَنْف Color. تَتوفَّر الكثير من الثوابت (constants) المُمثِلة للألوان القياسية (standard colors)، والتي يُمكِن اِستخدَامها كمُعامِل لهذا البرنامج الفرعي. تتراوح الألوان القياسية من الألوان الشائعة مثل Color.BLACK و Color.WHITE و Color.RED و Color.GREEN و Color.BLUE و Color.YELLOW، إلى بعض الألوان الغريبة مثل Color.CORNFLOWERBLUE. (يُمكِنك أيضًا إِنشاء ألوان جديدة مُخصَّصة). على سبيل المثال، إذا أردت مَلْئ الأشكال باللون الأحمر، فإنك ستَستدعِي البرنامج الفرعي g.setFill(Color.RED);. لاحظ أن اللون المُخصَّص أثناء الاستدعاء سيُستخدَم لجميع عمليات المَلْئ التالية وحتى الاستدعاء التالي لنفس البرنامج الفرعي، أما الأشكال المَرسومة مُسْبَّقًا فلا تتأثر بهذا التَغْيِير. البرنامج الفرعي g.setStroke(c): يَضبُط اللون المُستخدَم لتَحْدِيد حواف الأشكال (stroking)، ويَعمَل بصورة مشابهة للبرنامج الفرعي g.setFill. البرنامج الفرعي g.setLineWidth(w): يَضبُط حجم القلم المُستخدَم خلال عمليات تَحْدِيد الحواف التالية (stroking). لاحظ أن المُعامِل w يَستخدِم وَحدة البكسل. البرنامج الفرعي g.strokeLine(x1,y1,x2,y2): يَرسِم خطًا مُمتدًا من إِحداثيَّات نقطة البداية (x1,y1) وحتى إِحداثيَّات نقطة النهاية (x2,y2). يُرسَم الخط باللون الأسود وبحجم ١ بكسل افتراضيًا، ومع ذلك، يُمكِنك تَخْصِيص كلًا منهما باستدعاء g.setStroke() و g.setLineWidth() على الترتيب. البرنامج الفرعي g.strokeRect(x,y,w,h): يَرسِم الحواف الخارجية (stroking) لمستطيل مع جوانبه الأفقية والرأسية، بحيث يَبعُد الرُكْن العُلوِي الأيسر (top-left corner) لهذا المستطيل مسافة قدرها x بوَحدة البكسل عن الحافة اليسرى لمساحة الرسم (drawing area)، ومسافة قدرها y بوَحدة البكسل عن حافتها العُلوِية. يُحدِّد كلًا من المُعامِلين w و h عَرْض المستطيل الأفقي وارتفاعه الرأسي بوَحدة البكسل على الترتيب. يُمكِن ضَبْط لون الخط المُستخدَم وحجمه باستدعاء g.setStroke() و g.setLineWidth() على الترتيب. البرنامج الفرعي g.fillRect(x,y,w,h): يَعمَل بصورة مشابهة للبرنامج الفرعي g.strokeRect() باستثناء أنه يَملْئ المستطيل (filling) بدلًا من رسم حوافه الخارجية (stroking). اِستدعي g.setFill لضَبْط اللون المُستخدَم. البرنامج الفرعي g.strokeOval(x,y,w,h): يَرسِم الحواف الخارجية لشكل بيضاوي. يُرسَم الشكل البيضاوي بحيث يَقع ضِمْن المستطيل الذي كان سيُرسَم في حالة استدعاء g.strokeRect(x,y,w,h) بنفس قيم المُعامِلات. لاحِظ أنه يُمكِنك اِستخدَام نفس القيمة لكُلًا من المُعامِلين w و h لرسم حواف دائرة. البرنامج الفرعي g.fillOval(x,y,w,h): يَعمَل بصورة مشابهة للبرنامج الفرعي g.strokeOval() باستثناء أنه يَملْئ الشكل البيضاوي بدلًا من رَسْم حوافه الخارجية. تُعدّ هذه البرامج الفرعية كافية لرَسْم بعض الصور باِستخدَام الجافا. لنبدأ بشئ بسيط مثل رَسْم عشرة خطوط متوازية، كالتالي: نحتاج أولًا لمجموعة افتراضات هي كالتالي: سيكون طول الخطوط حوالي ٢٠٠ بكسل، والمسافة بين كل خط والخط الذي يَليه حوالي ١٠ بكسل، وأخيرًا، سنفْترِض أن نقطة بداية (start) أول خط تقع بالإِحداثيَّات (١٠٠،٥٠). الآن، كل ما نحتاج إليه لرَسْم خط هو استدعاء البرنامج الفرعي g.strokeLine(x1,y1,x2,y2) بقيم مُعامِلات مناسبة. نلاحِظ أن نقطة البداية (start) لجميع الخطوط لها نفس قيمة الإِحداثيّ x (x-coordinate) وتُساوِي ١٠٠، ومِنْ ثَمَّ، سنَستخدِم قيمة ثابتة تُساوِي ١٠٠ كقيمة للمُعامِل x1. لمّا كانت جميع الخطوط بطول ٢٠٠ بكسل، فإننا سنَستخدِم قيمة ثابتة تُساوِي ٣٠٠ كقيمة للمُعامِل x2. في المقابل، تَختلف إِحداثيَّات y (y-coordinates) بكل خط عن الخط الذي يَليه، ولكن يُمكِننا أن نرى أن قيمة الإِحداثيّ y بنقطتي البداية (start) والنهاية (end) لكل خط منها هو نفسه، وعليه، سنَستخدِم مُتَغيِّر وحيد لكُلًا من قيمتي y1 و y2، هو المُتَغيِّر y. الآن، أصبح أمر الاستدعاء لرَسْم أحد الخيوط كالتالي g.strokeLine(100,y,300,y). اِفْترَضنا قبلًا أن قيمة المُتَغيِّر y لأول خط هي ٥٠، ثم ستزداد تلك القيمة بمقدار ١٠ مع كل انتقال للخط التالي، مما يَعنِي أننا سنحتاج إلى التأكد من أن قيمة y تأخذ القيمة الصحيحة من متتالية الأعداد. يُمكِننا اِستخدَام حَلْقة تَكْرار for، كالتالي: int y; // إحداثي y للخط int i; // المتغير المتحكم بالحلقة y = 50; // تبدأ y بالقيمة 50 لأول خط for ( i = 1; i <= 10; i++ ) { g.strokeLine( 100, y, 300, y ); y = y + 10; // أزد y بمقدار 10 قبل رسم الخط التالي } نستطيع أيضًا اِستخدَام المُتَغيِّر y ذاته كمُتحكِّم بالحَلْقة (loop control variable). لاحِظ أن قيمة y للخط الأخير هي ١٤٠. انظر الشيفرة التالية: int y; for ( y = 50; y <= 140; y = y + 10 ) g.strokeLine( 100, y, 300, y ); إذا أردت تلوين الخطوط باللون الأزرق، اِستدعي البرنامج الفرعي g.setStroke(Color.BLUE) قبل رَسْمها، حيث سيُستخدَم اللون الأسود افتراضيًا إذا قُمت برَسْمها دون ضَبْط اللون. أما إذا أردت أن يَكُون حجم تلك الخطوط ٣ بكسل، اِستدعي البرنامج الفرعي g.setLineWidth(3) قَبْل رَسْمها. لننتقل إلى مثال أكثر تعقيدًا، فمثلًا، لنَرسِم عددًا كبيرًا من الدوائر بشكل عشوائي سواء فيما يَخُص مَوْضِعها (position) أو لونها. لمّا كنا على علم بعدد قليل من الألوان المُتوفرة، فإننا سنختار عشوائيًا واحدًا من الألوان التالية : الأحمر، والأخضر، والأزرق، والأصفر. سنستعمل تَعْليمَة switch بسيطة للاختيار، وذلك بطريقة شبيهة للمثال بالقسم الفرعي ٣.٦.٤: switch ( (int)(4*Math.random()) ) { case 0: g.setFill( Color.RED ); break; case 1: g.setFill( Color.GREEN ); break; case 2: g.setFill( Color.BLUE ); break; case 3: g.setFill( Color.YELLOW ); break; } لمّا كنا نريد للدوائر أن تكون عشوائية التَموْضع، فسنحتاج إلى اختيار مركز الدوائر (center of circles) بصورة عشوائية. بفَرْض أن عَرْض مساحة الرسم (drawing area) وارتفاعها مُعطيين من خلال المُتَغيِّرين width و height على الترتيب، فسينبغي للمَوْضِع الأفقي (horizontal position) للمركز أن يكون قيمة عشوائية تتراوح من القيمة ٠ وحتى width-1. بالمثل، يَنبغي للمَوْضِع الرأسي (vertical position) لمركز الدائرة أن يَكُون قيمة عشوائية تتراوح من القيمة ٠ وحتى height-1. أخيرًا، ما زِلنا بحاجة لتَحْدِيد حجم الدائرة. سنكتفي، في هذا المثال، باِستخدَام نصف قطر (radius) ثابت لجميع الدوائر مُساوِي للقيمة ٥٠ بكسل. تُرسَم الدائرة باِستخدَام التَعْليمَة g.fillOval(x,y,w,h)، لكن، في الواقع، لا يُمثِل المُعامِلان x و y، بهذا الأمر (command)، إِحداثيَّات مركز الدائرة؛ وإنما إِحداثيَّات الرُكْن العُلوِي الأيسر (upper left corner) للمستطيل المرسوم حول الدائرة، ولهذا سنحتاج إلى تَحرِيك مركز الدائرة بمسافة قدرها يُساوِي نصف قطر الدائرة أي ٥٠ بكسل؛ وذلك للحصول على قيم x و y المُناظِرة. في المقابل، يُمثِل المُعامِلان w و h عَرْض وارتفاع المستطيل على الترتيب، واللذين ستكون قيمتهما مُساوِية لضعف نصف قطر الدائرة أي ١٠٠ بكسل بهذا المثال. تُراعِي الشيفرة التالية جميع النقاط المذكورة بالأعلى، وتُستخدَم لرَسْم دائرة عشوائية واحدة: centerX = (int)(width*Math.random()); centerY = (int)(height*Math.random()); g.fillOval( centerX - 50, centerY - 50, 100, 100 ); لاحِظ أن الشيفرة بالأعلى تُستدَعى بَعْد استدعاء الشيفرة المسئولة عن ضَبْط اللون. تبدو الصورة عامةً بشكل أفضل بَعْد تَحْدِيد حافة الدائرة (border) باللون الأسود (stroking)، ولذلك أضيفت الشيفرة التالية: g.setStroke( Color.BLACK ); g.strokeOval( centerX - 50, centerY - 50, 100, 100 ); وأخيرًا، للحصول على عدد كبير من الدوائر، ضُمِّنت الشيفرة بالأعلى داخل حَلْقة تَكْرار for، ونُفِّذت ٥٠٠ مرة، فكانت الرسمة الناتجة عن البرنامج كالتالي: الرسم داخل برنامج كما تعلم، لا يُمكِن لأيّ شيفرة بلغة الجافا أن تكون مُستقلة بذاتها، فلابُدّ لها أن تُكتَب ضِمْن برنامج فرعي (subroutine)، والذي بدوره يَكُون مُعرَّفًا داخل صَنْف (class)، ولهذا تَعرِض الشيفرة التالية التَعرِيف الكامل لبرنامج فرعي (subroutine definition)، والذي يُستخدَم لرَسْم الصورة من المثال السابق : public void drawPicture(GraphicsContext g, int width, int height) { g.setFill(Color.WHITE); g.fillRect(0, 0, width, height); // املأ لون الخلفية بالأبيض // As an example, draw a large number of colored disks. // To get a different picture, erase this code, and substitute your own. int centerX; // احداثي x لمركز القرص int centerY; // احداثي y لمركز القرص int colorChoice; // قيمة اللون العشوائي int count; // المتغير التحكم بالحلقة for (count = 0; count < 500; count++) { centerX = (int)(width*Math.random()); centerY = (int)(height*Math.random()); colorChoice = (int)(4*Math.random()); switch (colorChoice) { case 0: g.setFill(Color.RED); break; case 1: g.setFill(Color.GREEN); break; case 2: g.setFill(Color.BLUE); break; case 3: g.setFill(Color.YELLOW); break; } g.fillOval( centerX - 50, centerY - 50, 100, 100 ); g.setStroke(Color.BLACK); g.strokeOval( centerX - 50, centerY - 50, 100, 100 ); } } // نهاية drawPicture() هذه هي المرة الأولى التي تَتعرَّض فيها لتَعرِيف برنامج فرعي (subroutine definition) -إلى جانب main()-. سنتناول هذا الموضوع تفصيليًا بالفصل التالي، ولكن سنَمر عليه سريعًا هنا، يُتيِح السَطْر الأول من التَعرِيف الولوج لبعض القيم التي يَحتاجها البرنامج الفرعي، وهي السِّياق الرُسومي g، وكلًا من عَرْض وارتفاع مساحة الرسم width و height. يَستقبِل البرنامج الفرعي هذه القيم من مصدر خارجي، ويستطيع اِستخدَامها. ما يَهمّ هنا هو أن تُدرِك أنه لكي تَرِسم شيئًا (يَقصِد الكاتب أن هذا هو هدف البرنامج الفرعي، فاِسم البرنامج الفرعي هو drawPicture)، فستحتاج فقط إلى كتابة مُحتوَى البرنامج الفرعي، مثلما تَكتُب مُحتوَى البرنامج main() عند كتابة برنامج (الهدف من main()). يَنبغي لتَعرِيف البرنامج الفرعي (subroutine definition) أن يَكُون بالصَنْف (class) الذي يُعرِّف البرنامج، وهو في هذه الحالة الصَنْف SimpleGraphicsStarter. شَّغِل البرنامج -مُتاح بالكامل بالملف SimpleGraphicsStarter.java- لترى الرَسمة، كما يُمكِنك اِستخدَام هذا البرنامج كنقطة بداية لرَسْم الصور الخاصة بك. لاحِظ أنك لن تَفهم كل الشيفرة المكتوبة بالبرنامج، لكن ما يزال بإمكانك التَعديل عليها، فلا حاجة إلى فهم الشيفرة بأكملها، كل ما قد يَعَنيك هو الشيفرة الموجودة بالبرنامج الفرعي drawPicture(). اِحذف تلك الشيفرة، وضَعْ مكانها شيفرة الرسوم خاصتك، وستَتمكَّن بعدها من عَرْض رسوماتك. بالمناسبة، قد تُلاحِظ أن الكلمة static مُستخدَمة بتَعرِيف البرنامج الفرعي main()، بعكس البرنامج الفرعي drawPicture()، الذي لا يَستخدِمها، وهو ما يَعنِي أن البرنامج الفرعي drawPicture() موجود بكائن (object) وليس بصَنْف (class). تُعدّ البرامج الفرعية التي تَستخدِم الكلمة static بتَعرِيفعها ساكنة (static)، أما التي لا تَستخدِمها فتُعدّ غَيْر ساكنة (non-static). الفرق بينهما مُهِمّ، ولكنه ليس بالأمر الذي يَنبغي أن تَقْلَق حِياله في الوقت الحاضر؛ حيث سنتناوله تفصيليًا بالفصل الخامس على أية حال. التحريكة (Animation) يَعتمِد التَحرِيك الحاسوبي (computer animation) على متتالية من الصور المُنفصلة، يُطلَق على كُل منها اسم الإطار (frame). تُعرَض هذه الصور بشكل سريع واحدة تلو الآخرى، فإذا كان التَغْيِير بين كل صورة والصورة التي تَليها طفيفًا، ستبدو متتالية الصور وكأنها تَحرِيكة مُستمرة (continuous animation). يُمكِنك اِستخدَام المثال التوضيحي بالملف SimpleAnimationStarter.java كنقطة بداية، حيث يَحتوِي على البرنامج الفرعي drawFrame() المَسؤول عن رَسْم إطار (frame) وحيد ضِمْن تَحرِيكة (animation)، بالإضافة إلى ذلك، يُنفَّذ البرنامج الفرعي drawFrame() أتوماتيكيًا حوالي ٦٠ مرة بالثانية، مما يَضمَن استمرار عَرْض الأُطُر (frames)، أيّ أنك تستطيع إِنشاء تَحرِيكة (animation) بمُجرَّد إضافة الشيفرة إلى هذا البرنامج الفرعي. تستطيع تمييز المرة الحالية من التَّنْفيذ من خلال مُتَغيِّرين إضافيين -إلى جانب السِّياق الرُسومي وكُلًا من عَرْض وارتفاع مساحة الرسم- يَستقبِلهما البرنامج الفرعي، وهما frameNumber و elapsedSeconds؛ حيث يأخذ المُتَغيِّر frameNumber القيم ٠، ١، ٢، ٣، .. والتي تَزداد بمقدار الواحد مع كل اِستدعاء للبرنامج الفرعي، أمَا قيمة المُتَغيِّر elapsedSeconds فتُشيِر إلى عدد الثواني التي مَرَّت على تَّنْفيذ التَحرِيكة حتى الآن. إجمالًا، تَستطيع رَسْم صورة مختلفة في كل مرة يُستدَعى فيها البرنامج الفرعي (subroutine) بالاعتماد على قيمة أيًا من هذين المُتَغيِّرين. سنَرسِم بالمثال التالي مجموعة من المستطيلات المُتداخِلة (nested rectangles)، والتي ستنكمش باتجاه مركز الرَسْمة، مما سيُعطِي انطباعًا زائفًا بوجود حركة لا نهائية (infinite motion). تَعرِض الصورة التالية إِطارًا واحدًا من التَحرِيكة (animation): لنُفكر كيف يُمكِن رَسْم مثل هذه الصورة. عامةً، يُمكِن اِستخدَام حَلْقة التَكْرار while لرَسْم المستطيلات، بحيث تبدأ أولًا برَسْم المستطيل الخارجي، ثُمَّ تنتقل إلى الداخل وهكذا. يَنبغي الآن أن نُفكر بالمُتَغيِّرات التي سنحتاج إليها خلال حَلْقة التَكْرار (loop)، وكذلك بالطريقة التي ستَتغَيَّر بها قيم تلك المُتَغيِّرات من تَكْرار (iteration) معين إلى التَكْرار الذي يليه. ستساعدنا الملاحظات التالية على مَعرِفة تلك المُتَغيِّرات، أولًا، مع كل تَكْرار، يكون المستطيل المرسوم أصغر منه في المرة السابقة، كما أنه يَتحرك للداخل قليلًا. يتركز عامةً الفارق بين أيّ مستطيلين على حجمهما وإِحداثيَّات (coordinates) رُكْنيهما اليساريين العُلوِيين (upper left corners)، ولهذا سنحتاج، أولًا، إلى مُتَغيِّرين لتَمثيِل كلًا من عَرْض المستطيل وارتفاعه، وهما المُتَغيِّران rectWidth و rectHeight على الترتيب. أما بالنسبة لإِحداثيَّات الرُكْن الأيسر العُلوِي x و y، فيُمكِن تَمثيِل كليهما بمُتَغيِّر وحيد للمستطيل الواحد، هو المُتَغيِّر inset؛ لأن قيمتهما مُتساوِية؛ حيث يَبعُد أيّ مستطيل عن حافتي مساحة الرسم (drawing area) بنفس مقدار المسافة. نُلاحِظ أنه مع كل تَكْرار، تَنقُص قيمة كلًا من عَرْض المستطيل rectWidth وارتفاعه rectHeight، بينما تزداد المسافة inset التي يَبعُدها المستطيل عن الحافتين. أخيرًا، تنتهي حَلْقة التَكْرار while عندما يُصبِح عَرْض المستطيل أو ارتفاعه أقل من أو يُساوِي الصفر. اُنظر خوارزمية رَسْم إِطار (frame) وحيد: // املأ مساحة الرسم باللون الأبيض Fill the drawing area with white // اضبط قيمة inset المبدئية للمستطيل الأول الخارجي Set the amount of inset for the first rectangle // اضبط قيمة عرض وارتفاع المستطيل الأول الخارجي Set the width and height for the first rectangle // اضبط اللون المستخدم لتحديد الحواف إلى اللون الأسود Set the stroke color to black // طالما كان العرض والارتفاع أكبر من الصفر while the width and height are both greater than zero: // ارسم مستطيل باستخدام البرنامج الفرعي g.strokeRect draw a rectangle (using the g.strokeRect subroutine) // أزد قيمة inset حتى ينتقل المستطيل التالي إلى الداخل increase the inset (to move the next rectangle over and down) // انقص عرض وارتفاع المستطيل التالي حتى يصبح المستطيل التالي أصغر decrease the width and height (to make the next rectangle smaller) ضُبطَت هذه النسخة من البرنامج بحيث يَبعُد كل مستطيل مسافة قدرها ١٥ بكسل عن المستطيل المُحيِط به، ولهذا فإن قيمة المُتَغيِّر inset تَزداد بمقدار ١٥ بكسل مع كل تَكْرار. في المقابل، يَتقَلص المستطيل حوالي ١٥ بكسل يمينًا ويسارًا، أيّ يَنقُص عَرْض المستطيل بمقدار ٣٠ بكسل. وبالمثل، يَنقُص ارتفاعه بمقدار ٣٠ بكسل مع كل تَكْرار ضِمْن الحلقة. يَسهُل إعادة كتابة الخوارزمية بلغة الجافا، لكن تتبقَى فقط حاجتنا إلى معرفة القيم المبدئية للمُتَغيِّرات inset و width و height لأول مستطيل-(المستطيل الخارجي). لحساب ذلك، سنُفكر في حقيقة كَوْن الصورة متحركة (animated)، أي يَعتمِد ما نَرسِمه بطريقة ما على رقم الإِطار (frame number) الحالي. لمّا كان الرُكْن الأيسر العُلوِي (top-left corner) للمستطيل الخارجي يتحرك للأسفل وللداخل من أيّ إِطار إلى الإِطار الذي يَليه، فإن قيمة المُتَغيِّر inset المبدئية تزداد مع كل إِطار. قد تُفكر إذًا بضَبْط قيمة المُتَغيِّر inset المبدئية إلى القيمة ٠ بالإِطار رقم ٠، وإلى القيمة ١ بالإطار رقم ١ وهكذا. للأسف، لن يكون هذا صالحًا إلى الأبد؛ فعندما تَصِل التحريكة للإِطار ١٥، يَنبغِي أن يَظهر مستطيل خارجي جديد بمساحة الرَسْم (drawing area)، هو في الواقع ليس جديدًا، وإنما أُعيد فقط ضَبْط قيمة المُتَغيِّر inset المبدئية إلى القيمة ٠. إجمالًا، يَنبغِي لقيمة المُتَغيِّر inset أن تأخذ القيم ٠، ١، ٢، ٣،… حتى تَصِل إلى القيمة ١٤، لتُعاد الكَرَّة من جديد، وهو ما يُمكِن إنجازه باِستخدَام الشيفرة التالية: inset = frameNumber % 15; لاحِظ أن المستطيل يَملأ مساحة الرسم باستثناء حافة (border) تُحيِط به، عَرْضها يُساوِي قيمة المُتَغيِّر inset، أي بعبارة آخرى، عَرْض المستطيل هو عَرْض مساحة الرسم مطروحًا منه ضعف قيمة المُتَغيِّر inset، وبالمثل لارتفاعه. انظر شيفرة البرنامج الفرعي drawFrame() كاملة بالأسفل والمسئولة عن تَحرِيك المستطيل: public void drawFrame(GraphicsContext g, int frameNumber, double elapsedSeconds, int width, int height) { g.setFill(Color.WHITE); g.fillRect(0,0,width,height); // املأ مساحة الرسم باللون الأبيض // المسافة بين بين المستطيل الخارجي ومساحة الرسم double inset; double rectWidth, rectHeight; // عرض وطول أحد المستطيلات // اضبط اللون المستخدم لرسم حواف المستطيل g.setStroke(Color.BLACK); // إضافة القيمة 0.5 هو أسلوب للحصول على صورة أكثر وضوحًا inset = frameNumber % 15 + 0.5; rectWidth = width - 2*inset; rectHeight = height - 2*inset; while (rectWidth >= 0 && rectHeight >= 0) { g.strokeRect(inset, inset, rectWidth, rectHeight); inset += 15; // تبعد المستطيلات عن بعضها بمقدار 15 بكسل rectWidth -= 30; rectHeight -= 30; } } البرنامج مُتاح بالكامل بالملف MovingRects.java. يُمكِنك أيضًا الإِطلاع على مثال توضيحي آخر للتحريك (animation) بالملف RandomCircles.java، والذي يُضيِف قرصًا ملونًا (colored disk) بشكل عشوائي مع كل إِطار جديد. سيُظهِر لك هذا المثال أن صورة الإِطار لا تُحذَف تلقائيًا قبل إِعادة رسم الإِطار التالي. ترجمة -بتصرّف- للقسم Section 9: Introduction to GUI Programming من فصل Chapter 3: Programming in the Small II: Control من كتاب Introduction to Programming Using Java.1 نقطة