الأربعاء، 10 سبتمبر 2014

تعلم AS3 - جزء2 [ OOP ] - الدرس 3 [خصائص الدوال و المتغيرات]

السلام عليكم و رحمة الله
مرحبا بكم في درس جديد من دورة تعلم الاكشن سكربت3






في الدرس السابق طلبت منكم انشاء كلاس player
و كتابة هذا الكود  داخل ال timeline

var  p1 : player = new player("player 1");
trace(p1.life);

و النتيجة كانت كما تبينه الصورة


1178: Attempted access of inaccessible property life through a reference with static type player.

يعني أن الدخول الى attribute  اسمه life غير مسموح لنا.

غير مسموح يعني أننا لا نستطيع قراءة محتواه و لا تغييره من timeline

_____________________________________________

من الذي منعنا من الدخول اليها؟ و لماذا؟ 
هل هو مشكل؟ و كيف يتم حله؟

يجب أن أقول ان لكل متغير و دالة في الأكشن سكربت 3 خصوصيات
و هناك  5 خصائص و هي:

internal: يمكن قراءة أو تغيير المحتوى فقط من كلاسات أخرى من نفس ال package.
public: المحتوى يمكن دخوله من أي كلاس اخر.
private : يمكن دخوله فقط من الكلاس الخاص به.
protected: يمكن دخول المحتوى من نفس الكلاس الخاص به و الكلاسات المشتقة منه (سنرى هذا في درس لاحق)
static: هذه الكلمة تجعل المتغير عام (global)  و يتم دخوله بكتابة اسم الكلاس و ليس اسم ال instance 
(سنشرحه في الاسفل)

و نكتبها قبل كلمة var  بهذا الشكل
property var varName:type;

لكننا في الكود السابق لم نعطي للمتغير أي خاصية !

في الاكشن سكربت 3, اذا لم نعطي للمتغير أي خاصية من الخواص الخمس التي ذكرناها 
فان المترجم يعتبره internal و هذا ما يعني أن الوصول اليها يكون فقط من كلاسات من نفس ال package.

و منه نستننج أن الكود المكتوب داخل timeline ليس من نفس الحزمة (package) 
الموجودة فيها كلاس player.
_____________

مهم ! :

عند تشغيل البرنامج (بالضغط على CTRL +R ) أول الأكواد التي يتم تنفيذها هي الأكواد 
الموجودة داخل ال timeline .

هناك الكثير من أنواع لغات البرمجة... سأتكلم عن نوعين و هما
لغات من نوع البرمجة الكائنية (oriented object programming) مثل جافا و الاكشن سكربت ..الخ
لغات عبارة عن مجموعة دوال نقوم بكتابتها ثم استعمالها مثل لغة C

كلاهما يملكان دالة  أساسية بحيث تكون هي أول دالة تنفذ تلقائيا
نقول أنها نقطة دخول البرنامج
أي منها يبدأ البرنامج
مثلا في C و جافا الدالة لها اسم main 

في الأكشن سكربت 3 الكلاس الرئيسي (نقطة الدخول) هو MainTimeline  
اكوادها مكتوبة داخل ال timeline

هل نحن مجبرون على كتابة أكواد نقطة الدخول في timeline؟

الجواب هو لا.. فيمكننا كتابة كلاس اخر من انشائنا بحيث منه يبدأ برنامجنا
سنشرح الطريقة في جزء اخر, لحد الان سنكتب الاكواد داخل timeline
____________


عد الى كود الكلاس player
ثم قم بتغيير المتغير life الى public
يعني
public var life:int;

أعد تشغيل البرنامج تاركا الكود السابق  في timeline 

var  p1 : player = new player("player 1");
trace(p1.life);

و سترى انه يمكننا الان الدخول الى المتغير life  لما وضعنا الخاصية public ! وقمنا بقراءة محتواه
فالان يمكننا قراءة و تغييره كما نريد


var  p1 : player = new player("player 1");
p1.life = 50;
trace(p1.life);

________________________________________

هل من الأفضل دائما كتابة المتغيرات على شكل public؟
 و هكذا يمكننا قراءتها و تغييرها من أي مكان...
فما الفائدة من الخصائص الاخرى..


لحسن الحظ أن الخصائص الاخرى مثل private , protected.. موجودة
و إلا فبامكاننا ارتكاب أخطاء فاذحة و نصدم بنتائج غير متوقة  بالرغم من أن الكود صحيح
فالخصائص معمولة لحماية المستخدمين عند تغييرهم لقيمة المتغير
و المستخدمون في هذه الحالة هم مستخدموا  الكلاسات -أي المبرمجون- و ليس مستخدموا  البرنامج 

و قد تكون الحماية ضدك أنت بالرغم من أنك صاحب الكلاس
فبعد مرور زمن طويل قد تنسى أو لا تنتبه و تقوم بتغيير قيمة المتغير بطريقة غير صحيحة

اليكم هذا السيناريو :


- قام أحمد بانشاء كلاس تدعى PageGen من اجل صنع صفحات كتابية

هذه الكلاس تحتوي على عدة دوال كاضافة صفحة و حذف صفحة ..الخ
و مجموعة متغيرات منها متغير (global) لحساب عدد الصفحات. فليكن هذا

public static var NB_PAGES : int;

في ال constructor نضيف 1 الى عدد الصفحات (فلقد أنشأنا صفحة جديدة)

- أحمد يقدم الكلاش لأسامة الذي يبرمج معه هذا البرنامج
أسامة لا يهمه كيف قام احمد بصنع تلك الدوال و أي متغيرات يستعمل
كل ما يهمة هو أن الدالة addPage مثلا تقوم باضافة صفحة ..الخ
أسامة يقوم بعرض عدد صفحاته بالدخول مباشرة الى المتغير الخاص بالكلاس NB_PAGES هكذا


trace(PageGen.NB_PAGES);

أثناء التطوير خطرة فكرة لأحمد و أراد تحديث كلاساته (update) 
حيث يقوم بوضع كل الصفحات داخل جدول و يتخلى عن المتغير NB_PAGES
لأنه يمكنه التعرف على عدد الصفحات عن طريف حساب طول الجدول

public static var PAGES_ARRAY:Array;

يستلم أسامة النسخة الجديدة المليئة بالتغييرات إلا أنه سيُصطم بأخطاء أثناء الترجمة
فالمتغير PageGen.NB_PAGE أصبح غير معروف 

أسامة يحاول اقناع احمد لارجاع النسخة القديمة لكن احمد سعيد بعمله الجديد و يرفض.
فمن المخطئ؟
أحمد هو المخطئ لأنه جعل التعامل مع المتغيرات مباشر و هذا شئ غير منصوح به
في البرمجة فلو قام أحمد بصنع دالة اسمها getNbPage:
- النسخة الاولى ترجع لنا المتغير NB_PAGES 
أسامة يستعمل getNbPage و لا يعلم كيف تعمل بالداخل 
- النسخة الثانية ترجع لنا طول الجدول
أسامة يستعمل دائما نفس الدالة و النتيجة دائما صحيحة !
____________

سيناريو 2:

لدينا كلاس User تحتوي على الكثير من المتغيرات

منها private var userName:String
private var pseudo:String
...
userName: اسم المستخدم الحقيقي
pseudo: اسمه الافتراضي

يقوم المستخدم بكتابة اسمه على هذا الشكل CHAfik
طبعا لو نعطي هذه القيمة مباشرة الى المتغير userName سيصبح 
userName يحمل القيمة CHAfik
و لكنني كمبرمج أريد أن يكون الحرف الاول كبير و بقية الاحرف صغيرة

اذن الدخول المباشر الى المتغير غير منصوح به هنا أيضا فمن الأفضل 
صنع دالة setUserName مثلا و التي نقوم فيها بتعديل الاسم

_____________

- و اذا أردت تغيير متغير private من كلاس اخر؟؟!

سنستعمل  ال getters و setters
و هناك طريقتين :

getter/setter  الطريقة الأولى :


1.Setter : 

سنعود الى السيناريو الثاني 
يجب انشاء دالة تقوم بفحص المداخل قبل اعطائه للمتغير 

public function setUserName(n : String):void
{
          this.userName = n.charAt(0).toUpperCase() + s.substr(1).toLowerCase();
}

نفرض أن المدخل هو chAfiK
(n.charAt(0 سبق وأن رأيناها في درس الجداول و السلاسل الحرفية
و تعني أننا سنذهب الى الحرف الأول 
()toUpperCase: تحويل الحرف الى حرف كبير

n.charAt(0).toUpperCase  
يعني اننا سنأخذ الحرف الاول و نقوم بتكبيره( لحد الان userName قيمته C)

 + : لل concatenation يعني اضافة كلمة لكلمة اخرى
(substr(1: هذه الدالة سنتكلم عنها في درس اخر 
لكن في هذا المثال ستقوم بحذف الحرف الاول فقط
اذن تعطينا hAfiK 
toLowerCase: تحويل الاحرف الى احرف صغيرة
hafik

اذن قمنا باضافة C الى hafik لتصبح Chafik  :)

لكتابة قيمة في المتغير نستدعي الدالة بهذا الشكل:
instanceName.setUserName("chafik");

(instanceName اسم الكائن)
_______________


2. Getter:

نبقى دائما مع السيناريو الثاني

في الكود السابق أردنا تغيير قيمة المتغير userName الى chAfiK

لكننا ادخلناه عن طريق دالة كوسيط لتعديله كما أردنا
كيف يمكننا أن نقرأ محتواه الان ؟
لو حاولنا الدخول اليه مباشرة فلن نستطيع لأنه private
trace(p1.userName);

يلزمنا من اجل هذا كتابة دالة داخل الكلاس و التي تقوم بارجاع قيمة المتغير 
بما ان الدالة موجودة داخل الكلاس اذن بامكانها الدخول الى المتغير private
سنستغل هذا لنرجع قيمة userName بهذه الدالة بهذا الشكل


public function getUserName() : String
{
       return (this.userName);
}

كما نرى فالدالة public يعني بامكاننا استدعائها من أي كود
 نوعها String يعني ترجع لنا قيمة من نوع سلسلة احرف
و هكذا نحصل على قيمة userName :)

لفحص ما يحتويه المتغير userName نعمل الاتي
trace( instanceName.getUserName() );
____________________________________

2.الطريقة الثانية getter/setter :
في الطريقة السابقة كنا نغير/نقرأ  قيم المتغيرات باستدعاء الدوال المخصص 
instance.setVar(value);
instance.getValue();
لكن هناك طريقة اخرى للكتابة و القراءة كما لو كانت المتغيرات public مثلا
instance.attribute = value;
trace(instance.attribute);

كما نرى لاوجود للقوسين ... 

و ذلك باستعمال الكلمة الدلالية set / get :

1.set
:  الهدف منها هو كتابة قيمة داخل المتغير المراد مثلما رأينا سابقا و يجب ان تحتوي على مدخل واحد على الاقل و الا سيكون هناك خطأ في الترجمة
public function set userName(n:String):
{
       this._userName = n.charAt(0).toUpperCase() + n.substr(1).toLowerCase();                    
}
يجب تغيير اسم المتغير من userName الى userName_ (مثلا) او اي اسم اخر
لانه يُمنع كتابة دالة لها نفس اسم متغير 
اذن للدخول الى المتغير userName_ نقوم بالتالي

instanceName.userName = value;

value: القيمة المراد ادخالها

_________________

2.get : 

هذه الدالة يجب ان ترجع لنا قيمة  و أيضا تكون بدون مداخل
و الا سيكون هناك خطأ في الترجمة
و تكتب هكذا

public function get userName():String
{
        return this._userName;
}
للحصول على قيمة ال userName 
trace(instanceName.userName);






ما قمنا بشرحه يسمى ب Encapsulation و يعني اخفاء المتغيرات و هذا جد مهم
___________________________________________


لحد الان تعرفنا على الخصائص internal و public و private 

الخاصية protected تعني أن المتغيرات يمكننا الدخول اليها من الكلاس الموجودة فيه (مثلما رأينا مع private)
و أيضا من الكلاسات المشتقة منها (و سنرى هذا لما ندخل في درس التوريث, inheritance او héritage بالفرنسية)



static:

 كلمة static تجعلنا ندخل الى المتغير عن طريق اسم الكلاس و ليس ال instance
و نقصد بهذا انها سيكون متغير عام (global) و لا يخص فقط الكائن و سنشرحه بالتفصيل في هذا الدرس

سابقا عندما أنشأنا  الكائنين p1 و p2  ثم قام الاعب 1 بمهاجمة الاعب 2
كان هناك تغير اختلاف في قيم كل لاعب
يعني المتغيرات الخاصة بالكائن p1 كانت لها قيم
أما الكائن p2 فكانت له هو أيضا قيما اخرى
مثلا p1.life تساوي 100
p2.life تساوي 99


للدخول الى قيمة life الخاصة بالاعب نكتب اسم الاعب . المتغير
p1.life
p2.life
...


الان نعود الى الكلاس player
و نضيف هذا ال attribute في الاعلى امام المتغيرات الاخرى
اسمه counter و الهدف منه هو حساب عدد الاعبين 
يعني في كل مرة ننشئ لاعبا جديدا نضيف قيمة 1 الى المتغير counter

public static var counter: uint= 0;



الان نغير ال constructor


public function player(playerName :String= "player_anonyme")
{
    this.playerName = playerName;
    xp = 0;
    life = 100;
    player.counter++;
}




المتغير counter هو static اذن يمنع كتابة this.counter داخل الكلاس فهو لن يخص أي كائن و انما الكلاس
ثم ندخل الى ال timeline و نكتب

var p1 : player = new player("Player1");
var p2 : player = new player("Player2");
trace( player.counter);

سكتب لنا 2


كما نلاحظ فان المتغير counter تم استدعاؤه عن طريق اسم الكلاس و ليس الكائن

بامكاننا أيضا اعطاءها خصوصيات اخرى مثل private static 
(و هو ما ينصح به في هذا المثال فالمتغير counter بما انه public بامكاننا تغييره
من كلاس اخر مثلا
player.counter = 50;
بالرغم اننا نملك فقط لاعبين
اذن من الافضل أن تكون private و في نفس الوقت صنع getter لمعرفة القيمة
فقط دون وضع setter)
و لكن مبرمج الحرية و طريقة تفكير خاصة به 


نلتقي في درس اخر ان شاء الله




ليست هناك تعليقات:

إرسال تعليق


جميع الحقوق محفوظة لمدونة 2014-2015 as3arabic |