لوحة المتصدرين
المحتوى الأكثر حصولًا على سمعة جيدة
المحتوى الأعلى تقييمًا في 10/08/21 في كل الموقع
-
السلام عليكم أكاديمية حسوب لقد أنهيت الآن أساسيات ال html وال css والjs والjquary وبدأت بعمل مشروع "صفحة أعمالي" معكم خطوة بخطوة، وسؤالي هو هناك أكواد كثيرة مستخدمة من قِبلكم غير مشروحة سابقا فمن أين لي معرفتها؟ خصوصا أنني أحاول اتباع الخطوات مثلكم، لكنني كثيرا ما أنقل الأكواد بدون فهم فما الحل؟ كما أنني لا أستطيع التطبيق بمفردي معظم الوقت دون تقليد ما شرحتموه في الفيديوهات فهل هذا طبيعي؟ وشكرا لكم2 نقاط
-
طبيعي أن تشعر في البداية أنك مشتت قليلاً أو تائه بعض الشيئ, ولكن يجب أن تتحدى نفسك وتحاول بعد التطبيق وراء المدرب أن تقوم بعمل الصفحة بمفردك , وإن واجهتك مشاكل في الشفرة البرمجية حاول أن تقوم بالتالي أن تقوم بالبحث وحل المشكلة بنفسك لأن هذا سيساعدك على الفهم وسيثبت المعلومات أن تقوم بإعادة مشاهدة الفيديوهات وتحاول أن تفهم منها ما سبب الخطأ الذي يقابلك إن لم تُوفق قم بسؤالنا وسنشرح لك المشكلة وسببها وخطوات الحل وإن وجدت مفهوماً أو شفرة برمجية أو خاصية...الخ شرحها المُحاضر في الدرس ولم تفهمها بشكل جيد أو وجدت بعض التشويش في فهمها يُمكنك سؤالنا وسنحاول أن نشرح لك ما ينقصك بقدر المستطاع بالتوفيق وحاول أن ﻻ تترك شيئاً في الدورات غير مفهوماً بالنسبة لك لأن هذا سيؤثر سلباً في باقي المسار , ﻻ تتردد أبداً في السؤال عن أي شيئ تجده غير واضح بالنسبة لك , فإن هذا طبيعي لأنك في بداية الطريق وﻻ عيب في ذلك كلنا مررنا بتلك التجربة2 نقاط
-
نستخدم الربط الذاتي self join عندما نريد الربط بين عناصر من نفس الجدول ويكون لها علاقة بينهم، مثل عمل استعلام يظهر أسماء الموظفين مع أسماء مردائهم، كما ترى جميع الموظفين والمدراء موجودين في نفس الجدول (جدول الموظفين) لذلك نعمل استعلام يدمج الجدول بنفسه ويتم الربط بناءً على أن رقم المدير لموظف ما هو نفسه رقم معرف أحد الموظفين.. الشكل العام: SELECT column_name(s) FROM table1 T1, table1 T2 WHERE condition; لاحظ أن اسم الجدول يظهر مرتين في عبارة الدمج join نأخذ اسمين مستعارين لنفس الجدول ونطبق الشرط عليهم يمكن استخدام INNER JOIN أو LEFT JOIN select e1.Name As Employee Name, e2.Name As Boss from employees e1 inner join employees e2 on e1.Boss_id = e2.Id جلبنا اسم الموظف واسم المدير من علاقة الربط، حيث أن معرف رقم المدير للموظف من أول نسخة من الجدول تقابل معرف موظف من النسخة الثانية للجدول مثلا استعلام آخر لمعرفة الموظفين من نفس المدينة: select e1.Name As Employee1, e2.Name As Employee2, e1.city As City Name from employees e1 inner join employees e2 on e1.city = e2.city ORDER BY e1.City; // مفيدة للترتيب2 نقاط
-
الإصدار 1.0.0
22200 تنزيل
كتاب إدفع لي وإلا هو كتاب مجاني لكاتبه Lior Frenkel مؤسس شريك في The nuSchool، مستقل ومطوّر ويب وهو الآن مستشار، مدوّن ومتحدث في الأنشطة حول العالم. قام Lior بكتابة هذا الكتاب في عام 2015 ليساعد المستقلين حول العالم في التعامل مع حالات امتناع العملاء عن الدفع بناءً على خبرته الممتدة لسنوات في العمل الحر.1 نقطة -
OAuth 2 هو آليّة للتّرخيص تسمح للتطبيقات بطلب وصول محدود إلى حسابات المستخدمين في خدمات HTTP، مثل Facebook وGitHub وDigitalOcean. يعمل OAuth 2 بتوكيل الخدمة المستضيفة لحساب المستخدم باستيثاق هذا الحساب، ثمّ السّماح للتطبيقات الخارجيّة بالوصول إلى حساب المستخدم هذا. يوفّر OAuth 2 مسارًا لترخيص تطبيقات الويب وتطبيقات سطح المكتب والأجهزة المحمولة. هذا الدّرس موجّه لمطوّري التّطبيقات، وهو يُلقي الضّوء على أدوار OAuth 2 وأنواع الرُّخَص المتاحة، وكذلك يستعرض مجالات استخدامه وسير عمليّة التّرخيص. لنبدأ بالتّعرّف على أدوار OAuth. أدوار OAuth يُحدِّد OAuth أربعة أدوار: مالك المحتوى العميل خادوم المحتوى خادوم التّرخيص سنُفصّل كلًّا من هذه الأدوار في الفقرات التّالية. مالك المحتوى: المستخدم مالك المحتوى هو المستخدم الذي يُرخِّص لتطبيقٍ الوصول إلى حسابه. وصول التّطبيق إلى حساب المستخدم محدودٌ "بنطاق" (scope) الترخيص الممنوح (مثلاً: صلاحيّة القراءة والكتابة). خادوم المحتوى/التّرخيص: الواجهة البرمجيّة (API) يستضيف خادوم المحتوى حسابات المستخدمين المحميّة، ويتحرّى خادوم التّرخيص هويّة المستخدم ثمّ يمنح التّطبيق رمز وصول (access token). من وجهة نظر مطوّر التّطبيقات، فإنّ الواجهة البرمجيّة للخدمة تؤدّي كلا الدّورين، دور خادوم المحتوى ودور خادوم التّرخيص. سنُشير إلى هذين الدّورين مجتمعين على أنّهما دور الخدمة أو الواجهة البرمجيّة. العميل: التّطبيق العميل هو التّطبيق الّذي يريد الوصول إلى حساب المستخدم، وقبل أن يستطيع ذلك، يجب أن يحصل على "ترخيص" المستخدم، وعلى هذا التّرخيص أن يُصادَق من الواجهة البرمجيّة. سير البروتوكول نظريًّا بعد أن تعرّفنا على أدوار OAuth، دعونا نلقِ نظرةً على المخطّط التالي، والذي يبيّن كيف تتفاعل هذه الأدوار فيما بينها: وفيما يلي شرح أكثرُ تفصيلًا للخطوات المُبيّنة في المُخطّط: يطلب التطبيق رخصةً للوصول إلى الخدمة من المستخدم إن رخّص المستخدم الطّلب، فإنّ التطبيق يحصل على إذن بالتّرخيص يطلب بعدها التطبيق رمز وصول (access token) من خادوم التّرخيص (الواجهة البرمجيّة) مُقدّمًا ما يُثبت هوّيته مع إذن التّرخيص الّذي حصل عليه. إن كانت هويّة التّطبيق موثّقة وإذن التّرخيص سليمًا، أصدر خادوم التّرخيص (الواجهة البرمجيّة) رمز وصول (access token) يمنحه للتّطبيق، لتكتمل حينئذٍ عمليّة الترخيص. يطلب التّطبيق من خادوم المحتوى (الواجهة البرمجيّة) المحتوى المطلوب، مُقدّمًا رمز الوصول الّذي حصل عليه. إن كان رمز الوصول سليمًا، قدّم خادوم المحتوى (الواجهة البرمجيّة) المحتوى المقصود للتطبيق قد يختلف مسار العمليّة الفعليّ بحسب نوع الرّخصة المُستخدمة، ولكن هذه هي الفكرة العامّة. سنستعرض أنواع الرُّخَص المُختلفة في فقرة لاحقة. تسجيل التّطبيق قبل استخدام OAuth في تطبيقاتك، عليك تسجيل التّطبيق في الخدمة المعنيّة. يجري التسجيل عادةً من خلال نموذج في قسم المُطوّرين أو الواجهة البرمجيّة في موقع الخدمة على الويب، حيث ينبغي عليك تقديم البيانات التالية (وربّما معلومات أخرى عن تطبيقك): اسم التّطبيق موقع التّطبيق رابط إعادة الّتوجيه (Redirect URL) أو الاستدعاء الرّاجع (Callback URL) تُعيد الخدمة توجيه المستخدم إلى رابط إعادة التّوجيه الّذي توفّره بعد ترخيصه لتطبيق (أو رفضه)، وعليه فإنّ هذا الرابط هو المسؤول عن التّعامل مع رموز الترخيص أو رموز الوصول (access tokens). مُعرّف العميل وكلمة سرّ العميل ستمنحك الخدمة بعد تسجيل تطبيقك "وثائق اعتماد العميل" المؤلّفة من معرّف العميل وكلمة سرّ العميل. معرّف العميل هو سلسلة من الحروف مكشوفة للعموم تستخدمها الواجهة البرمجيّة للخدمة لتحديد هويّة التّطبيق، ولبناء روابط التّرخيص المُقدّمة للمستخدمين. أمّا كلمة سر العميل فتُستخدم للاستيثاق من هويّة التّطبيق بالنّسبة للواجهة البرمجيّة للخدمة عندما يطلب التّطبيق الوصول إلى حساب المُستخدم، ويجب أن تبقى سرّيّة بين التّطبيق والواجهة البرمجيّة. إذن التّرخيص في فقرة "سير البروتوكول نظريًّا"، تبيّن الخطوات الأربع الأولى كيفيّة الحصول على إذن بالتّرخيص ورمز للوصول. يعتمد نوع الإذن على طريقة طلب التّطبيق للتّرخيص، وأنواع الأذون الّتي تدعمها الواجهة البرمجيّة. يعرّف OAuth 2 أربعة أنواع من أذون التّرخيص، يمكن الاستفادة من كلّ منها في حالات مُختلفة: رمز التّرخيص (Authorization Code): تستخدمه التّطبيقات الّتي تعمل على الخواديم ضمنيّ (Implicit): تستخدمه تطبيقات الويب وتطبيقات الأجهزة المحمولة (أي التّطبيقات الّتي تعمل على جهاز المُستخدم) كلمة مرور مالك المحتوى: تستخدمها التّطبيقات الموثوقة، كتلك الّتي تتبع للخدمة ذاتها كلمة مرور العميل: تستخدم في حالة الوصول للواجهة البرمجيّة للتّطبيقات سنشرح أنواع الأذون وحالات استخدامها بالتّفصيل في الفقرات التّالية. الإذن من نوع "رمز الترخيص": هذا النّوع من الأذون هو الأكثر استخدامًا لأنّه مُصمّم للتطبيقات الّتي تعمل على الخواديم، حيث لا يُكشف النّص المصدريّ للتّطبيق للعموم، وحيث يمكن الاحتفاظ بسرّيّة كلمة سرّ العميل بصورة تامّة. ويعتمد سير التّرخيص هنا على إعادة التّوجيه، ممّا يعني أنّه على التّطبيق أن يكون قادرًا على التّفاعل مع وكيل المستخدم (كمتصفّح الويب الّذي يستخدمه) وعلى استقبال رموز التّرخيص الّتي توفّرها الواجهة البرمجيّة والّتي تمرّ من خلال وكيل المستخدم. سنشرح الآن سير عمليّة التّرخيص في هذا النّوع من الأذون: سير التّرخيص بالرّمز الخطوة 1: رابط رمز الترخيص يُعطى المُستخدم في البداية رابطًا لرمز التّرخيص يُشبه هذا: https://cloud.digitalocean.com/v1/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read فيما يلي شرحٌ لمكوّنات الرابط: https://cloud.digitalocean.com/v1/oauth/authorize: نقطة الوصول إلى قسم التّرخيص في الواجهة البرمجيّة client_id=client_id: مُعرّف العميل للّتطبيق (كيفيّة تحديد الواجهة البرمجيّة لهويّة التّطبيق) redirect_uri=CALLBACK_URL: المكان الّذي تعيد الخدمة توجيه وكيل المستخدم إليه بعد منح رمز الترخيص response_type=code: يُبيّن أنّ تطبيقك يطلب إذنًا بالحصول على رمز ترخيص scope=read: يُعيّن مستوى الوصول الّذي يطلبه المُستخدم الخطوة 2: يُرخّص المستخدم التّطبيق عندما ينقر المُستخدم الرّابط، يجب عليه أوّلًا تسجيل الدّخول إلى الخدمة، وذلك للتّحقق من هويّة المُستخدم (ما لم يكن قد سجّل دخوله من قبل). ثم تعرض عليه الخدمة ترخيص أو رفض وصول التّطبيق إلى حسابه. فيما يلي مثال عن صفحة ترخيص التّطبيق: هذه الصّورة مُلتقطة من صفحة ترخيص DigitalOcean، ونرى فيها التّطبيق "Thedropletbook App" يطلب إذنًا بقراءة حساب المُستخدم "manicas@digitalocean.com". الخطوة 3: يتلقّى التطبيق رمز التّرخيص إذا نقر المُستخدم "Authorize Application"، فإنّ الخدمة تُعيد تحويل وكيل المستخدم إلى رابط إعادة التّوجيه الّذي حدّده التّطبيق أثناء تسجيل المُطوِّر له، وتُرفق الخدمة مع الرّابط رمز التّرخيص. مثال على الرّابط (مُفترضين أنّ التّطبيق هو "dropletbook.com"): https://dropletbook.com/callback?code=AUTHORIZATION_CODE الخطوة 4: يطلب التّطبيق رمز الوصول (Access Token) يطلب التّطبيق من الخدمة رمز وصول، مُمرّرًا لها رمز التّرخيص مع تفاصيله، بما في ذلك كلمة سرّ العميل، والّتي تُرسل جميعها إلى رابط الحصول على رمز الوصول الخاصّ بالخدمة. فيما بلي مثال على طلب POST يُرسل إلى رابط رمز الوصول في DigitalOcean: https://cloud.digitalocean.com/v1/oauth/token?client_id=CLIENT_ID&client_secret=CLIENT_SECRET&grant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=CALLBACK_URL الخطوة 5: يتلقّى التّطبيق رمز الوصول إن كان التّرخيص سليمًا، فإنّ الواجهة البرمجيّة تردّ على الطّلب بجواب يحوي رمز الوصول (مع رمز إعادة تجديد الرّخصة، غير إلزاميّ) إلى التّطبيق. يبدو الجواب مثل هذا: {"access_token":"ACCESS_TOKEN","token_type":"bearer","expires_in":2592000,"refresh_token":"REFRESH_TOKEN","scope":"read","uid":100101,"info":{"name":"Mark E. Mark","email":"mark@thefunkybunch.com"}} أصبح التّطبيق الآن مُرخّصًا! وبإمكانه استخدام الّرمز للوصول إلى حساب المُستخدم عن طريق الواجهة البرمجيّة للخدمة، محدودًا بنطاق الوصول، إلى أن تنتهي مدّة الرّمز أو يُسحب التّرخيص. في حال أُصدر رمز إعادة تجديد الرّخصة (refresh token)، فبإمكان التّطبيق استخدامه للحصول على رمز وصول جديد في حال انتهى مدّة السّابق. الإذن الضّمنيّ يُستخدم نوع الأذون الضّمنيّ في تطبيقات الويب (التي تعمل في المتصفح) وتطبيقات الأجهزة المحمولة، حيث يصعب ضمان سرّية كلمة سرّ العميل. يقوم هذا النّوع من الأذون على مبدأ إعادة التّوجيه أيضًا، إلّا أنّ رمز الوصول يُعطى لوكيل المُستخدم ليقوم بدفعه إلى التّطبيق، وبهذا قد يُكشف للمُستخدم وللتّطبيقات على جهازه. لا يتضمّن سير التّرخيص في هذا النّوع هوّيّة التّطبيق، بل يعتمد على رابط إعادة التّوجيه (الّذي سُجّل في الخدمة) للوصول إلى هذا الهدف. لا يدعم هذا النّوع من الأذون رموز إعادة تجديد التّرخيص. يسير التّرخيص في هذا النّوع كما يلي: يُطلب من المُستخدم ترخيص التّطبيق، ثمّ يُمرّر خادوم التّرخيص رمز الوصول إلى وكيل المُستخدم، الّذي ينقله بدوره إلى التّطبيق. إن كُنت مُهتمًّا بالتّفاصيل، فتابع القراءة. سير التّرخيص الضّمنيّ الخطوة 1: رابط التّرخيص الضّمني يُعرض على المُستخدم رابط التّرخيص، الّذي يطلب رمزًا من الواجهة البرمجيّة، يبدو هذا الرّابط مُشابهًا لرابط رمز التّرخيص، باستثناء أنّه يطلب رمز token بدلًا من code (لاحظ نوع الجواب المطلوب "token"): https://oauth.example.com/authorize?response_type=token&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read ملاحظة: لا تدعم DigitalOcean حاليًا التّرخيص الضّمني، لذا ذكرنا رابطًا وهميًّا "oauth.example.com". الخطوة 2: يرخّص المُستخدم التّطبيق عندما ينقر المُستخدم الرّابط، يجب عليه أوّلًا تسجيل الدّخول إلى الخدمة، وذلك للتّحقق من هويّة المُستخدم (ما لم يكن قد سجّل دخوله من قبل). ثم تعرض عليه الخدمة ترخيص أو رفض وصول التّطبيق إلى حسابه. فيما يلي مثال عن صفحة ترخيص التّطبيق: نرى في الصّورة التّطبيق "Thedropletbook App" يطلب إذنًا بقراءة حساب المُستخدم "manicas@digitalocean.com". الخطوة 3: يتلقّى وكيل المُستخدم رمز الوصول مع رابط إعادة التّوجيه إذا نقر المُستخدم "Authorize Application"، فإنّ الخدمة تُعيد تحويل وكيل المستخدم إلى رابط إعادة التّوجيه الّذي حدّده التّطبيق أثناء تسجيل المُطوِّر له، وتُرفق الخدمة مع الرّابط رمز الوصول. مثال على الرّابط: https://dropletbook.com/callback#token=ACCESS_TOKEN الخطوة 4: يتبع وكيل المُستخدم مسار إعادة التّوجيه يتبع وكيل المُستخدم رابط إعادة التّوجيه مع احتفاظه برمز الوصول. الخطوة 5: يُرسل التّطبيق نصًّا برمجيًّا لاستخراج رمز الوصول يُعيد التّطبيق صفحة ويب تحوي نصًّا برمجيًّا بإمكانه استخراج رمز الوصول من رابط إعادة التّوجيه الكامل الّذي احتفظ به وكيل المُستخدم. الخطوة 6: يُمرّر رمز الوصول إلى التّطبيق يُنفّذ وكيل المستخدم النّصّ البرمجيّ ويُمرّر رمز الوصول المُستخرَج إلى التّطبيق. أصبح التّطبيق الآن مُرخّصًا! وبإمكانه استخدام الّرمز للوصول إلى حساب المُستخدم عن طريق الواجهة البرمجيّة للخدمة، محدودًا بنطاق الوصول، إلى أن تنتهي مدّة الرّمز أو يُسحب التّرخيص. ملاحظة: لا تدعم DigitalOcean حاليًا التّرخيص الضّمني، لذا ذكرنا رابطًا وهميًّا "oauth.example.com". الإذن بالوصول إلى كلمة مرور مالك المُحتوى في هذا النّوع من التّرخيص، يزوّد المستخدم التّطبيق مباشرةً باسم حسابه وكلمة مروره، ليستخدمها للحصول على رمز الوصول من الخدمة. يجب استخدام هذا النّوع من الأذون في الخوادم عندما لا تكون الأنواع الأخرى مُناسبة فقط. ويجب استخدامه فقط في حال كان التّطبيق موضع ثقة المُستخدم، كأن يكون تابعًا للخدمة ذاتها، أو أن يكون نظام التّشغيل على حاسوب المُستخدم هو ما يطلب الوصول. سير التّرخيص بالحصول على كلمة مرور المُستخدم بعد أن يُعطي المستخدم كلمة مروره للتّطبيق، يطلب التّطبيق رمز الوصول من خادوم التّرخيص. يُشبه طلب POST ما يلي: https://oauth.example.com/token?grant_type=password&username=USERNAME&password=PASSWORD&client_id=CLIENT_ID إن كان اسم المُستخدم وكلمة المرور صحيحين، يُعيد خادوم التّرخيص رمز وصول للتّطبيق ويُصبح التّطبيق مُرخّصًا! ملاحظة: لا تدعم DigitalOcean حاليًا التّرخيص بالحصول على كلمة المرور، لذا ذكرنا رابطًا وهميًّا "oauth.example.com". الإذن بالوصول إلى كلمة مرور العميل في هذا النّوع من التّرخيص، يوفّر التّطبيق طريقة للوصول إلى حسابه الخاصّ على الخدمة. من الأمثلة الّتي يكون فيها استخدام هذا النّوع مُفيدًا أن يرغب التّطبيق بتحديث وصفه أو رابط إعادة التّوجيه المُسجّلين في الخدمة، أو أن يصل إلى بيانات أخرى حول حسابه على الخدمة عن طريق الواجهة البرمجيّة. سير التّرخيص بالحصول على كلمة مرور العميل يطلب التّطبيق رمز الوصول مُرسلًا مُعرَّفه وكلمة مروره إلى خادوم التّرخيص، فيما يلي مثال عن طلب POST: https://oauth.example.com/token?grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET إن كان مُعرّف التّطبيق وكلمة مروره صحيحين، يُعيد خادوم التّرخيص رمز وصول للتّطبيق ويُصبح التّطبيق مُرخّصًا باستخدام حسابه الخاصّ! ملاحظة: لا تدعم DigitalOcean حاليًا التّرخيص بالحصول على كلمة مرور العميل، لذا ذكرنا رابطًا وهميًّا "oauth.example.com". مثال على استخدام رمز الوصول بعد أن يحصل التّطبيق على رمز الوصول، إمكانه استخدام هذا الّرمز للوصول إلى حساب المُستخدم عن طريق الواجهة البرمجيّة للخدمة، محدودًا بنطاق الوصول، إلى أن تنتهي مدّة الرّمز أو يُسحب التّرخيص. فيما يلي مثال عن طلب يُرسل للواجهة البرمجيّة للخدمة باستخدام curl، لاحظ أنّه يتضمّن رمز الوصول: curl -X POST -H "Authorization: Bearer ACCESS_TOKEN""https://api.digitalocean.com/v2/$OBJECT" على فرض أنّ رمز الوصول سليم، فإنّ الواجهة البرمجيّة تُعالج الطّلب حسب ما صُمِّمت؛ وإلّا أعادت الواجهة خطأ "invalid_request"، كما يحدث عند انتهاء مدّة التّرخيص أو استخدام رمز خاطئ. سير الحصول على رمز إعادة تجديد الرُخصة يؤدّي استخدام رمز وصول بعد انتهاء مدّة صلاحيّته إلى "خطأ رمز غير سليم (Invalid Token Error)". في هذه النّقطة، يمكن استخدام رمز إعادة تجديد الرّخصة في حال أُصدَر مع رمز الوصول للحصول على رمز وصول جديد من خادوم التّرخيص. فيما يلي مثال على طلب POST للحصول على رمز وصول جديد مُستخدمين رمز إعادة تجديد: https://cloud.digitalocean.com/v1/oauth/token?grant_type=refresh_token&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&refresh_token=REFRESH_TOKEN الخاتمة إلى هناك نكون قد وصلنا إلى ختام دليل OAuth. من المُفترض أن لديك الآن فكرة جيّدة عن البروتوكول وكيف يعمل، ومتى يمكن استخدام كلّ نوع من الأذون. إذا أردت تعلّم المزيد عن OAuth 2، اطّلع على هذه المصادر القيّمة (بالإنكليزيّة): How To Use OAuth Authentication with DigitalOcean as a User or Developer How To Use the DigitalOcean API v2 The OAuth 2.0 Authorization Framwork ترجمة (بشيء من التّصرّف) لمقال Introduction to OAuth 2 لصاحبه Mitchell Anicas.1 نقطة
-
تحدثنا بالقسم السابق عن الأنواع الأساسية (primitive types) الثمانية بالإضافة إلى النوع String. هنالك فارق جوهري بينهما، وهو أن القيم من النوع String عبارة عن كائنات (objects). على الرغم من أننا لن نناقش الكائنات تفصيليًا حتى نَصِل إلى الفصل الخامس، فما يزال من المفيد أن نَطَّلِع قليلًا عليها وعلى مفهوم الأصناف (classes) المُرتبِط بها إلى حد كبير. لن يُمكِّننا استيعاب مفاهيم أساسية كالكائنات (objects) والأصناف (classes) على اِستخدَام السَلاسِل النصية من النوع String فقط، وإنما سيَفتَح لنا الباب لاستيعاب مفاهيم برمجية آخرى مُهِمّة مثل البرامج الفرعية (subroutines). الدوال (functions) والبرامج الفرعية (subroutines) المبنية مسبقًا تَذَكَّر أن البرنامج الفرعي (subroutine) ما هو إلا مجموعة من التَعْليمَات (instructions) مُضمَّنة معًا تحت اسم معين، ومُصمَّمة لتكون مسئولة عن إنجاز مُهِمّة واحدة مُحدَّدة. سنتعلَّم أسلوب كتابة البرامج الفرعية (subroutines) بالفصل الرابع، ومع ذلك يُمكِنك أن تُنجز الكثير فقط باستدعاء البرامج الفرعية التي كُتبَت بالفعل بواسطة مبرمجين آخرين. يُمكِنك أن تَستدعِي برنامجًا فرعيًا ليُنجز المُهِمّة المُوكَلة إليه باِستخدَام تَعْليمَة استدعاء برنامج فرعي (subroutine call statement). يُعرَّف أي برنامج فرعي بالجافا ضِمْن صَنْف (class) أو كائن (object)، وتَتَضمَّن بعض الأصناف القياسية بلغة الجافا برامجًا فرعية مُعرَّفة مُسْبَقًا يُمكِنك اِستخدَامها. فمثلًا، تحتوي القيم من النوع String التي هي عبارة عن كائن على برامج فرعية مبنية مُسْبَقًا يُمكِنها معالجة السَلاسِل النصية (strings)، والتي يُمكِنك أن تَستدعِيها دون فهم طريقة كتابتها أو كيفية عملها. في الواقع، هذا هو الغرض الأساسي منها: أي برنامج فرعي هو صندوق أسود (black box) يُمكِن اِستخدَامه بدون مَعرِفة ما يحدث داخله. لنَفْحَص أولًا البرامج الفرعية (subroutines) التي تُعدّ جزءًا من صَنْف (class). يُستخدَم أي صَنْف عمومًا لتجميع بعض المُتْغيِّرات (variables) والبرامج الفرعية (subroutines) المُعرَّفة بذلك الصنف معًا، ويُعرَف كلاهما باسم "أعضاء الصَنْف الساكنة (static members)". لقد رأينا مثالًا لذلك بالفعل: إذا كان لدينا صنف يُعرِّف برنامجًا، فإن البرنامج main() هو عضو ساكن (static member) بذلك الصَنْف. ينبغي أن تُضيف الكلمة المحجوزة static عندما تُعرِّف عضو ساكن مثل الكلمة static بالتَصْرِيح public static void main.... عندما يحتوي صنف (class) معين على مُتْغيِّر أو برنامج فرعي ساكن (static)، يُعدّ اسم الصنف جزءًا من الاسم الكامل لذلك المُتْغيِّر أو لذلك البرنامج الفرعي. على سبيل المثال، يحتوي الصنف القياسي System على البرنامج الفرعي exit لذا يُمكِنك أن تُشير إليه باستخدام الاسم System.exit والذي يَتَكوَّن من كُلًا من اسم الصنف المُتْضمِّن للبرنامج الفرعي متبوعًا بنقطة ثم باسم البرنامج الفرعي نفسه. يَستقبِل البرنامج الفرعي exit مُعامِلًا من النوع int، لذلك يُمكِنك اِستخدَامه بكتابة تَعْليمَة استدعاء برنامج فرعي (subroutine call statement) كالتالي: System.exit(0); يُنهِي الاستدعاء System.exit البرنامج ويُغلِق آلة جافا الافتراضية (Java Virtual Machine)، لذا يُمكِنك أن تَستخدِمه إذا كنت تُريد إنهاء البرنامج قبل نهاية البرنامج main(). تُشير قيمة المُعامِل المُمرَّرة إلى سبب إغلاق البرنامج، فإذا كانت تُساوِي القيمة ٠، يَعنِي ذلك أن البرنامج قد انتهى بشكل طبيعي. في المقابل، تَعنِي أي قيمة آخرى انتهاء البرنامج لوجود خطأ مثل الاستدعاء System.exit(1). تُرسَل قيمة المُعامِل المُمرَّرة إلى نظام التشغيل والذي عادةً ما يتجاهلها. يُعدّ الصَنْف System واحدًا فقط من ضِمْن مجموعة من الأصناف القياسية التي تأتي مع الجافا. كمثال آخر، يَتَضمَّن الصنف Math مُتْغيِّرات ساكنة (static variables) مثل Math.PI و Math.E والتي قيمها هي الثوابت الرياضية π و e على الترتيب كما يُعرِّف عددًا كبيرًا من الدوال (functions) الحسابية. يُنفِّذ أي برنامج فرعي (subroutine) في العموم مُهِمّة مُحدّدة. لبعض البرامج الفرعية، تَحسِب المُهِمّة قيمة إحدى البيانات ثم تُعيدها، وفي تلك الحالة، تُعرَف تلك البرامج الفرعية باسم الدوال (functions)، ونقول عندها أن تلك الدالة تُعيد (return value) قيمة، والتي يُفْترَض استخدامها بطريقة ما ضِمْن البرنامج المُستدعِي للدالة. لنَفْترِض مثلًا مسالة حِسَاب الجذر التربيعي (square root)، تُوفِّر لغة الجافا دالة (function) لهذا الغرض اسمها هو Math.sqrt. تُعدّ تلك الدالة عضو برنامج فرعي ساكن (static member subroutine) بالصنف Math. إذا كانت x هي أي قيمة عددية، فإن الاستدعاء Math.sqrt(x) يَحسِب الجذر التربيعي (root) لتلك القيمة ثم يُعيدها. لمّا كانت الدالة Math.sqrt(x) تُمثِل قيمة، فليس هناك أي مغزى من استدعائها بمفردها بتَعْليمَة استدعاء برنامج فرعي (subroutine call statement) كالتالي: Math.sqrt(x); لا يَفعَل الحاسوب أي شيء بالقيمة المُعادة من الدالة (function) بالأعلى، أي أنه يَحسِبها ثم يتجاهلها، وهو أمر غَيْر منطقي، لذا يُمكِنك أن تُخبره مثلًا بأن عليه طباعتها على الأقل كالتالي: System.out.print( Math.sqrt(x) ); // اعرض الجذر التربيعي أو قد تَستخدِم تَعْليمَة إِسْناد (assignment statement) لتُخبره بأن عليه تَخْزِينها بمُتْغيِّر كالتالي: lengthOfSide = Math.sqrt(x); يُمثِل استدعاء الدالة Math.sqrt(x) قيمة من النوع double أي يُمكِنك كتابة ذلك الاستدعاء أينما أَمْكَن اِستخدَام قيمة عددية مُصنَّفة النوع (numeric literal) من النوع double. يُمثِل x مُعامِلًا (parameter) يُمرَّر إلى البرنامج الفرعي (subroutine) والذي قد يَكُون مُتْغيِّرًا (variable) اسمه x أو قد يَكُون أي تعبير آخر (expression) بشَّرط أن يُمثِل ذلك التعبير قيمة عددية. على سبيل المثال، يَحسِب الاستدعاء Math.sqrt(2) قيمة الجذر التربيعي للعَدَد ٢ أما الاستدعاء Math.sqrt(a*a+b*b) فهو صالح تمامًا طالما كانت قيم a و b مُتْغيِّرات من النوع العددي. يحتوي الصَنْف Math على العديد من الدوال الأعضاء الساكنة (static member functions) الأخرى. اُنظر القائمة التالية والتي تَعرِض بعضًا منها: Math.abs(x): تَحسِب القيمة المطلقة للمُعامِل x. الدوال المثلثية العادية (trigonometric functions) Math.sin(x) و Math.cos(x) و Math.tan(x): تُقاس جميع الزوايا بوحدة قياس راديان (radians) وليس بوحدة الدرجات (degrees). الدوال المثلثية العكسية (inverse trigonometric functions) Math.asin(x) و Math.acos(x) و Math.atan(x): تُقاس القيمة المُعادة (return value) من تلك الدوال بوحدة قياس راديان وليس بوحدة الدرجات. تَحسِب الدالة الأسية Math.exp(x) قيمة العدد e مرفوعة للأس x أما دالة اللوغاريتم الطبيعي Math.log(x) فتَحسِب لوغاريتم x بالنسبة للأساس e. Math.pow(x,y) تحسب قيمة x مرفوعة للأس y. Math.floor(x): تُقرِّب x لأكبر عدد صحيح أقل من أو يُساوِي x. تَحسِب تلك الدالة عددًا صحيحًا بالمفهوم الرياضي، ولكنها مع ذلك تُعيد قيمة من النوع double بدلًا من النوع int كما قد تَتَوقَّع. فمثلًا يُعيد استدعاء الدالة Math.floor(3.76) القيمة 3.0 بينما يُعيد استدعاء الدالة Math.floor(-4.2) القيمة -5. علاوة على ذلك، تَتَوفَّر أيضًا الدالة Math.round(x) والتي تُعيد أقرب عدد صحيح للمُعامِل x وكذلك الدالة Math.ceil(x) والتي تُقرِّب x لأصغر عدد صحيح أكبر من أو يُساوِي x. Math.random() تُعيد قيمة عشوائية من النوع double ضِمْن نطاق يتراوح من ٠ إلى ١. تَحسِب تلك الدالة أعدادًا شبه عشوائية (pseudorandom number) أي أنها ليست عشوائية تمامًا وإنما إلى درجة كافية لغالبية الأغراض. سنكتشف خلال الفصول القادمة أن تلك الدالة لها استخدامات أخرى كثيرة مفيدة. تَستقبِل الدوال (functions) بالأعلى مُعامِلات (parameters) -أي x أو y داخل الأقواس- بأي قيم طالما كانت من النوع العددي أما القيم المُعادة (return value) من غالبيتها فهي من النوع double بغض النظر عن نوع المُعامِل (parameter) باستثناء الدالة Math.abs(x) والتي تَكُون قيمتها المُعادة من نفس نوع المُعامِل x، فإذا كانت x من النوع int، تُعيد تلك الدالة قيمة من النوع int أيضًا وهكذا. على سبيل المثال، تُعيد الدالة Math.sqrt(9) قيمة من النوع double تُساوِي 3.0 بينما تُعيد الدالة Math.abs(9) قيمة من النوع int تُساوِي 9. لا تَستقبِل الدالة Math.random() أي مُعامِلات (parameter)، ومع ذلك لابُدّ من كتابة الأقواس حتى وإن كانت فارغة لأنها تَسمَح للحاسوب بمَعرِفة أنها تُمثِل برنامجًا فرعيًا (subroutine) لا مُتْغيِّرًا (variable). تُعدّ الدالة System.currentTimeMillis() من الصنف System مثالًا آخرًا على برنامج فرعي (subroutine) ليس له أي مُعامِلات (parameters). عندما تُنفَّذ تلك الدالة، فإنها تُعيد الوقت الحالي مُقاس بحساب الفارق بين الوقت الحالي ووقت آخر قياسي بالماضي (بداية عام ١٩٧٠) بوحدة المللي ثانية. تَكُون القيمة المعادة من System.currentTimeMillis() من النوع long (عدد صحيح ٦٤ بت). يُمكِنك اِستخدَام تلك الدالة لحِساب الوقت الذي يَستَغْرِقه الحاسوب لتّنْفيذ مُهِمّة معينة. كل ما عليك القيام به هو أن تُسجِّل كُلًا من الوقت الذي بدأ فيه التّنْفيذ وكذلك الوقت الذي انتهى به ثم تَحسِب الفرق بينهما. للحصول على توقيت أكثر دقة، يُمكِنك اِستخدَام الدالة System.nanoTime() والتي تُعيد الوقت الحالي مُقاس بحساب الفارق بين الوقت الحالي ووقت آخر عشوائي بالماضي بوحدة النانو ثانية. لكن لا تَتَوقَّع أن يَكُون الوقت دقيقًا بحق لدرجة النانوثانية. يُنفِّذ البرنامج بالمثال التالي مجموعة من المهام الحسابية ويَعرِض الوقت الذي يَستَغْرِقه البرنامج لتّنْفيذ كُلًا منها: /** * This program performs some mathematical computations and displays the * results. It also displays the value of the constant Math.PI. It then * reports the number of seconds that the computer spent on this task. */ public class TimedComputation { public static void main(String[] args) { long startTime; // وقت البدء بالنانو ثانية long endTime; // وقت الانتهاء بالنانو ثانية long compTime; // زمن التشغيل بالنانو ثانية double seconds; // فرق الوقت بالثواني startTime = System.nanoTime(); double width, height, hypotenuse; // جوانب المثلث width = 42.0; height = 17.0; hypotenuse = Math.sqrt( width*width + height*height ); System.out.print("A triangle with sides 42 and 17 has hypotenuse "); System.out.println(hypotenuse); System.out.println("\nMathematically, sin(x)*sin(x) + " + "cos(x)*cos(x) - 1 should be 0."); System.out.println("Let's check this for x = 100:"); System.out.print(" sin(100)*sin(100) + cos(100)*cos(100) - 1 is: "); System.out.println( Math.sin(100)*Math.sin(100) + Math.cos(100)*Math.cos(100) - 1 ); System.out.println("(There can be round-off errors when" + " computing with real numbers!)"); System.out.print("\nHere is a random number: "); System.out.println( Math.random() ); System.out.print("\nThe value of Math.PI is "); System.out.println( Math.PI ); endTime = System.nanoTime(); compTime = endTime - startTime; seconds = compTime / 1000000000.0; System.out.print("\nRun time in nanoseconds was: "); System.out.println(compTime); System.out.println("(This is probably not perfectly accurate!"); System.out.print("\nRun time in seconds was: "); System.out.println(seconds); } // نهاية main() } // نهاية الصنف TimedComputation الأصناف (classes) والكائنات (objects) بالإضافة إلى استخدام الأصناف (classes) كحاويات للمُتْغيِّرات والبرامج الفرعية الساكنة (static). فإنها قد تُستخدَم أيضًا لوصف الكائنات (objects). يُعدّ الصنف ضِمْن هذا السياق نوعًا (type) بنفس الطريقة التي تُعدّ بها كلًا من int و double أنواعًا أي يُمكِننا إذًا أن نَستخدِم اسم الصنف للتَصْرِيح (declare) عن مُتْغيِّر (variable). تَحمِل المُتْغيِّرات في العموم نوعًا واحدًا من القيم يَكُون في تلك الحالة عبارة عن كائن (object). ينتمي أي كائن إلى صنف (class) معين يُبلِّغه بنوعه (type)، ويُعدّ بمثابة تجميعة من المُتْغيِّرات والبرامج الفرعية (subroutines) يُحدِّدها الصنف الذي ينتمي إليه الكائن أي تتشابه الكائنات (objects) من نفس الصنف (class) وتحتوي على نفس تجميعة المُتْغيِّرات والبرامج الفرعية. على سبيل المثال، إذا كان لدينا سطح مستو، وأردنا أن نُمثِل نقطة عليه باِستخدَام كائن، فيُمكِن إذًا لذلك الكائن المُمثِل للنقطة أن يُعرِّف مُتْغيِّرين (variables) x و y لتمثيل إحداثيات النقطة. ستُعرِّف جميع الكائنات المُمثِلة لنقطة قيمًا لكُلًا من x و y والتي ستكون مختلفة لكل نقطة معينة. في هذا المثال، يُمكِننا أن نُعرِّف صَنْفًا (class) اسمه Point مثلًا ليُعرِّف (define) البنية المشتركة لجميع الكائنات المُمثِلة لنقطة بحيث تَكُون جميع تلك الكائنات (objects) قيمًا من النوع Point. لنَفْحَص الاستدعاء System.out.println مرة آخرى. أولًا، System هو عبارة عن صَنْف (class) أما out فهو مُتْغيِّر ساكن (static variable) مُعرَّف بذلك الصنف. ثانيًا، يشير المُتْغيِّر System.out إلى كائن (object) من الصَنْف القياسي PrintStream و System.out.println هو الاسم الكامل لبرنامج فرعي (subroutine) مُعرَّف بذلك الكائن. يُمثِل أي كائن من النوع PrintStream مقصدًا يُمكِن طباعة المعلومات من خلاله حيث يَتَضمَّن برنامجًا فرعيًا println يُستخدَم لإرسال المعلومات إلى ذلك المقصد. لاحِظ أن الكائن System.out هو مُجرد مقصد واحد محتمل، فقد تُرسِل كائنات (objects) آخرى من النوع PrintStream المعلومات إلى مقاصد أخرى مثل الملفات أو إلى حواسيب آخرى عبر شبكة معينة. يُمثَل ذلك ما يُعرَف باسم البرمجة كائنية التوجه (object-oriented programming): عندما يَتَوفَّر لدينا مجموعة من الأشياء المختلفة في العموم والتي لديها شيئًا مشتركًا مثل كَوْنها تَعمَل مقصدًا للخَرْج، فإنه يُمكِن اِستخدَامها بنفس الطريقة من خلال برنامج فرعي مثل println. في هذا المثال، يُعبِر الصَنْف PrintStream عن الأمور المُشتركة بين كل تلك الأصناف (objects). تلعب الأصناف (classes) دورًا مزدوجًا وهو ما قد يَكُون مُربِكًا للبعض، ولكن من الناحية العملية تُصمَّم غالبية الأصناف لكي تؤدي دورًا واحدًا منها بشكل رئيسي أو حصري. لا تقلق عمومًا بشأن ذلك حتى نبدأ في التعامل مع الكائنات (objects) بصورة أكثر جديّة بالفصل الخامس. لمّا كانت أسماء البرامج الفرعية (routines) دائمًا متبوعة بقوس أيسر، يَصعُب خَلْطها إذًا مع أسماء المُتْغيِّرات. في المقابل، تُستخدَم أسماء كُلًا من الأصناف (classes) والمُتْغيِّرات (variables) بنفس الطريقة، لذا قد يَكُون من الصعب أحيانًا التَمْييز بينها. في الواقع، تَتَّبِع جميع الأسماء المُعرَّفة مُسْبَقًا بالجافا نَمْط تسمية تبدأ فيه أسماء الأصناف بحروف كبيرة (upper case) بينما تبدأ أسماء المُتْغيِّرات والبرامج الفرعية (subroutines) بحروف صغيرة (lower case). لا يُمثِل ذلك قاعدة صيغة (syntax rule)، ومع ذلك من الأفضل أن تَتَّبِعها أيضًا. كملاحظة عامة أخيرة، يُستخدَم عادةً مصطلح "التوابع (methods)" للإشارة إلى البرامج الفرعية (subroutines) بالجافا. يَعنِي مصطلح "التابع (method)" أن برنامجًا فرعيًا (subroutine) مُعرَّف ضِمْن صنف (object) أو كائن (object). ولأن ذلك يُعدّ صحيحًا لأي برنامج فرعي بالجافا، فإن أيًا منها يُعدّ تابعًا. سنميل عمومًا إلى اِستخدَام المصطلح الأعم "البرنامج الفرعي (subroutine)"، ولكن كان لابُدّ من إعلامك بأن بعض الأشخاص يُفضِّلون اِستخدَام مصطلح "التابع (method)". العمليات على السلاسل النصية من النوع String يُمثِل String صنفًا (class)، وأي قيمة من النوع String هي عبارة عن كائن (object). يَحتوِي أي كائن في العموم على بيانات (data) وبرامج فرعية (subroutines). بالنسبة للنوع String، فإن البيانات مُكوَّنة من متتالية محارف السِلسِلة النصية (string) أما البرامج الفرعية فهي في الواقع مجموعة من الدوال (functions) منها مثلًا الدالة length والتي تَحسِب عدد المحارف (characters) بالسِلسِلة النصية. لنَفْترِض أن advice هو مُتْغيِّر يُشير إلى String، يُمكِننا إذًا أن نُصرِّح عنه ونُسنِد إليه قيمة كالتالي: String advice; advice = "Seize the day!"; الآن، يُمثِل التعبير advice.length() استدعاءً لدالة (function call)، والتي ستُعيد في تلك الحالة تحديدًا عدد محارف السِلسِلة النصية "Seize the day!" أي ستُعيد القيمة ١٤. في العموم، لأي مُتْغيِّر str من النوع String، يُمثِل الاستدعاء str.length() قيمة من النوع int تُساوِي عدد المحارف بالسِلسِلة النصية (string). لا تَستقبِل تلك الدالة (function) أي مُعامِلات (parameters) لأن السِلسِلة النصية المطلوب حِسَاب طولها هي بالفعل قيمة المُتْغيِّر str. يُعرِّف الصَنْف String البرنامج الفرعي length ويُمكِن اِستخدَامه مع أي قيمة من النوع String حتى مع أي سِلسِلة نصية مُجرّدة (string literals) فهي في النهاية ليست سوى قيمة ثابتة (constant value) من النوع String. يُمكِنك مثلًا كتابة برنامج يَحسِب عدد محارف السِلسِلة النصية "Hello World" كالتالي: System.out.print("The number of characters in "); System.out.print("the string \"Hello World\" is "); System.out.println( "Hello World".length() ); يُعرِّف الصَنْف String الكثير من الدوال (functions) نَستعرِض بعضًا منها خلال القائمة التالية: s1.equals(s2): تُوازن تلك الدالة بين السِلسِلتين s1 و s2، وتُعيد قيمة من النوع boolean تُساوِي true إذا كانت s1 و s2 مُكوّنتين من نفس متتالية المحارف، وتُساوِي false إن لم تَكُن كذلك. s1.equalsIgnoreCase(s2): هي دالة من النوع المنطقي (boolean-valued function). مثل الدالة السابقة، تَفْحَص تلك الدالة ما إذا كانت السِلسِلتان s1 و s2 مُكوّنتين من نفس السِلسِلة النصية (string)، ولكنها تختلف عن الدالة السابقة في أن الحروف الكبيرة (upper case) والصغيرة (lower case) تُعدّ متكافئة. فمثلًا، إذا كانت s1 تحتوي على السِلسِلة النصية "cat"، فستُعيد الدالة s1.equals("Cat") القيمة false أما الدالة s1.equalsIgnoreCase("Cat") فستُعيد القيمة true. s1.length(): هي دالة من النوع الصحيح (integer-valued function)، وتُعيد قيمة تُمثِل عدد محارف السِلسِلة النصية s1. s1.charAt(N): حيث N هو عدد صحيح. تُعيد تلك الدالة قيمة من النوع char تُساوي قيمة محرف السِلسِلة النصية برقم المَوضِع N. يبدأ الترقيم من الصفر، لذا يُمثِل استدعاء الدالة s1.charAt(0) المحرف الأول أما استدعاء الدالة s1.charAt(1) فيُمثِل المحرف الثاني، وهكذا حتى نَصِل إلى المَوضِع الأخير s1.length() - 1. مثلًا، يُعيد "cat".charAt(1) القيمة 'a'. لاحظ أنه إذا كانت قيمة المُعامِل المُمرَّرة أقل من صفر أو أكبر من أو تُساوِي s1.length، فسيَحدُث خطأ. s1.substring(N,M): حيث N و M هي أعداد صحيحة. تُعيد تلك الدالة قيمة من النوع String مُكوَّنة من محارف السلسلة النصية بالمواضع N و N+1 و .. حتى M-1 (المحرف بالمَوضِع M ليس مُضمَّنًا). تُعدّ القيمة المُعادة "سلسلة جزئية (substring)" من السِلسِلة الأصلية s1. يُمكِنك ألا تُمرِّر قيمة للمُعامِل M كالتالي s1.substring(N) وعندها ستُعيد تلك الدالة سِلسِلة جزئية من s1 مُكوَّنة من محارف السلسلة النصية بدايةً من N إلى النهاية. s1.indexOf(s2): تُعيد عددًا صحيحًا. إذا كانت s2 هى سِلسِلة جزئية (substring) من s1، تُعيد تلك الدالة مَوضِع المحرف الأول من السِلسِلة الجزئية s2 بالسِلسِلة الأصلية s1. أما إذا لم تَكُن جزءًا منها، فإنها تُعيد القيمة -١. تَتَوفَّر أيضًا الدالة s1.indexOf(ch) حيث ch عبارة عن محرف من النوع char، وتُستخدَم للبحث عنه بسِلسِلة نصية s1. تُستخدَم أيضًا الدالة s1.indexOf(x,N) لإيجاد أول حُدوث من x بعد موضع N بسِلسِلة نصية، وكذلك الدالة s1.lastIndexOf(x) لإيجاد آخر حُدوث من x بسِلسِلة نصية s1. s1.compareTo(s2): هي دالة من النوع العددي الصحيح (integer-valued function) تُوازن بين سلسلتين نصيتين s1 و s2، فإذا كانت السِلسِلتان متساويتين، تُعيد الدالة القيمة ٠ أما إذا كانت s1 أقل من s2، فإنها تُعيد عددًا أقل من ٠، وأخيرًا إذا كانت s1 أكبر من s2، فإنها تُعيد عددًا أكبر من ٠. إذا كانت السلسلتان s1 و s2 مُكوّنتين من حروف صغيرة (lower case) فقط أو حروف كبيرة (upper case) فقط، فإن الترتيب المُستخدَم بموازنات مثل "أقل من" أو "أكبر من" تُشير إلى الترتيب الأبجدي. أما إذا تَضمَّنتا محارف آخرى فسيَكُون الترتيب أكثر تعقيدًا. تَتَوفَّر دالة آخرى مشابهة هي s1.compareToIgnoreCase(s2). s1.toUpperCase(): هي دالة من النوع النصي (String-valued function) تُعيد سِلسِلة نصية جديدة تُساوِي s1 ولكن بَعْد تَحْوِيل أي حرف صغير (lower case) بها إلى حالته الكبيرة (upper case). فمثلًا، تُعيد الدالة "Cat".toUpperCase() السِلسِلة النصية "CAT". تَتَوفَّر دالة آخرى مشابهة هي s1.toLowerCase. s1.trim(): هي دالة من النوع النصي (String-valued function) تُعيد سِلسِلة نصية جديدة (string) تساوي s1 ولكن بَعْد حَذْف أي محارف غَيْر مطبوعة -كالمسافات الفارغة (spaces)- من بداية السِلسِلة النصية (string) ونهايتها. فمثلًا إذا كانت s1 هي السِلسِلة النصية "fred "، فستُعيد الدالة s1.trim() السِلسِلة "fred" بدون أي مسافات فارغة بالنهاية. لا تُغيِّر الدوال s1.toUpperCase() و s1.toLowerCase() و s1.trim() قيمة s1، وإنما تُنشِئ سِلسِلة نصية جديدة (string) تُعَاد كقيمة للدالة يُمكِن اِستخدَامها بتَعْليمَة إِسْناد (assignment statement) مثلًا كالتالي smallLetters = s1.toLowerCase();. إذا كنت تُريد تَعْدِيل قيمة s1 نفسها، فيُمكِنك ببساطة أن تَستخدِم تَعْليمَة الإِسْناد التالية s1 = s1.toLowerCase();. يُمكِنك أن تَستخدِم عَامِل الزيادة (plus operator) + لضم (concatenate) سِلسِلتين نصيتين (strings)، وينتج عنهما سِلسِلة نصية جديدة مُكوَّنة من كل محارف السِلسِلة النصية الأولى متبوعة بكل محارف السِلسِلة النصية الثانية. فمثلًا، يؤول التعبير "Hello" + "World" إلى السِلسِلة النصية "HelloWorld". إذا كنت تُريد مسافة فارغة (space) بين الكلمات، فعليك أن تُضيفها إلى أي من السِلسِلتين كالتالي Hello " + "World". لنَفْترِض أن name هو مُتْغيِّر من النوع String يُشير إلى اسم مُستخدِم البرنامج. يُمكِنك إذًا أن تُرحِّب به بتّنْفيذ التَعْليمَة التالية: System.out.println("Hello, " + name + ". Pleased to meet you!"); يُمكِنك حتى أن تَستخدِم نفس العَامِل + لكي تَضُمُّ (concatenate) قيم من أي نوع آخر إلى سِلسِلة نصية (string). ستَتَحوَّل تلك القيم إلى سِلسِلة نصية (string) أولًا كما يَحدُث عندما تَطبَعها إلى الخَرْج القياسي (standard output) ثم ستُضَمّ إلى السِلسِلة النصية الآخرى. فمثلًا، سيؤول التعبير "Number" + 42 إلى السِلسِلة النصية "Number42". اُنظر التعليمات التالية: System.out.print("After "); System.out.print(years); System.out.print(" years, the value is "); System.out.print(principal); يُمكِنك اِستخدَام التَعْليمَة المُفردة التالية بدلًا من التَعْليمَات بالأعلى: System.out.print("After " + years + " years, the value is " + principal); تُعدّ النسخة الثانية أفضل بكثير ويُمكِنها أن تَختصِر الكثير من الأمثلة التي عَرَضَناها خلال هذا الفصل. مقدمة إلى التعدادات (enums) تُوفِّر الجافا ثمانية أنواع أساسية مَبنية مُسْبَقًا (built-in primitive types) بالإضافة إلى مجموعة ضخمة من الأنواع المُعرَّفة باِستخدَام أصناف (classes) مثل النوع String، ولكنها ما تزال غَيْر كافية لتغطية جميع المواقف المُحتمَلة والتي قد يحتاج المُبرمج إلى التَعامُل معها. لهذا، وبالمثل من غالبية اللغات البرمجية الآخرى، تَمنَحك الجافا القدرة على إنشاء أنواع (types) جديدة، والتي غالبًا ما تَكُون بهيئة أصناف (classes)، وهو ما سنَتَعلَّمه بالفصل الخامس. تَتَوفَّر مع ذلك طريقة آخرى وهي التعدادات (enumerated types - enums) سنناقشها خلال هذا القسم. تقنيًا، يُعدّ أي تعداد (enum) نوعًا خاصًا من صَنْف (class)، ولكن هذا غَيْر مُهِمّ في الوقت الحالي. سنَفْحَص خلال هذا القسم التعدادات بصيغتها البسيطة (simplified form)، والمُستخدَمة عمليًا في غالبية الحالات. أي تعداد (enum) هو عبارة عن نوع (type) يَتَضمَّن قائمة ثابتة مُكوَّنة من قيمه المُحتمَلة تُخصَّص عند إِنشاء نوع التعداد. بشكل ما، تُشبه أنواع التعداد (enum) النوع boolean والتي قيمه المحتملة هي true و false فقط. لكن لاحِظ أن النوع boolean هو نوع أساسي (primitive type) أما أنواع التعداد (enums) فليست كذلك. يُكْتَب تعريف تعداد (enum definition) معين بالصيغة التالية: enum <enum-type-name> { <list-of-enum-values> } لا يُمكِنك كتابة ذلك التعريف (definition) داخل أي برنامج فرعي (subroutine)، لذا قد تَضَعه خارج البرنامج main() أو حتى بملف آخر مُنفصِل. تُشير إلى اسم نوع التعداد (enum) بنفس الطريقة التي تُشير بها كلمات مثل "boolean" و "String" إلى النوع boolean والنوع String على الترتيب. يُمكِننا أن نَستخدِم أي مُعرّف بسيط (simple identifier) كاسم لنوع التعداد. أما فتُشير إلى قيم التعداد المُحتمَلة وتَتَكوَّن من قائمة من المُعرّفات (identifiers) يَفصِل بينها فاصلة (comma). يُعرِّف المثال التالي نوع تعداد (enum) اسمه هو Season وقيمه المُحتمَلة هي أسماء فصول السنة الأربعة: enum Season { SPRING, SUMMER, FALL, WINTER } عادةً ما تُكْتَب القيم المُحتمَلة لنوع التعداد (enum) بحروف كبيرة (upper case)، لكنه ليس أمرًا ضروريًا فهو ليس ضِمْن قواعد الصيغة (syntax rules). تُعدّ القيم المُحتمَلة لتعداد معين ثوابتًا (constants) لا يُمكِن تعديلها، وعادةً ما يُطلَق عليها اسم ثوابت التعداد (enum constants). لأن ثوابت التعداد لنوع مثل Season مُعرَّفة داخله، لابُدّ من أن نُشير إليها باستخدام أسماء مركبة مُكوَّنة من اسم النوع المُتْضمِّن ثم نقطة ثم اسم ثابت التعداد كالتالي: Season.SPRING و Season.SUMMER و Season.FALL و Season.WINTER. بمُجرّد إنشاء نوع تعداد (enum)، تستطيع أن تَستخدِمه للتَصْرِيح (declare) عن مُتْغيِّرات بنفس الطريقة التي تُصرِّح بها عن مُتْغيِّرات من أي أنواع آخرى. يمكنك مثلًا أن تُصرِّح عن مُتْغيِّر اسمه vacation من النوع Season باستخدام التَعْليمَة التالية: Season vacation; بعد التَصْرِيح عن مُتْغيِّر، يُمكِنك أن تُسنِد (assign) إليه قيمة باستخدام تَعْليمَة إِسْناد (assignment statement). يُمكن لتلك القيمة -على يمين عامل الإِسْناد- أن تَكُون أي من ثوابت التعداد من النوع Season. تَذَكَّر أنه لابُدّ من اِستخدَام الاسم الكامل لثابت التعداد بما في ذلك اسم النوع Season. اُنظر المثال التالي: vacation = Season.SUMMER; يُمكِنك أن تَستخدِم تَعْليمَة طباعة عادية مثل System.out.print(vacation) لطباعة قيمة تعداد، وتَكُون القيمة المطبوعة عندها عبارة عن اسم ثابت التعداد (enum constant) بدون اسم نوع التعداد أي سيَكُون الخَرْج في هذه الحالة هو "SUMMER". لأن التعداد (enum) هو عبارة عن صَنْف (class) تقنيًا، فلابُدّ إذًا من أن تَكُون قيم التعداد (enum value) عبارة عن كائنات (objects) وبالتالي يُمكِنها أن تَحتوِي على برامج فرعية (subroutines). أحد البرامج الفرعية المُعرَّفة بأي قيمة تعداد من أي نوع هي ordinal() والتي تُعيد العدد الترتيبي (ordinal number) لتلك القيمة بقائمة القيم المُحتمَلة لذلك التعداد. يُشير العدد الترتيبي ببساطة إلى مَوضِع القيمة بالقائمة، فمثلًا، يُعيد Season.SPRING.ordinal() قيمة من النوع int تُساوِي صفر أما Season.SUMMER.ordinal() فيُعيد ١ أما Season.FALL.ordinal() فيُعيد ٢ وأخيرًا Season.WINTER.ordinal() يُعيد ٣. يمكنك بالطبع أن تَستخدِم التابع (method) ordinal() مع مُتْغيِّر من النوع Season مثل vacation.ordinal(). يُساعد اِستخدَام أنواع التعداد (enums) على كتابة شيفرة مقروءة؛ لأنك ستَستخدِم بطبيعة الحال أسماءً ذات مغزى لقيم التعداد. علاوة على ذلك، يَفْحَص المُصرِّف (compiler) ما إذا كانت القيم المُسنَدة لمُتْغيِّر تعداد معين قيمًا صالحة أم لا مما يُجنِّبك أنواعًا مُحدَّدة من الأخطاء. وفي العموم، ينبغي أن تُدرك أهمية التعدادات (enums) بعدّها الطريقة الأولى لإنشاء أنواع (types) جديدة. يُوضِح المثال التالي كيفية اِستخدَام أنواع التعداد ضِمْن برنامج كامل: public class EnumDemo { // عرف نوعين تعداد خارج البرنامج main enum Day { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY } enum Month { JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC } public static void main(String[] args) { Day tgif; // صرح عن متغير من النوع Day Month libra; // صرح عن متغير من النوع Month tgif = Day.FRIDAY; // أسند قيمة من النوع Day إلى tgif libra = Month.OCT; // أسند قيمة من النوع Month إلى libra System.out.print("My sign is libra, since I was born in "); // قيمة الخرج ستكون OCT System.out.println(libra); System.out.print("That's the "); System.out.print( libra.ordinal() ); System.out.println("-th month of the year."); System.out.println(" (Counting from 0, of course!)"); System.out.print("Isn't it nice to get to "); // قيمة الخرج ستكون: FRIDAY System.out.println(tgif); System.out.println( tgif + " is the " + tgif.ordinal() + "-th day of the week."); } } كما ذَكَرنا مُسْبَقًا، يُمكِنك أن تُعرِّف التعدادات (enum) بملفات مُنفصِلة. لاحظ أن البرنامج SeparateEnumDemo.java هو نفسه البرنامج EnumDemo.java باستثناء أن أنواع التعداد المُستخدَمة قد عُرِّفت بملفات مُنفصلة هي Month.java و Day.java. ترجمة -بتصرّف- للقسم Section 3: Strings, Classes, Objects, and Subroutines من فصل Chapter 2: Programming in the Small I: Names and Things من كتاب Introduction to Programming Using Java. اقرأ أيضًا كيف تتعلم البرمجة1 نقطة
-
تُعدّ الأسماء واحدة من أساسيات البرمجة حيث تُستخدَم بالبرامج للإشارة إلى كثير من الأشياء المختلفة. لابُدّ من فهم قواعد تَسْمية الأشياء وطريقة اِستخدَام الأسماء لكي تَتَمكَّن من اِستخدَام الأشياء أي تحتاج في العموم إلى فهم كُلًا من صيغة (syntax) الأسماء ودلالتها (semantics). وفقًا لقواعد الصيغة (syntax rules) بالجافا، المُعرّفات (identifiers) واحدة من أبسط الأسماء، وتُستخدَم لتسمية كُلًا من الأصناف (classes) والمُتْغيِّرات (variables) والبرامج الفرعية (subroutines). يَتَكوَّن أي مُعرّف من متتالية تتألف من محرف (character) واحد أو أكثر بشَّرْط أن يَكُون أول محرف حرفًا أبجديًا (letter) أو شرطة سفلية (underscore) _، وأن تَكوُن المتتالية بالكامل مُكوَّنة من حروف أبجدية وأرقام (digits) وشرط سفلية فقط. اُنظر الأمثلة التالية لأسماء مُعرّفات (identifiers) صالحة: N n rate x15 quite_a_long_name HelloWorld لا يُسمَح للمسافات الفارغة (spaces) بأن تَكُون جزءًا من اسم أي مُعرّف، فمثلًا، في حين تستطيع اِستخدَام HelloWorld كاسم مُعرّف، لا يُمكِنك أن تَستخدِم "Hello World". تختلف كذلك الحروف الكبيرة عن الحروف الصغيرة فيما يَتَعلَّق بالتسمية أي أن الأسماء HelloWorld و helloworld و HELLOWORLD و hElloWorLD هي أسماء مُعرّفات مختلفة. بالإضافة إلى ذلك، لا يُمكِنك اِستخدَام الكلمات المحجوزة (reserved) المُخصَّصة لأغراض خاصة بالجافا كأسماء مُعرِّفات مثل الكلمات class و public و static و if و else و while وعشرات من الكلمات الآخرى. لاحِظ أن الكلمات المحجوزة ليست مُعرّفات فهي لا تُستخدَم كأسماء تُشير إلى أشياء. تَستخدِم الجافا محارف اليونيكود (Unicode character set) والتي تَتَضمَّن آلافًا من المحارف والحروف الأبجدية (alphabets) من مختلف اللغات. تُعدّ كثير من تلك المحارف بمثابة حروف أبجدية أو أرقام. سنَقْتصِر عمومًا على اِستخدَام المحارف المُتوفِّرة بأي لوحة مفاتيح إنجليزية عادية فقط. هنالك بعض الاصطلاحات والقواعد الإرشادية التي ينبغي اتباعها في العموم عند تَسْمية (naming) الأشياء. أولًا، تبدأ أسماء الأصناف (classes) بحروف كبيرة (upper case) بعكس أسماء كُلًا من المُتْغيِّرات (variables) والبرامج الفرعية (subroutines) والتي تبدأ عادةً بحروف صغيرة (lower case). يُساعِد الالتزام بذلك التَقْليد (convention) على تَجنُّب أي خلط مُحتمَل. ثانيًا، لا يَستخدِم غالبية مُبرمجي الجافا الشُرط السُفليّة (underscores) بالأسماء مع أن البعض يلجأ إلى اِستخدَامها ببداية أسماء أنواع مُحدَّدة من المُتْغيِّرات (variables). ثالثًا، إذا كان الاسم مُكوَّن من عدة كلمات مثل HelloWorld أو interestRate، فيُعتاد تَكْبير الحرف الأول (capitalize) من كل كلمة باستثناء الأولى فيما يُعرَف باسم نَمْط سنام الجمل (camelCase) حيث تُشبه الحروف الأبجدية الكبيرة (upper case) بمنتصف الاسم سنام الجمل. بالإضافة إلى الأسماء البسيطة (simple names)، قد تَكُون الأسماء أيضًا مُركَّبة من عدة أسماء بسيطة يَفصِل بين كُلًا منها نقطة (period) مثل الاسم System.out.println، وتُعرَف باسم "الأسماء المُؤهلة (qualified names)". لأن الجافا تَسمَح بتَضْمِين الأشياء ضِمْن بعضها البعض، فإن الاسم المُركَّب يَعمَل كمسار إلى شيء عبر واحد أو أكثر من مستويات الاحتواء (containment level)، فمثلًا يُشير الاسم System.out.println إلى شيء اسمه System يحتوي على شيء آخر اسمه out والذي بدوره يحتوي على شيء اسمه printn. المتغيرات (variables) تُستخدَم البرامج في العموم لمُعالجة البيانات المُخزَّنة بذاكرة الحاسوب. إذا كنا نُبرمج باِستخدَام لغة الآلة (machine language)، فإننا نَكُون مُضطرّين لاِستخدَام العنوان العددي (numerical memory address) لمَوضِع الذاكرة لكي نَتَمكَّن من الإشارة إلى البيانات المُخزَّنة به أما باللغات عالية المستوى (high-level language) مثل الجافا، فإننا في العموم نَستخدِم أسماءً وليس أعدادًا للإشارة إلى بيانات الذاكرة أي لا يحتاج المبرمج إلى ما هو أكثر من تَذكُّر تلك الأسماء، والتي يُطلَق عليها اسم "المُتْغيِّرات (variables)"، وفي المقابل، يَتعقَب الحاسوب مواضع الذاكرة الفعليّة لتلك البيانات. لا يُعدّ المُتْغيِّر (variable) اسمًا للبيانات نفسها وإنما لمَوضِع الذاكرة الحامل لتلك البيانات أي أنه يَعمَل كصندوق أو كحاوي يُمكِنك أن تُخزِّن به بعض البيانات التي قد تحتاج إليها لاحقًا. يُشير أي مُتْغيِّر إذًا إلى صندوق بصورة مباشرة وإلى البيانات الموجودة بذلك الصندوق بصورة غير مباشرة. لمّا كانت البيانات المُخزَّنة بالصندوق قابلة للتَعْديل، فقد يشير مُتْغيِّر معين إلى بيانات مختلفة بلحظات مختلفة من تّنْفيذ البرنامج، ولكنه دائمًا ما سيُشير إلى نفس الصندوق. عندما نَستخدِم مُتْغيِّرًا ضِمْن برنامج، فإنه -وبحسب الطريقة التي اُستخدِم بها ذلك المُتْغيِّر- إما يُشير إلى صندوق وإما إلى البيانات المُخزَّنة بذلك الصندوق، وهو ما قد يَكُون مُربِكًا لبعض المبرمجين المبتدئين. سنَفْحَص أمثلة لكلا الحالتين بالأسفل. تُستخدَم تَعْليمَات الإسناد (assignment statement) بالجافا لتَخْزين بيانات معينة بمُتْغيِّر أي بصندوق، وتُكْتَب على النحو التالي: <variable> = <expression>; يُمثِل التعبير -بالأعلى- أي شيء طالما كان يُشير إلى قيمة بيانات أو يَحسِبها. عندما يُنفِّذ الحاسوب تَعْليمَة إسناد معينة، فإنه يُحصِّل (evaluates) قيمة ذلك التعبير ثم يُخزِّنها بالمُتْغيِّر . اُنظر تَعْليمَة الإسناد (assignment statement) التالية على سبيل المثال: rate = 0.07; بالتَعْليمَة السابقة، يُمثِل rate المُتْغيِّر أما القيمة 0.07 فتُمثِل التعبير . تُخزِّن تَعْليمَة الإسناد السابقة القيمة 0.07 بالمُتْغيِّر rate بحيث تحلّ تلك القيمة محلّ قيمته السابقة. سنَفْحَص الآن مثالًا آخر لتَعْليمَة إِسناد أكثر تعقيدًا بقليل، والتي سنحتاج إليها لاحقا ضِمْن البرنامج: interest = rate * principal; تُسنِد تَعْليمَة الإسناد (assignment statement) بالأعلى قيمة التعبير rate * principal إلى المُتْغيِّر interest. يُشير المحرف * الموجود بالتعبير إلى "عامِل حاصل الضرب (multiplication operator)" المسئول عن حساب حاصل ضرب rate في principal. لمّا كانت الأسماء rate و principal ضِمْن التعبير هي نفسها مُتْغيِّرات، يُحسَب حاصل ضرب القيم المُخزَّنة بتلك المُتْغيِّرات أي يُحسَب حاصل ضرب قيمة rate في قيمة principal ثم تُخزَّن الإجابة بالصندوق الذي يُشير إليه interest. نستطيع أن نقول إذًا أنه وفي العموم عندما نَستخدِم مُتْغيِّرًا ضِمْن تعبير (expression)، فإن القيمة المُخزَّنة بذلك المُتْغيِّر هي كل ما يُهِمّ، ويبدو المُتْغيِّر في تلك الحالة كما لو كان يُشير إلى بيانات الصندوق وليس الصندوق ذاته. في المقابل، عندما نَستخدِم مُتْغيِّرًا على الجانب الأيسر من تَعْليمَة إسناد، فإنه في تلك الحالة يُشير إلى الصندوق ذاته المُمثِل لذلك المُتْغيِّر. تَعْليمَات الإِسناد ليست تَعْليمَات خبرية، وإنما هي بمثابة أوامر يُنفِّذها الحاسوب بأوقات محددة. لنَفْترِض مثلً أن برنامجًا معينًا يُنفِّذ التَعْليمَة rate = 0.07; ثم يُنفِّذ التَعْليمَة interest = rate * principal; بوقت لاحق ضِمْن البرنامج، هل يُمكِننا ببساطة أن نَدعِي أننا قد ضربنا principal بالقيمة 0.07؟ في الواقع لا! لأن قيمة rate قد تَتَغيَّر في أي لحظة بواسطة تَعْليمَة إِسناد آخرى. يختلف معنى تَعْليمَة الإِسناد تمامًا عن معنى أي معادلة رياضية على الرغم من أن كليهما يَستخدِم الرمز =. الأنواع (types) يُمكِن لأي مُتْغيِّر (variable) بالجافا أن يَحمِل نوعًا (type) واحدًا فقط من البيانات وليس أي نوع آخر. إذا حاولت أن تُسنِد (assigning) قيمة من نوع مختلف عن نوع مُتْغيِّر معين إلى ذلك المُتْغيِّر، فسيُعدّ ذلك انتهاكًا لتلك القاعدة، وسيُبلِّغ عنه المُصرِّف (compiler) على أساس كَوْنه خطأ في بناء الجملة (syntax error). نقول إذًا أن الجافا هي لغة صارمة في تَحْديد النوع (strongly typed language). تُوفِر الجافا 8 أنواع أساسية (primitive types) مَبنية مُسْبَقًا هي كالتالي: byte و short و int و long و float و double و char و boolean. تَحمِل الأنواع الأربعة الأولى أعدادًا صحيحة (integers) مثل 17 و -38477 و 0، ولكنها تختلف عن بعضها فيما يَتعَلَّق بنطاق الأعداد الصحيحة التي يُمكِن لكل نوع منها حَمْله. في المقابل، يَحمِل النوعان float و double أعدادًا حقيقية (real numbers) مثل 3.6 و -145.99، ويختلفان عن بعضهما بكُلًا من نطاق الأعداد المسموح به وبدقة العَدَد. تستطيع المُتْغيِّرات من النوع char أن تَحمِل إحدى محارف اليونيكود (Unicode character set) أما المُتْغيِّرات من النوع boolean فتَحمِل إما القيمة المنطقية true أو القيمة false. تُمثَل جميع قيم البيانات بذاكرة الحاسوب بهيئة عدد من النظام الثُنائي (binary number) عبارة عن سِلسِلة نصية (string) مُكوَّنة من الرقمين صفر وواحد. يُطلَق اسم "بت (bit)" على كُلًا منها بينما يُطلَق اسم "بايت (byte)" على أي سِلسِلة نصية مُكوَّنة من ٨ بتات، وتُقاس الذاكرة عادةً بتلك الوحدة. يُشير النوع byte إلى بايت واحد فقط بالذاكرة أي تَحمِل المُتْغيِّرات (variable) من النوع byte سِلسِلة نصية من ٨ بتات يُمكِنها أن تُمثِل ٢٥٦ عددًا صحيحًا (٢ مرفوعة للأس ٨) أي قد تُمثِل أي عدد صحيح بين -١٢٨ و ١٢٧. أما بالنسبة للأنواع الصحيحة (integer) الآخرى: short يُمثِل ٢ بايت (١٦ بت)، وتَحمِل المُتْغيِّرات من النوع short قيم ضِمْن نطاق يتراوح من -32768 إلى 32767. int يُمثِل ٤ بايت (٣٢ بت)، وتَحمِل المُتْغيِّرات من النوع int قيم تتراوح من -2147483648 إلى 2147483647. long يُمثِل ٨ بايت (٦٤ بت)، وتَحمِل المُتْغيِّرات من النوع long قيم بنطاق يتراوح من -9223372036854775808 إلى 9223372036854775807. لا تحتاج إلى تَذكُّر كل تلك الأعداد بالأعلى، فالغرض منها فقط هو إِعطائك فكرة عن حجم الأعداد الصحيحة التي يُمكِن لكل نوع أن يَحمِلها. وفي العموم عادةً ما يُستخدَم النوع int لتمثيل أي قيمة عددية صحيحة لأنه جيد كفاية لغالبية الأغراض. أما النوع float فيَستخدِم طريقة ترميز (encoding) قياسية لتمثيل الأعداد الحقيقية (real) بمساحة ٤ بايت من الذاكرة، ويُمكِنه أن يَحمِل قيمة قصوى تَصِل إلى ١٠ مرفوعة للأس ٣٨. علاوة على ذلك، يُمكِن للقيم من النوع float أن تَشتمِل على ٧ أرقام معنوية (significant digits) بحد أقصى. لابُدّ إذًا من تَقْرِيب كُلًا من العددين 32.3989231134 و 32.3989234399 إلى العَدَد 32.398923 حتى نَتَمكَّن من تَخْزِينه بمُتْغيِّر من النوع float. في المقابل، يُمثِل النوع double الأعداد الحقيقية بمساحة ٨ بايت، لذا يُمكِنه أن يَحمِل قيمة قصوى تَصِل إلى ١٠ مرفوعة للأس ٣٠٨، وقد يَشتمِل على ١٥ رقم معنوي (significant digit). في العموم عادةً ما يُستخدَم النوع double لتمثيل قيم الأعداد الحقيقية. أما المُتْغيِّرات من النوع char فتَحتلّ مساحة ٢ بايت من الذاكرة، وتَحمِل محرفًا وحيدًا مثل "A" أو "*" أو "x" أو مسافة فارغة (space) أو محرفًا خاصًا مثل المحرف "tab" أو محرف "العودة إلى بداية السطر (carriage return)" أو إحدى محارف اليونيكود (Unicode character) بلغات مختلفة. يَستخدِم النوع char ترميزًا عدديًا صحيحًا (integer code number) مُكوَّن من ١٦ بت لتمثيل أي محرف، لذلك فإنه قيمه تُعدّ مرتبطة نوعًا ما بقيم الأعداد الصحيحة (integer)، وسنرى لاحقًا أنه يُمكِننا حتى اِستخدَامها كما لو كانت أعدادًا صحيحة بمواقف معينة. لمّا كان الحاسوب يَستخدِم عددًا مُحدّدًا ومُتناهيًا (finite) من البتات لتمثيل قيمة أي نوع أساسي (primitive type)، فلا يُمكِن إذًا لقيم النوع int أن تُمثِل أي عدد صحيح عشوائي بل لابُدّ أن يَقَع العََدَد الصحيح ضِمْن نطاق مُحدّد ومُتناهي من القيم. بالمثل، يُمكِن للمُتْغيِّرات من النوعين float و double أن تَحمِل قيمًا محددة فقط أي أنها ليست أعدادًا حقيقية فعليًا بالمفهوم الرياضي. فمثلًا، لأن الثابت الرياضي π يَتَطلّب عددًا لامُتناهيًا من الأرقام العشرية لتمثيله، فلابُدّ من تقريبه أولًا قبل تَخْزِينه بمُتْغيِّر من النوع float أو النوع double. بالمثل، لابُدّ من تقريب الأعداد البسيطة مثل 1/3 قبل تَخْزِينها بمُتْغيِّرات من النوع float و double. القيم مصنَّفة النوع (literals) تُخزَّن أي بيانات بالحاسوب كمتتالية من البتات (bits). بالنسبة للقيم الثابتة (constant values)، فإنها تُمثَل باستخدَام ما يُعرَف باسم "القيم مُصنَّفة النوع (literals)"، والتي هي عبارة عن شيء يُمكِنك كتابته لتمثيل قيمة أي يُمكِن عدّه اسمًا لقيمة ثابتة. لكتابة قيمة من النوع char على سبيل المثال، لابُدّ من إحاطتها بعلامتي اقتباس مُفرّدتين مثل 'A' و '*' و 'x'. يُشكِّل المحرف وعلامتي الاقتباس معًا "قيمة مُصنَّفة النوع (literal)" من النوع char. بدون علامتي الاقتباس، سيُمثِل A مُعرّفًا (identifier) أما * فستُمثِل عامل حاصل الضرب (multiplication operator). إذا كنت تريد أن تُخزِّن المحرف A بمُتْغيِّر ch من النوع char، فيُمكِنك أن تَستخدِم تَعْليمَة الإِسناد (assignment statement) التالية. لا تُعدّ علامتي الاقتباس جزءًا من القيمة ولا تُخزَّن بالمُتْغيِّر (variable)، فهي فقط نَمْط لتسمية الثوابت المحرفية (character constant): ch = 'A'; تَستخدِم بعض المحارف الخاصة قيمًا خاصة مُصنَّفة النوع (literals) تَتَضمَّن خطًا مائلًا عكسيًا (backslash) يَعمَل كمحرف هروب (escape character)، فمثلًا، يُمثِل '\t' المحرف "tab"، ويُمثِل '\r' محرف العودة إلى بداية السطر (carriage return)، ويُمثِل '\n' محرف إضافة سطر جديد، ويُمثِل ''\' علامة اقتباس مُفردة، ويُمثِل '\\' محرف خط مائل عكسي (backslash). يَتَكوَّن كُلًا منها من محرفين إلى جانب علامتي الاقتباس، ومع ذلك فإنه يُشير إلى محرف واحد فقط، فمثلًا تُشير القيمة مُصنَّفة النوع '\t' إلى محرف "tab" واحد فقط. قد تَكُون القيم العَدَدية مُصنَّفة النوع (Numeric literals) أعقد قليلًا مما تَتَوقَّع. هنالك بالطبع أمثلة واضحة مثل 317 و 17.42، ولكن هنالك احتمالات وطرائق أخرى لتمثيل الأعداد بلغة الجافا. أولًا، قد تُستخدَم صيغة أُسية (exponential form) لتمثيل الأعداد الحقيقية مثل 1.3e12 أو 12.3737e-108 حيث "e12" و "e-108" هي أسس للعدد ١٠ أي يُكافئ العدد 1.3e12 حاصل ضرب ١,٣ في ١٢١٠ أما العدد 12.3737e-108 فيُكافئ حاصل ضرب ١٢,٣٧٣٧ في ١٠-١٠٨. عادةً ما تُستخدَم هذه الصيغة للتعبير عن الأعداد الصغيرة جدًا أو الكبيرة جدًا. ثانيًا، قد تحتوي بعض القيم العددية مُصنَّفة النوع (numeric literal) على علامة عشرية (decimal point) أو على أس (exponential)، وتُعدّ عندها قيمًا من النوع double أتوماتيكيًا أما إذا أردت أن تُنشِيء قيمة مُصنَّفة من النوع float، فينبغي أن تضيف الحرف "f" أو "F" إلى نهاية العدد، فتُمثِل "1.2F" مثلًا قيمة من النوع float تُساوِي 1.2. ولأن الجافا لا تَسمَح بإسناد (assign) قيمة من النوع double لمُتْغيِّر من النوع float، فستُواجهك رسالة خطأ سخيفة نوعًا ما إذا حاولت أن تقوم بشيء مثل x = 1.2; عندما يَكُون x مُتْغيِّر من النوع float، وعندها يَنبغي أن تُضيف الحرف "F" بالنهاية كالتالي x = 1.2F;. حاول عمومًا أن تَستخدِم النوع double لتمثيل الأعداد الحقيقية بالبرامج الخاصة بك. أما بالنسبة لقيم الأعداد الصحيحة مُصنَّفة النوع (integer literals)، فهنالك أعداد صحيحة عادية مثل 177777 و -32 والتي إما أن تَكُون من النوع byte أو النوع short أو النوع int بحسب حجمها. في المقابل، يُمكِنك أن تُنشِيء قيمة مُصنَّفة النوع (literal) من النوع long بإضافة الحرف "L" بنهاية العدد مثل 17L أو 728476874368L. تَسمَح الجافا أيضًا باستخدام قيم عددية ثنائية (binary) أو ثُمانيّة (octal) أو ست عشريّة (hexadecimal) مُصنَّفة النوع. لن نناقش أيًا منها على نحو تفصيلي وإنما سنَكتفِي بالنقاط التالية: أولًا، تَقْتصِر الأعداد الثُمانيّة (octal numbers) على الأرقام العشرية من ٠ إلى ٧ فقط، وتبدأ أي قيمة ثُمانيّة مُصنَّفة النوع (octal literal) بصفر مثل القيمة 045 والتي تُمثِل العدد 37 وليس العدد 45. نادرًا ما تُستخدَم الأعداد الثُمانيّة ومع ذلك تذكَّر على الأقل أنها تبدأ بصفر. ثانيًا، تَتَكوَّن الأعداد الست عشرية (Hexadecimal numbers) من ١٦ رقم هي الأرقام العشريّة العادية من ٠ إلى ٩ بالإضافة إلى الحروف A و B و C و D و E و F وبحيث تُمثِل تلك الحروف القيم من ١٠ إلى ١٥ على التوالي. لاحِظ أن الحروف الصغيرة (lower case letters) من a وحتى f لها نفس قيمة نظيراتها من الحروف الكبيرة (upper case). تبدأ القيم الست عشرية مُصنَّفة النوع (hexadecimal literal) بالجافا باستخدام 0x أو 0X مثل 0x45 أو 0xFF7A. وأخيرًا، تبدأ القيم الثنائية مُصنَّفة النوع (binary literals) باستخدام 0b أو 0B، وتَتَكوَّن من الرقمين ٠ و ١ فقط مثل 0b10110. قد تحتوي القيم العددية مُصنَّفة النوع (numeric literals) على محرف شرطة سفلية (underscores) "_" لفَصْل مجموعات الأرقام (digits) عن بعضها وبدون أي قاعدة فيما يَتَعلَّق بعَدَد الأرقام ضِمْن كل مجموعة. يُمكِننا مثلًا كتابة الثابت العددي ٢ بليون بهيئة 2_000_000_000 بدلًا من 2000000000 مما يُسهِل من قراءة العدد. تَبرُز أهمية الشُرَط السفليّة على نحو أكبر عند اِستخدَامها بالأعداد الثنائية (binary numbers) الطويلة مثل 0b1010_1100_1011. تُستخدَم الأعداد الست عشرية (hexadecimal numbers) أيضًا كقيم محرفية مُصنَّفة النوع (character literals) لتمثيل بعض محارف اليونيكود (unicode characters). تَتَكوَّن أي قيمة يونيكود مُصنَّفة النوع (unicode literal) من \u متبوعة بأربعة أرقام ست عشريّة. تُمثِل مثلًا القيمة المحرفية مُصنَّفة النوع '\u00E9' محرف يونيكود هو الحرف "é". أخيرًا، بالنسبة للنوع boolean، فتُستخدَم القيمتين true و false كقيم مُصنَّفة النوع (literal)، وتُكْتَب على نفس هذه الهيئة بدون علامتي اقتباس مع أنها تُمثِل قيم لا مُتْغيِّرات (variables). عادةً ما تُستخدَم القيم المنطقية كقيم للتعبيرات الشرطية (conditional expressions) كالمثال التالي: rate > 0.05 يُعيد التعبير المنطقي (boolean-valued expressions) السابق قيمة إما تُساوِي true إذا كانت قيمة المُتْغيِّر rate أكبر من 0.05 أو تُساوِي false إذا كنت قيمته أقل من أو تُساوِي 0.05. تُستخدَم التعبيرات المنطقية بكثرة ضِمْن بُنى التَحكُّم (control structure) كما سنرى بالفصل الثالث. إلى جانب ذلك، يُمكِننا أيضًا أن نُسنِد القيم المنطقية إلى مُتْغيِّرات من النوع boolean. فمثلًا إذا كان test مُتْغيِّرًا من النوع boolean فيُمكننا كتابة تَعْليمَتي الإِسْناد (assignment statement) التاليتين: test = true; test = rate > 0.05; السلاسل النصية (strings) والسلاسل النصية المجردة (string literals) إلى جانب الأنواع الأساسية (primitive types)، تُوفِّر الجافا أنواعًا آخرى قيمها عبارة عن كائنات (objects) وليس قيم أساسية (primitive). ليس هذا وقتًا مناسبًا بالطبع للحديث عن الكائنات (objects)، ولكننا مُضطرّون للحديث عن النوع String لأهميته. هو ببساطة نوع كائني (object type) مُعرَّف مُسْبَقًا تُوفِّره الجافا لتمثيل السَلاسِل النصية (strings). لا يَقَع ذلك النوع ضِمْن الأنواع الأساسية (primitive)، بل هو في الواقع اسم صَنْف (class)، وهو ما سنعود للحديث عنه بالقسم التالي. تَتَكوَّن القيم من النوع String من متتالية من المحارف مثل السِلسِلة النصية المُجرّدة "Hello World!" التي تَعرَضنا لها بقسم سابق. لابُدّ من إحاطة النص بعلامتي اقتباس مزدوجتين حيث تُعدّ كلتاهما جزءًا من السِلسِلة النصية المجرّدة (string literal)، ولكنهما مع ذلك ليسا جزءًا من قيمتها الفعليّة والتي تَقْتصِر على المحارف بين علامتي الاقتباس. قد تحتوي السِلسِلة النصية من النوع String على أي عدد من المحارف بما في ذلك الصفر، وعندها تَكُون بمثابة سِلسِلة نصية فارغة (empty string) تُمثَل بالسِلسِلة النصية المُجرّدة "" أي مُكوَّنة من علامتي اقتباس مزدوجتين فارغتين. انتبه جيدًا للفارق بين علامتي اقتباس فرديتين وآخريتين مزدوجتين حيث تُستخدَم الأولى مع القيم المحرفية مُصنَّفة النوع (char literals) أما الثانية فتُستخدَم مع السَلاسِل النصية المُجرّدة (String literals)، فمثلًا، لاحظ الفرق بين السِلسِلة النصية "A" والحرف 'A'. قد تَتَضمَّن السَلاسِل النصية المُجرّدة (string literal) محارفًا خاصة، ويُستخدم عندها خطًا مائلًا عكسيًا (backslash) لتمثيلها. ضِمْن هذا السياق، تُعدّ علامة الاقتباس المزدوجة محرفًا خاصًا، فإذا كان لدينا القيمة النصية التالية (string value): I said, "Are you listening!" يُمكِننا إذًا تمثيلها باستخدام سِلسِلة نصية مُجرّدة (string literal) كالتالي مع محرف "إضافة سطر جديد (linefeed)" بالنهاية: "I said, \"Are you listening!\"\n" يُمكِنك أيضًا أن تَستخدِم \t و \r و \\ وكذلك متتاليات اليونيكود مثل \u00E9 لتمثيل محارف خاصة آخرى ضمن السِلاسِل النصية المُجرّدة (string literals). المتغيرات بالبرامج ينبغي دائمًا أن تُصرِّح (declare) عن أي مُتْغيِّر (variable) قبل اِستخدَامه. تُستخدَم تَعْليمَة التَصْرِيح عن المُتْغيِّرات (variable declaration statement) للإعلان عن مُتْغيِّر واحد أو أكثر وكذلك تَخْصيص اسم لكُلًا منها. عندما يُنفِّذ الحاسوب تَعْليمَة تَصْرِيح عن مُتْغيِّر، فإنه يَحجِز له مساحة من الذاكرة كما يَربُط اسمه بتلك المساحة. تُكْتَب تَعْليمَة التَصْريح عن مُتْغيِّر بالصياغة التالية: <type-name> <variable-name-or-names>; تُمثِل اسم مُتْغيِّر واحد أو قائمة من أسماء المُتْغيِّرات يَفصِل بينها فاصلة (comma). يُمكِن لتَعْليمَات التَصْرِيح عن المُتْغيِّرات أن تَكُون أكثر تعقيدًا من ذلك كما سنرى لاحقًا. يُفضَّل عمومًا التَصْرِيح (declare) عن مُتْغيِّر واحد فقط بكل تَعْليمَة تَصْرِيح (declaration statement) إلا لو كانت تلك المُتْغيِّرات مُرتبطَة ببعضها. اُنظر الأمثلة التالية: int numberOfStudents; String name; double x, y; boolean isFinished; char firstInitial, middleInitial, lastInitial; يُفضَّل أيضًا إضافة بعض التعليقات (comment) لكل تَعْليمَة تَصْرِيح عن مُتْغيِّر (variable declaration)؛ لشرح غرضه بالبرنامج أو لتَوفِير بعض المعلومات التي يُمكِنها أن تُساعِد أي مبرمج آخر يَرغَب بقراءة البرنامج. اُنظر الأمثلة التالية: double principal; // مقدار النقود المُستثمَرَ double interestRate; // معدل الفائدة كقيمة وليس نسبة بهذا الفصل، سنَستخدِم المُتْغيِّرات (variables) المُصرَّح (declare) عنها داخل البرنامج الفرعي main() فقط. تُعرَف المُتغيرات المُصرَّح عنها داخل برنامج فرعي (subroutine) معين باسم "المُتْغيِّرات المحليّة (local)" وتَكُون مُتوفِّرة فقط داخل ذلك البرنامج الفرعي عند تَشْغِيله ولا يُمكِن الوصول إليها من خارجه نهائيًا. يُمكِنك أن تُصرِّح عن مُتْغيِّرات بأي مكان داخل البرنامج الفرعي بشَّرْط ألا تَستخدِم أبدًا مُتْغيِّر معين بدون أن تُصرِّح عنه أولًا. يُفضِّل بعض الأشخاص التَصْريح (declare) عن جميع المُتْغيِّرات ببداية البرنامج الفرعي بينما يُفضِّل آخرون إرجاء التَصْرِيح عن المُتْغيِّرات إلى حين الحاجة لاِستخدَامها مباشرةً. يُفضِّل الكاتب التَصْرِيح عن المُتْغيِّرات الهامة والمحورية ببداية البرنامج الفرعي مع إضافة تعليق (comment) على كل تَعْليمَة تَصْرِيح منها يشرح الغرض من المُتْغيِّر، وفي المقابل يرى الكاتب أنه من الممكن إرجاء التَصْرِيح عن المُتْغيِّرات غَيْر المُهِمّة إلى حين الحاجة لاِستخدَامها لأول مرة. يَستخدِم البرنامج التالي بعض المُتْغيِّرات (variables) وتَعْليمَات الإِسْناد (assignment statements): public class Interest { public static void main(String[] args) { /* صرح عن المتغيرات */ double principal; // قيمة الاستثمار double rate; // معدل الفائدة السنوي double interest; // قيمة معدل الفائدة بعام واحد /* إحسب القيم المطلوبة */ principal = 17000; rate = 0.027; interest = principal * rate; // احسب معدل الفائدة principal = principal + interest; // احسب قيمة الاستثمار بعد عام واحد مع الفائدة /* اعرض النتائج */ System.out.print("The interest earned is $"); System.out.println(interest); System.out.print("The value of the investment after one year is $"); System.out.println(principal); } // نهاية main() } // نهاية الصنف Interest يَستدعِي البرنامج بالأعلى مجموعة من البرامج الفرعية (subroutine call) مثل System.out.print و System.out.println لعَرْض بعض المعلومات للمُستخدِم. بخلاف البرنامج الفرعي الأول، يَطبَع البرنامج الثاني محرف "إضافة سطر جديد (linefeed)" بَعْد عَرْض المعلومة ذاتها، فمثلًا، تَطبَع تَعْليمَة الاستدعاء System.out.println(interest); قيمة المُتْغيِّر interest بنفس سطر السِلسِلة النصية المعروضة بواسطة تَعْليمَة الاستدعاء System.out.print السابقة. يُفْترَض للقيمة المطلوب طباعتها باِستخدَام System.out.print أو System.out.println أن تُكْتَب بَعْد اسم البرنامج الفرعي (subroutine) داخل أقواس ويُطلق عليها اسم المُعامِل (parameter). قد يَستقبِل أي برنامج فرعي معاملًا واحدًا أو أكثر لتوفير بعض المعلومات التي يَحتاج إليها ليُنفِّذ المُهِمّة المُوكَلة إليه. تُكْتَب تلك المُعامِلات (parameters) عند استدعاء البرنامج الفرعي (subroutine call) بَعْد اسمه داخل أقواس. في المقابل، قد لا يَستقبِل برنامج فرعي (subroutine) أي مُعامِلات، وفي تلك الحالة لابُدّ أن يُكْتَب زوج من الأقواس الفارغة بَعْد اسم البرنامج الفرعي بتَعْليمَة استدعائه. ملحوظة: جميع ملفات الشيفرة المصدرية (source code) للأمثلة التوضيحية بالكتاب متاحة بنسخة الكتاب المتوفرة عبر الانترنت كما أنها مُضمَّنة بمجلد اسمه "source" بالنسخة الأرشيفية لموقع الإنترنت. يمكنك مثلًا العثور على ملف الشيفرة المصدرية للبرنامج Interest بالملف Interest.java الموجود بمجلد فرعي اسمه "chapter2" داخل المجلد "source". ترجمة -بتصرّف- للقسم Section 2: Variables and the Primitive Types من فصل Chapter 2: Programming in the Small I: Names and Things من كتاب Introduction to Programming Using Java.1 نقطة
-
لدي استعلام SQL وفيه JOIN فهل تغيير ترتيب الجداول قبل JOIN وبعدها يغير في النتيجة؟1 نقطة
-
لدي مجموعة بيانات تمثل مساحة البيت وسعره وأريد تمثيل هذه البيانات لمعرفة توزعها؟ هل هناك طريقة لعرض ذلك في Matplotlib؟1 نقطة
-
ما الفرق بين استخدام الدمج الطبيعي و الدمج الداخلي natural join and inner join عند عمل الاستعلامات1 نقطة
-
1 نقطة
-
إن SSH هو Secure Shell (SSH) أي صدفة آمنة، وهو بروتوكول شبكي مشفّر يعمل على خلق نفق اتصال يتم فيه نقل البيانات وتنفيذ الأوامر عبر الشبكة وخاصة على استضافات لينوكس مهما كان أمان الشبكة التي يتم الاتصال عبرها. يستخدم SSH التوثيق Authentication بطريق منها: Public-Key Authentication: استعمال مفتاح خاص Key عوض اسم المستخدم وكلمة السر. Host-Key Authentication: هي الطريقة الأكثر انتشارا عند الاتصال بسيرفر، كتابة اسم المستخدم وكلمته السرية. يعمل SSH بواسطة TCP و وفق هرمية زبون-عميل client–server architecture للاتصال بالحاسوب البعيد، نحتاج اسم المستخدم و IP للحاسوب: ssh -p 22 username@remote_host ssh -p 22 waelaljamal@12.23.45.58 في هذا الجزء يطلب اسم المستخدم الذي تريدين الاتصال من خلاله و الاستضافة user@host نستبدل user باسم المستخدم و host إما بعنوان IP أو domain مثل example.com. ولاتظهر كلمة السر عند كتابتها (مخفية) حتى لانعلم عدد الأحرف التي نكتبهم. بعد تحقيق الاتصال (قد يطلب كلمة سر الاستضافة) يمكن نقل الملفات وتنفيذ أي أمر من خلال مدير الأوامر المحلي cmd أو حتى terminal عن بعد. قبل الاتصال علينا إنشاء مفاتيح الاتصال/التشفير أي نستخدم الأمر: ssh-keygen مما يولد مفتاحين: Your identification has been saved in /root/.ssh/id_rsa. Your public key has been saved in /root/.ssh/id_rsa.pub. ssh/id_rsa./~ : المفتاح الخاص. لا تنشر هذا الملفّ! ssh/id_rsa.pub./~ : المفتاح العام المُرتبط. هذا الملفّ يمكن مشاركته بحرية. مثلا نضعه في حساب github لنستطيع رفع الملفات للمستودع البعيد.. لتجنب فقطع اتصال SSH يمكنك استخدام أداة autossh أي auto ssh والتي تبقي الخدمة نشطة وتنفذ بالأمر: autossh -M 20000 -f -N your_public_server -R 1234:localhost:22 -C يستخدم خوارزميات التشفير التالية: Algorithms EdDSA,[27] ECDSA, RSA and DSA for public-key cryptography.[28] ECDH and Diffie–Hellman for key exchange.[28] HMAC, AEAD and UMAC for MAC.[29] AES (and deprecated RC4, 3DES, DES[30]) for symmetric encryption. AES-GCM[31] and ChaCha20-Poly1305 for AEAD encryption. SHA (and deprecated MD5) for key fingerprint. يتم العمل حالياُ بالإصدار 2 من SSH التي يرمز لها ب SSH-2. مقالات أكاديمية حسوب عن SSH1 نقطة
-
أنا كنت ابدئ بتعلم لغة php و كتبت الكود التالي <? php echo "hello world"; ?> فعند تنفيذ الكود في localhostظهر لي الخطأ التالي Forbidden You don't have permission to access this resource. ماذا افعل1 نقطة
-
ما هو السيرفر الذي تستخدمه؟ وما الرابط الذي تطلبه وأي وضعت ملف PHP؟ إن كنت تستخدم wamp تأكد من وضع الملف ضمن: C:\wamp64\www ثم في المسار: c:\wamp\alias\phpmyadmin.conf أو c:\wamp64\alias\phpmyadmin.conf لاتقم بتعديل السطر الأول تأكد من التالي: <Directory "c:/wamp64/apps/phpmyadmin5.0.2/"> Options +Indexes +FollowSymLinks +MultiViews AllowOverride all هذا Require local وهذا </Directory> تحت سطر options استبدل الذي لديك ب AllowOverride all Require all granted حل آخر: <Directory "c:/wamp22/apps/phpmyadmin3.5.1/"> Options Indexes FollowSymLinks MultiViews AllowOverride all Order Deny,Allow Deny from all Allow from localhost 127.0.0.1 ::1 </Directory> احفظ الملف وأعد تشغيل المخدم1 نقطة
-
كيفية إيجاد القيمة اللونية المطلوب تعقبها في الفضاء HSV؟1 نقطة
-
1 نقطة
-
إن SCP هو Secure copy protocol أي بروتوكول النسخ الآمن يستخدم لنقل الملفات بين الحواسيب يستخدم على الشبكة network. يفيد في: نقل ملفات بين الحاسوب المحلي والحاسوب البعيد local host and remote host أو بين حاسوبين بعيدين remote hosts يعمل باستخدام البروتوكول TCP port 22 يسمح برفع ملف مع الاحتفاظ بالخيارات permissions, timestamps الصلاحيات و التوقيع الزمني تم بناء هذا البروتوكول على Secure Shell protocol (SSH) ويعتمد عليه ويستخدم نفس طريقة المصادقة authentication مما يضمن موثوقية و سرية البيانات authenticity and confidentiality. يعمل خلال وضعين: source mode: قراءة ملف من القرص الصلب وإرساله عبر الشبكة sink mode: استقبال ملفات من الشبكة وكتابتهم على القرص كيفية نسخ ملف: من حاسوب محلي للحاسوب البعيد (على الشبكة) scp LocalSourceFile user@remotehost:directory/TargetFile LocalSourceFile مسار ملف محلي user@remotehost اسم المستخدم في الاستضافة directory/TargetFile مسار نسخ البيانات/الهدف أما بين حاسوبين على الشبكة: scp user@remotehost:directory/SourceFile LocalTargetFile يستخدم الوسيط r لجعل عملية النسخ عودية لتشمل جميع الملفات ضمن مجلد scp -r user@host:directory/SourceFolder LocalTargetFolder ^^ في حال كان الحاسوب المستقبل يشغل برمجية SCP على منفذ غير 22 يمكن تمريره كوسيط scp -P 2222 user@host:directory/SourceFile TargetFile ^^^^^^^ port مشكلة في البروتوكول: تسمح بالمرسل بالكتابة فوق ملفات موجودة مسبقاً عند المستقبل مما يمسح الملفات القديمة ويعدلها. overwrite arbitrary files بالإضافة لمشاكل في إرسال البيانات النصية. تم إيقاف التعامل معه في عام 2019 واستبدل مع البروتوكولين sftp and rsync1 نقطة
-
هل ممكن الإعتماد على العمل الحر لكسب المال وما المده التى ممكن أن أستغرقها للحصول على أول مشروع لى ؟1 نقطة
-
هل يمكن الاعتماد على العمل الحر ماديا؟ نعم العمل الحر هو العمل كمستقل غير مرتبط بوظيفة مع جهة محددة، انما يتم توظيفك لاتمام اعمال معينة وينتهي العقد بينك وبين صاحب العمل بمجرد انتهاء تنفيذ المطلوب، بعد مدة من دخولك السوق ستصبح تغطي وقتك بالكامل بعدة مشاريع مع عدة جهات وهو ما يجعلك معتمدا ماديا كليا على العمل الحر كيف أحصل على مشروعي الأول؟ بحسب مهاراتك وخبراتك ومجال عملك سيختلف الوقت الذي ستبدأ به باستلام المشاريع وتنفيذها بالاضافة الى تطوير مهاراتك وهو الأهم، يمكنك قراءة سلسلة مقالات دليل العامل المستقل هنا أيضا يجب أن تطلع على أساليب التسويق والتعامل مع الزبائن لكي تزيد فرصك، يمكنك القراءة أكثر عن ذلك1 نقطة
-
لدي مشروع عبارة عن موقع الكتروني مبني بلغة روبي وإطار عمل ريلز، أريد شرح خطوات رفعه على الاستضافة وتشغيله1 نقطة
-
الآن علينا تجهيز قواعد البيانات والجدار الناري للمساح بتشغيل التطبيق مع تثبيت مخدم الويب nginx #نحدث المكتبات في النظام apt update && apt upgrade #تعديل الجدار الناري apt install ufw && ufw allow 22 && ufw logging off && ufw enable && ufw status #تثبيت مخدم ويب و قواعد بيانات apt install curl git nginx postgresql libpq-dev ^^^^^ ^^^^^^^^^^ Installing NGINX & Passenger حيث أن passenger هي الحزمة التي تشغل تطبيق ريلز للاستجابة، لأنه لايتعامل مباشرة مع طلبيات HTTP #(a) تثبيت مفتاح PGP key sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7 #(b) تثبيت passenger من APT repository sudo sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger bionic main > /etc/apt/sources.list.d/passenger.list' sudo apt-get update #(c) Install Passenger and Nginx module sudo apt-get install -y libnginx-mod-http-passenger #(d) تحقق من ملف التكوين configuration files exist if [ ! -f /etc/nginx/modules-enabled/50-mod-http-passenger.conf ]; then sudo ln -s /usr/share/nginx/modules-available/mod-http-passenger.load /etc/nginx/modules-enabled/50-mod-http-passenger.conf ; fi sudo ls /etc/nginx/conf.d/mod-http-passenger.conf ثم تخصيص ملف التكوين نضيف ملف الإعداد passenger.conf ننشيئ sudo nano /etc/nginx/conf.d/mod-http-passenger.conf نضيف له passenger_ruby /home/deploy/.rbenv/shims/ruby; نحذف الإعداد الافتراضي sudo rm /etc/nginx/sites-enabled/default نضيف إعدادات مخصصة sudo nano /etc/nginx/sites-enabled/my_rails_app بعد تهيئة ملف الإعدادات نضع به القالب التالي مع التخصيص المناسب مثل المنفذ الذي سيعمل عليه التطبيق والمجلد الجذر للمشروع مع تفعيل passenger server { listen 80; listen [::]:80; server_name _; root /home/deploy/sample_rails_app/current/public; passenger_enabled on; passenger_app_env staging; } ونشغل nginx sudo service nginx start نثبت أداة Ruby Version Manager (RVM) مدير إصدارات لغة روبي، نحمل المفتاح: gpg2 --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB ثم نثبت روبي وريلز install ruby and rails: \curl -sSL https://get.rvm.io | bash -s stable --rails ثم للتحقق من إصدار روبي: $ ruby --version ruby 2.7.2p137 // ريلز gem $ gem list rails rails (6.0.3.4) يمكن تثبيت نسخة مخصصة عن طريق rvm بتحديدها كما في الأمر: rvm install ruby-2.7.2 rvm use 2.7.2 // تثبيت ريلز بطريقة مختلفة gem update --system && gem install bundler rails ثم نرفع الملفات على الاستضافة إما كملف مضغوط أو من خلال رفعهم على github ثم نثبت المكتبات الخاصة بمشروعنا بتنفيذ الأمر التالي في مجلد المشروع الرئيسي: bundle install ثم نعمل على تهجير قواعد البيانات وتشغيل المخدم تجهيز قواعد البيانات (تثبيت قاعدة بيانات و إنشاء مستخدم خاص بالمشروع) أحد الخيارين: postgres sudo apt-get install postgresql postgresql-contrib libpq-dev sudo su - postgres createuser --pwprompt deploy createdb -O deploy myapp exit mysql sudo apt-get install mysql-server mysql-client libmysqlclient-dev sudo mysql_secure_installation # Open the MySQL CLI to create the user and database mysql -u root -p CREATE DATABASE IF NOT EXISTS myapp; CREATE USER IF NOT EXISTS 'deploy'@'localhost' IDENTIFIED BY '$omeFancyPassword123'; CREATE USER IF NOT EXISTS 'deploy'@'%' IDENTIFIED BY '$omeFancyPassword123'; GRANT ALL PRIVILEGES ON myapp.* TO 'deploy'@'localhost'; GRANT ALL PRIVILEGES ON myapp.* TO 'deploy'@'%'; FLUSH PRIVILEGES; \q ننشئ ملف إعدادات البيئة ونضع فيه بيانات قاعدة البيانات # For Postgres DATABASE_URL=postgresql://deploy:PASSWORD@127.0.0.1/myapp # For MySQL DATABASE_URL=mysql2://deploy:$omeFancyPassword123@localhost/myapp RAILS_MASTER_KEY=ohai SECRET_KEY_BASE=1234567890 STRIPE_PUBLIC_KEY=x STRIPE_PRIVATE_KEY=y # etc... بقي تهجير قواعد البيانات وبدء المخدم بالأمرين: bin/rake db:migrate bin/rails s1 نقطة
-
اكتب تطبيقًا يحسب مربعات ومكعبات الأرقام من 0 إلى 10 ويطبع القيم الناتجة في تنسيق جدول1 نقطة
-
بفرض استخدام لغة PHP وفرض ان القصد من تنسيق الجدول هو جدول HTML، نقوم ببناء هيكلية الجدول كالتالي <table> <thead> <tr> <th>العدد</th> <th>مربع العدد</th> <th>مكعب العدد</th> </tr> </thead> <tbody> <!-- محتويات الجدول --> </tbody> </table> ثم نملئ محتويات الجدول بأسطر وخلايا بالترتيب التالي (العدد - مربع العدد - مكعب العدد) نستخدم حلقة for للمرور على الأعداد من 0 وحتى 10 نستخدم التابع pow لحساب عدد إلى قوة ما بتمرير الأساس ثم الأس، نستخدمه مرة لحساب المربع ومرة لحساب المكعب <tbody> <?php for ($i = 0; $i <= 10; $i++) : ?> <tr> <td><?php echo $i ?></td> <td><?php echo pow($i, 2) ?></td> <td><?php echo pow($i, 3) ?></td> </tr> <?php endfor ?> </tbody>1 نقطة
-
السلام عليكم اشتركت باستضافة Hostinger ورفعت موقعي الاول عليها بعد رفع الملفات تم اضافة كود onsubmit={send} داخل سطر form في ملف index.html هل هذا يعني انهم اعدو back-end للفورم الخاص بي ؟ ام ماذا1 نقطة
-
لم أفهم سؤالك جيدًا، هل تقصد أنك تريد إرسال رسائل إلى بريد إلكتروني معين من خلال JavaScript؟ إن كان هذا سؤالك، فلا يمكن إرسال الرسائل من خلال JavaScript وحدها، بل يجب أن يتم إستعمال خادم بريد Mail Server وذلك عبر كود معين يتم كتابته في الواجهة الخلفية Backend. يمكنك أن يتم إرسال الرسالة إلى الـ Backend ومنه يتم إرسالها إلى بريد إلكتروني معين حسب الحاجة.1 نقطة
-
يوجد شهادة SSL للاستضافة. ولكن السؤال كيف يمكنني اضافة الايميل للدالة المضافة لاستقبال الرسائل1 نقطة
-
أنا أستطيع عرض الصلاوات كاملة .. انا اريد فقط ان أعرف كيف احسب الوقت المتبقي من مثلا صلاة الفجر الى موعد الشروق ومن موعد الشروق الى صلاة الظهر.. الخ .. اريد ان اعرف كيف احسب صلاة الفجر "4:43" من الوقت الحالي وعندما ينتهي اريد ان يحسب الوقت المتبقي لموعد الشروق "6:06" وهكذا1 نقطة
-
من الطبيعي أن يكون هناك بعض الأكواد غير المعروفة أثناء عملية التطوير، ويمر كل شخص بهذا الأمر أثناء مرحلة التعلم، ويجب السؤال عن أي جزئية غير واضحة أو مفهومة بشكل كامل، بدون تخطي أي شيء، وذلك عبر كتابة تعليق أسفل الدرس الخاص بها، لأن تخطي أي جزء من الأساسيات سيؤدي إلى عدم فهم أجزاء من التطبيق العملي (المشروع). أيضًا يفضل تنويع مصادر المعرفة مثل القراءة في موسوعة حسوب أو تصفح المقالات الموجودة في الأكاديمية، وتصفح أسئلة الطلاب الأخرى .. إلخ. أما بالنسبة لنقل الكود من الدروس، فمن الطبيعي أن يكون التطبيق وحدك أمرًا صعيًا في البداية، حيث يكون الطالب على علم بالأكواد ولكنه لا يستطيع تجميعها معًا بطريقة تحقق الهدف، خصوصًا في البداية، وهذا الأمر شائع للغاية ولا يمكن حله إلا بالتدريب على تنفيذ المشاريع والصفحات، فبعد متابعة شرح المدرب في الفيديو، يجب محاولة إتمام نفس الخطوات بدون الرجوع إلى الدرس مرة أخرى، أي أنه يجب محاولة تطبيق الكود بدون مساعدة من الفيديو مرة أخرى، وسيكون الأمر صعيًا ومُرهقًا للغاية في أول مشروع، لكنه سيكون أسهل في الثاني، وأسهل أكثر في المشروع الثالث وهكذا. بالطبع سيكون هناك مشاكل وسيكون بعض الأكواد الغير مفهومة بالكامل، واو أجزاء في المشروع يصعب تطبيقها من أول مرة، والحل كما ذكرت هو الإلمام بالأساسيات بشكل جيد وتنويع مصادر المعرفة، ثم سؤال المدربين للمساعدة.1 نقطة
-
ستجد في موسوعة حسوب توثيق لجميع الأكواد , بالإضافة إلى أنه يمكنك السؤال عن الكود الذي تقابلك مشكلة في فهمه وسنحاول شرحه لك بقدرِ المُستطاع موسوعة حسوب1 نقطة
-
أريد عمل دمج بين قيمتي حقلين في الجدول وعرض الاستعلام لهما في خانة واحدة1 نقطة
-
بتطبيق معامل بين أسماء الأعمدة حسب نوع الدمج الذي تريده العمليات الحسابية إذا كانت عملية دمج حسابية يمكن استخدام المعاملات الحسابية (جمع + ، طرح - ، قسمة / ، ضرب *) كالتالي SELECT عمود2 + عمود1 FROM جدول; SELECT عمود2 - عمود1 FROM جدول; SELECT عمود2 / عمود1 FROM جدول; SELECT عمود2 * عمود1 FROM جدول; عمليات النصوص إذا كانت عملية دمج نصية تمرر أسماء الأعمدة بالترتيب للتابع CONCAT، يمكن أيضا تمرير قيمة نصية ثابتة مع المعاملات مثال نريد دمج قيم عمودين مع وضع فاصلة بينهما "," SELECT CONCAT(عمود2 ,',' ,عمود1) FROM جدول; تغيير اسم الحقل الناتج اسم العمود الناتج عن عملية الدمج يكون نفس نص صيغة الدمج كالتالي "عمود1 + عمود2" "(عمود2 ,',' ,عمود1)CONCAT" لكي تعطي العمود اسما معبرا عن العملية التي تمت عليه يمكنك استخدام AS بعد صيغة العمود وتمرير الاسم الذي تريده SELECT عمود2 + عمود1 AS المجموع FROM جدول; SELECT CONCAT(عمود2, ',', عمود1) AS الأسماء FROM جدول; تصبح أسماء الأعمدة في النتيجة "المجموع" "الأسماء"1 نقطة
-
كيف تعمل SELECT وكيف أحدد حقول محددة لتظهر نتيجتها وكيف أعرض محتوى كامل الجدول1 نقطة
-
يمكنك إستخدام الدالة CONCAT والتي تأخذ مُعاملان وتدمجهم ليصبحو معامل واحد كما في الشكل select concat(fname,lname) as fullname from employee ولكن هذا إن كان كلاً من الحقل الأول والثاني من نوع السلسلة النصية , إن كان أحد الحقول أو كﻻهما من نوع بيانات أخر يجب أولاً تغيير هذا النوع وتحويله إلى سلسلة نصية, وتتم تلك العملية بشكلٍ مختلف حسب قاعدة البيانات المُستخدمة في SQL-SERVER: يمكنك إستخدام الدالة CONVERT كما في الشفرة SELECT CONCAT( CONVERT(varchar(10), salary) , fname) as 'salary / name' FROM employee في postgres يمكنك إستخدام الدالة cast SELECT CONCAT(CAST(salary AS text),fname) AS name_salary في mysql ﻻ يجب فعل شيئ فستقوم دالة concat بتحويل الأرقام إلى نصوص بشكلٍ تلقائي select concat(salary, fname) as name_salary1 نقطة
-
الكلمة المفتاحية SELECT تعني اختار (أو حدد) ثم نكتب بعدها أسماء الحقول التي نريد جلب بياناتهم SELECT col1, col2, ... FROM table; وعلينا تحديد أسماء الحقول حسب الجدول الذي نجلب منه البيانات فلا نضع أسماء حقول غير موجودة.. مثلا لدينا جدول فيه رقم الزيون واسمه وعنوانه و رقمه، ونريد عرض اسم الزبون و عنوانه فقط، لذلك نحدد هذين الحقلين في الاستعلام: SELECT CustomerName,Address FROM Customers; أما لجلب جميع الحقول نضع نجمة * بدل أسماء الحقول SELECT * FROM Customers; يمكن ل SELECT فلترة النتائج مثلا نريد عرض المدن المختلفة التي ينتمي منها الزبائن فنستعمل DISTINCT لجلب القيم المختلفة: SELECT DISTINCT Country FROM Customers; أو جلب عدد محدد من الحقول: SELECT TOP 3 * FROM students WHERE Country='Germany'; وفي حال عمل دمج، يلزم تحديد الاسم المستعار لكل خاصية نريد عرضها:1 نقطة
-
Bitnami هي مكتبة من أدوات التثبيت أو حزم البرامج لتطبيقات الويب ومجموعات البرامج والأجهزة الافتراضية تعمل على تحزيم ثم نشر تطبيقات مفتوحة المصدر أي application packaging and publishing. تُستخدم حزم Bitnami لتثبيت البرامج على Linux و Windows و Mac OS X و Solaris توفر حزم تثبيت للعديد من المنصات مثلWordPress و Drupal و Joomla! و Redmine و AbanteCart و PrestaShop و Magento و MediaWiki وغيرها، تدعم تثبيتهم في مختلف البيئات من الحاسوب المحلي لنظم الحوسبة السحابية. حسب التوثيق الخاص بهم، جميع البرمجيات التي تنشرها تكون محدثة باستمرار ومدعمة بملفات الإعدادات والتكوين configuration والتي تكون مختبرة على جميع البيئات. يمكن نشر deploy التطبيقات على مختلف النظم Google Cloud, AWS, or Azure.. التنصيب: توفر Bitnami برنامج التنصيب / التثبيت installer متوافق مع جميع البيئات، وله واجهة wizard يمكن من خلالها اختيار التطبيقات التي تريد تثبيتها، تتضمن أيضاً هذه الخطوات إضافة إعداداتك الخاصة وبعد انتهاء التثبيت تكون البرمجية جاهزة للعمل.1 نقطة
-
يمكنك مشاهدة مسار أساسيات جافاسكربت من دورة تطوير التطبيقات باستخدام JavaScript، نحن نعلم أن جافاسكربت أصعب من HTML-CSS وهي لغة برمجية أما اللغتين الباقيتين لغتي توصيف ولا يوجد فيهم منطق أو تفكير.. وهذه الميزة لتوفرها أكاديمية حسوب تتيح للطلاب المسجلين في أي دورة من الوصول للمسار الأول من باقي المسارات. ضمن مسار أساسيات جافاسكربت ستتعلم الأساسيات ما في اللغة من المتغيرات والشروط والحلقات والأغراض ... وستفيدك بالطبع.1 نقطة
-
-بالنسبة للسؤال الأول: النظم الخبيرة هي نظم المعرفة، حيث أن النظام الخبير هو برنامج ذكي يستخدم القواعد المأخوذة من الخبرة الإنسانية (والمنظمة ضمن قواعد بيانات) على هيئة شروط ونتائج في مجال معين ويستخدم طرق الاشتقاق والاستدلال لاستخراج واستنتاج النتائج المعللة بالأسباب والناتجة عن تطابق هذه الشروط أو النتائج مع شرط أو نتيجة ما والخاصة بمشكلة معينة يراد إيجاد حل لها. هذه الأنظمة لديها قدرات مميزة في مجالات معينة مثل التخطيط و تحليل العوارض وتشخيص الأخطاء diagnostics وتشخيص الأمراض وفي التصميم وفي نظم دعم اتخاذ القرار وغيرها من المجالات المتخصصة التي تم فهم العمليات المطلوبة لها، والتي تتناسب مع القدرات التمثيلية والاستنتاجية لهياكل الأنظمة المستخدمة. ويتم تطوير أي نظام خبير من خلال ثلاثة مراحل أساسية هي : 1. تمثيل المعرفة knowledge representation: يحتاج المصمم في هذه المرحلة لتحديد الطريقة التي سيمثل بها المعارف والقوانين التي تحكم مجال العمل، واشهرها طريقة الاعتماد على القواعد (Rule based) وهي عبارة عن مجموعة من العبارات المنطقية تسمى بif_then Rules. 2. اكتساب المعرفة knowledge Acquisition: في هذه المرحلة يتم تجميع القوانين التي تحكم المجال الذي سيعمل فيه النظام الخبير، وعادة ما يكون مصدر هذه القوانين خبير بشري له معرفة شاملة بميدان العمل ومعرفة عميقة بكل تفاصيله. 3. محرك الاستدلال Inference Engine: يحتاج المصمم بعد اكتساب وتمثيل المعرفة إلى تحديد الطريقة التي يصل بها النظام الخبير إلى النتيجة بناء على مالديه من معلومات، وتعتبر هذه المرحلة أصعب المراحل. - السؤال الثاني: لا حالياً لاتوجد دورة لكن ربما في وقت لاحق. لكن توجد دورات أخرى في مجالات متنوعة يمكنك تصفحها من هنا. - السؤال الثالث: يفضل أن تبدأ مع تعلم لغة برولوغ أو LISP وكتاب مثل: Introduction to Expert Systems, Jackson P. , 3rd edition أو Introduction to Knowledge Systems, Stefik M., Morgan Kaufmann. يمكنك البدء مع أحد هذين الكتابين وكلاهما يعد نقطة انطلاق مناسبة لهذا الحقل.1 نقطة
-
بالإضافة إلى إجابة أستاذ وائل, فإن بيانات الفهرس يتم تخزينها على شاكلة هيكل بيانات الشجرة المتوازنة(b+tree) والتي تعتمد في طريقة تخزينها على أن يحمل كل عنصر في الهيكل ثﻻث معلومات قيمة العنصر مؤشر(pointer) يُشير إلى العنصر على يمينه مؤشر(pointer) يُشير إلى العنصر على يساره وتعتمد الأشجار المتوازنة في ألية عملها على أن دائما وأبداً يكون كل عنصر أكبر من العناصر على يمينه , وأصغر من العناصر على يساره مما يُسهل عملية البحث ويجعلها في أسوأ الحالات تأخذ تعقيد وقتي قيمته O(logn) فعندما نريد إذاً البحث عن عنصر ما في قاعدة البيانات ﻻ نحتاج أن نمر على جميع العناصر وإنما فقط نقوم بالمرور على عدد من العناصر يساوي لوغاريتم العنصر للأساس 2 في أسوأ الحالات ومن الممكن تحديد هيكل البيانات المُستخدم أن يكون من النوع جدول التجزئة( hash table) والذي يعتمد في ألية عمله أن يكون على هيئة القيمة والمفتاح (key&value) فيتم تخزين ناتج تجزئة مفتاح( element hashing) في المصفوفة الخاصة بالجدول, وعند الإحتياج للوصول إليه يتم ذلك في تعقيد وقتي O(1) حيث أن ناتج التجزئة يكون ثابت دائماً فﻻ نحتاج إذا للبحث, ولكن في الحياة العملية ﻻ يتم إستخدام الجدول بسبب وجود عدد من المشاكل مثل إن تم تخزين أكثر من مفتاح لهم نفس ناتج التجزئة(hashing) تحدث حالة تداخل(collision) فيتم تخزين كلا العنصرين في قائمة ويتم تخزين تلك القائمة في العنصر في المصفوفة, وكثرة التداخلات تُسبب إستهﻻك للموارد وأداء سيئ نسبياً ﻻ تعمل جيداً مع العمليات التي نحتاج فيها إلى إستخدام معاملات أكبر من, أو أصغر من , تعمل فقط مع معامل المساواة فمثلاً جملة مثل select * from student where indx>5 سيتم معالجتها بأداء سيئ عند إستخدام جدول التجزئة حيث أنه ﻻ يقوم بتخزين قيمة الفهرس وإنما يٌخزن قيمة التجزئة , فسيكون التعقيد الوقتي هنا مساوي لO(n)1 نقطة
-
أولا التأكد من تنصيب بيئة NodeJS على المخدم بواسطة CPanel يمكنك مراجعة خطوات التنصيب بالإجابة التالية: الآن نحتاج لتنصيب حزمة pm2 فهي process manager ل JavaScript runtime Node.js: npm install -g pm2 ثم في nginx نعطي سماحية للمنفذ الذي يعمل عليه تطبيق NextJS في nginx.conf/server/location أضف السطر proxy_pass http://localhost:6060 ثم نعمل على رفع الملفات الخاصة بالمشروع pages, public, src , package.json إلى مسار ما علة المخدم: /var/www/your-Next-folder ثم نعدل صلاحيات المجلد: ونقوم بتنصيب الحزم sudo chown -R $USER:$USER /var/www/your-Next-folder ثم نقوم بتنصيب الحزم > cd to your-Next-folder > run: npm -i ثم ضمن package.json نعدل أمر تشغيل التطبيق حسب المنفذ المستخدم next start -p your-port next start -p 6060 ثم بناء وتشغيل المشروع: npm run build pm2 start "npm run start" --name Next-project ملاحظة: إن اعترضتك مشكلة في بناء npm build تكون بسبب قلة الذاكرة الافتراضية، لذلك ينصح بعمل البناء محليا على حاسوبك، في حال رفع ملفات ساكنة هنا قد نحتاج لبرنامج يخدم المشروع، تأكد من معمارية المشروع كالتالي: public_html Next_project .next src server.js package.json ... حيث أن شيفرة server.js ستكون بالشكل: const { createServer } = require("http"); const { parse } = require("url"); const next = require("next"); const dev = process.env.NODE_ENV !== "production"; const port = !dev ? process.env.PORT : 3000; // Create the Express-Next App const app = next({ dev }); const handle = app.getRequestHandler(); app .prepare() .then(() => { createServer((req, res) => { const parsedUrl = parse(req.url, true); const { pathname, query } = parsedUrl; handle(req, res, parsedUrl); console.log("pathname", pathname); }).listen(port, (err) => { if (err) throw err; console.log(`> Ready on http://localhost:${port}`); }); }) .catch((ex) => { console.error(ex.stack); process.exit(1); }); الآن التشغيل ك node server.js. npm install cross-env "scripts": { "start": "cross-env NODE_ENV=production node server.js" } كما يمكن تنفيذ أمر next export الذي ينتج ملفات ساكنة يمكن رفعها على المخدم قد تحتاج لملف .htaccess لتوجه الطلبيات نحو مخدمك، سيكون بهذه الطريقة: RewriteEngine On RewriteRule ^$ http://127.0.0.1:3000/ [P,L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ http://127.0.0.1:3000/$1 [P,L] مع تغيير المنفذ للمستخدم لديك في مثالنا 6060. أفضل استضافة هي vercel لسهولة النشر عليها كما يمكن لأداء أفضل استخدام vps1 نقطة
-
nonce هو مرجع آمن يستخدم لمرة واحدة لمعلومات الدفع و هو العنصر الأساسي الذي يسمح لخادمك بإيصال معلومات الدفع الحساسة إلى paypal. الأن عندما يتم الدفع يتم إرجاع معلومات من paypal تخبرك بأن العملية نجحت أو فشلت وبناء على ذلك تحدد ما تريد أن تفعله .1 نقطة
-
حالة أكثر تعقيدًا كانت الحالة التي صادفتها في تطبيقنا السابق بسيطة لكونها متعلقة بعدد صحيح واحد. لكن ما العمل إن تطلب أحد التطبيقات التعامل مع حالة أكثر تعقيدًا؟ إنّ أكثر الطرق سهولة في إنجاز ذلك، هو استخدام دالة useState عدة مرات بحيث تتجزأ الحالة الكلية إلى "قطع" صغيرة. سنبني في الشيفرة التالية حالة من قطعتين لتطبيقنا تسمى الأولى "left" والأخرى "right" وستأخذ كل منهما 0 كقيمة ابتدائية. const App = (props) => { const [left, setLeft] = useState(0) const [right, setRight] = useState(0) return ( <div> <div> {left} <button onClick={() => setLeft(left + 1)}> left </button> <button onClick={() => setRight(right + 1)}> right </button> {right} </div> </div> ) } سنمنح المكوّن وصولًا إلى الدالتين setLeft وsetRight اللتان ستُستخدمَان لتحديث قطعتي الحالة. يمكننا تحقيق غرض التطبيق بحفظ عدد النقرات التي تحصل على كلا الزرين "left" و"right" ضمن كائن مفرد: { left: 0, right: 0 } وبهذا سيبدو التطبيق كالتالي: const App = (props) => { const [clicks, setClicks] = useState({ left: 0, right: 0 }) const handleLeftClick = () => { const newClicks = { left: clicks.left + 1, right: clicks.right } setClicks(newClicks) } const handleRightClick = () => { const newClicks = { left: clicks.left, right: clicks.right + 1 } setClicks(newClicks) } return ( <div> <div> {clicks.left} <button onClick={handleLeftClick}>left</button> <button onClick={handleRightClick}>right</button> {clicks.right} </div> </div> ) } يمتلك المكوّن الآن قطعة واحدة من الحالة، وستقع على عاتق معالجات الأحداث مهمة تغيير الحالة لكامل التطبيق. يبدو معالج الحدث فوضويًا بعض الشيء. فعندما يُنقر الزر "left" تُستدعى الدالة التالية: const handleLeftClick = () => { const newClicks = { left: clicks.left + 1, right: clicks.right } setClicks(newClicks) } وتُخزّن القيم الجديدة لحالة التطبيق ضمن الكائن التالي: { left: clicks.left + 1, right: clicks.right } ستحمل الخاصية left القيمة left+1، بينما ستبقى قيمة الخاصية right على ما كانت عليه في الحالة السابقة. يمكننا تعريف كائن الحالة الجديد بشكل أكثر أناقة مستخدمين صيغة توسيع الكائن (object spread) التي أضيفت إلى اللغة صيف 2018: const handleLeftClick = () => { const newClicks = { ...clicks, left: clicks.left + 1 } setClicks(newClicks) } const handleRightClick = () => { const newClicks = { ...clicks, right: clicks.right + 1 } setClicks(newClicks) } تبدو الصيغة غريبة بعض الشيء. حيث تنشئ العبارة {clicks... } كائنًا جديدًا يحمل نسخًا عن كل الخصائص التي يمتلكها الكائن clicks. فعندما نحدد خاصية معينة مثل right في العبارة {clicks,right: 1...}، ستكون القيمة الجديدة للخاصية right تساوي 1. لو تأملنا العبارة التالية في المثال السابق: {...clicks, right: clicks.right + 1 } سنجد أنها تنشئ نسخًا للكائن clicks، ستزداد فيها قيم الخاصية right بمقدار واحد. لا حاجة إلى إسناد قيمة الخاصية إلى متغيّر في معالج الحدث وبالتالي سنبسط كتابة الدوال إلى الشكل التالي: const handleLeftClick = () => setClicks({ ...clicks, left: clicks.left + 1 }) const handleRightClick = () => setClicks({ ...clicks, right: clicks.right + 1 }) قد يتساءل بعض القراء لماذا لم نحدّث الحالة مباشرة كما يلي: const handleLeftClick = () => { clicks.left++ setClicks(clicks) } فالتطبيق سيعمل أيضًا، لكن السبب أنّ React تمنع تغيير الحالة مباشرة، لأن ذلك قد يسبب أخطاء جانبية غير متوقعة. فالطريقة المعتمدة هي دائمًا إسناد الحالة إلى كائن جديد. إن لم يتغير الكائن الذي يضم الخصائص، فسُينسخ بكل بساطة إلى كائن جديد عن طريق نسخ خصائصه واعتماد الكائن الجديد ككائن للحالة. إنّ حفظ الحالة بكل قطعها في كائن واحد هو خيار سيئ في هذا التطبيق خصوصًا، فلن يقدّم الأمر أية فائدة وسيغدو التطبيق أكثر تعقيدًا. لذلك يفضل في حالات كهذه تقسيم الحالة إلى قطع أبسط. ستصادف حالات يكون فيها تخزين قطع من حالة التطبيق في بنًى أكثر تعقيدًا أمرًا مفيدًا، يمكنك الاطلاع على معلومات أكثر عن هذا الموضوع من خلال التوثيق الرسمي لمكتبة React. التعامل مع المصفوفات Handling arrays لنضف قطعة من الحالة إلى تطبيقنا. تتذكر المصفوفة allClicks كل نقرة على زر حدثت في التطبيق. const App = (props) => { const [left, setLeft] = useState(0) const [right, setRight] = useState(0) const [allClicks, setAll] = useState([]) const handleLeftClick = () => { setAll(allClicks.concat('L')) setLeft(left + 1) } const handleRightClick = () => { setAll(allClicks.concat('R')) setRight(right + 1) } return ( <div> <div> {left} <button onClick={handleLeftClick}>left</button> <button onClick={handleRightClick}>right</button> {right} <p>{allClicks.join(' ')}</p> </div> </div> ) } ستُخزّن كل نقرة في القطعة allClicks والتي هُيِّئت على شكل مصفوفة فارغة: const [allClicks, setAll] = useState([]) سيُضاف الحرف 'L' إلى المصفوفة allClicks عندما يُنقر الزر "left": const handleLeftClick = () => { setAll(allClicks.concat('L')) setLeft(left + 1) } إذًا فقطعة الحالة التي أنشأناها هي مصفوفة تضم كل عناصر الحالة السابقة بالإضافة إلى الحرف 'L'. وتتم إضافة العناصر الجديدة باستخدام التابع concat الذي لا يعدل في المصفوفة، بل يعيد مصفوفةً جديدةً يضاف إليها العنصر الجديد. وكما ذكرنا سابقًا، يمكننا إضافة العناصر إلى مصفوفة باستخدام التابع push، حيث يمكن دفع العنصر داخل مصفوفة الحالة ثم تحديثها وسيعمل التطبيق: const handleLeftClick = () => { allClicks.push('L') setAll(allClicks) setLeft(left + 1) } لكن، لا تقم بذلك. فكما أشرنا سابقًا، لا يجب تغيير حالة مكوّنات React مثل allClicks مباشرةً. وحتى لو بدا الأمر ممكنًا، فقد يقودك إلى أخطاء من الصعب جدًا تنقيحها. لنلق الآن نظرة أقرب إلى الآلية التي يُصيّر بها التسلسل الزمني للنقرات: const App = (props) => { // ... return ( <div> <div> {left} <button onClick={handleLeftClick}>left</button> <button onClick={handleRightClick}>right</button> {right} <p>{allClicks.join(' ')}</p> </div> </div> ) } استدعت الشيفرة السابقة التابع join الذي يجعل كل عناصر المصفوفة ضمن سلسلة نصية واحدة يفصل بينها فراغ وهو المعامل الذي مُرِّر إلى التابع. التصيير الشرطي Conditional rendering لنعدّل التطبيق بحيث يتولى الكائن الجديد History أمر تصيير تاريخ النقر على الأزرار: const History = (props) => { if (props.allClicks.length === 0) { return ( <div> the app is used by pressing the buttons </div> ) } return ( <div> button press history: {props.allClicks.join(' ')} </div> ) } const App = (props) => { // ... return ( <div> <div> {left} <button onClick={handleLeftClick}>left</button> <button onClick={handleRightClick}>right</button> {right} <History allClicks={allClicks} /> </div> </div> ) } سيعتمد الآن سلوك التطبيق على أحداث النقر على الأزرار. إن لم تُنقر أية أزرار، سيعني ذلك أن مصفوفة الحالة allClicks فارغة. سيُصيّر عندها المكوّن العنصر div وقد ظهرت ضمنه تعليمات للمستخدم: <div>the app is used by pressing the buttons</div> //..يستخدم التطبيق بالنقر على الأزرار بقية الحالات سيصيّر المكوّن تاريخ النقر على الأزرار: <div> button press history: {props.allClicks.join(' ')} </div> يصيّر المكوّن History مكوّنات React مختلفةً كليًا وفقًا لحالة التطبيق. يدعى هذا بالتصيير الشرطي. تقدم React طرقًا عدة للتصيير الشرطي والتي سنلقي عليها نظرةً أقرب في القسم 2 لاحقًا من هذه السلسلة. سنجري تعديلًا أخيرًا على تطبيقنا بإعادة صياغته مستخدمين المكوّن Button الذي عرّفناه سابقًا: const History = (props) => { if (props.allClicks.length === 0) { return ( <div> the app is used by pressing the buttons </div> ) } return ( <div> button press history: {props.allClicks.join(' ')} </div> ) } const Button = ({ onClick, text }) => ( <button onClick={onClick}> {text} </button> ) const App = (props) => { const [left, setLeft] = useState(0) const [right, setRight] = useState(0) const [allClicks, setAll] = useState([]) const handleLeftClick = () => { setAll(allClicks.concat('L')) setLeft(left + 1) } const handleRightClick = () => { setAll(allClicks.concat('R')) setRight(right + 1) } return ( <div> <div> {left} <Button onClick={handleLeftClick} text='left' /> <Button onClick={handleRightClick} text='right' /> {right} <History allClicks={allClicks} /> </div> </div> ) } كلمة حول React القديمة نستخدم في هذا المنهاج state hook لإضافة حالة إلى مكوّنات React، حيث تعتبر الخطافات جزءًا من نسخ React الأحدث وتتوفر ابتداء من النسخة 16.8.0. لم تكن هناك طرق لإضافة حالة إلى دالة المكوّن قبل إضافة الخطافات، حيث عُرّفت المكوّنات التي تحتاج إلى حالة كصفوف class باستخدام الصيغة class. لقد قررنا في هذا المنهاج استخدام الخطافات منذ البداية لكي نضمن أننا نتعلم الأسلوب المستقبلي لمكتبة React. وعلى الرغم من أن المكوّنات المبنية على شكل دوال هي مستقبل React، لا يزال تعلم استخدام الأصناف مهمًا. فهناك المليارات من سطور الشيفرة المكتوبة بالأسلوب القديم، وقد ينتهي بك الأمر يومًا وأنت تنقح أحدها، أو قد تتعامل مع توثيق أو أمثلة عن React القديمة على الإنترنت. إذًا سنتعلم أكثر عن الأصناف لكن ليس الآن. تنقيح تطبيقات React يمضي المطورون وقتًا طويلًا في تنقيح وتصيير الشيفرات الموجودة، ويتوجب عليهم بين الفينة والأخرى كتابة أسطر جديدة. لكن بطبيعة الحال سنقضي جُلّ وقتنا باحثين عن سبب خطأ ما أو عن الطريقة التي يعمل بها مكوّن ما. لذلك فاقتناء أدوات للتنقيح والتمكّن من تحري الأخطاء أمر مهم للغاية. ولحسن الحظ تقدم مكتبة React عونًا منقطع النظير للمطورين فيما يتعلق بموضوع التنقيح. قبل أن نتابع، دعونا نتذكر القاعدة الأكثر أهمية في تطوير الويب، حيث تنص القاعدة الأولى في تطوير الويب على مايلي: وعليك أن تبقي المحرر الذي تكتب فيه الشيفرة مفتوحًا في نفس الوقت مع صفحة الويب ودائمًا. وتذكر عندما تُخفق الشيفرة ويمتلئ المتصفح بالأخطاء أن لا تكتب المزيد من الشيفرة بل ابحث عن الخطأ وصححه مباشرةً. لقد مر زمن كان فيه كتابة المزيد من الشيفرة تمثل حلًا سحريًا للأخطاء، لكننا نجزم أن شيئًا كهذا لن يحدث في هذا المنهاج. لا تزال الطريقة القديمة في التنقيح والتي تعتمد على طباعة القيم على الطرفية فكرة جيدة. const Button = ({ onClick, text }) => ( <button onClick={onClick}> {text} </button> ) إن لم يعمل المكوّن كما يفترض، اطبع قيم متغيراته على الطرفية. ولكي تنفذ ذلك بشكل فعّال، عليك تحويل الدوال إلى شكلٍ أقل اختصارًا وأن تستقبل قيم جميع الخصائص دون أن تفككها مباشرةً. const Button = (props) => { console.log(props) const { onClick, text } = props return ( <button onClick={onClick}> {text} </button> ) } سيكشف ذلك مباشرة أنّ اسم إحدى الخصائص مثلًا قد كُتب بطريقة خاطئة عند استخدام المكوّن. ملاحظة: عندما تستخدم التعليمة console.log للتنقيح، لا تستخدم أسلوبًا مشابهًا لشيفرة Java. فلو أردت ضم معلومات مختلفة في الرسالة مثل استخدام إشارة (+). لا تكتب: console.log('props value is ' + props) بل افصل بين الأشياء التي تريد طباعتها بفاصلة ",": console.log('props value is', props) فعندما تستخدم طريقة مشابهة لطريقة Java بضم سلسلة نصية إلى كائن سينتهي الأمر بالحصول على رسالة غير مفهومة: props value is [Object object] بينما ستبقى العناصر التي تفصل بينها باستخدام الفاصلة واضحة على طرفية المتصفح. لا تُعتبر طريقة الولوج إلى الطرفية على أية حال هي الطريقة الوحيدة لتنقيح التطبيقات. إذ يمكنك إيقاف تنفيذ الشيفرة في منقح طرفية التطوير لمتصفح Chrome مثلًا باستخدام الأمر debugger في أي مكان ضمن الشيفرة. حيث سيتوقف التنفيذ عندما تصل إلى النقطة التي وضعت فيها الأمر السابق: انتقل إلى النافذة Console لتتابع الحالة الراهنة للمتغيّرات بسهولة: يمكنك إزالة الأمر debugger وإعادة تحميل الصفحة حالما تكتشف الخطأ في شيفرتك. يمكّنك المنقح أيضًا من تنفيذ الشيفرة سطرًا سطرًا باستخدام عناصر التحكم الخاصة الموجودة على يمين النافذة source في الطرفية. لا حاجة لاستخدام الأمر debugger للوصول إلى المنقح، يمكنك عوضًا عن ذلك استخدام نقاط التوقف (break point) الموجودة في النافذة source، وتتبّع قيم متغيرات المكوّن في القسم Scope: ننصحك بشدة أن تضيف إضافة مُوسِّعة (extension) لمكتبة React على متصفح Chrome تدعى React developer tools، الذي سيضيف النافذة الجديدة React إلى الطرفية: سنستخدم أدوات تطوير React الجديدة في تتبع حالة العناصر المختلفة في التطبيق وخصائصها. لكن النسخة الحالية من هذا الموسِّع تُغفل عرض المطلوب عن حالة المكوّنات التي تنشئها الخطافات: عُرّفت حالة المكوّن بالشكل التالي: const [left, setLeft] = useState(0) const [right, setRight] = useState(0) const [allClicks, setAll] = useState([]) تُظهر أدوات التطوير حالة الخطافات حسب تسلسل تعريفها: قواعد استخدام الخطافات Rules of Hooks هناك قواعد وحدود معينة يجب أن نتقيد بها لنتأكد أن الحالة التي تعتمد على الخطافات في التطبيق ستعمل كما يجب. فلا يجب استدعاء التابع useState (وكذلك التابع useEffect الذي سنتعرف عليه لاحقًا) من داخل الحلقات أو العبارت الشرطية أو من أي موقع لا يمثل دالة لتعريف مكوّن. ذلك لنضمن أن الخطافات ستُستدعى وفق الترتيب ذاته لتعريفها وإلا سيتصرف التطبيق بشكل فوضوي. باختصار استدع الخطافات من داخل جسم الدوال المعرِّفة للمكوّنات: const App = (props) => { // الشيفرة التالية صحيحة const [age, setAge] = useState(0) const [name, setName] = useState('Juha Tauriainen') if ( age > 10 ) { // هذه الشيفرة لن تعمل const [foobar, setFoobar] = useState(null) } for ( let i = 0; i < age; i++ ) { // هذه غير جيدة أيضًا const [rightWay, setRightWay] = useState(false) } const notGood = () => { // هذه غير مشروعة const [x, setX] = useState(-1000) } return ( //... ) } عودة إلى معالجة الأحداث أثبت موضوع معالجة الأحداث صعوبته ضمن الأفكار المتسلسلة التي قدمناها حتى الآن، لذلك سنعود إليه مجددًا. لنفترض أننا نطور هذا التطبيق البسيط: const App = (props) => { const [value, setValue] = useState(10) return ( <div> {value} <button>reset to zero</button> </div> ) } ReactDOM.render( <App />, document.getElementById('root') ) المطلوب هو النقر على الزر لتصفير الحالة المخزّنة ضمن المتغّير value. علينا إضافة معالج حدث للزر لكي يتفاعل مع حدث النقر عليه. تذكر أن معالجات الأحداث يجب أن تكون دائمًا دوال أو مراجع لدوال. لن يعمل الزر إذا أسند معالج الحدث إلى متغيّر من أي نوع آخر. فلو عرفنا معالج الحدث على شكل نص كما يلي: <button onClick={'crap...'}>button</button> ستحذرنا React من ذلك على الطرفية: index.js:2178 Warning: Expected `onClick` listener to be a function, instead got a value of `string` type. in button (at index.js:20) in div (at index.js:18) in App (at index.js:27) وكذلك لن يعمل المعالج التالي: <button onClick={value + 1}>button</button> حيث نعرف معالج الحدث في هذه الحالة بالعبارة value+1 التي ستعيد قيمة العملية، وستحذرنا React أيضًا: index.js:2178 Warning: Expected `onClick` listener to be a function, instead got a value of `number` type. هذه الطريقة لن تنفع أيضًا: <button onClick={value = 0}>button</button> فمعالج الحدث هنا لا يمثل دالة بل عملية إسناد لمتغيّر، وستحذرنا React مجددًا على الطرفية. وتعتبر هذه العملية خاطئة أيضًا من مبدأ عدم تغيير الحالة بشكل مباشر. لكن ماذا لو فعلنا التالي: <button onClick={console.log('clicked the button')}> button </button> ستُطبع العبارة المكتوبة داخل الدالة لمرة واحدة ولن يحدث بعدها شيء عند إعادة نقر الزر. لماذا لم يعمل معالج الحدث إذًا على الرغم من وجود الدالة console.log؟ المشكلة هنا أن معالج الحدث قد عُرّف كاستدعاء لدالة، أي أنه سيعيد قيمة الدالة والتي ستكون "غير محددة" في حالة console.log. ستُنفَّذ هذه الدالة تحديدًا عندما يُعاد تصيير المكوّن، ولهذا لم تظهر العبارة سوى مرة واحدة. ستخفق المحاولة التالية أيضًا: <button onClick={setValue(0)}>button</button> ستجعل معالج الحدث استدعاءً لدالة أيضًا في هذه المحاولة، لن يفلح ذلك. ستسبب هذه المحاولة تحديدًا مشكلة من نوع آخر. فعندما يُصيّر المكوّن، ستُنفَّذ الدالة (setvalue(0 والتي بدورها ستُعيد تصيير المكوّن. إعادة التصيير هذه تستدعي مجددًا (setvalue(0 وهذا ما يسبب حلقة لانهائية من الاستدعاءات. تُستدعى دالة محددة عند النقر على الزر كالتالي: <button onClick={() => console.log('clicked the button')}> button </button> يمثل معالج الحدث الآن دالة معرفة بالطريقة السهمية (console.log('clicked the button <= (). فلن تُستدعى الآن أية دوال عندما يصيّر المكوّن، لأن معالج الحدث قد أسند إلى مرجع لدالة سهمية، ولن تُستدعى هذه الدالة إلا بعد النقر على الزر. يمكننا استخدام نفس الأسلوب لإضافة زر تصفير الحالة في التطبيق الذي بدأناه: <button onClick={() => setValue(0)}>button</button> لقد جعلت الشيفرة السابقة الدالة (setValue(0 <=() معالج الحدث للزر. لا يعتبر تعريف معالج الحدث مباشرة ضمن الخاصية onClick الطريقة الأفضل بالضرورة. حيث نجده أحيانًا معرفًا في مكان آخر. سنعرّف في النسخة التالية من التطبيق الدالة السهمية التي ستلعب دور معالج الحدث ضمن جسم المكوّن ومن ثم سنسندها إلى المتغيّر handleClick: const App = (props) => { const [value, setValue] = useState(10) const handleClick = () => console.log('clicked the button') return ( <div> {value} <button onClick={handleClick}>button</button> </div> ) } يعتبر المتغيّر handleClick مرجعًا للدالة السهمية، وسيُمرَّر هذا المرجع إلى الزر كقيمة للخاصية onClick: <button onClick={handleClick}>button</button> قد تضم دالة معالج الحدث أكثر من أمر، فلا بد عندها من وضع الأوامر بين قوسين معقوصين: const App = (props) => { const [value, setValue] = useState(10) const handleClick = () => { console.log('clicked the button') setValue(0) } return ( <div> {value} <button onClick={handleClick}>button</button> v> ) } الدالة التي تعيد دالة يمكن تعريف معالج حدث بطريقة الدالة التي تعيد دالة. وقد لا تضطر إلى استخدام هذا النوع من الدوال في هذا المنهاج. إن بدا لك الأمر مربكًا بعض الشيء تجاوز هذا المقطع الآن وعد إليه لاحقًا. لنعدل الشيفرة لتصبح كما يلي: const App = (props) => { const [value, setValue] = useState(10) const hello = () => { const handler = () => console.log('hello world') return handler } return ( <div> {value} <button onClick={hello()}>button</button> </div> ) } ستعمل هذه الشيفرة بشكل صحيح على الرغم من مظهرها المعقد. يستدعي معالج الحدث هنا دالة: <button onClick={hello()}>button</button> لكننا قررنا سابقًا أن لا يكون معالج الحدث استدعاء لدالة، بل يجب أن يكون دالة أو مرجعًا لها. لماذا إذًا سيعمل المعالج في هذه الحالة؟ عندما يُصيّر المكوّن، ستُنفّذ الدالة التالية: const hello = () => { const handler = () => console.log('hello world') return handler } إنّ القيمة المعادة من الدالة السهمية الأولى هي دالة سهمية أخرى أسندت إلى المتغيّر handler. فعندما تصيّر React السطر التالي: <button onClick={hello()}>button</button> ستُسنَد القيمة المعادة من ()hello إلى الخاصية onClick، وسيتحول السطر مبدئيًا إلى: <button onClick={() => console.log('hello world')}> button </button> إذًا نجح الأمر لأن الدالة hello قد أعادت دالة وسيكون معالج الحدث دالة أيضًا. لكن ما المغزى من هذا المبدأ؟ لنغير الشيفرة قليلًا: const App = (props) => { const [value, setValue] = useState(10) const hello = (who) => { const handler = () => { console.log('hello', who) } return handler } return ( <div> {value} <button onClick={hello('world')}>button</button> <button onClick={hello('react')}>button</button> <button onClick={hello('function')}>button</button> </div> ) } يضم التطبيق الآن ثلاثة أزرار لكل منها معالج حدث تُعرّفه الدالة hello التي تقبل معاملًا. عُرّف الزر الأول كما يلي: <button onClick={hello('world')}>button</button> يٌنشأ معالج الحدث باستدعاء الدالة hello التي تعيد الدالة التالية: () => { console.log('hello', 'world') } يُعرّف الزر التالي بالشكل: <button onClick={hello('react')}>button</button> سيعيد استدعاء الدالة (hello(react (التي تُنشئ معالج الحدث الخاص بالزر) مايلي: () => { console.log('hello', 'react') } لاحظ أن كلا الزرين حصل على معالج حدث خاص به. فاستخدام الدوال التي تعيد دوال قد يساعد على تعريف الدوال المعمّمة التي يمكن تغييرها حسب الطلب باستخدام المعاملات. حيث تمثل الدالة hello التي أنشأت معالجات الأحداث مصنعًا لإنتاج معالجات أحداث يحددها المستخدم كما يشاء. يبدو تعريف الدالة التالي طويلًا نوعا ما: const hello = (who) => { const handler = () => { console.log('hello', who) } return handler } لنحذف متغيّرات الدالة المساعدة ونعيد التابع الذي أنشأناه: const hello = (who) => { return () => { console.log('hello', who) } } وطالما أن الدالة hello مؤلفة من عبارة برمجية واحدة، سنحذف القوسين المعقوصين أيضًا ونستخدم الشكل المختصر للدالة السهمية: const hello = (who) => () => { console.log('hello', who) } أخيرًا لنكتب كل الأسهم على السطر نفسه: const hello = (who) => () => { console.log('hello', who) } يمكننا استخدام الحيلة ذاتها لتعريف معالجات الأحداث لإسناد قيمة معينة إلى حالة المكوّن، لنغيّر الشيفرة كالتالي: const App = (props) => { const [value, setValue] = useState(10) const setToValue = (newValue) => () => { setValue(newValue) } return ( <div> {value} <button onClick={setToValue(1000)}>thousand</button> <button onClick={setToValue(0)}>reset</button> <button onClick={setToValue(value + 1)}>increment</button> </div> ) } عندما يُصيّر المكوّن، سيُنشأ الزر thousand: <button onClick={setToValue(1000)}>thousand</button> يضبط معالج الحدث ليعيد قيمة العبارة (setValue(1000، والتي تمثلها الدالة التالية: () => { setValue(1000) } يُعرّف زر الزيادة بالطريقة: <button onClick={setToValue(value + 1)}>increment</button> يُنشأ معالج الحدث باستدعاء الدالة (setToValue(value + 1 والتي تستقبل قيمة متغير الحالة value كمعامل لها بعد زيادته بواحد. فلو كانت قيمة المتغير 10 سيكون معالج الحدث الذي سيُنشأ كالتالي: () => { setValue(11) } لبس ضروريًا استخدام (دوال تعيد دوال) لتقديم وظائف كهذه، فلو أعدنا الدالة setToValue المسؤولة عن تحديث الحالة إلى دالة عادية: const App = (props) => { const [value, setValue] = useState(10) const setToValue = (newValue) => { setValue(newValue) } return ( <div> {value} <button onClick={() => setToValue(1000)}> thousand </button> <button onClick={() => setToValue(0)}> reset </button> <button onClick={() => setToValue(value + 1)}> increment </button> </div> ) } يمكننا عندها تعريف معالج الحدث كدالة تستدعي الدالة setToValue باستخدام المعامل المناسب. وسيكون عندها معالج الحدث لتصفير الحالة من الشكل: <button onClick={() => setToValue(0)}>reset</button> وفي النهاية اختيار أي من الطريقتين السابقتين في تعريف معالج الحدث هو أمر شخصي. تمرير معالجات الأحداث إلى المكوّنات الأبناء لنجزِّء مكوّن الزر إلى أقسامه الرئيسية: const Button = (props) => ( <button onClick={props.handleClick}> {props.text} </button> ) يحصل المكوّن على دالة معالج الحدث الخاصة به من الخاصية handleClick ويأخذ النص الذي سيظهر عليه من الخاصية text. إنّ استخدام المكوّن Button أمر سهل، لكن علينا التأكد من اسم الصفة قبل تمرير الخاصية إلى المكوّن. لا تعرّف المكوّنات داخل المكوّنات لنبدأ بإظهار قيمة التطبيق ضمن المكوّن Display الخاص به. سنعدل الشيفرة بتعريف مكوّن جديد داخل المكوّن App: // هذا هو المكان الصحيح لتعريف المكون const Button = (props) => ( <button onClick={props.handleClick}> {props.text} </button> ) const App = props => { const [value, setValue] = useState(10) const setToValue = newValue => { setValue(newValue) } // لا تعرف المكونات داخل المكونات const Display = props => <div>{props.value}</div> return ( <div> <Display value={value} /> <Button handleClick={() => setToValue(1000)} text="thousand" /> <Button handleClick={() => setToValue(0)} text="reset" /> <Button handleClick={() => setToValue(value + 1)} text="increment" /> </div> ) } سيعمل التطبيق كما يبدو، لكن لا تعرّف مكوّنًا ضمن آخر. لن يقدم لك ذلك أية فائدة وسيقود إلى مشاكل عديدة. إذًا دعونا ننقل المكوّن Display إلى مكانه الصحيح خارج دالة المكوّن App: const Display = props => <div>{props.value}</div> const Button = (props) => ( <button onClick={props.handleClick}> {props.text} </button> ) const App = props => { const [value, setValue] = useState(10) const setToValue = newValue => { setValue(newValue) } return ( <div> <Display value={value} /> <Button handleClick={() => setToValue(1000)} text="thousand" /> <Button handleClick={() => setToValue(0)} text="reset" /> <Button handleClick={() => setToValue(value + 1)} text="increment" /> </div> ) } مواد جديرة بالقراءة تمتلئ شبكة الإنترنت بمواد متعلقة بمكتبة React. لكننا في هذا المنهاج سنستخدم الأسلوب الجديد. ستجد غالبية المواد المتوفرة أقدم مما نبغي، لكن ربما ستجد الروابط التالية مفيدة: دروس ومقالات حول React من أكاديمية حسوب توثيقات React الرسمية: تستحق هذه التوثيقات المطالعة في مرحلة ما، حيث يغدو لمعظم محتوياتها أهمية في سياق المنهاج، ماعدا تلك المتعلقة بالمكوّنات المعتمدة على الأصناف. بعض مناهج Egghead.io الأجنبية: مثل Start learning React وتتمتع بقيمة عالية، وقد جرى تحديثها مؤخرًا. ويعتبر كذلك The Beginner's Guide to React جيدًا نوعًا ما. حيث يقدم المنهاجان مبادئ سنستعرضها في منهاجنا لاحقًا. وانتبه إلى أن المنهاج الأول يستخدم مكوّنات الأصناف، بينما يستخدم الثاني طريقة الدوال الجديدة. التمارين 1.6- 1.14 سلّم حلول التمارين برفع الشيفرة إلى GitHub ثم أشر إلى إتمام التمارين على منظومة تسليم التمارين. وتذكر أن تسلم تمارين القسم بالكامل دفعة واحدة , فلو سلمت تمارين قسمٍ ما، لن تكون قادررًا على تسليم غيرها من القسم ذاته. تشكل بعض التمارين جزءًا من التطبيق ذاته، يكفي في هذه الحالة رفع النسخة النهائية من التطبيق. يمكنك إن أردت الإشارة إلى ذلك بتعليق في نهاية كل تمرين، لكنه أمر غير ملزم. تحذير تنشئ الأداة create-react-app مستودع git محلي يحتوي المشروع ، إلا إن كان في المجلد مستودع محلي سابق. من المرجح أنك لا تريد أن يغدو المشروع مستودعًا، لهذا نفذ الأمر التاليrm -rf .git في مسار المشروع. عليك أحيانًا تنفيذ الأمر التالي في نفس المكان: rm -rf node_modules/ && npm i 1.6 unicafe: الخطوة 1 تجمع Unicafe كمعظم الشركات آراء عملائها. ستكون مهمتك إنشاء تطبيق لجمع آراء العملاء. حيث يقدم التطبيق ثلاث خيارات هي جيد وعادي وسيء. يجب أن يُظهر التطبيق العدد الكلي للآراء في كل فئة. يمكن لتطبيقك أن يكون بالشكل التالي: ينبغي لتطبيقك العمل خلال جلسة واحدة للمتصفح، وبالتالي لا بأس إن اختفت المعلومات عند إعادة تحميل الصفحة. يمكن وضع الشيفرة في ملف index.js واحد، كما يمكن الاستفادة من الشيفرة التالية كنقطة للبدء: import React, { useState } from 'react' import ReactDOM from 'react-dom' const App = () => { // save clicks of each button to own state const [good, setGood] = useState(0) const [neutral, setNeutral] = useState(0) const [bad, setBad] = useState(0) return ( <div> code here </div> ) } ReactDOM.render(<App />, document.getElementById('root') ) 1.8 unicafe: الخطوة 2 وسّع تطبيقك ليضم إحصائيات أخرى حول آراء العملاء كالعدد الكلي للآراء و التقييم الوسطي (1 للجيد و 0 للعادي و -1 للسيء) والنسبة المئوية للآراء الإيجابية. 1.9 unicafe: الخطوة 3 أعد صياغة تطبيقك بحيث توضع الإحصائيات في مكوّنها الخاص Statistics. ابق حالة التطبيق داخل المكوّن الجذري App وتذكر ألا تعرّف المكوّنات داخل المكوّنات الأخرى: // a proper place to define a component const Statistics = (props) => { // ... } const App = () => { const [good, setGood] = useState(0) const [neutral, setNeutral] = useState(0) const [bad, setBad] = useState(0) // do not define a component within another component const Statistics = (props) => { // ... } return ( // ... ) } 1.9 unicafe: الخطوة 4 غيّر تطبيقك لتعرض الإحصائيات حالما نحصل على رأي المستخدم. 1.10 unicafe: الخطوة 5 سنكمل إعادة صياغة التطبيق. إفصل المكوّنين التاليين ليقوما بالتالي: المكوّن Button سيُعرِّف الأزرار التي تُستخدم للحصول على الرأي. المكوّن Statistic ليظهر إحصائية واحدة مثل متوسط التقييم. لنوضح الأمر أكثر: يظهر المكوّن Statistic دائمًا إحصائية واحدة، بمعنى أن التطبيق يستخدم مكوّنات متعددة ليصيّر كل الإحصائيات كما يظهر في الشيفرة التالية: const Statistics = (props) => { /// ... return( <div> <Statistic text="good" value ={...} /> <Statistic text="neutral" value ={...} /> <Statistic text="bad" value ={...} /> // ... </div> ) } يجب أن تُبق حالة التطبيق ضمن المكوّن App. 1.11 unicafe: الخطوة 6 * اعرض الإحصائيات في جدول HTML ليبدو تطبيقك مماثلًا بشكل ما للصفحة التالية: تذكر أن تبقي الطرفية مفتوحةً دائمًا، وإن رأيت هذا الخطأ: حاول قدر استطاعتك التخلص من التحذيرات التي ستظهر لك. حاول البحث في Google عن رسائل الخطأ التي تصادفك إن لم تتمكن من إيجاد الحل. فمثلًا، المصدر النموذجي للخطأ: Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist هو موسّع للمتصفح Chrome. ادخل إلى /chrome://extensions ثم الغ تفعيل الموسّعات واحدًا تلو الآخر حتى تقف على الموسّع الذي سبب الخطأ، ثم أعد تحميل الصفحة. من المفترض أن يصحح ذلك الخطأ. احرص من الآن فصاعدًا أن لا ترى أية تحذيرات على متصفحك 1.12 طرائف: خطوة 1 * يمتلئ عالم هندسة البرمجيات بالطرائف التي تختصر الحقائق الخالدة في هذا المجال في سطر واحد قصير. وسّع التطبيق التالي بإضافة زر يعرض بالنقر عليه طرفة مختارة عشوائيًا من مجال هندسة البرمجيات: import React, { useState } from 'react' import ReactDOM from 'react-dom' const App = (props) => { const [selected, setSelected] = useState(0) return ( <div> {props.anecdotes[selected]} </div> ) } const anecdotes = [ 'If it hurts, do it more often', 'Adding manpower to a late software project makes it later!', 'The first 90 percent of the code accounts for the first 90 percent of the development time...The remaining 10 percent of the code accounts for the other 90 percent of the development time.', 'Any fool can write code that a computer can understand. Good programmers write code that humans can understand.', 'Premature optimization is the root of all evil.', 'Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.' ] ReactDOM.render( <App anecdotes={anecdotes} />, document.getElementById('root') ) ابحث في Google عن طريقة توليد الأرقام العشوائية في JavaScript. ولا تنس أن تجرب توليد الأرقام العشوائية بعرضها مباشرة على طرفيّة المتصفح. سيبدو التطبيق عندما ينتهي قريبًا من الشكل التالي: تحذير تنشئ الأداة create-react-app مستودع git محلي يحتوي المشروع ، إلا إن كان في المجلد مستودع محلي سابق. من المرجح أنك لا تريد أن يغدو المشروع مستودعًا، لهذا نفذ الأمر التاليrm -rf .git في مسار المشروع. 1.13 طرائف: خطوة 2 * وسّع تطبيقك بحيث يمكنك التصويت لصالح الطرفة المعروضة. ملاحظة: خزن نتائج التصويت على كل طرفة في مصفوفة كائنات ضمن مكوّن الحالة. وتذكر أن الطريقة الصحيحة لتحديث حالة التطبيق المخزنة في البنى المعقدة للبيانات كالمصفوفات والكائنات هو إنشاء نسخة للحالة. يمكنك نسخ الكائن كما يلي: const points = { 0: 1, 1: 3, 2: 4, 3: 2 } const copy = { ...points } // زيادة قيمة الخاصية 2 بمقدار 1 copy[2] += 1 أو نسخ مصفوفة على النحو: const points = [1, 4, 6, 3] const copy = [...points] // زيادة القيمة في الموقع 2 من المصفوفة بمقدار 1 copy[2] += 1 استخدم المصفوفة فقد يكون ذلك الخيار الأبسط في هذه الحالة. سيساعدك البحث في Google على إيجاد الكثير من التلميحات عن كيفية إنشاء مصفوفة من الأصفار بالطول الذي تريد. 1.14 طرائف: الخطوة 3* اجعل النسخة النهائية للتطبيق قادرة على عرض الطرفة التي تحقق أعلى عدد من الأصوات: يكفي أن تعرض إحدى الطرائف التي تحقق نفس العدد من الأصوات. لقد وصلنا إلى نهاية تمارين القسم 1 من المنهاج وحان الوقت لتسليم الحلول إلى GitHub. لا تنسى تحديد التمارين التي سلمتها في منظومة تسليم التمارين. ترجمة -وبتصرف- للفصل JavaScript من سلسلة Deep Dive Into Modern Web Development1 نقطة
-
لم يتسنى للناس العاديين، ومن ضمنهم معظم المبرمجين، الاقتراب من الحواسيب في الفترة التي تلت صدورها لأول مرة، إذ كانت الحواسيب محتجزةً في غرفٍ مقفلة مع مرافقين يرتدون اللباس الأبيض يأخذون برنامجك وبياناتك، ويقومون بتغذيتها للحاسوب، ومن ثم يعيدون لك استجابة الحاسوب بعد فترة معينة من الزمن. عند إدخال مفهوم "مشاركة الوقت" (timesharing) للحاسوب، وهو عملية تبديل الحاسوب لانتباهه بسرعة من شخص لآخر، وذلك في ستينيات القرن الماضي، أصبح بإمكان عدة أشخاص التفاعل مع الحاسوب في الوقت ذاته. في نظام معتمد على مشاركة الوقت، يجلس المستخدمون أمام "طرفيّة" (terminal) ويرسلون الأوامر إلى الحاسوب، ويرد الحاسوب بكتابة استجابته. استخدمت الحواسيب الشخصية الأولى أيضًا الأوامر المكتوبة والاستجابات، إلّا أنّ هناك في كل مرة شخص واحد معنيّ يستخدم الحاسوب. هذا النوع من التفاعل بين المستخدم والحاسوب يدعى بواجهة سطر الأوامر (Command-line interface). يتفاعل بالطبع معظم الناس في يومنا هذا مع الحواسيب بطريقة مختلفة كليًّا حيث يستخدمون واجهة مستخدم رسومية (Graphical User Interface، واختصارًا GUI). يرسم الحاسوب مكوّنات الواجهة على الشاشة، وتتضمن المكونات أشياء مثل النوافذ (windows) وأشرطة التمرير (scroll bars) والقوائم (menus) والأزرار (buttons) والأيقونات (icons). عادةً ما تُستخدم الفأرة للتحكم بهذه المكوّنات، أو في حالة شاشات اللّمس، تستخدم أصابعك. لا شكّ أنّك مطّلع على أساسيّات واجهات المستخدم الرسومية، هذا بالطبع ما لم تكن قد انتقلت عبر الزمن من السبعينيات. أصبح الكثير من مكوّنات واجهة GUI معياريًّا إلى حد كبير. نعني بذلك أنّ لهذه المكوّنات مظهرًا وسلوكًا متشابهًا على الكثير من منصات الحاسوب المختلفة ومن ضمنها أنظمة ماك وويندوز ولينكس. تستطيع برامج جافا -والتي يُفترض أن تُنفَّذ على الكثير من المنصات المختلفة بدون إجراء تعديل على البرنامج- استخدام جميع مكوّنات GUI المعياريَّة. قد يختلف مظهر هذه المكوّنات بين منصةٍ وأخرى، إلّا أنَّ وظيفتها متطابقةٌ على أي حاسوب يُنفّذ البرنامج عليه. تجد أدناه صورةً لبرنامج جافا بسيطٍ جدًا يوضّح بضع من المكوّنات المعياريّة لواجهة GUI. عندما يُنفّذ البرنامج، ستفتح نافذةٌ شبيهة بالصورة التي تظهر هنا على شاشة الحاسوب. هناك أربعة مكوّنات في النافذة يستطيع المستخدم التفاعل معها: زر، مربّع تأشير (checkbox)، حقل نصّي (text field) وقائمة منبثقة (pop-up menu). هناك بضعة مكوّنات أخرى في النافذة، التّسميات (labels) ذاتها هي مكوّنات (على الرغم من أنّك لا تستطيع التفاعل معها). النصف الأيمن من النافذة عبارةٌ عن مكوّن مساحة نصيّة (text area) يستطيع عرض عدّةٍ أسطر من النص. يظهر مكوّن شريط التّمرير جنبًا إلى جنب مع المساحة النصيّة عندما يصبح عدد أسطر النصّ أكبر من أن يتّسع في المساحة النصيّة. في الحقيقة، وفقًا لمصطلحات جافا، تُعدّ النافذة بأكملها "مكوّنًا". (إذا أردت تنفيذ هذا البرنامج، فالشيفرة المصدريّة، الملف GUIDemo.java، متوفرّة على الشبكة. لمزيد من المعلومات حول استخدامه والأمثلة الأخرى في هذا الكتاب، انظر القسم 2.6) تتضمّن جافا في الواقع ثلاث مجموعات كاملة من مكوّنات الواجهة الرسومية GUI. الأولى هي مجموعة أدوات النوافذ المجرّدة (Abstract Windowing Toolkit أو اختصارًا AWT)، وقد أصبحت متوفرة مع صدور النسخة الأصليّة من جافا. أمّا المجموعة الثانية وتُعرف باسم Swing فقد طُرحت مع النسخة 1.2 من جافا لتصبح بعدئذٍ المجموعة المعياريّة لأدوات GUI لسنوات عديدة. أما مجموعة الأدوات الثالثة وتدعى JavaFX فقد أصبحت جزءًا معياريًّا من جافا في نسختها الثامنة (لكنّها حُذفت مؤخّرًا ولذا تتطلّب تثبيتًا منفصلًا في بعض نسخ جافا). على الرغم من أنّك ما زلت قادرًا على استخدام كلٍّ من Swing وAWT، إلّا أنّه يُراد بمجموعة JavaFX أن تكون الوسيلة الحديثة لكتابة تطبيقات GUI. يتناول هذا الكتاب شرح JavaFX حصرًا. (إذا أردت تعلّم Swing، ألقِ نظرةً على النسخة السابقة من هذا الكتاب.) عندما يتفاعل المستخدم مع مكوّنات GUI، تولِّد أحداثًا (Events). على سبيل المثال، النقر على زر الضغط (push button) يولِّد حدثًا، والضغط على مفتاح من لوحة المفاتيح يولّد حدثًا. في كل مرة يتوّلد فيها حدث، تُرسل رسالة إلى البرنامج تخبره أنّ حدثًا قد جرى ويستجيب البرنامج وفقاً لبرنامجه. في واقع الأمر، يتألف برنامج GUI النمطي من "معالجات أحداث" (event handlers) تخبر البرنامج بكيفيّة الاستجابة إلى أنواع مختلفة من الأحداث. في المثال أعلاه، بُرمج البرنامج ليستجيب لكل حدث بعرض رسالةٍ في المساحة النصيّة. في مثال أكثر واقعيّة، يتوجّب على معالجات الأحداث القيام بأكثر من مجرد ذلك. استخدام المصطلح "رسالة" هنا مُتعمّد. كما رأيت في القسم السابق، تُرسل الرسائل إلى الكائنات. في الواقع، تُحقّق مكوّنات GUI على أنها كائنات. تتضمن جافا العديد من الأصناف المعرّفة مسبقًا والتي تُمثّل أنواعًا مختلفة من مكوّنات GUI. بعض هذه الأصناف عبارةٌ عن أصناف فرعيّةٍ من أصناف أخرى. إليك مخطّطًا يوضح فقط بعضًا من صفوف JavaFX GUI وعلاقاتها: لا تقلق حيال التفاصيل في الوقت الحاضر، لكن حاول أن تفهم كيف تُستخدم البرمجة كائنيّة التوجه والوراثة هنا. لاحظ أنّ جميع أصناف الواجهة GUI الموضحة هنا هي أصناف فرعيّة، إما بشكلٍ مباشرٍ أو غير مباشر، من الصنف الرئيسي Control والذي يُمثل الصفات العامة المشتركة بين الكثير من مُكوّنات JavaFX. في المخطط، لدى اثنين من الأصناف المتفرِّعة مباشرًة من الصنف Control أصنافًا فرعيّة بدورها. جُمع الصنف TextField والصنف TextArea واللذان يشتركان بأنماط سلوك محددّة معًا كصنفين فرعيين للصنف TextInputControl. وعلى غرار ذلك، نجد أنّ الصنفين Button و CheckBox هما صنفان فرعيّان من الصنف ButtonBase الذي يُمثّل الخصائص المشتركة لكل من الأزرار ومربعات التأشير. (بالمناسبة، فإنّ الصنف ComboBox هو الصنف الذي يُمثّل القوائم المنبثقة.) ربما ترى من هذا النقاش المقتضب كيف أنّ برمجة واجهات المستخدم الرسومية يستغّلُ التصميم كائني التوجّه بفعاليّة. والواقع أنّ واجهات المستخدم الرسومية بكائناتها المرئيّة هي عامل رئيسي ساهم في شعبيّة البرمجة كائنية التوجّه. تُعدّ البرمجة بمكوّنات واجهات المستخدم الرسومية والأحداث واحدًا من أكثر جوانب جافا متعةً وأهميةً. على أيّة حال، سنقضي بضع فصول مع الأساسيّات قبل العودة إلى هذا الموضوع في الفصل السادس. ترجمة وبتصرف للفصل The Modern User Interface من كتاب Introduction to Programming Using Java1 نقطة
-
في هذا الدرس سوف نقوم بتحويل تصميم تم إعداده باستخدام فوتوشوب وجعله صفحة ويب جاهزة وذلك باستخدام لغتي HTML وCSS (وهو أمر يُعرف أيضا تحت اسم “التكويد”). هذا هو التصميم الذي سوف نعمل على تكويده في هذا الدرس: استخراج بعض الصورقبل أن نبدأ في تكويد التصميم سوف نحتاج إلى استخراج بعض الصور منه (في الأسفل يوجد صورة توضيحية للملفات التي نحتاجها، وكوننا لا نملك الملف لاستخراج الملفات منه فيمكنك استعمال أي بديل تراه مناسبًا فالمهم هو أن تعرف كيفية التكويد وكتابة أكواد مناسبة). بنية ملف HTML <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Chris Spooner Design Portfolio</title> <link href="style.css" rel="stylesheet" type="text/css" media="screen" /> </head> <body> <div id="container"> </div> </body> </html> يبدأ ملف الـHTML كما هو معتاد على وسم <doctype> و <head> وأخيرًا وسم <body>. كما أننا قمنا بربط ملف CSS بواسطة استعمال وسم <link> وأضفنا أيضًا وسم <div id="container"> ليعمل كحاوٍ لجميع محتوى الصفحة. <p id="logo"> <a href="#"><img src="images/logo.png" alt="Chris Spooner logo" /></a> </p> <ul id="nav"> <li><a href="index.html">Home</a></li> <li><a href="about.html">About</a></li> <li><a href="portfolio.html">Portfolio</a></li> <li><a href="contact.html">Contact</a></li> </ul> <div id="header"> <h1>Hello, I'm Chris Spooner.</h1> <h2>I craft websites that are beautiful on both the inside and out.</h2> <p class="btn"><a href="portfolio.html">View my portfolio</a></p> </div>لو نظرت إلى التصميم سوف تجد أن القائمة تأتي قبل الشعار ولكن مع ذلك فإننا سوف نقوم بإضافة الشعار قبل القائمة حتى نبقي كل شيء مرتّبًا ومنظمًا. وضعنا الشعار داخل وسم <p> واستعملنا العنصر <ul> ليحتوي على عناصر القائمة وأضفنا أيضًا وسمي <h1> و <h2> وبداخلهما عنوان ومقدمة بسيطة. <div id="content"> <h3>About Chris Spooner</h3> <p>I earn a living by creating custom brands and logo designs from scratch, as well as designing and building high quality websites and blogs, but I also enjoy producing the odd t-shirt graphic, illustration or character design. I pride myself in having the nerdy skills to build top notch creations online, as well as being knowledgeable in the print side of design.</p> <h3>My latest work</h3> <p>I’m forever creating design work for both myself as personal projects and as a hired gun for clients from around the world. Here’s a few of my most recent works.</p> <div class="portfolio-item"> <a href="#"><img src="images/portfolio-1.jpg" alt="View more info" /></a> <p class="btn"><a href="#">See more</a></p> </div> <div class="portfolio-item"> <a href="#"><img src="images/portfolio-2.jpg" alt="View more info" /></a> <p class="btn"><a href="#">See more</a></p> </div> <div class="portfolio-item"> <a href="#"><img src="images/portfolio-3.jpg" alt="View more info" /></a> <p class="btn"><a href="#">See more</a></p> </div> <div class="portfolio-item"> <a href="#"><img src="images/portfolio-4.jpg" alt="View more info" /></a> <p class="btn"><a href="#">See more</a></p> </div> </div> <div id="footer"> <p id="copyright">© Chris Spooner / SpoonGraphics (Please don’t steal my work)</p> <p id="back-top"><a href="#">Going up?</a></p> </div>قمنا بعد ذلك بإضافة وسم <h3> وبداخله نص يعتبر أقل أهمية عن النص السابق (فكما تعلم أنّ وسم <h1> أهم من <h2> ومن <h3> وهكذا). بعد ذلك قمنا بإضافة العنصر <div id="content"> وبداخله يوجد المحتوى الرئيسي للصفحة والعديد من وسوم <div> كل واحد منها يحتوي على صورة مصغرة عن أحد الأعمال التي قمنا بها (وكأنها معرض أعمال مُصغّر). وأخيرًا يوجد هناك التذييل (footer) ممثلًا بالعنصر <div id="footer"> وبداخله حقوق الملكية وزر العودة للأعلى. الآن وبعد أن انتهينا من ملف الـHTML دعونا ننتقل إلى تنسيق الصفحة باستعمال CSS. تنسيقات CSSbody, div, h1, h2, h3, h4, h5, h6, p, ul, ol, li, dl, dt, dd, img, form, fieldset, input, textarea, blockquote { margin: 0; padding: 0; border: 0; } body { background: #f2f0eb url(images/bg.png); font: 16px Helvetica, Arial, Sans-Serif; color: #636363; line-height: 24px; } #container { width: 960px; margin: 0 auto; } #logo { margin: 10px auto 0 auto; position: relative; width: 183px; } بدأنا ملف الـCSS بتنسيقات بسيطة لإزالة التنيسقات الافتراضية للمتصفحات، وبعد ذلك قمنا بإضافة بعض التنسيقات لجسم المدونة (وسم <body>). لاحظ أننا قمنا في البداية بإضافة خلفية مزخرفة (صورة) إلى جسم المدونة وبعدها أضفنا بعض التنسيقات التي تخص الخطوط في الصفحة. قمنا بعدها بإعطاء العنصر الحاوي (container div) عرضًا بقيمة 960px واستعملنا أيضًا الخاصية margin: 0 auto لتوسيط العنصر في منتصف الصفحة، كما أننا أضفنا نفس الخاصية السابقة إلى الشعار حتى يتوسط في الصفحة. ul#nav { width: 940px; list-style: none; overflow: hidden; margin: -134px auto 25px auto; } ul#nav li { width: 126px; height: 33px; float: left; padding: 13px 0 0 0; background: url(images/nav-bg.png); font-weight: bold; text-align: center; text-transform: uppercase; } ul#nav li:nth-child(1) { margin: 0 60px 0 0; } ul#nav li:nth-child(2) { margin: 0 316px 0 0; } ul#nav li:nth-child(3) { margin: 0 60px 0 0; } ul#nav li:nth-child(4) { margin: 0; } ul#nav li a { color: #616369; text-decoration: none; } ul#nav li a:hover { color: #a12121; } سوف نحتاج لإضافة مجموعة من الخصائص للقائمة الرئيسية حتى تتماشى وتتوافق مع التصميم الذي نعمل عليه، فقمنا أولًا بتحريك العنصر <ul> إلى الأعلى وذلك باستخدام قيمة margin سالبة وبعدها قمنا بإعطاء كل عنصر من عناصر القائمة (عناصر <li>) مجموعة خصائص، أبعاد، خلفيات وتنسيقات خطوط حتى تتوافق مع التصميم الذي نريده. وحتى نجعل الصفحة تبدو كالتصميم تمامًا فإننا استخدمنا المحدد ()nth-child: حتى نُعطي قيم margin مختلفة لكل عنصر. #header { height: 244px; padding: 52px 0 0 57px; background: url(images/home-header.jpg); } #header h1 { font: 38px Georgia, Serif; color: #f2f0eb; letter-spacing: 2px; margin: 0 0 20px 0; text-shadow: 0px 3px 3px #494949; } #header h2 { width: 510px; font: 30px Georgia, Serif; color: #f2f0eb; letter-spacing: 2px; margin: 0 0 20px 0; text-shadow: 0px 3px 3px #494949; } #header p.btn a { display: block; width: 225px; height: 50px; overflow: hidden; background: url(images/home-header-btn.jpg); text-indent: -9999px; } لاحظ أننا أعطينا الترويسة (header) ارتفاعًا بقيمة 244px وذلك لأن ارتفاع صور الخلفية الذي أعطيناها لها هو 244px. بعد ذلك استخدمنا padding مناسب حتى نُبعد النصوص عن الحواف ونجعل كل شيء مناسبًا ومتوافقًا مع التصميم، وقمنا أيضًا بإعطاء الوسمين <h1> و <h2> الموجودين في الترويسة بعض تنسيقات الخطوط حتى تتوافق مع التصميم (نوع الخط Georgia واستخدمنا أيضًا الخاصية letter-spacing لزيادة المسافة بين كل أحرف الكلمات). يمكننا كذلك محاكاة تأثير الظل عن طريق استخدام الخاصية text-shadow، بينما أضفنا عرضًا بقيمة 510px للوسم <h2> حتى نمنع النص من الظهور فوق المنطقة المخصصة له. وأخيرًا قمنا باستخدام الخاصية ()background: url وبعض الخصائص الأخرى على العنصر الذي يحمل الفئة btn. وذلك لتحويله إلى زر كما هو موجود في التصميم. #content { background: url(images/content-bg.png) repeat-y; padding: 57px 69px 50px 69px; overflow: hidden; } #content h2 { font: 30px Georgia, Serif; letter-spacing: 2px; margin: 0 0 20px 0; } #content h3 { font: 26px Georgia, Serif; letter-spacing: 2px; margin: 0 0 20px 0; } #content p { margin: 0 0 30px 0; } #content a { color: #a12121; text-decoration: none; } #content a:hover { color: #671111; } #content .portfolio-item { width: 182px; padding: 4px; background: #eee; text-align: center; float: left; margin: 0 7px 14px 7px; } #content .portfolio-item p.btn { margin: 0; } #content .portfolio-item p.btn a { display: block; width: 183px; height: 29px; padding: 7px 0 0 0; background: url(images/see-more-bg.png); font-weight: bold; text-align: center; text-transform: uppercase; text-decoration: none; } الآن سنقوم بتنسيق المحتوى الرئيسي للمدونة. لاحظ أننا أعطينا العنصر content# صورة كخلفية وأضفنا padding بقيم معينة حتى نُبعد المحتوى عن الأطراف. بعد ذلك استخدمنا overflow: hidden حتى نتأكد من أنّ جميع العناصر الموجودة داخل هذا العنصر والتي تحمل الخاصية float لن تقوم بتشويه التصميم وتخطيط الصفحة (استخدام الخاصية overflow: hidden في مثل هذه الحالة يسمى clearing floats). قمنا كذلك باستخدام بعض الخصائص البسيطة للنصوص الموجودة داخل هذا العنصر (كنوع الخط وحجمه وبعض الأمور الأخرى). قمنا بعد ذلك بتنسيق الصور المصغرة وذلك بإعطائها خلفية بلون رمادي وإعطائها الخاصية float: left حتى تظهر جميع الصور إلى جانب بعضها أفقيًا، وأخيرًا قمنا بتنسيق عناصر <a> لنجعلها تبدو وكأنها أزرار وذلك بإعطائها خلفية باستعمال الخاصية ()background: url. #footer { background: url(images/footer-bg.png) no-repeat; padding: 40px 0 0 0; overflow: hidden; margin: 0 0 30px 0; } #footer p#copyright { font-size: 12px; float: left; margin: 0 0 0 30px; color: #b8b6b2; } #footer p#back-top { font-size: 12px; float: right; margin: 0 30px 0 0; } #footer a { color: #a12121; text-decoration: none; } #footer a:hover { color: #671111; } بقي علينا الآن تنسيق التذييل الخاص بالصفحة. الجزء الأسفل من المحتوى تم إضافته كخلفية للتذييل، وبعدها أضفنا padding بقيم مناسبة حتى ندفع بمحتوى التذييل إلى أسفل صورة الخلفية. لاحظ أننا استخدمنا no-repeat وذلك حتى نتأكد بأنّ الصورة تظهر مرة واحدة فقط ولا تتكرر. قمنا بإضافة خصائص نصيّة لكل من حقوق الملكية وكذلك زر العودة إلى الأعلى وقمنا أيضًا باستخدام الخاصية float لإزاحة العنصرين إلى يمين ويسار الصفحة. إضافة بعض الجافاسكربت لدعم متصفح IE8 وأقلإنّ متصفح IE8 والنسخ الأقدم منه لا تدعم المحدد nth-child: لذلك إذا أردت أن تدعم هذه المتصفحات فبإمكانك أن تستخدم مكتبة jQuery لتساعدنا في ذلك: $(document).ready(function() { $("ul#nav li:nth-child(1)").css("margin-right", "60px"); $("ul#nav li:nth-child(2)").css("margin-right", "316px"); $("ul#nav li:nth-child(3)").css("margin-right", "60px"); $("ul#nav li:nth-child(4)").css("margin-right", "0px"); });حتى وإن كانت تلك المتصفحات لا تدعم المحدد nth-child إلا أن استخدام هذا المحدد مع jQuery ممكن وسوف تقوم تلك المتصفحات بتطبيق التنسيقات بدون أي مشاكل. إنهاء الصفحات الداخليةبعد أن قدمنا بإنهاء الصفحة الرئيسية فإننا سوف نقوم ببناء الصفحات الداخلية للموقع. سوف تكون بنية هذه الصفحات متشابهة نوعًا ما مع القليل من الاختلافات كما أن فيها بعض العناصر المشتركة لذلك سيكون بناؤها أمرًا يسيرًا. <div id="header" class="page"> <h1>About Chris Spooner</h1> </div>لنقوم بتنسيق ترويسة أخرى يمكننا بكل بساطة أن نضيف فئة (class) للترويسة الخاصة بالصفحات الداخلية وبعدها نقوم بإعطاء هذه الترويسة حجمًا أصغر وصورة خلفية معينة. لقد قمنا مسبقًا بإنشاء الشيفرة البرمجية الخاصة بعناصر معرض الأعمال، لذلك يمكننا تكرار هذه العناصر لكل مشروع على حدة، وكل ما نحتاج لتغييره هو الصورة المصغرة الخاصة بكل مشروع. خاتمةوهكذا نكون قد قمنا بتكويد كامل التصميم. أتمنى أن تكونوا قد استفدتم من الدرس. ترجمة -وبتصرّف- للمقال How to Code a Stylish Portfolio Design in HTML/CSS لصاحبه Iggy.1 نقطة