منتديات للامريم للتكنولوجيا الحديثة المجتمع الرقمي
هل تريد التفاعل مع هذه المساهمة؟ كل ما عليك هو إنشاء حساب جديد ببضع خطوات أو تسجيل الدخول للمتابعة.

مقدمة إلى البرمجة الوظيفية لمطوري net.

اذهب الى الأسفل

مقدمة إلى البرمجة الوظيفية لمطوري net. Empty مقدمة إلى البرمجة الوظيفية لمطوري net.

مُساهمة  Samah الأحد سبتمبر 18, 2011 9:44 am

إلى الآن ,من الجيد أنك سمعت حول لغة الF# ,وهي الإضافة الأخيرة لعائلة الفيجوال ستوديو.
يوجد العديد من الأسباب لتعلم لغة
F#. وذلك لسهولة قواعد كتابتها ,وهي متوافقة مع لغات إطار .NET
لغة #F
بها العديد من الأفكار الجديدة المهمة التي يجب فهمها قبل تناول مزاياها.
لغة
#F هي لغة مختلفة ,وهي لغة برمجة وظيفية ,وأتت بمفاهيم جديدة يمكن أن تكون تفتقدها,
علاوة على ذلك فد جرت العادة بتناول اللغات الوظيفية في الأوساط الأكاديمية,لذلك المفاهيم الجديدة من الصعب فهمها.

من حسن حظك #F
لم يتم تصميمها لتكون لغة أكاديمية, قواعدها تسمح لك لتستخدم تقنيات فعالة لحل المشاكل بطريقة جديدة وبطرق أفضل,
طالما أنها تدعم البرمجة كائنية التوجه والأنماط التي اعتدتها كمطور
.NET
بخلاف لغات ال

.NET الأخرى #F هي لغة متعددة الهياكل مما يعني أنك تستطيع اختيار النمط الأفضل لبرمجة حل للمشكلة التي تواجهك.
وظيفية لغة #F

تكمن في كتابة أكواد موجزة وقوية لحل مشاكل برمجية عملية.
وهي لغة تجعل من السهل فهم واختبار وتخصيص أكوادك بإزالة التعقيدات منها.


الآن لمعرفة مزايا تلك الخصائص الجميلة للغة #F

. أنت تحتاج لفهم مبادئها.
في هذه المقالة سيتم شرح هذه الأفكار باستخدام مفاهيم مألوفة لديك كمطور
.NET
سيتم أيضا عرض التقنيات التي تستطيع تطبيقها على أكوادك وبعض الطرق التي كنت تبرمج باستخدامها بوظيفية مسبقا.




مبادئ البرمجة الوظيفية:



من السهل لأغلب مطوري ال

.NET فهم ماهية البرمجة الوظيفية بفهمهم هي ما ليست عليه.
البرمجة الإجرائية والبرمجة الوظيفية تختلفان بشكل أساسي ,الأن انظر من الأبسط في الأكواد التالية:












رموز PHP:




int number = 0;
number++;









الكود أعلاه يقوم بزيادة قيمة المتغير بمقدار 1,لكن هذا ليس مثيرا جدا.
لكن ضع في الاعتبار الطرق المختلفة لحل المشكلة:

رموز PHP:




const int number = 0;
const
int result = number + 1;









أيضا قمنا بزيادة قيمة الرقم بمقدار 1,لكن لم يتم فعل ذلك في ذات المكان.
بالمقابل,النتيجة حفظت كثابت آخر لأن المترجم لا يسمح لك لتعديل قيمة الثابت.
وقتها ستقول أن الثوابت غير قابلة للتغيير لأنك لا تستطيع تغيير قيمها وقت تعريفها.
بشكل مقابل المتغير في أول مثال كان قابل للتغيير لأنك تستطيع تغيير قيمته.
هذان الأمران يوصلانا لأحد الاختلافات الأساسية بين البرمجة الإجرائية والبرمجة الوظيفية.
البرمجة الإحرائية تؤكد على استخدام المتغيرات القابلة للتغيير,بينما البرمجة الوظيفية تستخدم القيم غير قابلة للتغيير.
غالبية مطوري ال.NET كانوا سيقولون أن الرقم والنتيجة في المثال التوضيحي السابق متغيرات.
لكن كمبرمج وظيفي عليك أن تكون حذر.
المبرمجون الوظيفيون يقولون أن الأرقام والنتائج هي قيم, بعد كل هذا الثوابت مربكة في أفضل الأحوال.

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

رموز PHP:




string stringValue = "world!";
string result = stringValue.Insert(0, "hello ");









دالة "Insert" في المثال أعلاه ترجع النص "hello world!" ,
لكن كما تعلم أن دالة Insert لا تعدل قيمة النص المصدر ,هذا لأن النصوص غير قابلة للتغيير في ال.NET .
مطوروا إطار ال.NET صمموا طريقة وظيفية والتي تجعل من السهل كتابة كود أفضل مع النصوص .
والتي هي الأكثر استخداما في إطار ال.NET



التجهيز للعمل مع #F


أتت لغة #F مع فيجوال ستوديو 2010,تستطيع العثور على آخر إصدار من msdn.microsoft.com/vstudio .
إن كنت تستخدم فيجوال ستوديو 2008 فبالإمكان تحميل إضافة #F من مركز مطوري F# msdn.microsoft.com/fsharp .
لغة البرمجة الوظيفية #F تضيف نافذة جديدة للفيجوال ستوديو تسمى F# Interactive والتي تسمح لتنفيذ أكواد #F .
سنستخدم F# Interactive في هذه المقالة لنرى ما سيحدث عند تنفيذ الأكواد القادمة.
هذا مثال بسيط بلغة #F .

رموز PHP:




let number = 0
let result
= number + 1









قم بتحديد الكود والضغط على Enter + Alt ستظهر نافذة F# Interactive. وستحصل على ما يلي:


رموز PHP:




val number : int = 0
val result
: int = 1









ربما ستخمن أنه باستخدام المصطلح val أن الرقم والنتيجة كلاهما قيم غير قابلة للتغيير.
يمكنك أن ترى ذلك بتطبيق المثال التالي والذي استخدمنا فيه معامل الارتبطا <- :


رموز PHP:




number <- 15









سيظهر خطأ يخبرك بأن القيمة غير قابلة للتغيير هذا لأنه كما تعلم فإن البرمجة الوظيفية بنيت على مصطلح عدم القابلية للتغيير.
هذا الخطأ من المفترض أن يوحي بمعنى.
الكلمة المفتاحية "let" تنشيء ارتباطات غير متغيرة بين أسماء وقيم.
لاحظ أنه كل شيء في #F هو ثابت بشكل افتراضي لكن يمكنك عمل متغيرات قابلة للتغيير إذا أردت ذلك.
الأشياء الافتراضية هي مجرد عكس ما تألفه في البرمجة الإجرائية.
لاحظ المثال التالي:

رموز PHP:




let mutable myVariable = 0
myVariable
<- 15










الاستنتاج المحلّي للنوع Type Inference, والتحسّس للمسافات البيضاء Whitespaces

#F تعرف المتغيرات وقيمها دون الحاجة لتحديد نوعها,

لذلك ربما كنت قد فرضت أن #F ديناميكية,لكن هذا غير صحيح.
من المهم فهم أن #F لغة مشابهة ل #C و++C .
بالرغم من ذلك فإن #F تملك نظام واجة نوع بيانات يسمح لك بتجنب تحديد أنواع الكائنات في أماكن مختلفة.
هذا يسمح بقواعد بيسطة وموجزة,طالما بقى النوع مدعوم بأمان في اللغات الستاتيكية.
بالرغم من أن نظام واجة النوع يختلف عما هو موجود في اللغات الإجرائية,
فإن واجة نوع البيانات غير مرتبطة مباشرة بالبرمجة الوظيفية .
بغض النظر عن ذلك,واجهة نوع البيانات هي فكرة حرجة لفهم إن كنت تود تعلم لغة ال#F.
من حسن الحظ,إن كنت مطور #C ,الفرص مألوفة مسبقا لديك مع واجهة نوع البيانات بسبب الكلمة المفتاحية var.




رموز PHP:




// هنا ,نوع البيانات تم التصريح عنه
Dictionary<string, string> dictionary = new Dictionary<string, string>();

// لكن هنا نوع البيانات استدلالي
var dictionary = new Dictionary<string, string>();









كلا السطرين في الكود السابق الخاص ب#C ينشيء كائنات جديدة من نوع Dictionary<string, string> ,
لكن الكلمة var تخبرنا أن ندع المترجم يستدل على نوع المتغير.
#F أخذت هذه الفكرة للمرحلة القادمة. كمثال: هنا دالة جمع باستخدام #F .




رموز PHP:




let add x y = x + y
let four
= add 2 2









وبتحديد الكود وبالضغط على Alt + Enter نلاحظ التالي:

رموز PHP:




val add : int -> int -> int
val four
: int = 4









الآن أنت تستطيع تفسير معنى أن الدالة add عرفت لتأخذ وسيطين من نوع int . والمغير four عرف لint .
الاستدلال لنوع البيانات هو طريقة تمكن #F من تقليل الفوضى في شيفرتك,
لكن لاحظ أنه لا يوجد أقواس مجعدة أو كلمات مفتاحية لتحديد جسم دالة add أو كلمة مفتاحية لإرجاع قيمتها.
هذا لأن ال#F حساس للمسافات البيضاء بشكل افتراضي. في #F أنت تحدد جسم الدالة بفجوات,
وترجع قيمة الدالة بكونك متأكدا من آخر سطر في الدالة.



تأثيرات جانبية

الآن بت تعرف الفرق بين البرمجة الوظيفية والبرمجة الإجرائية اعتمادا على
القيم الغير قابلة للتغيير مقابل المتغيرات القابلة للتغيير.


لكن هذه الحقيقة ليست ذات جدوى في حد ذاتها.
الخطوة التالية هي أن تفهم الآثار الجانبية.
في البرمجة الإحرائية,مخرجات الدالة تعتمد على الوسيطات المدخلة والحالة الحالية للبرنامج,
بينما في البرمجة الوظيفية فالدوال تعتمد فقط على مدخلاتها.
بكلمات أخرى,عند استدعائك لدالة أكثر من مرة بنفس القيمة المدخلة,
فإنك تحصل على نفس النتائج وهذا ليس صحيحا بالنسبة للبرمجة الإجرائية تبعا لآثار جانبية,
كما هو موضح في المثال التوضيحي التالي:







رموز PHP:




public MemoryStream GetStream()
{
var
stream = new MemoryStream();
var
writer = new StreamWriter(stream);
writer.WriteLine("line one");
writer.WriteLine("line two");
writer.WriteLine("line three");
writer.Flush();
stream.Position = 0;
return
stream;}

[
TestMethod]
public
void CausingASideEffect()
{
using (var reader = new StreamReader(GetStream()))
{
var
line1 = reader.ReadLine();
var
line2 = reader.ReadLine();
Assert.AreNotEqual(line1, line2);
}
}








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




رموز PHP:




public class PiggyBank
{
public
PiggyBank(int coins){ Coins = coins; }
public
int Coins { get; set; }
}

private
void DepositCoins(PiggyBank piggyBank)
{
piggyBank.Coins += 10;}
private
void BuyCandy(PiggyBank piggyBank)
{
if (
piggyBank.Coins < 7)
throw new
ArgumentException( "Not enough money for candy!", "piggyBank");
piggyBank.Coins -= 7;
}









إذا كان لديك خمس قطع معدنية ,تستطيع استدعاء الدالة "DepositCoins" قبل "BuyCandy" لكن عكس الطلب يطلق استثناء,




رموز PHP:




// هذا سيعمل جيدا
var piggyBank = new PiggyBank(5);
DepositCoins(piggyBank);
BuyCandy(piggyBank);
// لكن ذلك سيطلق استثناءً
var piggyBank = new PiggyBank(5);
BuyCandy(piggyBank);
DepositCoins(piggyBank);








دالة BuyCandy و DepositCoins كلاهما قامتا بعمل تحديث لحالة piggybank بسبب التأثيرات الجانبية.
بناءً على ذلك فإن سلوك كل دالة يعتمد على حالة piggybank وذلك بسبب أن عدد العملات قيمة قابلة للتغيير.
الآن دعونا نجعل عدد العملات "Coins" للقراءة فقط, في محاولة لجعلها غير قابلة للتغيير,
المثال التالي يظهر أن الدوال BuyCandy و DepositCoins ترجع كائنات جديدة بدلا من تحديث الحالي.




رموز PHP:




public class PiggyBank
{
public
PiggyBank(int coins){ Coins = coins; }
public
int Coins { get; private set; }
}
private
PiggyBank DepositCoins(PiggyBank piggyBank)
{
return new
PiggyBank(piggyBank.Coins + 10);
}
private
PiggyBank BuyCandy(PiggyBank piggyBank)
{
if (
piggyBank.Coins < 7) throw new ArgumentException( "Not enough money for candy!", "piggyBank");
return new
PiggyBank(piggyBank.Coins - 7);
}








كالسابق, لو حاولت استدعاء BuyCandy قبل DepositCoins ,ستحصل على استثناء.


رموز PHP:




// ما زال يطلق استثناء
var piggyBank = new PiggyBank(5);
BuyCandy(piggyBank);
DepositCoins(piggyBank);








لكن الآن لو أعدت الطلب,ستحصل على نفس النتيجة:



رموز PHP:




// الآن ذلك سيطلق استثناء أيضا
var piggyBank = new PiggyBank(5);
DepositCoins(piggyBank);
BuyCandy(piggyBank);








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

في #F أنت تركز على تقييم الدوال بمخرجاتها بدلا من أي تأثيرات جانبية.


في البرمجة الإجرائية, من الشائع استدعاء الدوال لصنع شيء ما,
في البرمجة الوظيفية فإن الدوال تستخدم للحصول على نتائج,
تستطيع رؤية ذلك في #F من خلال النظر في تعليمة if






رموز PHP:




let isEven x = if x % 2 = 0 then "yes" else "no"








في #F حتى تعليمات if صممت لترجع قيم.



رموز PHP:




let isEven2 x = let result = if x % 2 = 0 then "yes" else "no"








نوع النتيجة هو نص, ويتم تعيينها مباشرة خلال تعليمة if , وهذا شبيه بطريقة معامل الشرط في #C :



رموز PHP:




string result = x % 2 == 0 ? "yes" : "no";









الدوال المركبة

الآن وبما أنك شاهدت بعض المزايا للدوال الحرة من التأثيرات الجانبية,


فأنت مستعد لاستخدام دوال بإمكاناتها التامة في ال #F .
أولا, دعونا نبدأ بكود #C لأخذ مربعات الأرقام من 0 ل 10








رموز PHP:




IList<int> values = 0.Through(10).ToList();
IList<int> squaredValues = new List<int>();
for (
int i = 0; i < values.Count; i++)
{
squaredValues.Add(Square(values[i]));
}









بالنسبة للدالتين Through و Square فهي دوال مساعدة,
خبراء ال#C من المحتمل أن يستاؤوا بسبب استخدام for بدلا من foreach , وهم محقون في ذلك.
اللغات الحديثة كال #C تتيح حلقات foreach ,
التي من شانها تسهيل التعامل مع التعدادات بإزالة الحاجة لاستخدام الفهارس.
وهم نجحوا في ذلك, لكن لاحظ المثال التالي :




رموز PHP:




IList<int> values = 0.Through(10).ToList();
// square a listIList<int> squaredValues = new List<int>();
foreach (int value in values)
{
squaredValues.Add(Square(value));
}
// filter out the even values in a list
IList<int> evens = new List<int>();
foreach(
int value in values)
{
if (
IsEven(value))
{
evens.Add(value); }
}
// take the square of the even values
IList<int> results = new List<int>();
foreach (
int value in values)
{
if (
IsEven(value))
{
results.Add(Square(value)); }
}








حلقات foreach في المثال السابق متشابهة لكن جسم كل حلقة ينفذ عملية مختلفة قليلا.
أما في البرمجة الوظيفية, يتم استخدام دوال حرة من التأثيرت الجانبية :



رموز PHP:




let numbers = {0..10}
let squaredValues = Seq.map Square numbers










كود ال#F هذا أيضا يقوم بتربيع متتابعة من الأرقام,
لكنه يقوم بذلك باستخدام دوال عالية الطلب, وهي ببساطة دوال تقبل دوال أخري كمدخلات.
في هذه الحالة, الدالة Seq.map تقبل دالة Square كمدخل.
وهي تقوم بتطبيق دالة Square لكل رقم في متتابعة الأرقام وترجع متتابعة بالأرقام المربعة.
الدوال عالية الطلب هي السبب في جعل المطورين يتحدثوا عن كون البرمجة الوظيفية تستخدم الدوال كبيانات.
وهذا يعني أن الدوال من الممكن استخدامها كوسيطات أو يتم تعيين قيم متغيرات مباشرة من الدوال,
وهو ما يشبه في C# ال"delegates " و "lambda expressions" .
الدوال عالية الطلب من التقنيات التي تجعل البرمجة الوظيفية قوية .
بإمكانك استخدام الدوال عالية الطلب لتجنب الأكواد المكررة في حلقات foreach ,
وتغليفها لدوال مستقلة حرة من التأثيرات الجانبية.
هذه الدوال كل منها ينفذ عملية صغيرة والتي ضمنا داخلها تم تنفيذ حلقة foreach.
وبسبب أنها دوال حرة من التأثيرات الجانبة تستطيع دمج تلك الدوال وجعلها أكثر قابلية للقراءة




رموز PHP:




let squareOfEvens = numbers |> Seq.filter IsEven |> Seq.map Square










الجزء المحتمل أن يكون قد أحدث تشويشا هو المتعلق بما يعني المعامل <| .
ما قام به هذا المعامل هنا أنه أخذ numbers أولا وبعد ذلك تم فلترة
المتتابعة وأخذ الزوجي منها ثم تم تربيعه, بمعنى هنا مرت المتتابعة في عدة
مراحل في سطر واحد فقط !
وهو ما يوازي السطر التالي:



رموز PHP:




let squareOfEvens2 = Seq.map Square (Seq.filter IsEven numbers)









لو كنت تستخدم LINQ , تنفيذ الدوال عالية الطلب بهذا الأسلوب يجب أن يكون مألوف جدا لك.
وذلك لأن LINQ تم التأسيس عليها في البرمجة الوظيفية.
الكود التالي في #C موازي للكود السابق:



رموز PHP:




var squareOfEvens = numbers .Where(IsEven) .Select(Square);









وهو ما يوازي باستخدام الLINQ:


رموز PHP:




var squareOfEvens = from number in numbers where IsEven(number) select Square(number);










استخدام LINQ في ال#C أو فيجوال بيسك يسمح لك لاستغلال بعض من قوة البرمجة الوظيفية.
وهي وسيلة عظيمة لتتعلم تقنيات البرمجة الوظيفية.



التمشيط والتطبيق الجزئي

الآن أصبح لديك المعرفة الكافية للبدء بالعمل مع #F . لكن هناك بعض المفاهيم عليها أن تكون مألوفة لديك.


في الأمثلة السابقة المعامل <| والأسهم كلاهما موصولة بمفهوم معروف ب"التمشيط".
التمشيط يعني تجزئة الدالة مع بعض المتغيرات إلى سلسلة من الدوال,
والتي كل منها يأخذ أحد المتغيرات والتي بالنهاية تنتج نفس النتيجة كالدالة الأصلية.
التمشيط فعليا هو الموضوع الأكثر تحديا في هذه المقالة لمطور الNET. ,
وبشكل خاص بسبب أنها في الغالب مشوِّشة مع التطبيق الجزئي.
تستطيع رؤية كلاهما في هذا المثال :





رموز PHP:




let multiply x y = x * y
let double
= multiply 2
let ten
= double 5










التعليمة الثانية تنشئ دالة جديدة تسمى double بتمرير وسيط واحد لدالة تأخذ اثنتين.
الناتج دالة تقبل وسيط واحد و تنتج نفس المخرج كما أنك لو استدعيت الدالة multiply بجعل x =2 و بجعل y مساوية لهذا الوسيط.
وهو ما يشبه سلوك الكود التالي:




رموز PHP:




let double2 z = multiply 2 z










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

لاحظ مراحل الدالة multiply باستخدام نافذة F# Interactive :



رموز PHP:




val multiply : int -> int -> int










وحقيقة ما حصل هو التالي :
دالة multiply هي سلسلة من دالتين, تقوم بربط x بقيمة محددة.كما يتم فعل ذلك مع y.
بعد استدعاء الدالة الثانية ,فالنتيجة هي حاصل ضرب x و y كما هو معرف في جسم الدالة double.


استخدام #F والبرمجة الوظيفية

الآن لديك مصطلحات كافية للبدء بالعمل مع #F والبرمجة الوظيفية.


لديك الكثير من الخيارات لما يمكنك فعله لاحقا.
F# Interactive تسمح لك بعرض كود #F مع بناء سريع لأكواد #F
#F تبرع في التعبير عن خوارزميات معقدة,
لذا تستطيع تغليف تلك الأجزاء من تطبيقاتك إلى مكتبات #F والتي تستطيع لاحقا استخدامها في لغات أخرى تنطوي تحت منصة .NET
وهذا مفيد خصوصا في هندسة التطبيقات.
أخيرا,تستطيع استخدام تقنيات البرمجة الوظيفية بدون كتابة أكود #F
.يمكنك استخدام LINQ بدلا من حلقات foreach.
حاول استخدام delegates لإنشاء دوال عالية الطلب,
قلل من استخدام المتغيرات القابلة للتغيير والتأثيرت الجانبية في البرمجة الاعتيادية.
في حال بدأت كتابة أكواد بالشكل الوظيفي ,قريبا ستجد نفسك تميل أكثر للكتابة باستخدام#F















__________________

سُبْحَانَك اللَّهُم لَا عِلْم لَنَا إِلَّا مَا عَلَّمْتَنَا
Samah
Samah
مديرة فريق البرمجة
مديرة فريق البرمجة

عدد المساهمات : 226
نقاط : 533
السٌّمعَة : 14
تاريخ التسجيل : 22/11/2010
الموقع : casablanca

الرجوع الى أعلى الصفحة اذهب الى الأسفل

الرجوع الى أعلى الصفحة

- مواضيع مماثلة

 
صلاحيات هذا المنتدى:
لاتستطيع الرد على المواضيع في هذا المنتدى