لوحة المتصدرين
المحتوى الأكثر حصولًا على سمعة جيدة
المحتوى الأعلى تقييمًا في 11/03/21 في كل الموقع
-
الإصدار 1.0.0
116548 تنزيل
سطع نجم لغة البرمجة بايثون في الآونة الأخيرة حتى بدأت تزاحم أقوى لغات البرمجة في الصدارة وذاك لمزايا هذه اللغة التي لا تنحصر أولها سهولة كتابة وقراءة شيفراتها حتى أصبحت الخيار الأول بين يدي المؤسسات الأكاديمية والتدريبية لتدريسها للطلاب الجدد الراغبين في الدخول إلى مجال علوم الحاسوب والبرمجة. أضف إلى ذلك أن بايثون لغةً متعدَّدة الأغراض والاستخدامات، لذا فهي دومًا الخيار الأول في شتى مجالات علوم الحاسوب الصاعدة مثل الذكاء الصنعي وتعلم الآلة وعلوم البيانات وغيرها، كما أنَّها مطلوبة بشدة في سوق العمل وتعتمدها كبرى الشركات التقنية. دورة تطوير التطبيقات باستخدام لغة Python احترف تطوير التطبيقات مع أكاديمية حسوب والتحق بسوق العمل فور انتهائك من الدورة اشترك الآن بني هذا العمل على كتاب «How to code in Python» لصاحبته ليزا تاغليفيري (Lisa Tagliaferri) وترجمه إلى العربية محمد بغات وعبد اللطيف ايمش، وحرره جميل بيلوني، ويأتي شارحًا المفاهيم البرمجية الأساسية بلغة بايثون، ونأمل في أكاديمية حسوب أن يكون إضافةً نافعةً للمكتبة العربيَّة وأن يفيد القارئ العربي في أن يكون منطلقًا للدخول إلى عالم البرمجة من أوسع أبوابه. رُبط هذا الكتاب مع توثيق لغة بايثون في موسوعة حسوب لتسهيل عملية الاطلاع على أي جزء من اللغة مباشرة وقراءة التفاصيل باللغة العربية. هذا الكتاب مرخص بموجب رخصة المشاع الإبداعي Creative Commons «نسب المُصنَّف - غير تجاري - الترخيص بالمثل 4.0». يمكنك قراءة فصول الكتاب على شكل مقالات من هذه الصفحة، «المرجع الشامل إلى تعلم لغة بايثون»، أو مباشرةً من الآتي: المقال الأول: دليل تعلم بايثون اعتبارات عملية للاختيار ما بين بايثون 2 و بايثون 3 المقال الثاني: تثبيت بايثون 3 وإعداد بيئتها البرمجية المقال الثالث: كيف تكتب أول برنامج لك المقال الرابع: كيفية استخدام سطر أوامر بايثون التفاعلي المقال الخامس: كيفية كتابة التعليقات المقال السادس: فهم أنواع البيانات المقال السابع: مدخل إلى التعامل مع السلاسل النصية المقال الثامن: كيفية تنسيق النصوص المقال التاسع: مقدمة إلى دوال التعامل مع السلاسل النصية المقال العاشر: آلية فهرسة السلاسل النصية وطريقة تقسيمها المقال الحادي عشر: كيفية التحويل بين أنواع البيانات المقال الثاني عشر: كيفية استخدام المتغيرات المقال الثالث عشر: كيفية استخدام آلية تنسيق السلاسل النصية المقال الرابع عشر: كيفية إجراء العمليات الحسابية المقال الخامس عشر: الدوال الرياضية المضمنة المقال السادس عشر: فهم العمليات المنطقية المقال السابع عشر: مدخل إلى القوائم المقال الثامن عشر: كيفية استخدام توابع القوائم المقال التاسع عشر: فهم كيفية استعمال List Comprehensions المقال العشرون: فهم نوع البيانات Tuples المقال الحادي والعشرين: فهم القواميس المقال الثاني والعشرين: كيفية استيراد الوحدات المقال الثالث والعشرين: كيفية كتابة الوحدات المقال الرابع والعشرين: كيفية كتابة التعليمات الشرطية المقال الخامس والعشرين: كيفية إنشاء حلقات تكرار while المقال السادس والعشرين: كيفية إنشاء حلقات تكرار for المقال السابع والعشرين: كيفية استخدام تعابير break وcontinue وpass عند التعامل مع حلقات التكرار المقال الثامن والعشرين: كيفية تعريف الدوال المقال التاسع والعشرين: كيفية استخدام *args و**kwargs المقال الثلاثين: كيفية إنشاء الأصناف وتعريف الكائنات المقال الحادي والثلاثين: فهم متغيرات الأصناف والنسخ المقال الثاني والثلاثين: وراثة الأصناف المقال الثالث والثلاثين: كيفية تطبيق التعددية الشكلية (Polymorphism) على الأصناف المقال الرابع والثلاثين: كيف تستخدم منقح بايثون المقال الخامس والثلاثين: كيفية تنقيح شيفرات بايثون من سطر الأوامر التفاعلي المقال السادس والثلاثين: كيف تستخدم التسجيل Logging المقال السابع والثلاثين: كيفية ترحيل شيفرة بايثون 2 إلى بايثون 31 نقطة -
فضاء الاسم Namespace هو المساحة أو المنطقة داخل البرنامج التي يكون الاسم صالحًا فيها، سواءً كان ذلك الاسم متغيرًا، أو دالةً، أو صنفًا، أو غير ذلك. يُستخدم هذا المبدأ في الحياة العملية كل يوم، فلو كنت تعمل في شركة كبيرة مثلًا ولك زميل اسمه عماد، ويوجد عماد آخر في قسم المحاسبة، فإنك تشير إلى عماد زميلك في القسم باسمه المجرد "عماد"؛ أما عماد الآخر فستقول "عماد الذي في قسم المحاسبة"، أي أنك تستخدم أسماء أقسام الشركة معرِّفات للعاملين فيها، وهذا هو مبدأ فضاء الاسم في البرمجة، فهو يخبر المبرمج والمترجم بالاسم المقصود فيما لو وجد أكثر من اسم. لقد ظهرت فضاءات الأسماء لأن لغات البرمجة القديمة -مثل BASIC- لم تكن فيها إلا متغيرات عامة Global Variables، أي متغيرات يمكن رؤيتها في البرنامج كله وداخل الدوال أيضًا، لكن هذا جعل متابعة أداء البرامج الكبيرة وصيانتها صعبًا، لأن التعديل على متغير في جزء ما من البرنامج سيتسبب في تغير وظيفة جزء آخر دون أن يدرك المبرمج ذلك، وهو ما يسمى بالآثار الجانبية، وقد قدمت اللغات التالية -بما فيها النسخ الأحدث من BASIC- مبدأ فضاء الأسماء إلى البرمجة، بل إن لغةً مثل C++ تسمح للمبرمج بإنشاء فضاء اسم خاص به في أي مكان داخل البرنامج، وهذا مفيد لمنشئي المكتبات الذين يرغبون في الاحتفاظ بأسماء دوالهم فريدةً عند اختلاطها مع مكتبات أخرى. يشار أحيانًا إلى فضاء الاسم بالنطاق Scope، ونطاق الاسم هو امتداد البرنامج الذي يمكن استخدام الاسم فيه، كأن يكون داخل دالة أو وحدة module، وفضاء الاسم والنطاق هما وجهان لعملة واحدة باستثناء فروق طفيفة بين المصطلحين، ولن يناقش فيها إلا عالم حاسوب يحب الجدل؛ أما بالنسبة لمستوى الشرح الذي نريده فهما متطابقان. سيشرح هذا المقال: مفهوم فضاء الاسم أو النطاق وأهميته. كيفية عمل فضاء الاسم في بايثون. مفهوم فضاء الاسم في لغتي جافاسكربت وVBScript. فضاء الاسم في بايثون تنشئ كل وحدة في بايثون فضاء الاسم الخاص بها، وللوصول إلى تلك الأسماء لا بد أن نسبقها باسم الوحدة، أو أن نستورد الأسماء التي نريد استخدامها إلى فضاء الاسم الخاص بالوحدة، وقد كنا نفعل هذا مع وحدتي sys وtime في المقالات السابقة، كما أن تعريف الصنف Class ينشئ فضاء اسم خاص به. وبناءً عليه، فإذا أردنا الوصول إلى تابع أو خاصية في صنف ما، فسنحتاج إلى استخدام اسم متغير النسخة أو اسم الصنف أولًا، وسنتحدث عن هذا بالتفصيل في مقال لاحق. تتيح بايثون خمسة فضاءات أسماء -أو نطاقات- هي: النطاق المضمَّن: الأسماء المعرفة داخل بايثون نفسها، وهي متاحة دائمًا من أي مكان في البرنامج. نطاق الوحدة: وهي أسماء معرَّفة ومرئية داخل ملف أو وحدة، لكن هذا النطاق يشار إليه في بايثون باسم النطاق العام global scope، في حين أن المعنى الذي يتبادر للذهن عند سماع الاسم هو أن النطاق العام يمكن رؤيته في أي جزء من البرنامج. النطاق المحلي: وهي الأسماء المعرَّفة داخل دالة أو تابع صنف، بما في ذلك المعامِلات. نطاق الصنف: الأسماء المعرفة داخل الأصناف، وسننظر فيها في مقال لاحق. النطاق المتشعب nested Scope: هذا موضوع معقد قليلًا تستطيع تجاهله حاليًا. لننظر الآن في الشيفرة التالية التي تحتوي على أمثلة لأول ثلاثة نطاقات: def square(x): return x*x data = int(input('Type a number to be squared: ')) print( data, 'squared is: ', square(data) ) يسرد الجدول التالي كلًا من الاسم والنطاق الذي ينتمي إليه: table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } الاسم فضاء الاسم square الوحدة-عام x محلي للنطاق square data الوحدة-عام int مضمَّن input مضمَّن print مضمَّن لاحظ أننا لا نَعُد def وreturn من الأسماء، وذلك لأنهما كلمتان مفتاحيتان أو جزء من تعريف اللغة نفسها، وسنحصل على خطأ إذا استخدمنا كلمةً مفتاحيةً اسمًا لمتغير. لنطرح سؤالًا جديدًا الآن، ماذا يحدث عندما يكون للمتغيرات التي في فضاءات أسماء مختلفة نفس الاسم؟ وماذا يحدث عند الإشارة إلى اسم ليس موجودًا في فضاء الاسم الحالي؟ الوصول إلى الأسماء التي خارج النطاق الحالي تحدد بايثون الأسماء حتى لو لم تكن في فضاء الاسم الحالي بالنظر إلى: فضاء الاسم المحلي -الدالة الحالية- أو الدالة المغلِّفة أو الصنف المغلِّف إذا كانت دالةً متشعبةً أو تابعًا متشعبًا. نطاق الوحدة، أي الملف الحالي. النطاق المضمَّن. فإذا كان الاسم في وحدة أخرى، فسنستورد الوحدة باستخدام import كما رأينا في المقالات السابقة، وعند استيرادها سيكون اسم الوحدة مرئيًا في نطاق تلك الوحدة، وسنستطيع حينئذ أن نستخدم اسم الوحدة للوصول إلى أسماء المتغيرات فيها باستخدام نمط module.name المعتاد، ويتبين من هذا أن استيراد جميع الأسماء من وحدة إلى الملف الحالي ليس صحيحًا، إذ قد يتطابق اسم وحدة ما مع اسم أحد متغيراتنا، مما سيتسبب في سلوك غريب للبرنامج لأن أحد الاسمين سيغطي على الآخر. لنعرِّف وحدتين تستورد الثانية فيهما الأولى على سبيل المثال: ##### module first.py ######### spam = 42 def print42(): print( spam ) ############################### ##### module second.py ######## from first import * # استورد جميع الأسماء من الأولى spam = 101 # لتخفي النسخة الأولى spam أنشئ متغير print42() # ما الذي سيُطبع، هل 42 أم 101 ################################ إذا ظننت أن هذا المثال سيطبع 101 فستكون مخطئًا، لأنه سيطبع 42 بسبب تعريف المتغير في بايثون، فكما شرحنا -في مقال: البيانات وأنواعها-؛ الاسم هو عنوان يُستخدم للإشارة إلى كائن، وقد كان الاسم print42 في الوحدة الأولى يشير إلى كائن الدالة المعرَّف في الوحدة، (وسننظر في ذلك بالتفصيل حين نشرح تعبير لامدا في مقال لاحق)، ومع أننا استوردنا الاسم إلى وحدتنا، إلا أننا لم نستورد الدالة التي لا تزال تشير إلى نسخة الوحدة الخاصة بها من spam، وعلى ذلك فقد أنشأنا متغير spam جديد ليس لديه تأثير على الدالة المشار إليها بالاسم print42. يشرح المخطط التالي هذا لكلا النوعين من الاستيراد، ويمكنك ملاحظة كيف يكرر الاستيراد الثاني الاسم print42 من first.py إلى second.py: ينبغي أن يكون هذا اللبس قد شرح سهولة الوصول إلى الأسماء في الوحدات المستوردة باستخدام ترميز النقطة dot notation. رغم أننا سنكتب شيفرةً أكثر، لكن توجد وحدات قليلة -مثل Tkinter الذي سنتعرض لها لاحقًا-، تُستخدم لاستيراد جميع الأسماء، لكنها تُكتب بطريقة تقلل خطر تعارض الأسماء رغم وجود الخطورة على أي حال، كما قد تنشأ عنها زلات برمجية bugs يصعب العثور عليها، وعلى أي حال توجد طريقة أكثر أمانًا لاستيراد اسم وحيد من وحدة ما، كما يلي: from sys import exit وهنا نأتي بدالة exit فقط إلى فضاء الاسم المحلي، لكن لا نستطيع استخدام أسماء أخرى من sys، ولا حتى sys نفسها. تجنب تعارض الأسماء إذا كانت الدالة تشير إلى متغير اسمه X، مع وجود X آخر في الدالة أي في النطاق المحلي، فسيكون هذا الأخير هو الذي ستراه بايثون وتستخدمه، وهنا يقع على عاتق المبرمج تجنب تعارض الأسماء، بحيث إذا كان لدينا متغيران أحدهما محلي والآخر متغير وحدة لهما الاسم نفسه، فلا نطلبهما في نفس الدالة، إذ سيغطي المتغير المحلى على اسم الوحدة. ولن نواجه مشكلةً إذا أردنا قراءة متغير عام داخل دالة، إذ ستبحث بايثون عن الاسم محليًا، فإذا لم تجده فستبحث في النطاق العام، بل وفي النطاق المضمَّن كذلك إذا دعت الحاجة، وإنما ستظهر المشكلة عندما نريد إسناد قيمة إلى متغير عام، إذ سينشئ هذا متغيرًا محليًا جديدًا داخل الدالة، فكيف نسند القيمة إلى متغير عام دون إنشاء متغير محلي بنفس الاسم إذًا؟ يمكن تنفيذ هذا باستخدام الكلمة المفتاحية global: var = 42 def modGlobal(): global var # محلي var تمنع إنشاء var = var - 21 def modLocal(): var = 101 print( var ) # تطبع 42 modGlobal() print( var ) # تطبع 21 modLocal() print( var ) # تطبع 21 نرى هنا كيف غيرت الدالة modGlobal من المتغير العام، على عكس الدالة modLocal التي لم تغيره، لأنها تنشئ متغيرها الداخلي الخاص بها وتسند قيمةً إليه، ثم يُجمع هذا المتغير في نهاية الدالة ليُحذف، ويكون غير مرئي في مستوى الوحدة، لكن عمومًا ينبغي أن نقلل من استخدام تعليمات global، فمن الأفضل أن نمرر المتغير معامِلًا للدالة، ثم نعيد المتغير المعدَّل. يعيد المثال التالي كتابة دالة modGlobal دون استخدام تعليمة global: var = 42 def modGlobal(aVariable): return aVariable - 21 print( var ) var = modGlobal(var) print( var ) في هذه الحالة نسند القيمة التي أعادتها الدالة إلى المتغير الأصلي في نفس الوقت الذي نمررها فيه وسيطًا، وتكون النتيجة نفسها، لكن لا تعتمد الدالة الآن على أي شيفرة خارجها، مما يسهل إعادة استخدامها في برامج أخرى، كما يسهل رؤية كيف تتغير القيمة العامة global value، حيث نستطيع أن نرى حدوث الإسناد الصريح هنا، ويطبق المثال التالي كل ذلك عمليًا، إذ يوضح النقاط التي شرحناها إلى الآن، لهذا ادرسه جيدًا حتى تعرف استخدام الأسماء والقيم في كل خطوة فيه: # متغيرات نطاقها الوحدة W = 5 Y = 3 # المعامِلات تشبه متغيرات الدوال # نطاق محلي X وعليه يكون لـ def spam(X): # أخبر الدالة أن تنظر في مستوى الوحدة # خاصة بها w وألا تنشئ وحدة global W Z = X*2 # الذي له نطاق محلي Z أنشأنا متغير W = X+5 # كما شرحنا أعلاه w استخدم الوحدة if Z > W: # هو اسم نطاق مضمَّن pow print( pow(Z,W) ) return Z else: return Y # محلي Y لا يوجد # لذا نستخدم نسخة الوحدة print("W,Y = ", W, Y ) for n in [2,4,6]: print( "Spam(%d) returned: " % n, spam(n) ) print( "W,Y = ", W, Y ) فضاء الاسم في VBScript إذا صرحنا عن متغير خارج الدالة أو البرنامج الفرعي في VBScript، فسيكون عامًا globally أما إذا صرحنا عنه داخل الدالة أو البرنامج الفرعي، فسيكون محليًا في الوحدة وسيخفي أي متغير عام له نفس الاسم، كما سيكون المبرمج هنا هو المسؤول عن إدارة التعارض بين هذه الأسماء، وبما أن متغيرات VBScript تُنشأ باستخدام تعليمة Dim فلن يحدث غموض أو لبس حول المتغير المقصود على عكس بايثون، وبهذا نرى أن منظور VBScript لقواعد النطاقات أبسط وأوضح من بايثون، لكن هناك بعض الأمور الخاصة بصفحات الويب، حيث ستكون المتغيرات العامة مرئيةً في كامل الملف، وليس داخل حدود الوسم script الذي عُرَّفت فيه فقط، وتوضح الشيفرة التالية ذلك: <script type="text/vbscript"> Dim aVariable Dim another aVariable = "This is global in scope" another = "A Global can be visible from a function" </script> <script type="text/vbscript"> Sub aSubroutine Dim aVariable aVariable = "Defined within a subroutine" MsgBox aVariable MsgBox another ' uses global name End Sub </script> <script type="text/vbscript"> MsgBox aVariable aSubroutine MsgBox aVariable </script> توجد بعض مزايا النطاقات في VBScript، والتي نتيح بها إمكانية الوصول إلى المتغيرات بين الملفات المختلفة في صفحة ويب -من الفهرس مثلًا إلى المحتوى والعكس-، لكن لن نتحدث عن هذا المستوى من برمجة صفحات الويب هنا، لذا سنكتفي بالإشارة إلى وجود كلمات مفتاحية مثل Public وPrivate. فضاء الاسم في جافاسكربت تتبع جافاسكربت نفس القواعد تقريبًا، إذ تكون المتغيرات المصرح عنها داخل الدوال مرئيةً داخل تلك الدوال فقط؛ أما المتغيرات التي خارج الدوال فيمكن أن تراها الشيفرة التي خارج الدوال، إضافةً إلى إمكانية رؤيتها داخل الدوال أيضًا. وكما هو الحال في VBScript؛ ليس هناك تعارض أو غموض بشأن المتغير المقصود، لأن المتغيرات تُنشأ صراحةً باستخدام تعليمة var، والمثال التالي شبيه بمثال VBScript السابق لكنه مكتوب بلغة جافاسكربت: <script type="text/javascript"> var aVariable, another; // متغيرات عامة aVariable = "This is Global in scope<BR>"; another = "A global variable can be seen inside a function<BR>"; function aSubroutine(){ var aVariable; // متغير محلي aVariable = "Defined within a function<BR>"; document.write(aVariable); document.write(another); // يستخدم متغيرًا عامًا } document.write(aVariable); aSubroutine(); document.write(aVariable); </script> ولا أظننا بحاجة إلى تكرار شرح المثال مرةً أخرى هنا. خاتمة نرجو في نهاية هذا المثال أن تكون تعلمت ما يلي: النطاقات وفضاءات الأسماء وجهان لعملة واحدة، ويشيران إلى نفس الشيء. المفاهيم واحدة بين اللغات المختلفة، لكن الذي يختلف هو التطبيق الدقيق لها وفق قواعد كل لغة. تحتوي بايثون على خمسة نطاقات هي: النطاق المضمَّن built in، ونطاق الصنف class، والنطاق المتشعب nested، ونطاق الملف أو النطاق العام global، ونطاق الدالة أو النطاق المحلي function، وهذه النطاقات الثلاثة الأخيرة هي أهم نطاقات فيها من حيث كثرة الاستخدام في البرمجة. تحتوي كل من جافاسكربت وVBScript على نطاقين لكل واحدة منهما، هما نطاق الملف أو النطاق العام file، ونطاق الدالة أو النطاق المحلي function. ترجمة -بتصرف- للفصل الخامس عشر: Namespaces من كتاب Learn To Program لصاحبه Alan Gauld. اقرأ أيضًا المقال التالي: التعابير النمطية في البرمجة المقال السابق: كيفية التعامل مع الأخطاء البرمجية فضاء الأسماء (namespaces) في PHP1 نقطة
-
أريد تحويل مصفوفة نمباي التي تمثل الصورة إلى bytes فهل هناك طريقة ما للقيام بذلك؟1 نقطة
-
لدي تطبيق مصمم بلغة PHP , Javascript و بقاعدة بيانات MySQL عند تحويله الى تطبيق سطح مكتب لا يعمل شاشة تسجيل الدخول مع العلم بأنه يعمل على الويب بشكل سليم تماما index.php1 نقطة
-
اتبع واحدةً من التعليمات الموجودة في الأسفل لإنشاء قاعدة بيانات لووردبريس، وحساب مستخدم إن كنت تثبت ووردبريس على خادم الويب الخاص بك. إنشاء قاعدة بيانات باستخدام لوحة التحكم Plesk إذا كنت تريد تثبيت ووردبريس يدويًا وكانت الاستضافة خاصتك توفر لوحة التحكم Plesk، فعليك اتباع التعليمات التالية لإنشاء قاعدة البيانات: سجل دخول للوحة التحكم Plesk. اضغط على Databases في منطقة Custom Website من صفحة Websites & Domains. اضغط على Add New Database وغير اسم قاعدة البيانات إن أردت، ثم أنشئ مستخدمًا لقاعدة البيانات من خلال ملء بياناته والضغط على ok لتكون أنجزت المهمة. إنشاء قاعدة بيانات باستخدام لوحة التحكم cPanel تستطيع اتباع التعليمات البسيطة التالية لإنشاء اسم مستخدم وقاعدة بيانات لووردبريس إذا كانت الاستضافة خاصتك توفر لوحة التحكم cPanel. يمكنك إيجاد مجموعة أوسع من التعليمات حول كيفية استخدام cPanel لإنشاء قاعدة البيانات والمستخدم في المقال استخدام cPanel، كما يلي: سجل دخول للوحة التحكم cPanel. اضغط على أيقونة MySQL Database Wizard ضمن قسم Databases. أنشئ قاعدة بيانات في الخطوة الأولى بإدخال اسم قاعدة البيانات، ثم اضغط على Next Step. أنشئ في الخطوة الثانية مستخدمًا لقاعدة البيانات بإدخال اسم المستخدم وكلمة مروره، وتأكد من قوة كلمة المرور، ثم اضغط على Create User. اضف المستخدم لقاعدة البيانات في الخطوة الثالثة واضغط على خيار All Privileges، ثم اضغط على Next Step. املأ الحقول hostname وusername وdatabasename وpassword في الخطوة الرابعة بالاعتماد على القيم من الخطوات السابقة، وانتبه أن hostname يكون عادةً localhost. إنشاء قاعدة بيانات باستخدام phpMyAdmin تستطيع اتباع التعليمات التالية لإنشاء قاعدة بيانات واسم مستخدم لها إن كانت استضافتك توفر phpMyAdmin. إن كنت تعمل على حاسوبك فتستطيع تثبيت phpMyAdmin على معظم توزيعات لينكس. أنشئ أولًا قاعدة بيانات في حال عدم وجود واحدة مرتبطة بووردبريس ضمن القائمة الموجودة على يسار الشاشة. اختر اسم قاعدة بيانات ووردبريس الخاصة بك: يمكنك استخدام الاسم wordpress أو blog، لكن معظم الاستضافات (خصوصًا المشتركة) تفرض عليك بادئةً لاسم قاعدة البيانات وبعدها إشارة '_'. حتى إن عملت على حاسوبك ننصحك بالتحقق من متطلبات الاستضافة لكي لا تواجهك أي مشاكل عند نقل قاعدة البيانات من حاسوبك إليها. ادخل اسم قاعدة البيانات الذي تريد في حقل Create Database، ثم اختر المجموعة collation المناسبة للغة الموقع، وفي معظم الحالات من الأفضل اختيار أحد مجموعات "utf8_" وإذا كنت لا تستطيع إيجاد اللغة المناسبة لك، فاختر "utf8mb4generalci". (المرجع: 1). اضغط ثانيًا على أيقونة phpMyAdmin في الزاوية العلوية اليسرى للعودة للصفحة الرئيسية، ثم اضغط على Users. يجب عليك إنشاء مستخدم في حال عدم وجود مستخدم مرتبط بقاعدة البيانات السابقة. اضغط على Add user. اختر اسم المستخدم (wordpress اسم جيد) وادخله ضمن الحقل User name، وتأكد من أنك اخترت Use text field من القائمة المنسدلة. اختر كلمة مرور قوية (يجب أن تتضمن تشكيلةً من الأحرف اللاتينية بالحجم الكبير والصغير والأرقام والرموز)، ثم أدخلها ضمن حقل Paswword. وتأكد من أنك اخترت Use text field من القائمة المنسدلة، ثم أعد إدخال كلمة المرور في حقل Re-type. احفظ اسم المستخدم وكلمة المرور الذين اخترتهما في مكان آمن. ابقِ على جميع الخيارات أسفل Global privileges كما هي بحالتها الافتراضية. اضغط على Go. عد إلى صفحة Users واضغط على أيقونة Edit privileges للمستخدم الذي أنشأته. اختر قاعدة البيانات التي أنشأتها ضمن قسم Database-specific privileges من القائمة المنسدلة أسفل Add privileges to the following database، ثم اضغط على Go. سوف يُعاد تحديث معلومات الصفحة مع الصلاحيات التابعة لقاعدة البيانات. اضغط على Check All لاختيار جميع الصلاحيات، ثم اضغط على Go. انتبه إلى اسم المُضيف الظاهر في أعلى الصفحة بعد كلمة Server طبعًا بعد انتهاء الصفحة من التحميل بعد الخطوة السابقة. عادةً يكون localhost. إنشاء قاعدة بيانات باستخدام MySQL Client تستطيع إنشاء قواعد بيانات ومستخدمين MySQL بسرعة وسهولة من خلال تشغيل mysql من shell، يظهر في الأسفل سياق العملية، حيث ما هو بعد إشارة $ عبارةً عن الأوامر البرمجية. $ mysql -u adminusername -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 5340 to server version: 3.23.54 Type 'help;' or '\h' for help. Type '\c' to clear the buffer. mysql> CREATE DATABASE databasename; Query OK, 1 row affected (0.00 sec) mysql> GRANT ALL PRIVILEGES ON databasename.* TO "wordpressusername"@"hostname" -> IDENTIFIED BY "password"; Query OK, 0 rows affected (0.00 sec) mysql> FLUSH PRIVILEGES; Query OK, 0 rows affected (0.01 sec) mysql> EXIT Bye $ يُظهر المثال السابق ما يلي: أن مُستخدم الجذر root هو أيضًا adminusername. اختيار حساب مختلف عن الجذر ليكون مدير mysql أفضل وأكثر أمانًا. أنت تُقلص احتمالات تعرضك للاختراق في كل مرة تُخفض استخدامك فيها لحساب الجذر، حيث يعتمد الاسم الذي تستخدمه على الاسم الذي أسندته بصفة مدير لقاعدة البيانات باستخدام mysqladmin. إن wordpress أو blog هي قيم جيدة لتكون مكان databasename بصفة اسم لقاعدة البيانات. يمكنك استخدام wordpress بصفته اسمًا للمستخدم wordpressusername، لكن عليك أن تعلم أنه سيكون علني للجميع طالما أنك استخدمته هنا. تكون قيمة hostname هي عادةً localhost. إن كنت لا تعلم ما هي القيمة، فعليك التحقق من مدير النظام لديك إن لم تكن أنت هو المدير؛ أما إن كنت المدير، فعليك استخدام حساب غير حساب الجذر لإدارة قاعدة البيانات. يجب أن تكون قيمة كلمة المرور password صعبة التكهن وتتضمن تشكيلةً من الأحرف اللاتينية والأرقام والرموز. عليك استخدام أول حرف من كل كلمة واردة في جملة طويلة لتفادي استخدام كلمة شائعة في أحد القواميس. تجنب كتابة القيم السابقة في نفس النظام الذي يستخدمها لحماية بيانات أخرى وبنفس الوقت عليك تذكر قيم databasename وwordpressusername وhostname وpassword، لذلك استخدمهم مباشرةً في ملف wp-config.php حتى لا تضطر لحفظهم في مكان غير آمن. إنشاء قاعدة بيانات باستخدام DirectAdmin إن كنت مستخدمًا متكررًا لحساب استضافة بموقع واحد، فتستطيع تسجيل الدخول ثم الضغط على MySQL Management، وإن كنت لا تستطيع العثور على الخيار السابق، فغالبًا يجب على الاستضافة تعديل حزمتك لتفعيل MySQL، ثم تابع الخطوة 3. إذا كان حسابك Reseller، فسوف يحتاج المدير للضغط على User Level، لكن أولًا عليه الدخول بصفة Reseller إذا كان النطاق المطلوب هو النطاق الرئيسي لحساب Reseller، أو التسجيل بصفة مستخدم إذا كان النطاق ليس الرئيسي. إذا كان النطاق رئيسيًا، فعند الدخول اضغط على User Level؛ أما إذا كان غير رئيسي فعند الدخول، فاضغط على MySQL Management. إن كنت لا تستطيع العثور على الخيار السابق فعليك العودة لحساب Reseller أو المدير وتعديل MAnage user package، أو Manage Reseller package لتفعيل MySQL. اضغط ضمن MySQL Management على Create new database، حيث يطلب منك إضافة لاحقتين لقاعدة البيانات واسم مستخدم لها. ولتحقيق أعلى درجات الأمان، استخدم مجموعتين مختلفتين من الأحرف كل منها بطول 4-6 محارف، ثم استخدم زر توليد كلمة المرور لتوليد كلمة مرور قوية من 8 أحرف. اضغط على Create ليظهر لك في الصفحة التالية ملخص عن قاعدة البيانات واسم المستخدم وكلمة المرور واسم المضيف، وانسخ المعلومات السابقة مع إلصاقها في ملف wp-config.php. ترجمة -وبتصرف- للمقال Creating Database for WordPress من موقع ووردبريس. اقرأ أيضًا استخدام الروابط الدائمة في ووردبريس تطوير ووردبريس للمبتدئين: الودجت والقوائم تحليل نظام الملفات لإدارة البيانات وتخزينها واختلافه عن نظام قاعدة البيانات نمذجة البيانات وأنواعها في عملية تصميم قواعد البيانات1 نقطة
-
1 نقطة
-
يوجد في جهاز الحاسب الألي وحدة معالجة مركزية CPU وذاكرة mamory ويوجد ما يُسمى بالbus ال bus هي مجموعة من الموصلات أو أشباه الموصلات التي تقوم بحمل البيانات ويوجد ثﻻث أنواع control bus : والتي تقوم بإرسال الإشارات من المعالج إلى باقي وحدات الإدخال والإخراج data bus : والتي تقوم بإرسال البيانات من المعالج إلى باقي وحدات الإدخال والإخراج والعكس صحيح address bus : والتي تقوم بإرسال عناوين الذاكرة من المعالج إلى الذاكرة حتى تقوم بعد ذلك الذاكرة بإرسال تلك البيانات المُضمنة في تلك العناوين إلى المُعالج الaddress bus يتم إرسال العناوين من خﻻله على هيئة إشارات كهربية 0 أو 1 مما يعني أننا إن أردنا أن نرسل عنوان مُكون من 8 أجزاء فبالتالي سنحتاج إلى 8 بيتات أي نحتاج إلى 8 سلوك لتمر من خﻻلها الإشارة الكهربية وبالتالي إن كان لدينا 8 سلوك نستطيع القول أن أقصى عدد من البيتات نستطيع إرسالها يكون 2 مرفوعة لأس 8 وبالتالي إن كان لدينا 32 سلك فأقصى عنوان يمكننا الإشارة إليه هو 2 مرفوع لأس32 أي ما يساوي 4 جيجا , أي إن كان لدينا ذاكرة مساحتها أكبر من 4 جيجا لن نستطيع الإستفادة إلا من 4 جيجا منها فقط ولذلك تم إستخدام معمارية ال64 والتي ترمز إلى أنه يمكننا إرسال 64 بيت مما يوفر لنا إستخدام لموارد ذاكرة أكبر. إذا ماذا مقصود بالويندوز 64 بيت؟ المقصود بالويندوز 64 بيت أنه تم تصميمه ليتمكن من إستهﻻك ال64 بيت الخاصيين بعناوين الذاكرة وﻻ يمكن تشغيله إلا على معمارية تسمح بذلك المقصود بويندوز 32 بيت أو نظام تشغيل 32 بيت بوجه عام أنه تم تصميمه ليتمكن من إستهﻻك 32 بيت فقط أي 4 جيجا رام وﻻ يمكنه الوصول إلى باقي الذاكرة1 نقطة
-
1 نقطة
-
برجاء إرفاق رسالة الخطأ التي تظهر لك عند محاولة تشغيل الكود1 نقطة
-
للأسف لا يمكنني تحديد المشكلة لأن هناك ملفات ناقصة للمشروع , هل يمكنك وضع الكود الذي تتوقع به الخطأ ؟1 نقطة
-
استخدم الدالة createSelectiveSearchSegmentation لتنفيذ بعض عمليات البحث الانتقائي على الصور لكن ماسبب الخطأ التالي: import cv2 as cv from cv2.ximgproc.segmentation import createSelectiveSearchSegmentation serach = createSelectiveSearchSegmentation() -------------------------------------------------------------------------------------- Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: module 'cv2.cv2' has no attribute 'ximgproc' علماً أنني قمت بتجريب تثبيت حزمة opencv-contrib-python ولم ينجح الأمر.1 نقطة
-
السلام عليكم.. انا مشترك فى نفس الدورة واجهات المستخدم حضرتك قلت ان المدربون اسلوبهم سلس و دى اشعر بها و انا الان ادر الجكويري و اقارن نفسى الان و بين فترات المذاكرة من اليوتيوب اجد اننى فى طريقى للافضل. من ناحية انك بتقول ان فيه معلومات كتير مش معروضه ف ده معناه انك عارفها بالفعل و تقدر اما تطبق فى المشاريع تضيفها على مشروع المدرب..1 نقطة
-
الcontext api هي عبارة عن مكتبة لتسهيل إدارة الحالة(state management) حيث تمكنك من جعل الstate يتم مشاركتها في التطبيق ككل وليست خاصة بكل صفحة, ونقوم بإنشاء الcontext عبر الشفرة البرمجية التالية const MyContext = React.createContext({ tasks:defaultValue, setTasks:()=>{} }); وتستبدل ال defaultValue بالقيمة الإبتدائية للstate , في حالتك هنا من المنطقي أن تكون القيمة الإبتدائية عبارة عن مصفوفة فارغة حيث يكون المستخدم عن بداية التطبيق ليس لديه أي مهام بعد وعندما نريد الوصول إلى الstate الموجودة في الcontext نقوم بوضع الcomponent التي نريد إتاحة الcontext لها داخل وسم يحمل إسم الcontext متبوعاً بكلمة .profider بعد إستدعاء ال MycContext كما مُوضح في الشفرة التالية import MyContext from "./Context/MyContext"; import Header from "./Components/Header"; function App() { const [tasks, setTask] = useState(false); return ( <MyContext.Provider value = {tasks, setTask}> <div> <Header /> </div> </MyContext.Provider> ); } export default App; ومن أجل إستدعاء قيمة ال state داخل إحدى تلك الcomponents الموضوعة داخل الMyContext.Provider نقوم بإستخدام الخُطاف(hoom) المُسمى useContext const [tasks,setTasks]= useContext(MyContext) ونقوم بتغيير قيمة الtask عن طريق إستخدام setTask setTasks(yourValue) وذلك رابط لتوثيق ال context https://wiki.hsoub.com/React#.D8.A7.D8.B3.D8.AA.D8.AE.D8.AF.D8.A7.D9.85_.D8.A7.D9.84.D8.B3.D9.8A.D8.A7.D9.82_.28Context.291 نقطة
-
وجود فراغ بين أي محددين في CSS يعني أن المحدد الثاني داخل أبناء المحدد الأول table .class في مثالك تستهدف أبناء الوسم table الحاوين على الصنف class table .class { ... } <table> <tr class="class" /> <!-- مستهدف --> <tr> <td> <div class="class">...</div><!-- مستهدف --> </td> </tr> </table> عدم وجود فراغ يعني أن العنصر المستهدف بالمحدد الأول يجب أن يحوي نفسه على المحدد الثاني table.class في مثالك تستهدف أي وسم table يحوي نفسه على الصنف class table .class { ... } <table class="class"> <!-- مستهدف --> ... </table>1 نقطة
-
عند إستخدام table.class_name فأنت تقوم بإختيار أي جدول لديه نفس الclass وتطبق عليه التنسيق أما إن قمت بإستخدام table .class_name فأنت تقوم بتطبيق التنسيق على العناصر التي لديها نفس ال class وفي داخدل جدول <table class="testtbl"> <div class="testtbl"> <!-- elements--> </div> </table> <style> table.testtbl{ //style } </style> هذا التنسيق سيتم تطبيقه على الجدول نفسه ولكن لا يتم تطبيقه على الdiv بداخله بينما: <table class="testtbl"> <div class="testtbl"> <!-- elements--> </div> </table> <style> table .testtbl{ //style } </style> سيتم تطبيقه على الdiv وليس على الجدول نفسه1 نقطة
-
يجب أن تقوم بحفظ كل التغييرات التي قمت بها أولًا أما من خلال عمل commit لكل التغييرات أو وضع كل التغييرات في stash وحفظ التغييرات حتى لا تضيع، ثم يمكنك سحب كل التغييرات من مستودع GitHub من خلال الأمر: git pull وهكذا سيتم إضافة التغييرات إلى مشروعك، ثم قم برفع التغيرات التي حفظتها سابقًا من خلال الأمر: git push origin main1 نقطة
-
تُستخدَم الأصناف (classes) لتمثيل مجموعة كائنات (objects) تَتَشارَك نفس البِنْية (structure) والسلوك (behavior). يُحدِّد الصَنْف بِنْية تلك الكائنات عن طريق تَخْصِيصه للمُتْغيِّرات الموجودة ضِمْن أي نسخة (instance) من الصَنْف، كما يُحدِّد سلوكها بتَعْريفه لتوابع نُسخ (instance methods) تُعبر عن سلوك (behavior) تلك الكائنات. هذه الفكرة قوية للغاية، ولكنها مع ذلك مُمكِنة بغالبية اللغات البرمجية التقليدية، فما هي إذًا الفكرة الجديدة التي تُقدِّمها البرمجة كائنية التوجه (object-oriented programming)، وتُفرِّقها عن البرمجة التقليدية؟ في الواقع، تُدعِّم البرمجة كائنية التوجه كُلًا من الوراثة (inheritance) والتعددية الشكلية (polymorphism)، والتي تسمح للأصناف (classes) بتمثيل التشابهات بين مجموعة كائنات تَتَشارَك بعضًا من بِنْيتها وسُلوكها، وليس كلها. توسيع (extend) الأصناف الموجودة لابُدّ لأي مبرمج أن يفهم المقصود بكُلًا من المفاهيم التالية: الصَنْف الفرعي (subclass)، والوراثة (inheritance)، والتعددية الشكلية (polymorphism). قد تَستغرِق بعض الوقت حتى تستطيع تَوْظِيف مواضيع مثل الوراثة (inheritance) تَوْظِيفًا فعليًا بعيدًا عن مُجرَّد تَمْديد (extending) بعض الأصناف الموجودة، وهو ما سنُناقشه بالجزء الأول من هذا القسم. ببداية تَعامُل المبرمجين مع الكائنات، عادةً ما يَقْتصر اِستخدَامهم للأصناف الفرعية على موقف واحد فقط: "التَعْديل على صَنْف موجود بالفعل أو الإضافة إليه بحيث يَتلائَم مع ما يُريدونه". يُعدّ ذلك أكثر شيوعًا من تصميم مجموعة كاملة من الأصناف الأعلى والأصناف الفرعية (subclasses). تستطيع أن تُمدِّد (extend) صَنْف فرعي من صَنْف آخر باستخدام الصيغة التالية: public class <subclass-name> extends <existing-class-name> { . . // التعديلات والإضافات . } لنَفْترِض أنك تُريد كتابة برنامج للعبة الورق "بلاك جاك (Blackjack)" تَستخدِم فيه الأصناف Card و Hand و Deck من القسم ٥.٤. أولًا، تَختلِف اليد (hand) بلعبة بلاك جاك نوعًا ما عن اليد في العموم؛ حيث تُحسَب قيمة اليد بلعبة بلاك جاك وِفقًا لقواعد اللعبة، والتي يُمكِن تَلخَّيصها كالتالي: تُحسَب قيمة اليد بجَمْع قيم ورق اللعب باليد، بحيث تَكُون قيمة ورقة اللعب العددية (numeric card)، مثل "ثلاثة" أو "عشرة"، مُساوِية لنفس قيمتها العددية، أما ورقة اللعب المُصورة، مثل "الرجل" أو "الملكة" أو "الملك"، فقيمتها تُساوِي عشرة. وأخيرًا، قيمة ورقة "الآص" قد تُساوِي ١ أو ١١. إنها في العموم تُساوِي ١١ إلا لو أدى ذلك إلى قيمة كلية أكبر من ٢١، مما يَعنِي أن ورقة "الآص" الثانية والثالثة والرابعة قيمها بالضرورة ستُساوِي ١. أحد الطرائق لمعالجة ما سبق هو توسيع (extend) الصنف Hand الموجود بإضافة تابع (method) يَحسِب قيمة اليد وفقًا للعبة بلاك جاك. اُنظر تعريف (definition) الصنف: public class BlackjackHand extends Hand { /** * Computes and returns the value of this hand in the game * of Blackjack. */ public int getBlackjackValue() { int val; // قيمة اليد boolean ace; int cards; // عدد ورق اللعب باليد val = 0; ace = false; cards = getCardCount(); // تابع معرف بالصنف الأعلى for ( int i = 0; i < cards; i++ ) { // أضف قيمة ورقة اللعب الحالية Card card; // ورقة اللعب الحالية int cardVal; // قيمة بلاك جاك لورقة اللعب الحالية card = getCard(i); cardVal = card.getValue(); // قيمة ورقة اللعب if (cardVal > 10) { cardVal = 10; } if (cardVal == 1) { ace = true; // هناك على الأقل ورقة آص } val = val + cardVal; } if ( ace == true && val + 10 <= 21 ) val = val + 10; return val; } // نهاية getBlackjackValue } // نهاية الصنف BlackjackHand لمّا كان الصَنْف BlackjackHand هو صَنْف فرعي (subclass) من الصَنْف الأعلى Hand، فإن أيّ كائن من الصَنْف الفرعي BlackjackHand سيَتَضمَّن، إلى جانب تابع النسخة getBlackjackValue() المُعرَّف ضِمْن صَنْفه الفرعي، جميع مُتْغيِّرات النسخ (instance variables) وتوابع النسخ (instance methods) المُعرَّفة بالصَنْف الأعلى Hand. فمثلًا، إذا كان bjh مُتْغيِّرًا من النوع BlackjackHand، يَصِِح اِستخدَام أي من الاستدعاءات التالية: bjh.getCardCount() و bjh.removeCard(0) و bjh.getBlackjackValue(). على الرغم من أن التابعين الأول والثاني مُعرَّفين بالصَنْف Hand، إلا أنهما قد وُرِثَا إلى الصَنْف BlackjackHand. تُورَث المُتْغيِّرات والتوابع من الصَنْف الأعلى Hand إلى الصَنْف الفرعي BlackjackHand، ولهذا تستطيع اِستخدَامها بتعريف الصَنْف الفرعي BlackjackHand كما لو كانت مُعرَّفة فعليًا بذلك الصَنْف، باستثناء تلكم المُعرَّفة باِستخدَام المُبدِّل private ضِمْن الصَنْف الأعلى؛ حيث يَمنَع ذلك المُبدِّل الوصول إليها حتى من الأصناف الفرعية (subclasses). مثلًا، بتعريف التابع getBlackjackValue() -بالأعلى-، تَمَكَّنت التَعْليمَة cards = getCardCount(); من استدعاء تابع النسخة getCardCount() المُعرَّف بالصَنْف Hand. يُساعد تَمْديد (extend) الأصناف على الاعتماد على أعمالك السابقة، وفي الواقع لقد كُتبَت الكثير من الأصناف القياسية (standard classes) خصيصًا لتَكُون قاعدةً وأساسًا لإنشاء أصناف فرعية. تُستخدَم مُبدِّلات الوصول (access modifiers) مثل public و private للتَحكُّم بالوصول إلى أعضاء (members) الصَنْف. عندما نَأخُذ الأصناف الفرعية (subclasses) بالحُسبان، يُصبِح من المناسب الحديث عن مُبدِّل وصول آخر تُوفِّره الجافا، هو protected. عند اِستخدَام ذلك المُبدِّل أثناء التَّصْريح عن تابع (method) أو مُتْغيِّر عضو (member variable) بصَنْف، يُصبِح اِستخدَام ذلك العضو مُمكنًا ضِمْن الأصناف الفرعية لذلك الصَنْف وليس بأي مكان آخر. هنالك استثناء: عندما تَستخدِم المُبدِّل protected للتّصريح عن عضو (member)، فإنه يَكُون قابلًا للوصول من أي صَنْف بنفس الحزمة (package). ذَكَرَنا من قَبْل أنه في حالة عدم تخصيص أي مُبدِّل وصول (access modifier) لعضو معين، فإنه يُصبِح قابلًا للوصول من جميع الأصناف الواقعة ضِمْن نفس الحزمة وليس بأي مكان آخر. يُعدّ المُبدَّل protected أكثر تحررًا من ذلك بقليل؛ فبالإضافة إلى الأصناف الواقعة ضِمْن نفس الحزمة، فإنه يجعل العضو أيضًا قابلًا للوصول من الأصناف الفرعية (subclasses) التي لا تقع ضِمْن نفس الحزمة. عندما تُصرِّح عن تابع أو مُتْغيِّر عضو باستخدام المُبدِّل protected، يُصبِح ذلك العضو جزءًا من تّنْفيذ (implementation) الصنف لا واجهته (interface) العامة، كما يُسمَح للأصناف الفرعية (subclasses) باِستخدَام ذلك الجزء من التّنْفيذ (implementation) أو تَعْدِيله. على سبيل المثال، يَتَضمَّن الصَنْف PairOfDice مُتْغيِّري النسخة die1 و die2 لتمثيل الأعداد الظاهرة على حجرى نَّرد. قد نُصرِّح عنهما باِستخدَام المُبدِّل private لكي يُصبِح تَعْديل قيمتهما من خارج الصنف مستحيلًا، ثُمَّ نُعرِّف توابع جَلْب (getter methods) للسماح بقراءة قيمتهما من خارج الصنف، لكن لو تَبيَّن لك إمكانية الحاجة لاِستخدَام الصنف PairOfDice لإنشاء أصناف فرعية (subclasses)، فقد يَكُون من الأفضل حينها السماح لتلك الأصناف الفرعية (subclasses) بتَعْديل قيم تلك الأعداد، أي تعديل قيم تلك المُتْغيِّرات. فمثلًا، يَرسم الصنف الفرعي GraphicalDice حجري النَّرد، وقد يَضطرّ إلى تعديل قيم تلك الأعداد بتوقيت غَيْر ذلك الذي يُرمَي فيه حجري النَّرد. بدلًا من اِستخدَام المُبدِّل public في تلك الحالة، قد نَستخدِم المُبدِّل protected للتَّصْريح عن كُلًا من die1 و die2، مما سيَسمَح للأصناف الفرعية فقط -دون العالم الخارجي- بتَعْديل قيمتهما. كبديل، قد تُفضِّل تعريف توابع ضَبْط (setter methods) لتلك المُتْغيِّرات ضِمْن الصنف الأعلى، بحيث يُصرَّح عنها باِستخدَام المُبدِّل protected، وذلك لكي تَتَمكَّن من التَأكُّد من أن القيمة المطلوب إِسْنادها للمُتْغيِّر تقع ضِمْن نطاق يتراوح بين ١ و ٦. الوراثة (inheritance) وسلالة أصناف (class hierarchy) يُمكِن لصَنْف معين أن يَرِث جزء أو كل بِنيته (structure)، وسلوكه (behavior) من صَنْف آخر، وهو ما يُعرَف باسم الوراثة (inheritance). يُقال عن الصنف الوَارِث أنه صنفًا فرعيًا (subclass) من الصنف المَورُوث. إذا كان الصنف B هو صنف فرعي من الصنف A، فإننا نستطيع أيضًا أن نقول أن الصنف A هو صنف أعلى (superclass) من الصنف B. قد يُضيف الصنف الفرعي إلى ما وَرِثه من بنية وسلوك، كما قد يُعدِّل أو يَستبدِل ما وَرِثه من سلوك. تَستخدِم بعض اللغات البرمجية الآخرى، مثل لغة C++، مصطلحات أخرى كالصنف المُشتقّ (derived class) والصنف الأساسي (base class) بدلًا من الصنف الفرعي (subclass) والصنف الأعلى (superclass). عادةً ما تُوضَح العلاقة بين الصنف الفرعي (subclass) والصنف الأعلى (superclass) برسم توضيحي، يقع فيه الصنف الفرعي (subclass) أسفل صنفه الأعلى (superclass) ويَكُون مُتصلًا به، تمامًا كما هو مُوضَح بيسار الصورة التالية: تُستخدَم الشيفرة التالية لإنشاء صَنْف اسمه B بحيث يكون صنفًا فرعيًا من صنف آخر اسمه A: class B extends A { . . // إضافات أو تعديلات على الموروث من الصنف A . } يُمكِنك أن تُصرِّح عن عدة أصناف على أساس كَوْنها أصناف فرعية (subclasses) من نفس الصنف الأعلى (superclass)، وتُعدّ الأصناف الفرعية في تلك الحالة "أصناف أخوة (sibling classes)" تَتَشارَك بعضًا من بِنيتها (structure) وسلوكها (behavior)، تَحْديدًا تلك المُوروثة من الصَنْف الأعلى المشترك، فمثلًا، الأصناف B و C و D بيمين الصورة السابقة هي أصناف أخوة (sibling classes). يُمكِن للوراثة (inheritance) أن تتمدَّد أيضًا عَبْر أجيال من الأصناف، فمثلًا، الصنف E -بالصورة السابقة- هو صنف فرعي من الصنف D، والذي هو بدوره صنف فرعي من الصنف A، ويُعدّ الصنف E عندها صنفًا فرعيًا من الصنف A حتى وإن لَمْ يَكُن صنفًا فرعيًا مباشرًا. تُشكِّل مجموعة الأصناف تلك سُلالة أصناف (class hierarchy) صغيرة. مثال والآن، لنَفْترِض أننا نريد كتابة برنامج ينبغي له التَعامُل مع المركبات المتحركة كالسيارات، والشاحنات، والدراجات النارية (قد يَستخدِمه قسم المركبات المتحركة لأغراض تَعقُّب التسجيلات). يُمكِننا أن نُعرِّف صَنْفًا اسمه Vehicle لتمثيل جميع أنواع المركبات، ولأن السيارات والشاحنات والدراجات النارية هي أنواع من المركبات، يُمكِننا تمثيلها باستخدام أصناف فرعية (subclasses) من الصنف Vehicle، كما هو مُوضَح بسُلالة الأصناف (class hierarchy) التالية: قد يَتَضمَّن الصنف Vehicle مُتْغيِّرات نُسخ (instance variables) مثل registrationNumber و owner، بالإضافة إلى توابع نُسخ (instance methods) مثل transferOwnership(). ستَملُك جميع المركبات تلك المُتْغيِّرات والتوابع. نستطيع الآن تعريف الأصناف الفرعية (subclasses) الثلاثة Car و Truck و Motorcycle المُشتقّة من الصَنْف Vehicle بحيث يَحمِل كُلًا منها مُتْغيِّرات وتوابع تَخُصّ ذلك النوع المُحدَّد من المركبات، فمثلًا، قد يُعرَّف الصنف Car مُتْغيِّر نسخة numberOfDoors، بينما قد يُعرَّف الصنف Truck مُتْغيِّر نسخة numberOfAxles، وقد يُضيف الصنف Motorcycle مُتْغيِّرًا منطقيًا اسمه hasSidecar. يُمكِننا التَّصْريح عن تلك الأصناف كالتالي (ببرنامج حقيقي، ستُعرَّف تلك الأصناف عادةً بملفات مُنفصلة وعلى أساس كَوْنها عامة): class Vehicle { int registrationNumber; Person owner; // (بفرض تعريف الصنف Person) void transferOwnership(Person newOwner) { . . . } . . . } class Car extends Vehicle { int numberOfDoors; . . . } class Truck extends Vehicle { int numberOfAxles; . . . } class Motorcycle extends Vehicle { boolean hasSidecar; . . . } لنَفْترِض أن myCar هو مُتْغيِّر من النوع Car صُرِّح عنه وهُيئ باِستخدَام التَعْليمَة التالية: Car myCar = new Car(); الآن، نستطيع الإشارة إلى myCar.numberOfDoors؛ لأن numberOfDoors مُتْغيِّر نسخة بالصنف Car. بالإضافة إلى ذلك، لمّا كان الصنف Car مُشتقًّا من الصنف Vehicle، فإن أي كائن من الصنف Car يَملُك بنية وسلوك الكائنات من الصنف Vehicle، مما يعني إمكانية الإشارة إلى كُلًا من myCar.registrationNumber و myCar.owner و myCar.transferOwnership(). بالعالم الحقيقي، تُعدّ كُلًا من السيارات والشاحنات والدراجات النارية مركبات، وهو ما أَصبَح مُتحقِّقًا بالبرنامج أيضًا، فأيّ كائن من النوع Car أو Truck أو Motorcycle يُعدّ كذلك كائنًا من النوع Vehicle تلقائيًا. يَقُودنا ذلك إلى الحقيقة الهامة التالية: إذا أمكَّن لمُتْغيِّر حَمْل مَرجِع إلى كائن من الصَنْف A، فحتمًا بإِمكانه حَمْل مَرجِع إلى الكائنات المُنتمية لأيّ من أصناف A الفرعية. عمليًا، يَعنِي ذلك إمكانية إِسْناد كائن من الصنف Car إلى مُتْغيِّر من النوع Vehicle، كالتالي: Vehicle myVehicle = myCar; أو كالتالي: Vehicle myVehicle = new Car(); بَعْد إجراء أيّ من التَعْليمَتين -بالأعلى-، سيَحمِل المُتْغيِّر myVehicle مَرجِعًا (reference) إلى كائن من النوع Vehicle، والذي هو في حقيقته نُسخة (instance) من الصَنْف الفرعي Car. يُدرك الكائن حقيقة انتماءه للصنف Car وليس فقط للصنف Vehicle، كما تُخزَّن تلك المعلومة -أيّ صنف الكائن الفعليّ- كجزء من الكائن نفسه، وتستطيع حتى اختبار ما إذا كان كائن معين ينتمي إلى أحد الأصناف عن طريق العَامِل instanceof، كالتالي: if (myVehicle instanceof Car) ... تَفْحَص التَعْليمَة -بالأعلى- ما إذا كان الكائن المُشار إليه باستخدام المُتْغيِّر myVehicle هو بالأساس من النوع Car. في المقابل، لا تَصِح تَعْليمَة الإِسْناد (assignment) التالية: myCar = myVehicle; // خطأ لأن myVehicle قد يُشير إلى أنواع آخرى غَيْر النوع Car. يُشبه ذلك المشكلة التي تَعرَّضنا لها بالقسم الفرعي ٢.٥.٦: لن يَسمَح الحاسوب بإِسْناد قيمة من النوع int إلى مُتْغيِّر من النوع short؛ لأن ليس كل int هو short بالضرورة. بالمثل، فإنه لن يَسمَح بإِسْناد قيمة من النوع Vehicle إلى مُتْغيِّر من النوع Car؛ لأن ليس كل كائن من النوع Vehicle هو كائن من النوع Car بالضرورة. بإِمكانك تَجاوُز تلك المشكلة عن طريق إجراء عملية التَحْوِيل بين الأنواع (type-casting)، فمثلًا، إذا كنت تَعلَم -بطريقة ما- أن myVehicle يُشير فعليًا إلى كائن من النوع Car، تستطيع اِستخدَام (Car)myVehicle لإجبار الحاسوب على التَعامُل مع myVehicle كما لو كان من النوع Car. لذا تستطيع كتابة التالي: myCar = (Car)myVehicle; تستطيع حتى الإشارة إلى ((Car)myVehicle).numberOfDoors. لاحِظ أن الأقواس ضرورية لأسباب تَتَعلَّق بالأولوية (precedence)؛ فالعَامِل . لديه أولوية أكبر من عملية إجراء التَحْوِيل بين الأنواع (type-cast) أيّ أن التعبير (Car)myVehicle.numberOfDoors سيُقرأ كما لو كان مَكْتُوبًا على الصورة (Car)(myVehicle.numberOfDoors) وستُجرَى عندها محاولة للتَحْوِيل من النوع int إلى Vehicle وهو أمر مستحيل. سنَفْحَص الآن مثالًا لكيفية اِستخدَام ما سبق ضِمْن برنامج. لنَفْترِض أننا نُريد طباعة البيانات المُتعلّقة بالكائن المُشار إليه باِستخدَام المُتْغيِّر myVehicle، فمثلًا، نَطبَع numberOfDoors إذا كان الكائن من النوع Car. لا نستطيع ببساطة الإشارة إليه باستخدام myVehicle.numberOfDoors؛ لأن الصَنْف Vehicle لا يَتَضمَّن أي numberOfDoors، وإنما نستطيع كتابة التالي: System.out.println("Vehicle Data:"); System.out.println("Registration number: " + myVehicle.registrationNumber); if (myVehicle instanceof Car) { System.out.println("Type of vehicle: Car"); Car c; c = (Car)myVehicle; // تحويل للنوع للوصول إلى numberOfDoors System.out.println("Number of doors: " + c.numberOfDoors); } else if (myVehicle instanceof Truck) { System.out.println("Type of vehicle: Truck"); Truck t; t = (Truck)myVehicle; // تحويل للنوع للوصول إلى numberOfAxles System.out.println("Number of axles: " + t.numberOfAxles); } else if (myVehicle instanceof Motorcycle) { System.out.println("Type of vehicle: Motorcycle"); Motorcycle m; m = (Motorcycle)myVehicle; // تحويل للنوع للوصول إلى hasSidecar System.out.println("Has a sidecar: " + m.hasSidecar); } فيما هو مُتعلِّق بالأنواع الكائنية (object type)، عندما يُجرِي الحاسوب عملية التَحْويل بين الأنواع (type cast)، فإنه يَفْحص أولًا ما إذا كانت تلك العملية صالحة أم لا، فمثلًا، إذا كان myVehicle يُشير إلى كائن من النوع Truck، فلا تَصِح العملية (Car)myVehicle، وعليه، سيُبلَّغ عن اعتراض (exception) من النوع ClassCastException، ولهذا تَستخدِم الشيفرة -بالأعلى- العَامِل instanceof لاختبار نوع المُتْغيِّر قَبْل إجراء عملية التحويل بين الأنواع (type cast)؛ لتتجنَّب حُدوث الاعتراض ClassCastException. لاحِظ أن هذا الفَحْص يَحدُث وقت التنفيذ (run time) وليس وقت التصريف (compile time)؛ لأن النوع الفعليّ للكائن الذي يُشير إليه المُتْغيِّر myVehicle لا يَكُون معروفًا وقت تصريف البرنامج. التعددية الشكلية (polymorphism) لنَفْحص برنامجًا يَتعامَل مع أشكال تُرسَم على الشاشة، والتي قد تَكُون مستطيلة (rectangles)، أو بيضاوية (ovals)، أو مستطيلة بأركان دائرية (roundrect)، ومُلوَّنة جميعًا بألوان مختلفة. سنَستخدِم الأصناف الثلاثة Rectangle و Oval و RoundRect لتمثيل الأشكال المختلفة بحيث تَرِث تلك الأصناف من صَنْف أعلى (superclass) مُشترك Shape، وذلك لتمثيل السمات المشتركة بين جميع الأشكال. فمثلًا، قد يَتَضمَّن الصنف Shape مُتْغيِّرات نُسخ (instance variables) لتمثيل كُلًا من لون الشكل، وموضعه، وحجمه، كما قد يَتَضمَّن توابع نُسخ (instance methods) لتَعْديل قيم تلك السمات. مثلًا، لتَعْديل لون أحد الأشكال، فإننا سنُعدِّل قيمة مُتْغيِّر نُسخة ثم نُعيد رسم الشكل بلونه الجديد: class Shape { Color color; // لابد من استيرادها من حزمة javafx.scene.paint void setColor(Color newColor) { // تابع لتغيير لون الشكل color = newColor; // غير قيمة متغير النسخة redraw(); // أعد رسم الشكل ليظهر باللون الجديد } void redraw() { // تابع لرسم الشكل ? ? ? // ما الذي ينبغي كتابته هنا؟ } . . . // المزيد من متغيرات النسخ والتوابع } // نهاية الصنف Shape يُرسَم كل نوع من تلك الأشكال بطريقة مختلفة، ولهذا ستُواجهنا مشكلة بخصوص التابع redraw(). نستطيع عمومًا استدعاء التابع setColor() لأي نوع من الأشكال، وبالتالي سيَحتَاج الحاسوب إلى تَّنْفيذ الاستدعاء redraw()، فكيف له إذًا أن يَعرِف الشكل الذي ينبغي عليه رَسْمه؟ نظريًا، يَطلُب الحاسوب من الشكل نفسه أن يقوم بعملية الرسم، فبالنهاية، يُفْترَض أن يَكُون كل كائن من النوع Shape على علم بما عليه القيام به لكي يَرسِم نفسه. عمليًا، يَعنِي ذلك أن كل صنف من الأصناف الثلاثة ينبغي له أن يُعرِّف نسخته الخاصة من التابع redraw()، كالتالي: class Rectangle extends Shape { void redraw() { . . . // تعليمات رسم مستطيل } . . . // المزيد من توابع ومتغيرات النسخ } class Oval extends Shape { void redraw() { . . . // تعليمات رسم شكل بيضاوي } . . . // المزيد من توابع ومتغيرات النسخ } class RoundRect extends Shape { void redraw() { . . . // تعليمات رسم مستطيل دائري الأركان } . . . // المزيد من توابع ومتغيرات النسخ } لنَفْترِض أن someShape هو مُتْغيِّر من النوع Shape، أيّ أنه يستطيع الإشارة إلى أيّ كائن ينتمي لأيّ من الأنواع Rectangle و Oval و RoundRect. أثناء تَّنْفيذ البرنامج، قد تَتغيَّر قيمة المُتْغيِّر someShape، مما يَعنِي أنه قد يُشير إلى كائنات من أنواع مختلفة بأوقات مختلفة! لذا، عندما تُنفَّذ التَعْليمَة التالية: someShape.redraw(); فإن نسخة التابع redraw المُستدعَاة فعليًا تَكُون تلك المُتناسبة مع النوع الفعليّ للكائن الذي يُشير إليه المُتْغيِّر someShape. بالنظر إلى نص الشيفرة فقط، قد لا تَتَمكَّن حتى من مَعرِفة الشكل الذي ستَرسِمه تلك التَعْليمَة حيث يعتمد ذلك بالأساس على القيمة التي تَصادَف أن احتواها someShape أثناء تَّنْفيذ البرنامج. علاوة على ذلك، لنَفْترِض أن تلك التَعْليمَة مُضمَّنة بحَلْقة تَكْرار (loop) وتُنفَّذ عدة مرات. إذا كانت قيمة someShape تتغيَّر بينما تُنفَّذ حَلْقة التَكْرار، فقد تَستدعِي نفس تلك التَعْليمَة someShape.redraw(); -أثناء تَّنْفيذها عدة مرات- توابعًا (methods) مختلفة، وتَرسِم أنواعًا مختلفة من الأشكال. يُقال عندها أن التابع redraw() هو مُتعدِّد الأشكال (polymorphic). في العموم، يُعدّ تابع معين مُتعدد الأشكال (polymorphic) إذا كان ما يُنفِّذه ذلك التابع مُعتمدًا على النوع الفعليّ (actual type) للكائن المُطبقّ عليه التابع أثناء وقت التَّنْفيذ (run time)، وتُعدّ التعددية الشكلية (polymorphism) واحدة من أهم السمات المميزة للبرمجة كائنية التوجه (object-oriented programming). تَتضِح الصورة أكثر إذا كان لديك مصفوفة أشكال (array of shapes). لنَفْترِض أن shapelist هو مُتْغيِّر من النوع Shape[] يُشير إلى مصفوفة قد أُنشأت وهُيأت عناصرها بمجموعة كائنات، والتي قد تَكُون من النوع Rectangle أو Oval أو RoundRect. نستطيع رسم جميع الأشكال بالمصفوفة بكتابة التالي: for (int i = 0; i < shapelist.length; i++ ) { Shape shape = shapelist[i]; shape.redraw(); } بينما تُنفَّذ الحلقة (loop) -بالأعلى-، فإن التَعْليمَة shape.redraw() قد تَرسِم مستطيلًا أو شكلًا بيضاويًا أو مستطيلًا دائري الأركان اعتمادًا على نوع الكائن الذي يُشير إليه عنصر المصفوفة برقم المَوضِع i. ربما تَتضِح الفكرة أكثر إذا بدّلنا المصطلحات قليلًا كالتالي: بالبرمجة كائنية التوجه (object-oriented programming)، تُعدّ تَعْليمَة استدعاء أي تابع بمثابة إرسال رسالة إلى كائن، بحيث يَرُد ذلك الكائن على تلك الرسالة بتَّنْفيذ التابع المناسب. مثلًا، التَعْليمَة someShape.redraw(); هي بمثابة رسالة إلى الكائن المُشار إليه باِستخدَام someShape، ولمّا كان هذا الكائن يَعرِف بالضرورة النوع الذي ينتمي إليه، فإنه يَعرِف الكيفية التي ينبغي أن يَرُد بها على تلك الرسالة. وفقًا لذلك، يُنفِّذ الحاسوب الاستدعاء someShape.redraw(); بنفس الطريقة دائمًا، أيّ يُرسِل رسالة يَعتمِد الرد عليها على المُستقبِل. تَكُون الكائنات (objects) عندها بمثابة كيانات نَشِطة تُرسِل الرسائل وتَستقبِلها، كما تَكُون التعددية الشكلية (polymorphism) -وفقًا لهذا التصور- جزءًا طبيعًيا، بل وضروريًا، فتَعنِي فقط أنه من الممكن لكائنات مختلفة الرد على نفس الرسالة بطرائق مختلفة. تَسمَح لك التعددية الشكلية (polymorphism) بكتابة شيفرة تُنفِّذ أشياء قد لا تَتَصوَّرها حتى أثناء الكتابة، وهو في الواقع أحد أكثر الأشياء الرائعة المُتعلِّقة بها. لنَفْترِض مثلًا أنه قد تَقرَّر إضافة مستطيلات مشطوفة الأركان (beveled rectangles) إلى أنواع الأشكال التي يُمكِن للبرنامج التَعامُل معها. لتَّنْفيذ (implement) المستطيلات مشطوفة الأركان، يُمكِننا أن نُضيف صنفًا فرعيًا (subclass) جديدًا، وليَكُن BeveledRect، مُشتقًّا من الصنف Shape، ثم نُعرِّف به نسخته من التابع redraw(). تلقائيًا، ستَجِدْ أن الشيفرة التي كنا قد كتبناها سابقًا، أيّ someShape.redraw()، قد أَصبَح بإِمكانها فجأة رسم المستطيلات مشطوفة الأركان على الرغم من أن صَنْف تلك المستطيلات لم يَكُن موجودًا من الأساس أثناء كتابة تلك التَعْليمَة. تُرسَل الرسالة redraw إلى الكائن someShape أثناء تَّنْفيذ التَعْليمَة someShape.redraw();. لنَفْحَص مرة آخرى التابع المسئول عن تَعْديل لون الشكل والمُعرَّف بالصنف Shape: void setColor(Color newColor) { color = newColor; // غير قيمة متغير النسخة redraw(); // أعد رسم الشكل ليظهر باللون الجديد } بالأعلى، تُرسَل الرسالة redraw ولكن إلى أي كائن؟ حسنًا، يُعدّ التابع setColor هو الآخر بمثابة رسالة كانت قد أُرْسِلَت إلى كائن معين. لذا، فإن الرسالة redraw تُرسَل إلى نفس ذلك الكائن الذي كان قد اِستقبَل الرسالة setColor. إذا كان هذا الكائن مستطيلًا، فإنه يَحتوِي على تابع redraw() لرسم المستطيلات وذاك التابع هو ما سيُنفَّذ. أما إذا كان هذا الكائن شكلًا بيضاويًا، فسيُنفَّذ التابع redraw() المُعرَّف بالصنف Oval. يَعنِي ذلك أنه ليس من الضروري لتَعْليمَة redraw(); بالتابع setColor() أن تَستدعِي التابع redraw() المُعرَّف بالصنف Shape، وإنما قد يُنفَّذ أي تابع redraw() طالما كان مُعرَّفًا بصنف فرعي مشتق من Shape. هذه هي فقط حالة آخرى من التعددية الشكلية (polymorphism). الأصناف المجردة (abstract classes) وقتما يَضطرّ كائن من الصنف Rectangle أو Oval أو RoundRect إلى رَسْم نفسه، سيُنفَّذ التابع redraw() المُعرَّف بالصنف المناسب، وهو ما يَقُودنا للسؤال التالي: ما دور التابع redraw() المُعرَّف بالصنف Shape؟ وكيف لنا أن نُعرِّفه؟ قد تتفاجئ من الإجابة، ففي الحقيقة ينبغي أن نَترُكه فارغًا لأن الصنف Shape لا يُمثِل سوى فكرة مُجردة (abstract) لشكل، وبالتأكيد ليس هناك طريقة لرسم شيء كذلك، وإنما يُمكِن فقط رَسْم الأشكال الحقيقية (concrete) كالمستطيلات والأشكال البيضاوية. إذًا، لماذا ينبغي أن نُعرِّف التابع redraw() بالصنف Shape من الأساس؟ حسنًا، نحن مُضطرون إلى ذلك وإلا لن يَصِح استدعائه بالتابع setColor() المُعرَّف بالصنف Shape كما لن يَصِح كتابة شيئًا مثل someShape.redraw();؛ حيث سيَعترِض المُصرِّف لكَوْن someShape مُتْغيِّرًا من النوع Shape في حين لا يَحتوِي ذلك الصَنْف على التابع redraw(). وعليه، لن يُستدعَى التابع redraw() المُعرَّف بالصنف Shape نهائيًا، وربما -إذا فكرت بالأمر- ستَجِدْ أنه ليس هناك أي سبب قد يَضطرّنا حتى إلى إنشاء كائن فعليّ من الصنف Shape. تستطيع بالتأكيد إنشاء مُتْغيِّرات من النوع Shape، ولكنها دومًا ستُشير إلى كائنات (objects) من الأصناف الفرعية (subclasses) للصَنْف Shape. في مثل تلك الحالات، يُعدّ الصنف Shape صنفًا مُجرَّدًا (abstract class). لا تُستخدَم الأصناف المُجرّدة (abstract class) لإنشاء كائنات، وإنما فقط تُمثِل قاعدة وأساسًا لإنشاء أصناف فرعية أيّ أنها موجودة فقط للتعبير عن بعض السمات والخاصيات المشتركة بجميع أصنافها الفرعية (subclasses). أما الصَنْف غَيْر المُجرّد فيُعدّ صنفًا حقيقيًا (concrete class). في العموم، تستطيع إنشاء كائنات تنتمي لأصناف حقيقية (concrete class) وليس لأصناف مُجرّدة (abstract class)، كما يستطيع مُتْغيِّر من نوع هو عبارة عن صنف مُجرّد (abstract class) أن يُشير فقط إلى كائنات تنتمي لأحد الأصناف الفرعية الحقيقية (concrete subclasses) المُشتقّة من ذلك الصنف المُجرّد (abstract class). بالمثل، يُعدّ التابع redraw() المُعرَّف بالصنف Shape تابعًا مجردًا (abstract method)؛ فهو لا يُستدعَى نهائيًا وإنما تُستدعَى توابع redraw() المُعرَّفة بالأصناف الفرعية للصنف Shape لرَسْم الأشكال بصورة فعليّة. اِضطرّرنا مع ذلك لتعريف التابع redraw() بالصَنْف Shape للتأكيد على فِهم جميع الأشكال للرسالة redraw، أي أنه موجود فقط لتَخْصِيص الواجهة (interface) المُشتركة لنُسخ التابع redraw() الفعليّة والحقيقية (concrete) والمُعرَّفة بالأصناف الفرعية، وبالتالي ليس هناك ما يَستدعِي احتواء التابع المُجرَّد redraw() بالصنف Shape على أيّ شيفرة. كما ذَكَرَنا سابقًا، يُعدّ كُلًا من الصنف Shape وتابعه redraw() بمثابة أفكارًا مجردةً (abstract) على نحو دلالي فقط حتى الآن، ولكن تستطيع إِعلام الحاسوب بهذه الحقيقة على نحو صياغي (syntactically) أيضًا عن طريق إضافة المُبدِّل abstract إلى تعريف (definition) كُلًا منهما. بخلاف أي تابع عادي، لا يُكتَب الجزء التنفيذي (implementation) للتوابع المُجرّدة (abstract method) وإنما تُستخدَم فاصلة منقوطة (semicolon). في المقابل، لابُدّ من كتابة الجزء التنفيذي (implementation) للتابع المُجرّد (abstract method) ضِمْن أي صَنْف فرعي حقيقي (concrete subclass) مُشتقّ من الصنف المُجرّد (abstract class). تَستعرِض الشيفرة التالية طريقة تعريف الصَنْف Shape كصنف مُجرّد (abstract class): public abstract class Shape { Color color; // لون الشكل void setColor(Color newColor) { // تابع لتغيير لون الشكل color = newColor; // غير قيمة متغير النسخة redraw(); // أعد رسم الشكل ليظهر باللون الجديد } abstract void redraw(); // تابع مجرد ينبغي أن يُعرَّف بالأصناف الفرعية الحقيقية . . . // المزيد من التوابع والنسخ } // نهاية الصنف Shape بمُجرّد اِستخدَامك للمُبدِّل abstract أثناء التَّصْريح عن الصَنْف، لا تستطيع إنشاء كائنات فعليّة من النوع Shape، وسيُبلِّغ الحاسوب عن حُدوث خطأ في بناء الجملة (syntax error) إذا حاولت القيام بذلك. ربما يُعدّ الصَنْف Vehicle -الذي ناقشناه بالأعلى- صنفًا مجردًا (abstract class) أيضًا، فليس هناك أي طريقة لامتلاك مركبة كتلك، وإنما لابُدّ للمركبة الفعليّة أن تَكُون سيارة أو شاحنة أو دراجة نارية أو حتى أي نوع حقيقي (concrete) آخر. ذَكَرنا بالقسم الفرعي ٥.٣.٢ أن أيّ صنف لا يُصرَّح عن كَوْنه صنف فرعي (subclass) من أيّ صنف آخر -أيّ بدون الجزء extends- فإنه تلقائيًا يُصبِح صنفًا فرعيًا من الصنف القياسي Object كالتالي: public class myClass { . . . يُعدّ التَّصْريح بالأعلى مُكافئًا للتالي تمامًا: public class myClass extends Object { . . . يُمكِننا القول إذًا أن الصَنْف Object يقع بقمة سُلالة أصناف (class hierarchy) ضخمة تَتَضمَّن جميع الأصناف الآخرى. من الناحية الدلالية، يُعدّ الصنف Object صنفًا مجردًا (abstract class) بل ربما أكثر صنف مجرد على الإطلاق، ومع ذلك، لم يُصرَّح عنه على هذا الأساس من الناحية الصياغية، أيّ تستطيع إنشاء كائنات من النوع Object ولكن لن تَجِدْ الكثير لتفعله بها. ولأن أيّ صَنْف هو بالضرورة صنف فرعي (subclass) من الصَنْف Object، فيُمكِن لمُتْغيِّر من النوع Object أن يُشير إلى أي كائن مهما كان نوعه. بالمثل، تستطيع مصفوفة من النوع Object[] أن تَحمِل كائنات من أي نوع. تَستخدِم الشيفرة المصدرية بالملف ShapeDraw.java الصَنْف المُجرّد Shape، بالإضافة إلى مصفوفة من النوع Shape[] لحَمْل قائمة من الأشكال. قد تَرغَب بإلقاء نظرة على ذلك الملف مع أنك لن تَتَمكَّن من فهمه بالكامل حاليًا؛ فتعريفات الأصناف الفرعية بالملف مختلفة نوعًا ما عن تلك التي رأيتها بالأعلى. مثلًا، يَستقبِل التابع draw() مُعامِلًا (parameter) من النوع GraphicsContext؛ لأن الرَسْم بالجافا يَتطلَّب سياقًا رُسوميًا. سنَتَعرَّض لأمثلة شبيهة بالفصول اللاحقة بعدما تَتَعرَف على برمجة واجهات المُستخدِم الرُسومية (GUI). يُمكِنك مع ذلك فَحْص تعريف الصنف Shape وأصنافه الفرعية (subclasses) بالإضافة إلى طريقة اِستخدَام المصفوفة لحَمْل قائمة الأشكال. تَستعرِض الصورة التالية لقطة للشاشة أثناء تَشْغِيل البرنامج: بعد تَشْغِيل البرنامج ShapeDraw، تستطيع أن تُضيف شكلًا جديدًا إلى الصورة عن طريق الضغط على أيّ من الأزرار الموجودة بالأسفل. يُمكِنك أيضًا اختيار لون ذلك الشكل من خلال قائمة الألوان الموجودة أسفل مساحة الرسم (drawing area). سيَظهَر الشكل الجديد بالركن الأيسر العلوي من مساحة الرسم (drawing area)، وبمُجرّد ظُهوره، تستطيع سَحْبه باستخدَام الفأرة. سيُحافِظ الشكل دومًا على ترتيبه بالنسبة لبقية الأشكال الموجودة على الشاشة حتى أثناء عملية السَحْب، ومع ذلك، تستطيع اختيار شكل معين ليُصبِح أمام جميع الأشكال الأخرى عن طريق الضغط باستمرار على المفتاح "shift" بينما تَضغَط على ذلك الشكل. في الحقيقة، يُستخدَم الصنف الفعليّ للشكل أثناء إضافته إلى الشاشة فقط. بعد ذلك، يُعالَج بالكامل على أساس كَوْنه شكلًا مجردًا (abstract). على سبيل المثال، يَتعامَل البرنامج (routine) المسئول عن عملية السَحْب مع مُتْغيِّرات من النوع Shape، ولا يُشير نهائيًا إلى أي من الأصناف الفرعية (subclasses). عندما يحتاج إلى رَسْم شكل، فإنه فقط يَستدعِي التابع draw أي أنه غَيْر مضطرّ لمَعرِفة طريقة رَسْم الشكل أو حتى مَعرِفة نوعه الفعليّ، وإنما تُوكَل عملية الرسم إلى الكائن ذاته. إذا أردت أن تُضيف نوعًا جديدًا من الأشكال إلى البرنامج، كل ما عليك القيام به هو الآتي: أولًا، ستُعرِّف صنفًا فرعيًا (subclass) جديدًا مُشتقًّا من Shape، ثم ستُضيف زرًا جديدًا وتُبرمجه بحيث يُضيف الشكل إلى الشاشة. ترجمة -بتصرّف- للقسم Section 5: Inheritance, Polymorphism, and Abstract Classes من فصل Chapter 5: Programming in the Large II: Objects and Classes من كتاب Introduction to Programming Using Java.1 نقطة
-
يُمكِن تطبيق المفاهيم كائنية التوجه (object-oriented) على عملية تصميم البرامج وكتابتها في العموم بأكثر من طريقة، منها التحليل والتصميم كائني التوجه (object-oriented analysis and design). تُطبِق تلك الطريقة الأساليب كائنية التوجه (object-oriented) على أُولَى مراحل تطوير البرمجيات، والمسئولة عن تصميم البرنامج ككل. تَتَلخَّص تلك الطريقة بتَحْديد مجموعة الكيانات المُرتَبِطة بموضوع المشكلة (problem domain)، والتي يُمكِن تمثيلها ككائنات (objects). على مستوى آخر، تُشجِع البرمجة كائنية التوجه (object-oriented programming) المبرمجين على إنشاء "أدوات برمجية مُعمَّمة" قابلة للاِستخدَام بالعديد من المشروعات البرمجية المختلفة. سنَستخدِم "الأدوات البرمجية المُعمَّمة" بصورة أكبر حين نبدأ باِستخدَام الأصناف القياسية (standard classes) بالجافا. سنبدأ هذا القسم بفَحْص بعضًا من تلك الأصناف المَبنية مُسْبَقًا (built-in)، والمُستخدَمة لإنشاء أنواع معينة من الكائنات (objects)، وسنعود مُجدّدًا إلى الصورة العامة بنهاية القسم. بعض الأصناف المبنية مسبقًا بالبرمجة كائنية التوجه (object-oriented programming)، يَكُون التركيز عادةً على تصميم أصناف جديدة، وكتابة تَّنْفيذها (implementation)، ولكن يَنبغي أيضًا ألا نُهمِل ذلك العدد الكبير من الأصناف (classes) التي وفرها لنا مُصمّمي الجافا. يُمكِننا أن نَستخدِم بعضًا من تلك الأصناف لإنتاج أصناف جديدة، بينما قد نَستخدِم البعض الآخر مُباشرة لإِنشاء كائنات. لكي تَتَمكَّن فعليًا من لغة الجافا، ينبغي أن تَكُون على دراية بعدد كبير من تلك الأصناف المَبنية مُسْبَقًا (built-in classes)، وهو ما يَحتاج إلى الكثير من الوقت والخبرة لتطويره. سنَفْحَص خلال هذا القسم بعضًا من تلك الأصناف. يُمكِننا أن نُنشِئ سِلسِلة نصية (string) من نصوص أصغر باِستخدَام العَامِل +، ولكن لا يَكُون ذلك دائمًا هو الحل الأفضل. فمثلًا، إذا كان str مُتْغيِّرًا من النوع String و ch محرفًا (character)، فإن تّنْفيذ الأمر str = str + ch; يَتَضمَّن إنشاء كائن جديد كليًا من النوع String، مُكوَّن من نُسخة من str مع قيمة ch مُلحقَّة بآخره. يَستغرِق نَسخ السِلسِلة النصية بعض الوقت كما يَتَطلَّب قدرًا كبيرًا من المعالجة. في المقابل، يَسمَح الصنف StringBuilder بإنشاء سِلسِلة نصية طويلة من نصوص أصغر بكفاءة. يُمكِننا أن نُنشِئ كائنًا ينتمي إلى الصنف StringBuilder كالتالي: StringBuilder builder = new StringBuilder(); تُصرِّح تلك التَعْليمَة عن المُتْغيِّر builder، وتُهيئه مبدئيًا، بحيث يُشير إلى كائن من الصَنْف StringBuilder. بالقسم الفرعي ٤.٨.١، ناقشنا دمج عمليتي التّصريح (declaration) والتهيئة المبدئية (initialization) ضِمْن تَعْليمَة واحدة فيما يَتعَلَّق بالأنواع الأساسية (primitive types)، ولكن الأمر هو نفسه بالنسبة للكائنات. تمامًا كأي كائن من الصَنْف String، تَتَكوَّن كائنات الصَنْف StringBuilder من مُتتالية من المحارف. ولكن بإمكانك إلحاق محارف جديدة بدون إنشاء نُسخ جديدة من نفس البيانات التي تَتَضمَّنها تلك الكائنات بالفعل. إذا كانت x عبارة عن قيمة من أيّ نوع، وكان builder هو المُتْغيِّر المُعرَّف بالأعلى، فإن الأمر builder.append(x) سيُضيف تمثيل x النصي (string representation) إلى نهاية البيانات الموجودة بالمُتْغيِّر builder، وهو ما يُعدّ أكثر كفاءة عمومًا من الاستمرار بنَسْخ البيانات مع كل مرة نُلحِق خلالها شيئًا جديدًا. تستطيع إنشاء سِلسِلة نصية طويلة باِستخدَام كائن من الصَنْف StringBuilder، وعبر مُتتالية من أوامر append()، وعندما تَكتمِل السِلسِلة النصية، ستُعيد الدالة builder.toString() نُسخة من السِلسِلة النصية كقيمة عادية من النوع String. يَتوفَّر الصَنْف StringBuilder ضِمْن الحزمة (package) القياسية java.lang، ولذلك يُمكِن اِستخدَام الاسم البسيط للصَنْف بدون استيراده (import). تَحتوِي حزمة java.util على عدد من الأصناف المفيدة، فمثلًا، تَتَوفَّر أصناف للتَعامُل مع تجميعات (collections) من الكائنات، والتي سنتناولها تفصيليًا بالفصل العاشر. تَعرَّضنا للصَنْف java.util.Scanner المُعرَّف ضِمْن نفس الحزمة بالقسم الفرعي ٢.٤.٦. يَتوفَّر أيضًا الصَنْف java.util.Date، والذي يُستخدَم لأغراض تمثيل الوقت، فمثلًا، عندما تُنشِئ كائنًا (object) من النوع Date بدون مُعامِلات (parameters)، تُمثِل النتيجة كُلًا من التاريخ والوقت الحالي. يُمكِن عَرْض تلك المعلومة كالتالي: System.out.println( new Date() ); لمّا كان الصَنْف Date مُعرَّفًا بحزمة java.util، كان لابُدّ من إِتاحته أولًا بالبرنامج عن طريق استيراده، وذلك بكتابة أي من التَعْليمَتين import java.util.Date; أو import java.util.*; ببداية البرنامج، وبَعْدها ستَتَمكَّن من اِستخدَام الصَنْف. (ناقشنا الحزم والاستيراد بالقسم الفرعي ٤.٦.٣). لنَفْحَص أيضًا الصَنْف java.util.Random. تُعدّ كائنات ذلك الصَنْف مصدرًا للأعداد العشوائية، فيُمكِن لكائن من النوع Random أن يُنتج أعدادًا عشوائية سواء كانت صحيحة (integers) أو حقيقية (real). وفي الحقيقة، تَستخدِم الدالة القياسية Math.random() كائنًا من ذلك الصَنْف وراء الكواليس؛ لإنتاج أعدادها العشوائية. اُنظر الشيفرة التالية لإنشاء كائن من الصَنْف Random: Random randGen = new Random(); بفَرْض أن N هو عدد صحيح موجب، سيُنتج randGen.nextInt(N) عددًا صحيحًا عشوائيًا ضِمْن نطاق يتراوح من صفر وحتى N-1. يُمكِننا استخدام ذلك بتجربة رمِي حجري النَّرد لتَسهيل المُهِمّة؛ فبدلًا من كتابة die1 = (int)(6*Math.random())+1;، يُمكِننا كتابة die1 = randGen.nextInt(6)+1;. قد لا تتفق على أن تلك الطريقة أسهل نوعًا ما خاصة مع اضطرارنا لاستيراد (import) الصَنْف java.util.Random، وإنشاء كائن منه. يُمكِننا أيضًا اِستخدَام كائن من النوع Random لإنتاج ما يُعرَف باسم "التوزيع الاحتمالي الغاوسي (gaussian distribution)". تُستخدَم الكثير من أصناف جافا القياسية ببرمجة واجهات المُستخدِم الرسومية (GUI)، وسنَمُرّ عبر الكثير منها بالفصل السادس، ولكن سنَمُرّ هنا سريعًا على الصَنْف Color من حِزمة javafx.scene.paint حتى نَستخدِمه بالمثال التالي. يُمثِل أي كائن من الصَنْف Color لونًا يُستخدَم أثناء الرسم، ولقد تَعرَّضنا بالفعل لعدة ألوان ثابتة (constants) مثل Color.RED بالقسم ٣.٩. في الواقع، تلك الثوابت ما هي إلا مُتْغيِّرات أعضاء ساكنة (static) ونهائية (final) مُعرَّفة بالصَنْف Color، وقيمها عبارة عن كائنات من النوع Color. بالإضافة إلى تلك الألوان المُعرَّفة مُسْبَقًا، يُوفِّر الصَنْف Color مجموعة من البَوانِي (constructors) تَسمَح لك بإنشاء كائنات جديدة من الصَنْف لتَمثيل أيّ لون آخر. يَستقبِل إحداها ٣ مُعامِلات (parameters) من النوع double ويُمكِنك استدعائه باِستخدَام new Color(r,g,b). تُمثِل تلك المُعامِلات كُلًا من اللون الأحمر والأخضر والأزرق بنظام الألوان RGB، ويَنبغي أن تَقع قيمها ضِمْن نطاق يتراوح من ٠ وحتى ١. تَعنِي القيمة ٠ للمُعامِل r أن اللون الفعليّ لا يَتَضمَّن اللون الأحمر نهائيًا، بينما تَعنِي القيمة ١ أنه يَتَضمَّن أكبر قدر مُمكن من اللون الأحمر. يَتوفَّر باني آخر يُستدعَى على الصورة new Color(r,g,b,t)، والذي يَستقبِل مُعامِلًا إضافيًا من النوع double، ويَنبغي أن تَقع قيمته ضِمْن نطاق يتراوح من ٠ وحتى ١. يُحدِّد ذلك المعامل درجة شفافية اللون (transparency)، بحيث تُمثِل القيم الأكبر من المُعامِل t لونًا أقل شفافية، فمثلًا، عندما تَرسم بلون شفاف جزئيًا، تَظهَر الخلفية عبر اللون إلى حد معين. .تَتَضمَّن الكائنات من الصَنْف Color عددًا قليلًا من توابع النسخ (instance methods). على سبيل المثال، تَتَوفَّر دوال (functions) مثل getRed() لجَلْب قيمة اللون الأحمر بنظام RGB، وبالمثل للون الأخضر والأزرق. مع ذلك، لا يُوفِّر الصَنْف أي توابع ضَبْط (setter methods) لتَعْديل قيم تلك الألوان، حيث تُعدّ الكائنات من الصَنْف Color كائنات ثابتة أو غَيْر قابلة للتعديل (immutable)، بمعنى أن جميع مُتْغيِّرات النُسخ (instance variables) المُعرَّفة بداخلها هي نهائية -مُعرَّفة باِستخدَام المُبدِّل final-، وبالتالي لا يُمكِن تَعْديلها بَعْد إنشاء الكائن. لاحِظ أن السَلاسِل النصية من النوع String هي مثال آخر على الكائنات غَيْر القابلة للتعديل (immutable). النقطة الرئيسية مما سبق هو التأكيد على أن أصناف جافا القياسية تُوفِّر حلولًا للكثير من المشاكل التي قد تواجهها، لذا إذا تَعرَّضت لمُهِمّة (task) شائعة نوعًا ما، فلرُبما من الأفضل أن تَبحَث قليلًا بمَرجِع الجافا، لتَرى ما إذا كان هناك صَنْف يؤدي الغرض الذي تحتاج إليه. الصنف Object تُعدّ القدرة على إنشاء أصناف فرعية (subclasses) مُشتقَّة من صَنْف واحدة من أهم سمات البرمجة كائنية التوجه (object-oriented programming). يَرِث (inherit) الصَنْف الفرعي جميع خاصيات (properties) الصَنْف الأصلي وسلوكياته (behaviors)، ولكن بإِمكانه تَعْديلها وكذلك الإضافة إليها. ستَتَعلَّم طريقة إنشاء الأصناف الفرعية (subclasses) في القسم ٥.٥. في الواقع، أيّ صَنْف بلغة الجافا -باستثناء صَنْف وحيد- هو بالنهاية صَنْف فرعي من صَنْف آخر، فحتى لو أنشأت صَنْفًا، ولم تُصرِّح عن كَوْنه صَنْفًا فرعيًا من صَنْف آخر، فإنه تلقائيًا يُصبِح صنفًا فرعيًا من صَنْف خاص اسمه Object مُعرَّف بحزمة java.lang، وهو الصَنْف الاستثنائي الوحيد الذي لا يُعدّ صَنْفًا فرعيًا من أي صَنْف آخر. يُعرِّف الصنف Object مجموعة من توابع النُسخ (instance methods)، والتي تَرثها (inherit) جميع الأصناف الآخرى، لذا يُمكِن لأيّ كائن (object) مهما كان أن يَستخدِم تلك التوابع. سنَذكُر واحدة منها فقط بهذا القسم، وسنَتَعرَّض لمجموعة آخرى منها لاحقًا. يُعيد تابع النسخة toString() المُعرَّف بالصنف Object قيمة من النوع String، والتي يُفْترَض أن تَكُون بمثابة تمثيلًا نصيًا (string representation) للكائن. في أي مرة نَطبَع فيها كائنًا أو نَضُمّه (concatenate) إلى سِلسِلة نصية، أو بصورة أعم نَستخدِمه بسياق يَتطلَّب سِلسِلة نصية، يُستدعَى هذا التابع ضمنيًا ليُحوِّل ذلك الكائن تلقائيًا إلى النوع String. تُعيد نسخة toString المُعرَّفة بالصنف Object اسم الصنف الذي ينتمي إليه الكائن مع ترميز التجزئة (hash code) الخاص به، وهو ما قد لا يَكُون مفيدًا. لذا عندما تُنشِئ صَنْفًا، تستطيع إعادة تعريف التابع toString() بحيث تَحلّ النسخة الجديدة مَحلّ النسخة الموروثة، فمثلًا، يُمكِننا إضافة التابع (method) التالي لأيّ من أصناف PairOfDice المُعرَّفة بالقسم السابق: /** * يعيد تمثيلًا نصيًا لحجري نرد */ public String toString() { if (die1 == die2) return "double " + die1; else return die1 + " and " + die2; } إذا كان dice مُتْغيِّرًا يُشير إلى كائن من الصَنْف PairOfDice، فسيُعيد dice.toString() سَلاسِل نصية مثل "3 and 6" و "5 and 1" و "double 2" بِحَسْب الأعداد الظاهرة على حجري النَّرد. يُستدَعى هذا التابع تلقائيًا لتَحْوِيل المُتْغيِّر dice إلى النوع String بالتَعْليمَة التالية: System.out.println( "The dice came up " + dice ); قد يَكُون خَرْج التعليمة -بالأعلى- "The dice came up 5 and 1" أو "The dice came up double 2". سنَتَعرَّض لمثال آخر للتابع toString() بالقسم التالي. كتابة صنف واستخدامه سنَكْتُب الآن برنامج تَحرِيكة (animation)، بالاعتماد على نفس المنصة المُستخدَمة بالقسم الفرعي ٣.٩.٣، سيَكُون بمثابة مثالًا على تصميم صَنْف (class) جديد واِستخدَامه. ستُظهِر التَحرِيكة عددًا من الأقراص شبه الشفافة، والمُلوَّنة عشوائيًا، والمُتموضِعة بأماكن عشوائية عَبْر النافذة. عند تشغيل التحريكة، سيَزدَاد حجم تلك الأقراص، وعندما يَصِل حجم أي منها إلى حد معين، فإنه سيَختفِي ليَحلّ مَحلّه قرص آخر جديد بمكان عشوائي. قد يَحدُث ذلك -أيّ الاختفاء والظهور- بصورة عشوائية أيضًا بعيدًا عن حجم القرص. تَستعرِض الصورة التالية لقطة للشاشة أثناء تَشْغِيل البرنامج: سيَكُون كل قرص ضِمْن التَحرِيكة عبارة عن كائن (object)، ولأن أيّ قرص يَملُك عدة خاصيات (properties)، مثل اللون والموقع والحجم، فسنَستخدِم مُتْغيِّرات نُسخ (instance variables) لتمثيل كُلًا منها ضِمْن الكائن. أما بالنسبة لتوابع النُسخ (instance methods)، فيَنبغِي لنا التفكير أولًا بالكيفية التي سنَستخدِم بها القرص عَبْر البرنامج. سنَحتاج عمومًا إلى رسم القرص، لذا سنُضيف تابع النسخة draw(g)، حيث g هو كائن السِّياق الرُسومي (graphics context) المُستخدَم للرسم. يُمكِن للصَنْف أيضًا أن يَتَضمَّن بَانِي كائن (constructors) واحد أو أكثر لتهيئة الكائنات مبدئيًا. لا تَكُون البيانات المُفْترَض تمريرها للباني كمُعامِلات (parameters) واضحة دومًا. في هذا المثال، سنكتفي بتمرير كُلًا من موقع الدائرة وحجمها كمُعامِلات، أما لونها فسيَصنَعه الباني باِستخدَام قيم عشوائية للألوان الثلاثة بنظام RGB. اُنظر تعريف الصَنْف كاملًا: import javafx.scene.paint.Color; import javafx.scene.canvas.GraphicsContext; public class CircleInfo { public int radius; // نصف قطر الدائرة public int x,y; // موقع مركز الدائرة public Color color; // لون الدائرة /** * Create a CircleInfo with a given location and radius and with a * randomly selected, semi-transparent color. * @param centerX The x coordinate of the center. * @param centerY The y coordinate of the center. * @param rad The radius of the circle. */ public CircleInfo( int centerX, int centerY, int rad ) { x = centerX; y = centerY; radius = rad; double red = Math.random(); double green = Math.random(); double blue = Math.random(); color = new Color( red,green,blue, 0.4 ); } /** * Draw the disk in graphics context g, with a black outline. */ public void draw( GraphicsContext g ) { g.setFill( color ); g.fillOval( x - radius, y - radius, 2*radius, 2*radius ); g.setStroke( Color.BLACK ); g.strokeOval( x - radius, y - radius, 2*radius, 2*radius ); } } لاحِظ أننا قد صَرَّحنا عن مُتْغيِّرات النُسخ (instance variables) على أساس كَوْنها عامة (public)؛ لتبسيط الأمور، لكن يُفضَّل عمومًا كتابة ضوابط (setters) وجوالب (setters) لكُلًا منها. سنُعرِّف البرنامج main داخل الصنف GrowingCircleAnimation. ولأن البرنامج سيَستخدِم ١٠٠ قرص، كُلًا منها عبارة عن كائن من الصنف CircleInfo، فإنه سيَحتاج إلى مصفوفة من الكائنات لتَخْزِينها. لذا يُعرِّف البرنامج مُتْغيِّر مصفوفة (array variable) كمُتْغيِّر نُسخة (instance variable) ضِمْن الصنف، كالتالي: private CircleInfo[] circleData; // يحمل بيانات 100 قرص لاحِظ أن المُتْغيِّر circleData ليس ساكنًا (static). تَعتمِد برمجة واجهات المُستخدِم الرسومية (GUI) في العموم على الكائنات (objects) بدلًا من المُتْغيِّرات والتوابع الساكنة. يُمكِننا عمومًا تَخيُّل وجود عدة كائنات من الصنف GrowingCircleAnimation تَعمَل بصورة مُتزامنة. لذا ينبغي لكُلًا منها أن يمتلك مصفوفته الخاصة من الأقراص. بصيغة آخرى، كل تحريكة (animation) عبارة عن كائن، وكل كائن يَمتلك نسخته الخاصة من مُتْغيِّر النسخة circleData. إذا كان circleData ساكنًا، فسيَكُون هناك مصفوفة واحدة فقط، وستبدو جميع التَحرِيكات مُتطابقة تمامًا. الآن، لابُدّ أن نُنشِئ المصفوفة ونَملؤها بالبيانات. في البرنامج التالي، قُمنا بذلك حتى قَبْل رسم أول إطار (frame) ضِمْن التَحرِيكة. أولًا، اِستخدَمنا التعبير new CircleInfo[100] لإنشاء المصفوفة، ثُمَّ أنشأنا مائة كائن من النوع CircleInfo لمَلئ المصفوفة. لاحِظ أن الكائنات الجديدة تَكُون عشوائية فيما يَتعلَّق بحجمها ومَوضِعها. بفَرْض أن width و height هي أبعاد مساحة الرسم (drawing area)، اُنظر الشيفرة التالية: circleData = new CircleInfo[100]; // أنشئ المصفوفة for (int i = 0; i < circleData.length; i++) { // أنشئ الكائنات circleData[i] = new CircleInfo( (int)(width*Math.random()), (int)(height*Math.random()), (int)(100*Math.random()) ); } يَزداد نصف قطر القرص بكل إطار (frame)، ثم يُعاد رسمه كالتالي: circleData[i].radius++; circleData[i].draw(g); قد تبدو التَعْليمَات -بالأعلى- مُعقدة نوعًا ما، لذا دَعْنَا نَفْحَصها عن قرب. أولًا، يُمثِل circleData أحد عناصر المصفوفة circleData، أيّ أنه مُتْغيِّر من النوع CircleInfo. يُشير ذلك المُتْغيِّر إلى كائن من النوع CircleInfo، والذي لابُدّ أن يَحتوِي على مُتْغيِّر النُسخة (instance variable) العام radius، ويَكُون اسمه الكامل هو circleData.radius. لمّا كان مُتْغيِّر النسخة ذاك من النوع int، نستطيع تطبيق عامل الزيادة ++ عليه لزيادة قيمته بمقدار الواحد، أيّ أن تأثير التَعْليمَة circleData.radius++ هو زيادة نصف قطر الدائرة بمقدار الواحد. يُعدّ السطر الثاني من الشيفرة مشابهًا للأول، باستثناء أن circleData.draw يُمثِل تابع نُسخة (instance method) بالكائن. تَستدعِي التَعْليمَة circleData.draw(g) تابع النُسخة ذاك، وتُمرِّر له المُعامِل g، والذي يُمثِل كائن السِّياق الرسومي المُستخدَم للرسم. يُمكِنك الإطلاع على الشيفرة المصدرية للبرنامج بالملف GrowingCircleAnimation.java إذا كنت مهتمًا. ولأن البرنامج يَستخدِم الصَنْف CircleInfo، ستحتاج أيضًا إلى نسخة الملف CircleInfo.java لتَصْرِيف البرنامج وتَشْغِيله. التحليل والتصميم كائني التوجه يَبنِي كل مبرمج عادةً مَخزونًا من التقنيات والخبرات، تَتَمثَل بهيئة قطع من الشيفرة مَكْتُوبة ومُجربَة، بحيث يَتمكَّن من إعادة اِستخدَامها ضِمْن أي برامج جديدة. تُنسَخ الشيفرة القديمة ببساطة إلى البرنامج الجديد، ثم تُعدَّل بما يَتلائَم مع البرنامج الجديد. يَعتمِد عندها كامل المشروع على قدرة المبرمج على سَحْب جزء الشيفرة ذاك، الذي كان قد كَتبَه ضِمْن مشروع سابق، والذي بدا أنه قابل للتَخْصِيص بحيث يَتَناسب مع المشروع الجديد. ولكن في الواقع، تُعانِي تلك الطريقة من كَوْن عملية التَعْدِيل تلك عُرضة للخطأ، كما أنها مُستهلِكة للوقت. تُحبِّذ أيضًا الشركات الكبيرة توفير الموارد بتَجنُّب إعادة اختراع العجلة مع كل مشروع جديد، ولكن تُصبِح مُهِمّة تَعقُّب كل تلك العجلات القديمة أكثر صعوبة مع الحجم الهائل من المشروعات بتلك الشركات. من الجهة الآخرى، تُعدّ الأصناف قطعًا برمجية قابلة لإعادة الاِستخدَام بدون الحاجة لإجراء أي تَعْديلات عليها بشَّرْط أن تَكُون قد صُمّمت تصميمًا جيدًا. مثلًا، لا تُعدّ الأصناف المصنوعة خصيصًا لإنجار مُهِمّة مُحدَّدة جدًا ضِمْن برنامج مُحدَّد مُصمّمة تصميمًا جيدًا، وإنما لابُدّ أن يَكُون الصنف مصنوعًا بحيث يُمثِل نوعًا واحدًا محددًا من الأشياء أو مفهومًا واحدًا مترابطًا. لمّا كانت تلك المفاهيم والأشياء عامة كفاية لأن تَتكرَّر ضِمْن العديد من المشاكل، تزداد إمكانية الحاجة لاِستخدَام تلك الأصناف ضِمْن عدة مشروعات وبدون إجراء أي تَعْديلات. ولأننا نَتعامَل مع لغة كائنية التوجه (object-oriented)، يُمكِننا أيضًا إنشاء أصناف فرعية (subclasses) من صَنْف موجود، وهو ما يُزيد من قابلية الأصناف لإعادة الاستخدَام. فمثلًا، إذا احتاج صَنْف معين لمزيد من التَخْصيص، يُمكِننا ببساطة إنشاء صَنْف فرعي (subclass) منه، وإجراء أي إضافات أو تَعْديلات على الصَنْف الفرعي بدون تَعْديل الصَنْف الأصلي. نستطيع القيام بذلك حتى لو لم يَكُن لدينا صلاحية وصول لشيفرة الصَنْف، ولا نعلم أي شيء عن التفاصيل التَّنْفيذية (implementation) الداخلية الخاصة به. في الواقع، يُعدّ الصنف PairOfDice -من القسم السابق- مثالًا على قطعة برمجية مُعمَّمة، حيث يُمثِل الصنف مفهومًا واحدًا متماسكًا هو "حجري نَّرد". تَحمِل مُتْغيِّرات النُسخ (instance variables) بيانات مُتعلِّقة بحالة حجري النَّرد أي العدد الظاهر بكل حجر، كما يُمثِل تابع النُسخة (instance method) سلوك حجري النَّرد أي القدرة على رَميهما. على الرغم من إمكانية تحسِّين ذلك الصَنْف، فإنه قابلًا لإعادة الاستخدام بالكثير من المشروعات البرمجية المختلفة. في المقابل، لا يُعدّ الصنف Student -من القسم السابق- قابلًا لإعادة الاستخدام؛ حيث يُبدو وكأنه قد صُمّم خِصيصًا لتمثيل مجموعة طلبة ضِمْن دورة تدريبية من نوع خاص، تَعتمِد درجاتها على ثلاثة اختبارات فقط، فإذا كان هناك حاجة لمزيد من الاختبارات أو الأوراق، فسيُصبِح الصنف عديم الفائدة. علاوة على ذلك، ستَحدُث مشكلة في حالة وجود شخصين لهما نفس الاسم، لذا عادةً ما يُستخدَم رقم هوية عددي بدلًا من الاسم. ولكن للإنصاف، فإن إنشاء صَنْف مُتعدد الأغراض لتمثيل الطلبة هو أمر أصعب كثيرًا من مُجرّد إنشاء صَنْف مُتعدد الأغراض لتمثيل حجري نَّرد. تَتَكوَّن عملية تطوير أيّ مشروع برمجي ضخم من مجموعة من المراحل، بدايةً من مرحلة توصيف (specification) المشكلة المطلوب حلّها، ثُمَّ تحليلها (analysis)، وتصميم (design) البرنامج اللازم لحلّها، ثُمَّ تأتي مرحلة كتابة الشيفرة (coding)، والتي يَتحوِّل خلالها التصميم إلى لغة برمجية فعليّة، وبعد ذلك تأتي مرحلة الاختبار (testing)، وتَنْقيح الأخطاء (debugging). يَتبَع ذلك فترة طويلة من الصيانة (maintenance)، والتي تَتَضمَّن إصلاح أي مشاكل جديدة عُثر عليها بالبرنامج، وكذلك تَعْديله بحيث يَتكيَّف مع أي تَغْيير بمُتطلبات البرنامج. تُسمَى مُتتالية المراحل تلك باسم "دورة حياة تطوير البرمجيات (software life cycle)". نادرًا ما تَتتابَع تلك المراحل على نحو مُتتالي تمامًا، فمثلًا، قد يَتَّضِح أن التوصيف مُتعارض أو غَيْر مُكتمل أثناء مرحلة التحليل، أو قد يَتطلَّب العُثور على مشكلة أثناء مرحلة الاختبار (testing) عودة سريعة إلى مرحلة كتابة الشيفرة (coding) على الأقل، أو حتى تصميمًا جديدًا إذا كانت المشكلة كبيرة بما فيه الكفاية. وأخيرًا، عادةً ما تَتَضمَّن مرحلة الصيانة (maintenance) إعادة بعض الأعمال من المراحل السابقة. نجاح أيّ مشروع برمجي ضخم عادةً ما يَكُون مَشْرُوطًا بتبَنّي أسلوب منهجي دقيق خلال جميع مراحل التطوير. يُعرَف ذلك الأسلوب المنهجي، والذي يَستخدِم مبادئ التصميم الجيد، باسم "هندسة البرمجيات (software engineering)". يُحاوِل مهندسي البرمجيات عمومًا بناء برامج مستوفية لتوصيفاتها (specifications)، وبحيث تَكُون مكتوبة بطريقة تُسهِل من تَعْدِيلها إن اقْتَضَت الضرورة. هنالك الكثير من الأساليب التي يُمكِن تطبيقها لتصميم البرامج بطريقة منهجية، وتَشتمِل غالبيتها على رسم صناديق صغيرة تُمثِل مُكوِّنات البرنامج مع أسهم مُعنوَنة لتمثيل العلاقات بين تلك الصناديق. لقد ناقشنا كائنية التوجه (object orientation) فيما يَتعلَّق بمرحلة كتابة الشيفرة (coding)، لكن تَتوفَّر أيضًا أساليب كائنية التوجه لمرحلتي التحليل والتصميم. يَكُون السؤال بهذه المرحلة من مراحل تطوير البرمجيات: كيف نَتَمكَّن من اكتشاف أو اختراع البنية الكلية للبرنامج؟ اتبع النصيحة التالية، والتي تُعدّ بمثابة أسلوبًا بسيطًا كائني التوجه لمرحلتي التحليل والتصميم: سَجِّل توصيف المشكلة، ثُمَّ ارسم خطًا أسفل جميع الأسماء الموجودة بذلك التوصيف. ينبغي أن تَكُون تلك الأسماء مُرشَّحة كأصناف (classes) أو ككائنات (objects) ضِمْن تصميم البرنامج. بالمثل، ارسم خطًا أسفل جميع الأفعال (verbs) الموجودة بالتوصيف. ينبغي أن تَكُون تلك الأفعال مُرشَّحة كتوابع (methods). هذه هي نقطة البداية فقط، فقد يَكشِف مزيد من التحليل (analysis) عن الحاجة لإضافة أصناف أو توابع آخرى، كما قد يَكشِف عن إمكانية اِستخدَام أصناف فرعية (subclasses) تَتشارَك الخاصيات والسلوكيات المُتشابهة بينها. "حلّل المشكلة لاكتشاف المفاهيم المُتضمَّنة، ثم عرِّف أصنافًا لتمثيل تلك المفاهيم. ينبغي أن يُحاكِي التصميم المشكلة، أو بتعبير آخر، ينبغي أن تَعكِس بنية البرنامج بنية المشكلة نفسها بصورة طبيعية." قد يَكُون ذلك تبسيطًا مُخِلًّا، ولكنه واضح وفعال عمومًا. ترجمة -بتصرّف- للقسم Section 3: Programming with Objects من فصل Chapter 5: Programming in the Large II: Objects and Classes من كتاب Introduction to Programming Using Java.1 نقطة
-
الانفوجرافيك هو مقال ولكن يتم تبسيطه بقدر الامكان مع تعويضه ببعض الصور التى تعبر عن المكتوب وتثير ذهن القارئ - فمثلا اذا كنت تكتب مقال عن اضرار التدخين فيمكن اختصاره لبعض صور الاعضاء التى تتضرر منه وبعض الكلمات المعبره " مثل المثال المرفق الذى من تصميمي " - لكى يكون التصميم مناسب يجب ان يحتوى على اربع اساسيات وهى : 1 - الالوان المستخدمه في التصميم 2 - الخطوط المستخدمه في التصميم 3 - الايقونات والصور الرمزيه التعبيريه 4 - الموضوع نفسه - اذا اتقنت العمل في الاربع جوانب فأنت انشئت تصميم انفوجرافيك مميز وراقي افضل البرامج من وجهة نظرى هو "ادوبي اليستراتور" سهل ويمكنك التعامل معه بخبره بسيطه . السؤال الثانى لا يوجد ما يسمى بفيديوهات الانفوجرافيك بل تقصد الموشن جرافيك هو يمكن بكل بساطه اعتباره مثل الانفوجرافيك في جميع نواحيه ولكن يزيد بأنه متحرك اى يجذب الانتباه اكثر ويمكن استخدامه كتقديم لشركات او اعلانات او فيديوهات رسوميه افضل البرامج الخاصه بالموشن جرافيك هو " ادوبي افتر افيكت "1 نقطة