الاثنين، 29 سبتمبر 2014

تعلم AS3 - جزء2 [ OOP ] - الدرس 4 [التوريث inheritance]

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




في هذا الدرس سنرى مفهوما جديدا و مهما في البرمجة الكائنية و هو التوريث

__________________

كلمة توريث مأخوذة من العالم الحي 
كل عنصر في العالم يأخذ خصائص عنصر اخر 
مثلا الانسان له الكثير من خصائص الثدييات (الانسان يرث من الثدييات)
الموز له خصائص  الفواكه (الموز يرث من الفواكه تلك الخصائص)

يُستعمل التوريث في العلاقات من نوع "is a" أي "هو.."
مثلا في لعبة ما لدينا الاعبون العاديون(player) و المشرفون على اللعبة(admin)
المشرفون  'هم لاعبون'  (فهم يملكون نفس الخصائص) 

المشرف يرث كل خصائص الاعب العادي
الكلاس player تدعى الكلاس الأم أو سوبر كلاس (super class)
و الكلاس admin تدعى الكلاس البنت (sub-class)

_______________________

الوراثة في الأكشن سكربت 3 :


لكي ترث كلاس نفس خصائص كلاس اخر نستعمل الكلمة الدلالية
extends



مثال الكلاس admin ترث من الكلاس player


package
{
         public class admin extends player
        {
               public function admin()
             {
                      // constructor code
             }
        }
}

الان لكي ننشئ instance للكلاس admin نستعمل نفس الطريقة السابقة  نذهب الى timeline و نكتب الكود

var myAdmin : admin = new admin();


اذا جربنا هذا الكود ستظهر لنا  رسالة خطأ 1203

عندما ترث كلاس بنت من كلاس أم 
يجب علينا أن نمرّ على ال constructor الخاص بالأم مع احترام المداخل الخاص به

لكي ندخل الى constructor الخاص بالأم نستعمل الكلمة super


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




في هذا الدرس سنرى مفهوما جديدا و مهما في البرمجة الكائنية و هو التوريث

__________________

كلمة توريث مأخوذة من العالم الحي 
كل عنصر في العالم يأخذ خصائص عنصر اخر 
مثلا الانسان له الكثير من خصائص الثدييات (الانسان يرث من الثدييات)
الموز له خصائص  الفواكه (الموز يرث من الفواكه تلك الخصائص)

يُستعمل التوريث في العلاقات من نوع "is a" أي "هو.."
مثلا في لعبة ما لدينا الاعبون العاديون(player) و المشرفون على اللعبة(admin)
المشرفون  'هم لاعبون'  (فهم يملكون نفس الخصائص) 

المشرف يرث كل خصائص الاعب العادي
الكلاس player تدعى الكلاس الأم أو سوبر كلاس (super class)
و الكلاس admin تدعى الكلاس البنت (sub-class)

_______________________

التوريث في الأكشن سكربت 3 :


لكي ترث كلاس نفس خصائص كلاس اخر نستعمل الكلمة الدلالية
extends



مثال الكلاس admin ترث من الكلاس player


package
{
         public class admin extends player
        {
               public function admin()
             {
                    super(); 
             }
        }
}

________________________________________

الهدف من التوريث :



فلنفرض ان في الكلاس player هذه المتغيرات 
var playerName:String;
var xp:int;
var life:int;

و هذه الدوال 
    public function attack( p2:player):void
public function receive(xp:int):void
public function die():void


و أردنا انشاء نوع اخر من الاعبين بحيث تكون له نفس خصائص الاعب العادي و لكن اضافة الى هذا 
يمكنه الاشراف عليهم 
مثلا طرد الاعبين الغشاشين 
حذف الاعب ..الخ

عوضا ان ننشئ كلاس admin نضع بداخلها المتغيرات 
var playerName:String;
var xp:int;
var life:int;

و الدوال 

    public function attack( p2:player):void
public function receive(xp:int):void
public function die():void


يكفي أن نستعمل extends و ستأخذ الكلاس admin كل خصائص الكلاس player

________________________________________

مثال اخر

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

عوضا أن ننشئ الكلاسات الثلاث و نكتب نفس المتغيرات و الدوال في كل كلاس  مثلا
var maxSpeed
var sound
..
.function set MaxSpeed(s:int):void
function get MaxSpeed():int

....

يكفي انشاء كلاس animal و وضع كل الخصائص
ثم نستعمل extends لكي ترث باقي الكلاسات منها
public class lion extends animal
public class dog extends animal
..

_____________________________________________________________________


super-types / sub-types

بفضل التوريث الكلاس admin تملك نوعين
player  : يعتبر super-type
admin: يعتبر sub-type

لو نكتب  الكود التالي

var myAdmin : admin = new admin();
trace(myAdmin is admin); //true
trace(myAdmin is player);  //true

سيظهر لنا true و هذا يعني أن myAdmin هو admin و player

كلما قمنا بتوسيع ال super type (توسيع يعني هناك كلاسات ترث منه)
يمكننا تمرير instance من sub-type
يعني المتغيرات من نوع player يمكنها تخزين كائنات من نوع admin

مثال
var myAdmin : player = new admin();
trace(myAdmin is admin); //true
trace(myAdmin is player);  //true


كل admin هو player 
المترجم يعلم ان كل الخصائص الموجودة في player هي موجودة أيضا في admin

العكس غير صحيح 
var myAdmin : admin = new player();


_________________________

ما الفائدة من تمرير متغير sub type الى متغير super type؟

فلنفرض أننا نملك دالة ترجع لنا  أنواع مختلفة من الكائنات 
مثلا
getBestPlayer():player

هذه الدالة مثلا ترجع لنا أقوى لاعب في اللعبة من نوع player
لكن قد يكون أقوى لاعب من نوع admin أو نوع اخر
يمكننا و بدون أي مشكل ارجاع أحسن لاعب مهما كان نوعه player او admin
return (admin_type);

مثال اخر

هذه الدالة موجودة داخل الفلاش بحيث سترجع لنا كل انواع الكائنات الجرافيكية (سنراها في الاجزاء القادمة)

public function getChildAt(index:int):DisplayObject

هناك الكثير من المتغيرات الجرافيكية مثل 
shape MovieClip Sprite SimpleButton

كل هذه الانواع ترث من الكلاس DisplayObject و لهذا فإن تلك الدالة ترجع لنا مختلف الانواع الجرافيكية 

______________


ماذا لو جربنا الكود التالي؟


نفرض أن في الكلاس admin الدالة التالية
banPlayer(p:player):void

  الهدف منها طرد لاعب لسبب معين

هل هذا الكود صحيح؟

var playerHacker : player = new player();
var myAdmin : player = new admin();
myAdmin.banPlayer(playerHacker);


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

الكلاس player لا تملك الدالة banPlayer  بالرغم من أننا مررنا للمتغير myAdmin كائن من نوع admin

لكي نتمكل من تنفيذ الدالة banPlayer نستعمل نفس الطريقة التي رأيناها في درس المتغيرات (casting)
هكذا
 type ( myObject)

ليصبح الكود هكذا


var playerHacker : player = new player();
var myAdmin : player new admin();

admin( myAdmin ).banPlayer(playerHacker);



هذه المفاهيم في البرمجة الكائنية تدعى Polymorphism 
____________



تعديل دالة موروثة (override) :

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

لكن أحيانا نحتاج الى تغيير دالة ما كإضافة بعض الاكواد مثلا

فلنفرض أن في الكلاس player لدينا دالة introduceMySelf
حيث سنظهر فيها اسم الاعب

public function introduceMySelf():void
{
       trace("my name is :"+ this.playerName);
}

أنا في الكلاس admin عوضا أن أظهر اسم الاعب أريد أن أظهر رسالة i'm admin 

override public function introduceMySelf():void
{
      trace("I'm admin!");
}

ملاحظة مهمة:
الدالة المعدلة يجب أن تملك نفس اسم و المداخل و نوع ارجاع الدالة الام


عندما نعدل دالة ما فهذا لن يفقدنا الدالة الموروثة .يمكننا الدخول الى الدالة الام عن طريق الكلمة super

override public function introduceMySelf():void
{
      super.introduceMySelf();  // الدخول الى الدالة الام و التي تظهر اسم الاعب
      trace("I'm admin!");
}



نلتقي  في الدرس القادم ان شاء الله
تابع القراءة Résuméabuiyad

الأربعاء، 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)
و لكن مبرمج الحرية و طريقة تفكير خاصة به 


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



تابع القراءة Résuméabuiyad

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