المحتوى عن 'interface'.



مزيد من الخيارات

  • ابحث بالكلمات المفتاحية

    أضف وسومًا وافصل بينها بفواصل ","
  • ابحث باسم الكاتب

نوع المُحتوى


التصنيفات

  • التخطيط وسير العمل
  • التمويل
  • فريق العمل
  • دراسة حالات
  • نصائح وإرشادات
  • التعامل مع العملاء
  • التعهيد الخارجي
  • مقالات عامة

التصنيفات

  • PHP
    • Laravel
    • ووردبريس
    • Magento
  • جافاسكريبت
    • Node.js
    • jQuery
    • AngularJS
  • HTML5
  • CSS
    • Sass
    • إطار عمل Bootstrap
  • سي شارب #C
    • منصة Xamarin
  • بايثون
    • Flask
    • Django
  • لغة روبي
    • إطار العمل Ruby on Rails
  • لغة Go
  • لغة جافا
  • برمجة أندرويد
  • لغة R
  • سير العمل
    • Git
  • صناعة الألعاب
    • Unity3D
  • مقالات عامّة

التصنيفات

  • تجربة المستخدم
  • الرسوميات
    • إنكسكيب
    • أدوبي إليستريتور
    • كوريل درو
  • التصميم الجرافيكي
    • أدوبي فوتوشوب
    • أدوبي إن ديزاين
    • جيمب
  • التصميم ثلاثي الأبعاد
    • 3Ds Max
  • مقالات عامّة

التصنيفات

  • خواديم
    • الويب HTTP
    • قواعد البيانات
    • البريد الإلكتروني
    • DNS
    • Samba
  • الحوسبة السّحابية
    • Docker
  • إدارة الإعدادات والنّشر
    • Chef
    • Puppet
    • Ansible
  • لينكس
  • FreeBSD
  • حماية
    • الجدران النارية
    • VPN
    • SSH

التصنيفات

  • التسويق بالأداء
    • أدوات تحليل الزوار
  • تهيئة محركات البحث SEO
  • الشبكات الاجتماعية
  • التسويق بالبريد الالكتروني
  • التسويق الضمني
  • استسراع النمو
  • المبيعات

التصنيفات

  • إدارة مالية
  • الإنتاجية
  • تجارب
  • مشاريع جانبية
  • التعامل مع العملاء
  • الحفاظ على الصحة
  • التسويق الذاتي
  • مقالات عامة

التصنيفات

  • الإنتاجية وسير العمل
    • مايكروسوفت أوفيس
    • ليبر أوفيس
    • جوجل درايف
    • شيربوينت
    • Evernote
    • Trello
  • تطبيقات الويب
    • ووردبريس
  • أندرويد
  • iOS
  • macOS
  • ويندوز

التصنيفات

  • شهادات سيسكو
    • CCNA
  • شهادات مايكروسوفت
  • شهادات Amazon Web Services
  • شهادات ريدهات
    • RHCSA
  • شهادات CompTIA
  • مقالات عامة

أسئلة وأجوبة

  • الأقسام
    • أسئلة ريادة الأعمال
    • أسئلة العمل الحر
    • أسئلة التسويق والمبيعات
    • أسئلة البرمجة
    • أسئلة التصميم
    • أسئلة DevOps
    • أسئلة البرامج والتطبيقات
    • أسئلة الشهادات المتخصصة

التصنيفات

  • ريادة الأعمال
  • العمل الحر
  • التسويق والمبيعات
  • البرمجة
  • التصميم
  • DevOps

تمّ العثور على 3 نتائج

  1. فيما سبق من دروس هذه السلسلة، تعلمنا أساسيات لغات البرمجة وجافا خاصة، هناك بعض الأمور الهامة في لغة جافا والتي يتم استخدامها بكثرة في تطبيقات الأندرويد، وسنكمل في هذا الدرس ما بدأناه من أساسيات لغة Java. Method Overriding لشرح هذا المفهوم دعنا نوضح هذا المثال والذي يقوم بتعريف صنف جديد يدعى Shape وبداخله ثلاث توابع كما يلي: class Shape{ protected int width; protected int height; public void setWidth(int a){ width = a; } public void setHeight(int b){ height = b; } public int getArea(){ return 0; } } وإذا قمنا بعمل كائن جديد يمكننا من خلاله استدعاء التوابع الخاصة به. Shape sh = new Shape(); sh.setWidth(10); sh.setHeight(5); int area = sh.getArea(); في المثال السابق سيتم تخزين 0 في المتغير area وذلك ما يفعله التابع ()getArea، حيث يقوم دائمًا بإعادة القيمة 0 أيًا كانت قيمة الطول والعرض وذلك لأننا نعتبر هذا الصنف نوعًا عام غير محدد الشكل ولا يمكننا معرفة مساحته. سنقوم الآن بصنع صنف جديد يرث من Shape: class Square extends Shape{ } كما ذكرنا سابقًا سيرث منه المتغيرات والخصائص كما سيرث منه التوابع الخاصة به فلا داعي لتعريفها بداخله مرة أخرى. Square sq = new Square(); sq.setWidth(10); sq.setHeight(10); int area = sq.getArea(); ستظل في هذه الحالة قيمة المتغير area كما هي تساوي 0 حيث ورث الصنف Square التابع ()getArea كما هو دون أي تغيير. إذا أردنا تغيير القيمة التي يعيدها هذا التابع نستخدم مفهوم Method Overriding، وهو ببساطة يعني تجاوز المحتوى السابق لهذا التابع والذي تم استخدامه داخل الأب لهذا الصنف واستخدام محتوى جديد بدلًا منه عند استدعاء التابع. ولتطبيق هذا المفهوم نقوم بكتابة التابع والحفاظ على اسمه ونوع البيانات التي يُعيدها وتغيير المحتوى الداخلي له لتنفيذ الوظيفة الجديدة. class Square extends Shape{ public int getArea(){ return width * height; } } والآن عند كتابة الكائن السابق سنجد أن المتغير area تغيرت قيمته ليقوم بتخزين حاصل ضرب الطول والعرض وحساب المساحة، ولن يتغير المحتوى الخاص بالتابع الأصلي ()getArea المتواجد داخل الصنف Shape. Square sq = new Square(); sq.setWidth(10); sq.setHeight(10); int area = sq.getArea(); //area = 10 والميزة الرئيسية لهذا المفهوم أنه يجعل للصنف الذي يرث من صنف آخر القدرة على صنع خطواته الخاصة لتنفيذ أحد التوابع التي يرثها دون التغيير في التابع الأصلي للأب. قواعد تطبيق مفهوم Method Overriding يجب كتابة التابع دون تغيير في الاسم الخاص به أو تغيير نوع البيانات التي تُمرر له أو تغيير ترتيبها الأصلي كما لا يمكن تغيير نوع البيانات التي يُعيدها. غير مسموح بتقليل القيود المتواجدة في التابع والتي يتم تحديدها عن طريق Access Modifiers. فمثلًا إذا تم تعريف التابع على أنه public فلا يمكن تقييده وجعله protected أو private لأن ذلك أقل في صلاحيات الوصول لهذا التابع، أما إذا كان التابع الأصلي protected وتم تغييرها إلى public فهذا مسموح به لأنه أعطى صلاحية وصول أكبر للتابع. لا يمكن تجاوز التابع المُعرف على أنه final. ولاحظ أن final عند تعريف المتغيرات تعني ثابت لا يمكن تغيير قيمته، وعند تعريف التوابع تعني تابع لا يمكن تجاوزه وتغيير محتواه. إذا أردت تنفيذ محتوى التابع الأصلي لتابع تم تجاوزه يمكنك ذلك عن طريق استخدام super، وبتطبيق ذلك على المثال السابق: class Square extends Shape{ public int getArea(){ int a = super.getArea(); if ( a == 0 ){ return width * height; } else { return a; } } } الحواشي Annotations هي طريقة لتقديم معلومات معينة عن الشيفرة المكتوبة والتي تشير إليها هذه الملاحظة ولا يتم اعتبارها أنها جزء من الشيفرة ولا تؤثر بشكل مباشرة فيها. هناك استخدامات مختلفة للحواشي annotations منها إعطاء أوامر للمترجم الخاص بالبرنامج، ولهذا الاستخدام يوجد عدة حواشي مبنية داخل جافا وهي: Override@ ويتم استخدامها عند تجاوز تابع وذلك لجعل المترجم يقوم بالتأكد أننا نقوم حقًا بتجاوز تابع متواجد داخل الأب ونكتبه بشكل صحيح غير مخالف للقواعد. class Square extends Shape{ @Override public int getArea(){ return width * height; } } Deprecated@ ويتم استخدامها لإشارة إلى أن هذا التابع أو الصنف تم إزالته من اللغة وأصبح قديمًا لذا لا يجوز استعماله مرة أخرى ويجب استبداله، وعند كتابة هذه الملاحظة يقوم المترجم بكتابة تحذير للمطور بذلك حتى يُذكّره. وتعتبر الحواشي من الأشياء المهمة عند كتابة الشيفرات ومن الجيد التعود على استخدامها. Method Overloading هي طريقة للسماح للصنف بتعريف تابع أو أكثر لها نفس الاسم. وهناك بعض الشروط الواجب توافرها لتطبيق هذا المفهوم بشكل صحيح فيجب أن تختلف التوابع ذات الاسم نفسه في إحدى هذه العناصر عدد المعاملات Parameters التي يتمر تمريرها. نوع المعاملات التي يتم تمريرها. ترتيب المُعاملات التي يتم تمريرها. class Exampe{ public int add(int x,int y){ return x+y; } public int add(int x,int y,int z){ return x+y+z; } public float add(float x,float y){ return x+y; } } في المثال السابق استخدمنا نفس التابع ()add ولكن بأشكال مختلفة وقمنا بتطبيق إحدى القواعد المطلوبة في كل تابع لتحقيق شرط هذا المفهوم. ولاحظ أنه لا يمكننا كتابة هذا الشكل: public int add(int y,int x){ return x+y; } حيث أنه لا يوجد فرق بينه وبين الشكل الأول فلا يمكننا التمييز بتغيير اسم المتغيرات. ArrayList يوجد داخل لغة جافا صنف يدعى ArrayList وهو يعبر عن قائمة من البيانات تتميز بالمرونة والقدرة على القيام بوظائف عديدة، ويتم تفضيلها عادة على استخدام مصفوفة البيانات التقليدية وذلك لأن المصفوفة يتم تحديدها بعدد من البيانات لا يمكن تغييره، فلا يمكن إضافة عناصر جديدة لها كما لا يمكن إزالة عناصر منها وتقليل العدد. وهذا ما يميز ArrayList لقدرتها على تغيير حجمها والتكييف حسب البيانات المخزنة بداخلها. كما تتميز ArrayList عن Array أو المصفوفة التقليدية بوجود توابع مختلفة تقوم بوظائف عديدة على عكس Array الذي يملك توابع. لتعريف كائن جديد من الصنف ArrayList: ArrayList<String> strObject = new ArrayList<String>(); في المثال السابق قمنا بتعريف كائن من الصنف ArrayList يدعى strObject ويستطيع تخزين بداخله بيانات من النوع String. الآن بعد أن قمنا بتعريف الكائن هناك عدة توابع يمكننا استخدامها مثل: (add(o وهو يقوم بإضافة العنصر (o) إلى القائمة. strObject.add(“Ahmed”); // [“Ahmed”] strObject(“Mohamed”); // [“Ahmed”,”Mohamed”] strObject(“Mariam”); // [“Ahmed”,”Mohamed”,”Mariam”] في المثال السابق نضيف عناصر من النوع String إلى القائمة باستدعاء التابع ()add وتمرير له العنصر الذي نريد إضافته، وفي كل مرة يتم استدعاء التابع يتم تغيير حجم القائمة بشكل مرن. (add(I ,o يختلف عن التابع السابق بأنه يقوم بتحديد المكان (I) الذي يرغب بتخزين العنصر فيه، ففي التابع السابق يتم إضافة العناصر في ذيل القائمة. strObject.add(2,“Sara”); // [“Ahmed”,”Mohamed”,”Sara”,”Mariam”] (set(I,O تقوم بتبديل العنصر المتواجد في المكان (I) بالعنصر (O). strObject.set(1,“Tarek”); // [“Ahmed”,”Tarek”,”Sara”,”Mariam”] في المثال السابق سيتم استبدال العنصر "Mohamed" بالعنصر "Tarek". لاحظ أنه يبدأ الترقيم الخاص بالعناصر من صفر. (get(I يُعيد هذا التابع العنصر المخزن في المكان (I). String name = strObject.get(0); //nama = “Ahmed” في المثال السابق نحصل على العنصر "Ahmed" والمتواجد في المكان 0 (رأس القائمة). ()size لمعرفة عدد العناصر المخزنة داخل القائمة. int numberOfElements = strObject.size(); // numberOfElements = 4 (remove(O لإزالة عنصر محدد من القائمة. strObject.remove(“Mariam”); // [“Ahmed”,”Tarek”,”Sara”] (remove(I لإزالة عنصر المتواجد في المكان (I). strObject.remove(1); // [“Ahmed”,”Sara”] ()clear لإزالة كافة عناصر القائمة. strObject.clear(); // [] وكما قمنا باستخدام ArrayList مع النصوص يمكننا استخدامها مع أي صنف أخر فمثلا يمكنا عمل قائمة من المربعات (صنف Square)، وهو الصنف الذي قمنا بصناعته في أول الدرس. ArrayList<Square> obj = new ArrayList<Square>(); ولإضافة مربع جديد للقائمة. Obj.add(new Square()); ولتغيير الطول والعرض الخاصين بهذا المربع. Obj.get(0).setWidth(15); Obj.get(0).setHeight(15); وهكذا يمكننا التعامل مع عناصر القائمة بنفس الطريقة، فكل عنصر داخل القائمة هو كائن من Square. لاحظ أنه هناك توابع خاصة بالصنف ArrayList وأخرى خاصة بالصنف Square. Interface تتشابه الواجهات (Interface) في بينتها مع الأصناف (Class)، فيمكننا بداخله تعريف توابع ومتغيرات لكنها ذات طبيعة خاصة. فجميع التوابع داخل الواجهة تتكون من تعريف فقط ولا يوجد لها محتوى، وعلى الصنف الذي يُنفذ الواجهة أن يكتب المحتوى الخاص بالتابع. public interface MyInterface { public int method1(); public void method2(); } ولا يمكن إنشاء كائنات من الواجهة (Interface). ولتنفيذ الواجهة نستخدم implements، ويمكن للأصناف (Classes) فقط أن تُنفذ الواجهات (Interfaces). وبداخل الصنف يجب كتابة المحتوى الخاص بالتوابع التي تم تعريفها داخل الواجهة، كما يمكننا أن نكتب التوابع الخاصة بالصنف كما سبق. public class X implements MyInterface{ public int method1(){ return 0; } public void method2(){ } } بعد ذلك يمكننا إنشاء كائنات من الصنف X واستدعاء التوابع كما فعلنا سابقًا. X obj = new X(); Obj.method2(); واستخدام الواجهة هو الطريقة المثالية لتزويد المطور بالتوابع اللازم كتابتها لأداء مهمة ما، فهي تضمن أن الصنف الذي يُنفذ الواجهة قد قام بكتابة كافة التوابع الخاصة به.
  2. يتحدّث هذا الدرس عن موضوعين مهمّين في سي شارب ألا وهما الواجهات Interfaces والمجموعات Collections. تُعتبر المجموعات collections من المزايا القويّة والمهمّة في سي شارب، وهي وسيلة لتخزين العناصر ضمن بنية قابلة للتوسّع تلقائيًّا وسهلة التعامل. إذًا فهي تشبه المصفوفات، باستثناء أنّ المصفوفات ذات حجم ثابت وهي تبقى كذلك منذ لحظة إنشائها، أمّا المجموعات فهي على النقيض من ذلك، فهي بنية قابلة للتوسّع بقدر ما نرغب. بالإضافة إلى أنّ للمجموعات أشكال عديدة مفيدة جدًّا، فهناك المكدّس stack والرتل queue والقاموس dictionary وغيرها من البنى المهمّة. سنتناول في هذا الدرس المجموعات العاديّة، والمجموعات العموميّة generics collections. سنبدأ هذا الدرس بالحديث المختصر عن الواجهات، وهي تشبه الأصناف ولكن بصورة مجرّدة، حيث تصف سمات عامّة ينبغي أن تتحلّى بها الأصناف التي تحقّقها. كلمة عن الواجهات ليست فكرة الواجهات Interfaces محصورةً بسي شارب، فهي موجودة أيضًا في لغات برمجة أخرى مثل جافا Java. تشبه الواجهة Interface الصنف Class بشكل كبير، فهي تضم خصائص وتوابع، ولكن كتصاريح فقط، بدون شيفرة تحوي عبارات برمجيّة، وبدون محدّدات وصول. كما لا تحتوي على حقول fields ولا على بواني. تستطيع القول أنّ الواجهة تعرّف الهيكل العام بشكل مجرّد فحسب. يمكن الاستفادة من الواجهة عندما نحقّقها implement. فعندما يرث صنف ما من واجهة، فيجب عليه أن يعيد التصريح عن جميع الخصائص والتوابع الموجودة ضمن هذه الواجهة ولكن مع تزويدها بحواضن تحوي الشيفرة المطلوب تنفيذها. فكأنّ الواجهة تحقّق مبدأ التعدديّة الشكليّة بشكل ممتاز، فهي تصرّح عن تابع أو خاصيّة، وتترك للصنف الذي يحقّقها (يرث منها) حريّة التعبير عن هذا التابع أو هذه الخاصيّة بالشكل الملائم. تُستخدم الواجهات على نحو واسع جدًّا في سي شارب، وتتبع تسميتها لنفس أسلوب تسمية الأصناف، مع الاصطلاح على أن يكون الحرف الأوّل من اسم أيّ واجهة هو الحرف الطباعي الكبير I وهو الحرف الأوّل من كلمة Interface. في الحقيقة لقد حلّت الواجهات مشكلة الوراثة المتعدّدة (الوراثة من أكثر من صنف بنفس الوقت) والتي كانت تتمتّع بها لغة C++ فقط. الآن أصبح بإمكان أي صنف أن يرث من صنف آخر واحد فقط، بالإضافة إلى تحقيقه لأي عدد يرغبه من الواجهات. يُصرّح عن الواجهة بالكلمة المحجوزة interface. انظر البرنامج Lesson10_1 الذي يوضّح استخدام الواجهات. هل تذكر الأصناف Animal و Bird و Frog و Fish؟ لقد استعرت الصنفين Animal و Frog لتوضيح فكرة استخدام الواجهات، تذكّر أنّ الصنف Frog كان يرث من الصنف Animal. في الحقيقة لقد استفدت من فكرة أنّ الكائنات الحيّة تتنفّس Breathing. لذلك أنشئت واجهة اسمها IBreathing لتعبّر عن عمليّة التنفّس، وبما أنّ الضفدع Frog هو كائن حيّ، فمن الطبيعي أن يحقّق هذه الواجهة. تحتوي الواجهة IBreathing على تابع وحيد اسمه TakeBreath (السطر 9) يقبل وسيطًا واحدًا من النوع double يُعبّر عن كميّة الأكسجين التي سيحصل عليها الكائن الحيّ عند التنفّس، كما يُرجع هذا التابع قيمة من النوع double أيضًا تمثّل كميّة الأكسجين التي بقيت بعد عمليّة التنفّس. يرث الصنف Frog هذه الواجهة في السطر 20، ويحقّقها من خلال إعادة تعريف التابع TakeBreath في الأسطر من 27 حتى 30 حيث يُعبّر عن عمليّة التنفّس بالشكل الذي يناسبه (سيستهلك في مثالنا هذا 20% من كميّة الأكسجين التي يحصل عليها). في الحقيقة يمكن استخدام الواجهة IBreathing مع أيّ "كائن حيّ" بصرف النظر عن كونه يرث من الصنف Animal (الذي يمثّل الصنف الحيواني) أو من الصنف Mammal (الثديّيات) مثلًا أو غيره، وذلك لأنّ جميع الكائنات الحيّة تشترك معًا بخاصيّة التنفّس، وهنا تمكن قوّة الواجهات. 1 using System; 2 3 namespace Lesson10_01 4 { 5 class Program 6 { 7 interface IBreathing 8 { 9 double TakeBreath(double oxygen_amount); 10 } 11 12 class Animal 13 { 14 public virtual void Move() 15 { 16 Console.WriteLine("Animal: Move General Method"); 17 } 18 } 19 20 class Frog : Animal, IBreathing 21 { 22 public override void Move() 23 { 24 Console.WriteLine("Frog - Move: jumping 20 cm"); 25 } 26 27 public double TakeBreath(double oxygen_amount) 28 { 29 return oxygen_amount * 0.8; 30 } 31 32 } 33 34 static void Main(string[] args) 35 { 36 double oxygent_to_breath = 10; 37 38 IBreathing frog = new Frog(); 39 40 Console.WriteLine("Oxygen amount before breath: {0}", oxygent_to_breath); 41 Console.WriteLine("Oxygen amount after breath: {0}", frog.TakeBreath(oxygent_to_breath)); 42 } 43 } 44 } هناك أمر آخر جدير بالملاحظة. انظر إلى السطر 38 ستجد أنّنا قد صرّحنا عن المتغيّر frog من النوع IBreathing ثمّ أسندنا إليه مرجعًا لكائن من النوع Frog، وهذا أمر صحيح تمامًا وشائع جدًّا لأنّ الصنف Frog يرث من الواجهة IBreathing. عند تنفيذ البرنامج ستحصل على الخرج التالي: Oxygen amount before breath: 10 Oxygen amount after breath: 8 المجموعات في سي شارب المجموعات العادية توجد الأصناف المعبّرة عن هذه المجموعات ضمن نطاق الاسم System.Collection. تلعب نطاقات الأسماء دورًا تنظيميًّا للأصناف، وسنتحدّث عنها بشكل أكبر في درس لاحق. من أبرز المجموعات في نطاق الاسم هذا هو الصنف ArrayList. يحقّق الصنف ArrayList كلّ من الواجهات IList و ICollection و IEnumerable و ICloneable. جميع هذه الواجهات تقع في مكتبة FCL في إطار عمل دوت نت، حيث تعرّف هذه الواجهات العمليّات الأساسيّة التي ينبغي أن يتمتّع بها الصنف ArrayList. تسمح الكائنات من هذه المجموعة بإضافة أي نوع من العناصر لها، حيث من الممكن أن نضيف عناصر من النوع object. يمكن إضافة العناصر إلى هذه المجموعة باستخدام التابع Add الذي يقبل وسيطًا من النوع object. أي أنّنا فعليًّا نستطيع أن نضيف عناصر من أنواع مختلفة لنفس المجموعة. أيّ عنصر تتمّ إضافته سيوضع آخر المجموعة التي هي ذات حجم مرن، فمن الممكن إضافة أي عدد نرغبه من العناصر. أمّا إذا أردنا إضافة عنصر إلى مكان محدّد ضمن القائمة، فعلينا استخدام التابع Insert الذي يحتاج إلى وسيطين، الأوّل هو الدليل المراد إدراج العنصر الجديد ضمنه، والثاني هو العنصر نفسه. انظر البرنامج Lesson10_02 البسيط التالي: 1 using System; 2 using System.Collections; 3 4 namespace Lesson10_02 5 { 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 ArrayList values = new ArrayList(); 11 12 values.Add("My "); 13 values.Add("age: "); 14 values.Add(36); 15 16 foreach(object item in values) 17 { 18 Console.Write(item); 19 } 20 21 Console.WriteLine(); 22 } 23 } 24 } استطعنا الحصول على عناصر هذه المجموعة باستخدام حلقة foreach. ولكن إذا أردنا الوصول إلى عنصر محدّد فحسب، ولنقل أنّه العنصر الثالث (القيمة 36) في مثالنا السابق، فيمكن ذلك من خلال الشكل التالي: values[2] تذكّر دومًا أنّ دليل العنصر الأوّل هو 0. في الواقع ستكون القيمة التي سنحصل عليها من [values[2 هي قيمة من نوع object رغم أنّها في حقيقة الأمر تحوي القيمة 36 وهي قيمة من نوع int بطبيعة الحال. السبب في ذلك منطقيّ وهو أنّنا عندما أضفنا القيمة 36 إلى المجموعة كان ذلك باستخدام التابع Add الذي يقبل وسيطًا من النوع object. إذا أردنا الاستفادة من القيمة الفعليّة المخزّنة ضمن [values[2 فعلينا هنا أن نستخدم عامل التحويل (int) على الشكل التالي: int age = (int) values[2]; ملاحظة: عند تمرير القيمة 36 في المثال السابق إلى التابع Add الذي يتوقّع وسيط من نوع object تحدث ظاهرة نسميها التعليب boxing. حيث تُعلَّب القيمة 36 ليصبح بالإمكان تمريرها مكان وسيط يتطلّب النوع object (يبقى هذا الأمر صحيحًا من أجل أي قيمة value type). أمّا عندما نريد استرجاع القيمة الفعليّة فإنّنا نقوم بعمليّة معاكسة تدعى بإلغاء التعليب unboxing باستخدام عامل التحويل بين الأنواع كما فعلنا بالعبارة البرمجيّة الأخيرة: int age = (int) values[2]; هناك العديد من المجموعات الأخرى الموجودة ضمن نطاق الاسم System.Collection، ولكن لن أتحدّث عنها هنا. في الحقيقة إذا أردت نصيحتي حاول ألّا تستخدم المجموعات العاديّة أبدًا! يكمن السبب في ذلك في الفقرة التالية عندما نتحدّث عن المجموعات العموميّة generic collection، حيث سنطّلع على مجموعات تشبه إلى حدٍّ بعيد المجموعات العاديّة الموجودة هنا، ولكنّها عمليّة وأكثر أمانًا. المجموعات العمومية تشبه المجموعات العموميّة generic collections من حيث المبدأ المجموعات العاديّة باستثناء أنّها أكثر أمنًا وأفضل أداءً. حيث ينبغي تعيين نوع العناصر التي ستتعامل معها المجموعة عند التصريح عنها، فتتعامل المجموعة في هذه الحالة مع نوع مُحدّد. من أشهر المجموعات العموميّة هي المجموعة <List<T وهي تعبّر عن القائمة list. قد يبدو الشكل السابق غريبًا قليلًا، ولكنّه في الحقيقة بسيط. استبدل الحرف T بأيّ نوع (صنف) ترغبه وستقبل المجموعة نتيجة لذلك أن يكون عناصرها من هذا النوع. تقع المجموعات العموميّة في نطاق الاسم System.Collections.Generic. سنعدّل البرنامج Lesson09_02 من الدرس السابق الذي كان يسمح بإدخال أسماء ودرجات خمسة طلاب فقط، ويخزّنها على شكل كائنات Student ضمن مصفوفة من النوع []Student وذلك لإيجاد مجموع الدرجات والمعدّل. سنجعل هذا البرنامج يستخدم المجموعة العموميّة <List<Student (مجموعة يمكن لعناصرها تخزين مراجع لكائنات من النوع Student)، سننشئ البرنامج Lesson10_03 لهذا الغرض. 1 using System; 2 using System.Collections.Generic; 3 4 namespace Lesson10_03 5 { 6 class Student 7 { 8 public string Name { get; set; } 9 public int Mark { get; set; } 10 } 11 12 class Program 13 { 14 static void Main(string[] args) 15 { 16 List<Student> listStudents = new List<Student>(); 17 int sum = 0; 18 bool continueCondition = true; 19 int counter = 0; 20 string response; 21 22 Console.WriteLine("Input Students Marks"); 23 Console.WriteLine("====================="); 24 25 //input loop. 26 while(continueCondition) 27 { 28 Student student = new Student(); 29 30 Console.Write("Input student {0} th name: ", counter + 1); 31 student.Name = Console.ReadLine(); 32 33 Console.Write("Input student {0} th mark: ", counter + 1); 34 string tmpMark = Console.ReadLine(); 35 student.Mark = int.Parse(tmpMark); 36 37 listStudents.Add(student); 38 39 Console.WriteLine(); 40 Console.Write("Add another student? (y/n) : "); 41 response = Console.ReadLine(); 42 43 if(response=="n" || response == "N") 44 { 45 continueCondition = false; 46 } 47 48 counter++; 49 } 50 51 Console.WriteLine(); 52 Console.WriteLine("Students Marks Table"); 53 Console.WriteLine("===================="); 54 Console.WriteLine("No\tName\tMark"); 55 56 //calculating sum and display output loop. 57 for (int i = 0; i < listStudents.Count; i++) 58 { 59 sum += listStudents[i].Mark; 60 Console.WriteLine("{0}\t{1}\t{2}", i + 1, listStudents[i].Name, listStudents[i].Mark); 61 } 62 63 Console.WriteLine("-------------------"); 64 Console.WriteLine("Sum\t\t{0}", sum); 65 Console.WriteLine("Average\t\t{0}", sum / (double)listStudents.Count); 66 } 67 } 68 } لقد أجرينا هنا بعض التحسينات. بدأنا البرنامج في السطر 16 بالتصريح عن المتغيّر listStudents من النوع <List<Student وإنشاء كائن من هذا النوع وإسناده لهذا المتغيّر. تقبل المجموعة العموميّة <List<Student بتخزين كائنات من النوع Student ضمنها. لاحظ أنّنا لم نحدّد عدد الكائنات مسبقًا (مع أنّه يمكن ذلك بهدف تحسين الأداء لا غير). وضعنا حلقة while في السطر 26 بدلًا من حلقة for القديمة وذلك لأنّنا لا نعرف على وجه التحديد عدد الطلّاب الذين يرغب المستخدم بإدخال بياناتهم. لاحظ شرط استمرار الحلقة continueCondition الذي يحمل القيمة true بشكل افتراضيّ. أصبح البرنامج غير مقيّدٍ بعدد محدّد من الطلاب، فبعد إدخال بيانات كل طالب، سيعرض البرنامج رسالة يخيّر فيها المستخدم في إضافة المزيد أم التوقّف (السطر 40) فإذا اختار المستخدم التوقّف بإدخاله النص "N" أو "n" عندها سيسند البرنامج القيمة false للمتغيّر continueCondition مما يؤدّي إلى الخروج من حلقة while عند بدء الدورة التالية. تنحصر وظيفة المتغيّر counter الذي صرّحنا عنه في السطر 19 في إظهار ترتيب الطالب الحالي على الشاشة. نستخدم الخاصيّة Count للمجموعة listStudents لمعرفة عدد العناصر الفعليّة المخزّنة ضمنها (تذكّر الخاصيّة Length المماثلة لها في المصفوفات). بعد تنفيذ البرنامج وإدخال بيانات ستة طلّاب، ستحصل على شكل شبيه بما يلي: يوجد تابع اسمه RemoveAt ضمن هذه المجموعة يسمح بإزالة عنصر من القائمة، حيث نمرّر لهذا التابع دليل index العنصر المراد إزالته (دليل العنصر الأوّل هو 0) ليعمل هذا التابع على إزالته وإعادة تعيين أدلّة جميع العناصر بعد إزالة العنصر المطلوب. انظر الشيفرة التالية: List<string> listStrings = new List<string>(); listStrings.Add("Bird"); listStrings.Add("Fish"); listStrings.Add("Frog"); listStrings.RemoveAt(1); أنشأنا في الشيفرة السابقة مجموعة من النوع <List<string (عناصرها من النوع string)، ثمّ أضفنا إليها ثلاثة عناصر. يؤدّي استدعاء التابع (RemoveAt(1 إلى إزالة العنصر ذو الدليل 1 من هذه المجموعة، أي أنّ العنصر ذو القيمة Fish سيُزال من هذه القائمة. يوجد تابع مشابه لهذا التابع اسمه Remove يتطلّب أن تمرّر إليه مرجعًا لكائن موجود في هذه المجموعة لتتم إزالته. فإذا كان النوع العمومي لهذه المجموعة عبارة عن نوع قيمة مثل <List<int أو <List<double فعندها يكفي تمرير القيمة المراد إزالتها للتابع Remove فحسب. علمًا أنّ هذا التابع يزيل أوّل نتيجة تطابق يصادفها في هذه المجموعة. يوجد أيضًا التابع Reverse الذي يعمل على عكس ترتيب العناصر الموجودة في المجموعة، حيث يصبح العنصر الأوّل هو الأخير، والعنصر الأخير هو الأوّل. كما يوجد التابع Sort الذي يعمل على ترتيب العناصر ضمن المجموعة وفق الترتيب الافتراضي (بالنسبة للأنواع المضمّنة) أو وفق ترتيب كيفيّ يمكن للمبرمج أن يختاره. وهناك تابع مفيد آخر وهو BinarySearch الذي يجري خوارزمية البحث الشهيرة على عناصر المجموعة، حيث نمرّر إليه القيمة المراد البحث عنها (أو مرجع الكائن الذي نريد البحث عنه) ويُرجع هذا التابع دليل العنصر ضمن المجموعة في حال وجده. مع الانتباه إلى أنّ هذا الدليل يمثّل دليل العنصر ضمن المجموعة على اعتبارها مرتّبة. إذ أنّه يقوم بترتيبها بشكل داخليّ قبل أن يجري عملية البحث. إذا أردت الحصول على نتائج منطقيّة، فاعمل على ترتيب مجموعتك باستخدام التابع Sort قبل استدعاء BinarySearch. يمكننا الوصول إلى عنصر محدّد ضمن مجموعة عموميّة بنفس الأسلوب التي تحدّثنا عنه في المجموعات العاديّة، مع ملاحظة أنّنا لن نحتاج إلى عامل التحويل بين الأنواع، وبالتالي التخلّص من عمليتيّ التعليب boxing وإلغاء التعليب unboxing. كما يمكن الاستفادة أيضًا من التابع Insert للإدراج ضمن موقع مُحدّد، والذي تحدّثنا عنه أيضًا في المجموعات العاديّة. توجد توابع أخرى مفيدة ضمن المجموعة <List<T ولكنّنا لن نستطيع الخوض فيها قبل أن نتحدّث عن النوّاب delegates في درس لاحق. ملاحظة: توجد طريقة سريعة وفعّالة لإنشاء مجموعة <List<T وإسناد عناصر إليها مباشرةً في حال كان عدد العناصر محدّد ومعروف سلفًا. فإذا أردنا إنشاء مجموعة من النوع <List<int تحوي العناصر 1، 2، 5، 10 يمكنك كتابة العبارة التالية لهذا الغرض: List<int> listNumbers = new List<int>() { 10, 5, 2, 1 }; تنشئ هذه العبارة مجموعة من النوع <List<int وتضيف إليها العناصر 10 و5 و2 و1 ثمّ تسند هذه المجموعة إلى المتغيّر listNumbers. تمارين داعمة تمرين 1 اكتب برنامجًا يطلب من المستخدم إدخال خمس قيم نصيّة، ويخزّنها ضمن مجموعة من النوع <List<string. ثمّ استخدم التابع Reverse لعكس ترتيب العناصر ضمن هذه المجموعة، ثمّ اطبع النتائج على الشاشة. تمرين 2 اكتب برنامجًا يطلب من المستخدم إدخال قيم عدديّة من النوع double بقدر ما يريد، وبعد أن يفرغ من الإدخال، احسب المتوسّط الحسابي (المعدّل) لهذه الأعداد، ورتّبها باستخدام التابع Sort، ثم اطبعها على الشاشة، مع المتوسّط الحسابي لها. (تلميح: استفد من البرنامج Lesson10_03 لسؤال المستخدم هل يريد إضافة عدد جديد أم لا) الخلاصة تعرّفنا في هذا الدرس على الواجهات Interfaces والمجموعات Collections. من النادر أن يخلو أيّ برنامج فعليّ من استخدام المجموعات أو الواجهات، وفي الحقيقة هناك العديد من بنى المجموعات المفيدة التي لم نتناولها في هذا الدرس. سنحاول أن نتوسّع في المزايا القويّة والرّائعة للمجموعات في سلسلة قادمة.
  3. التجريد – Abstraction نعلم أنَّ الصنف المُشتَق يأخذ خاصياته من الصنف الأب لكن الصنف المُشتَق مستقل تمامًا عن الصنف الأب؛ وقد يكون في بعض الأحيان من الجيد أن نرسم خطوطًا عريضة لآلية سلوك الصنف الابن، وهذه هي مهمة الأصناف والدوال المجردة. إذ أنَّ الصنف المجرد يحتوي على دوال غير مكتملة (أي مجردة) التي يجب أن يملأها الابن لكي يكون صنفًا وعدا ذلك سيكون صنفًا مجردًا أيضًا. بكلامٍ آخر، الصنف المُجرَّد (Abstract Class) هو صنف يحتوي على أسماء دوال دون كتابة الشيفرات المسؤولة عن عملها وتسمى تلك الدوال بالدوال المجردة، وقد يحتوي أيضًا على دوال كاملة اعتيادية تؤدي وظيفتها تمامًا. انظر إلى المثال الآتي لمزيدٍ من الإيضاح: <?php // تعريف صنف مجرد abstract class AbsClass { function __construct() { echo "this is an abstract class <br>"; } // دالة مجردة abstract public function abs_function(); function full_function() { echo "this is not an abstract function <br>"; } } class SubClass extends AbsClass { function __construct() { echo "this is child class <br>"; parent::full_function(); } // تعريف الدالة المجردة public function abs_function() { echo "this is completed abstract function <br>"; } } $obj = new SubClass(); $obj->abs_function(); ?> نستعمل الأصناف المجردة عندما يلزمنا إنشاء طريقة محددة للتعامل مع عدِّة أصناف مُشتقَّة، التي قد تتشارك ببعض الوظائف. ملاحظة: لا يمكن إنشاء كائن من صنف مجرد، حيث لا يمكن إلا اشتقاق تلك الأصناف. يستعمل الصنف المجرد لتقييد عمل الصنف الابن. الواجهات interfaces من بين الحالات التي نستعمل فيها الواجهات (interfaces) هي عندما نريد تطبيق التعددية الشكلية أي أن تكون طريقة تعاملنا متشابهة مع عدِّة أصناف. الواجهة هي مجموعة من الدوال المجردة أي أنك تعرف اسم الدالة مع المعاملات التي تقبلها لكن دون تحديد طريقة عمل الدالة، ويمكن للصنف أن يستعمل أكثر من واجهة، لكن يجب أن يعيد تعريف كل الدوال الموجودة في تلك الواجهة، انظر إلى المثال الآتي لأخذ فكرة عن الواجهات: <?php // تعريف واجهة interface MyInterface { // abstract functions // all must be public public function display(); public function another($argument); } // واجهة أخرى interface AnotherInterface { public function complete_it(); public function one_more(); } class Parent { function parent_fun() { echo "parent function"; } } // صنف يشتق صنفًا آخر ويستعمل واجهة class Demo extends Parent implements MyInterface, AnotherInterface { public function display() { echo "display complete"; } public function another($argument) { #code } public function complete_it() { #code } public function one_more() { #code } } ?> نستعمل الواجهات عندما نريد إنشاء طريقة موحدة للتعامل مع عدِّة أصناف، فمثلًا، نُنشِئ واجهة اسمها Database فيها دوال مجردة مثل select و insert وغيرها، ثم نستعمل تلك الواجهة في صنف MySQL وفي صنف SQLite، ونعيد تعريف الدوال الموجودة في الواجهة بما يلائم طريقة عمل كل نوع من أنواع قواعد البيانات. وبهذه الطريقة نستطيع أن نستعمل قواعد بيانات MySQL أو SQLite بنفس الآلية تمامًا. ملاحظة: يجب أن تكون جميع الدوال داخل الواجهة عامةً، يمكن أن يرث صنفٌ ما صنفًا آخر ويستعمل واجهة بنفس الوقت، لكن يجب أن يكون تعريف الاشتقاق قبل الواجهات. السمات Traits قدم الإصدار 5.4.0 من PHP ميزة السّمات Traits، وهي طريقة تسمح بإعادة استعمال الشيفرات في اللغات التي لا تسمح بالوراثة المتعددة، وهي تقلل من المحدوديات الناتجة عن عدم السماح بالوراثة المتعددة عن طريق إتاحة استعمال مجموعة من الدوال في عدة أصناف. أي لو كانت عندك مجموعة من الدوال العامة، وترغب في مشاركتها بين أكثر من صنف، فضعها في Trait ثم استعملها (use) في تلك الأصناف. يُعرف Trait عبر ذكر الكلمة المحجوزة trait متبوعةً باسمه، ثم تُعرَّف الدوال داخله. وتُستعمل الكلمة use عند تعريف صنف يستعمل Trait معين كما في المثال الآتي: <?php trait HelloWorld { public function sayHello() { echo 'Hello World!'; } } class World { use HelloWorld; } $obj = new World(); $obj->sayHello(); // ستُطبع عبارة Hello World! ?> يمكن إعادة تعريف الدوال داخل الأصناف التي تستعمل Trait معيّن، كما في المثال الآتي: <?php trait HelloWorld { public function sayHello() { echo 'Hello World!'; } } class World { use HelloWorld; } class NewWorld { use HelloWorld; public function sayHello() { echo 'Hello Universe!'; } } $obj1 = new World(); $obj1->sayHello(); // ستُطبع عبارة Hello World! $obj2 = new NewWorld(); $obj2->sayHello(); // ستُطبع عبارة Hello Universe! ?> يمكن استعمال أكثر من Trait في نفس الصنف عبر ذكر أسمائهم في عبارة use مفصولًا بينهم بفاصلة، لاحظ أنه إذا عُرِّفَت دالتين بنفس الاسم في أكثر من Trait، ثم استعملناها في صنف، فسيحدث تضارب وتظهر رسالة خطأ fetal error، ويمكنك حل مشكلة التضارب في الأسماء عبر استعمال المعامل insteadof أو عبر as كما في المثال الآتي: <?php trait A { public function smallTalk() { echo 'a'; } public function bigTalk() { echo 'A'; } } trait B { public function smallTalk() { echo 'b'; } public function bigTalk() { echo 'B'; } } class Talker { // لدينا في A و B دالتين اسمهما bigTalk و smallTalk // ما يلي سيجعل الصنف يستعمل الدالة smallTalk من B عوضًا عن مثيلتها في A // و bigTalk من A عوضًا عن B use A, B { B::smallTalk insteadof A; A::bigTalk insteadof B; } } class Aliased_Talker { use A, B { B::smallTalk insteadof A; A::bigTalk insteadof B; // لاحظ كيف استعملنا as لتغير اسم الدالة في الصنف B::bigTalk as talk; } } ?> مصادر مقالة Abstraction and Interface in PHP لصاحبها Harish Kumar فصل Objects في كتاب Practical PHP Programming فصل البرمجة غرضية التوجه في كتاب تعلم البرمجة بلغة PHP صفحات Object Interfaces و Traits في دليل PHP وغيرها.