اعادة التعريف من المميزات التي لايتسغني عنها اي مبرمج محترف لعمل فئات وتطبيقات احترافية، سواء اعادة تعريف للطرق Method overloading او اعادة تعريف للعوامل Operator overloading. في هذا الدرس ساحاول ان اوضح اساسيات هذه المفاهيم مع ذكر امثلة.
اعادة التعريف للطرق Method overloading
اعادة التعريف للطرق ببساطة هي امكانية عمل عدة طرق سواءا اوامر او وظائف، لها نفس الاسم والنوع، ولكن الاختلاف يكون في الوسيطات Parameters التي تمرر الى الطريقة. مثلا يمكنك عمل وظيفتين الاثنين لهم نفس الاسم MyFunc ولكن الاختلاف في الباراميترات التي تمرر اليها، فالاولى لا تقبل اي باراميتر، والثانية تقبل باراميتر واحد من نوع int . باختصار شديد، عمل عدة نسخ من الوظيفة متماثلة في الاسم وتختلف في طريقة تقبلها للبيانات. حتى تتضح الفكرة اكثر سأذكر امثلة على ذلك.
أمثلة حول الاعادة التعريف للطرق
المثال الاول سيكون بسيط، وهو تطبيق لمبدا الاعادة التعريف على وظيفة بسيطة:
| CODE: |
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
0025
0026
0027
0028
0029
0030
0031
0032
|
class olTest
{
// النسخة الاولى من الوظيفة
public void Print()
{
Console.WriteLine(\"Welcome !
\");
}
// النسخة الثانية
// لاحظ انها تاخذ باراميتر واحد
// من نوع نصي وتقوم بطباعته
public void Print(string name)
{
Console.WriteLine(\"Welcome {0}!
\",name);
}
// النسخة الثالثة
// تقبل باراميتران، الاول نصي
// والثاني رقمي يحدد عدد مرات الطباعة
public void Print(string name,int times)
{
for (int i=0;i < times;i++)
{
Console.WriteLine(\"{0}- Welcome {1}!\",i+1,name); }
}
}
|
الان دعونا نجرب الفئة السابقة:
| CODE: |
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
|
class Class1
{
public static void Main()
{
olTest test = new olTest();
// تجربة الاجراء الاول
Console.WriteLine(\"*****First one******\");
test.Print();
// تجربة الاجراء الثاني
Console.WriteLine(\"******Second one******\");
test.Print(\"mohammed\");
// تجربة الاجراء الثالث
Console.WriteLine(\"******Third one******\");
test.Print(\"mohammed\",4);
Console.Read();
}
} |
عند تنفيذك للمثال السابق المخرجات ستكون كالتالي:
| CODE: |
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
|
*****First one******
Welcome !
******Second one******
Welcome mohammed!
******Third one******
1- Welcome mohammed!
2- Welcome mohammed!
3- Welcome mohammed!
4- Welcome mohammed!
|
في المثال السابق، قمنا بعمل اجراء اسمه Print ، واعدنا تعريفه ثلاثة مرات، المرة الاولى الاجراء لايقبل اي باراميتر فقط يقوم بطباعة كلمة Welcome ، في الاجراء الثاني قمنا باضافة باراميتر واحد من نوع نصي، وعند تمرير هذا الباراميتر الى الاجراء يتم طباعة كلمة Welcome بالاضافة الى الاسم الذي تم تمريره، الاجراء الثالث يقبل باراميتران، الاول من نوع نصي ليتم طباعته، والثاني من نوع رقمي int يحدد عدد مرات طباعة الجملة. كما ترى بدلا من تسمية كل اجراء باسم مختلف، تم تسمية جميع الاجراءات باسم واحد مع اختلاف الباراميترات، وللمعلومية اكثر الوظائف والاجراءات في بيئة NET. تكون لها عدة نسخ Overloaded.
مثال اخر: عادة يستخدم اعادة التعريف Overloading في منشئات الفئات Constructors . المقصود بالمنشئات هي الاجراءات التي يتم تنفيذها عند عمل نسخة جديدة من الفئة. فمثلا فئة المستطيل rectangle تمكنك من انشاء المستطيل فورا بطريقتين، الاولى كالتالي:
| CODE: |
0001
0002
0003
|
Rectangle rect = new Rectangle(Point, Size);
|
لاحظ انك ستمرر باراميتران، الاول هم نقطة والثاني هو الحجم. وفي نفس الوقت يمكنك انشاء المستطيل بطريقة اخرى كالتالي:
| CODE: |
0001
0002
0003
|
Rectangle rect = new Rectangle(int, int, int, int);
|
حيث انك ستمرر اربعة باراميترات، اثنين للنقطة، والاثنين الاخرين للطول والعرض. كما ترى يمكنك بواسطة الاعادة التعريف تسهيل عملية التعامل مع الفئة التي تنشئها لتجعلها اكبر مرونة. على العموم سأذكر مثال كامل يبين كيفية التعامل مع الاعادة التعريف في المشيدات constructors:
| CODE: |
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
0025
0026
0027
0028
0029
0030
0031
0032
0033
0034
0035
0036
0037
0038
|
class Rect
{
int X;
int Y;
int Width;
int Height;
// المنشئ الاول
// يقبل قيمتين، الاولى النقطة والثانية الحجم
public Rect(Point point, Size size)
{
this.X = point.X;
this.Y = point.Y;
this.Width = size.Width;
this.Height = size.Height;
}
// المنشئ الثاني
// يقبل اربعة قيم رقمية
// تحدد النقطة والطول والعرض
public Rect(int x, int y, int width, int height)
{
this.X = x;
this.Y = y;
this.Width = width;
this.Height = height;
}
public void Print()
{
Console.WriteLine(\"X={0},Y={1}\",X,Y);
Console.WriteLine(\"Width={0},Height={1}\",
Width, Height);
}
}
|
لتجربة المثال السابق اكتب التالي:
| CODE: |
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
|
class Class1
{
public static void Main()
{
// تجربة المنشئ الاول
Console.WriteLine(\"First const.\");
Rect test = new Rect(1,2,3,4);
test.Print();
// تجربة المنشئ الثاني
Console.WriteLine(\"
Second const.\");
Point point = new Point(10,20);
Size size = new Size(30,40);
test = new Rect(point, size);
test.Print();
Console.Read();
}
}
|
كما ترى في المثال السابق، يمكنك انشاء نسخة من الفئة rect والتي تحاكي مستطيل بطريقتين مختلفتين، الاولى بواسطة تمرير نقطة point تحدد نقطة البداية للمربع، والثانية من نوع Size تحدد حجم المربع او المستطيل. والطريقة الثانية بواسطة تمرير اربعة نقاط تحدد نقطة البداية والطول والعرض. لاحظ ان المثال السابق كان للتوضيح لا اكثر.
طرق خاطئة في التعامل مع الاعادة التعريف
كما ذكرت في اول المقال، اعادة التعريف يمكننا من انشاء عدة وظائف او اجراءات لها نفس الاسم والنوع ولكن الاختلاف في الباراميترات التي يتم تمريرها، لذلك لايمكنك مثلا من انشاء وظيفتين لهم نفس الاسم ولكن الاختلاف في النوع، فمثلا لايمكنك انشاء وظيفة اسمها MyFunc تعود بقيمة نصية string ووظيفة اخرى اسمها MyFunc ايضا ولكنها تعود بقيمة رقمية. مثلا:
| CODE: |
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
0025
|
class olWrongTest
{
void Func(string input)
{
Console.WriteLine(\"First version\");
}
// خطا
// نوع الوظيفة لايطابق الوظيفة الاولى
double Func(string input)
{
Console.WriteLine(\"Second version\");
}
// ايضا خطأ
// لاحظ ان الباراميترات متشابهه في النوع
void Func(string something)
{
Console.WriteLine(\"Second version\");
}
}
|
ببساطة، عند استخدامك للاعادة التعريف يجب ان تكون كل النسخ متشابهة في القيمة المعادة، ومختلفة في الباراميترات التي يتم تمريرها.
اعادة التعريف للمعاملات Operator overloading
على غرار اعادة التعريف overloading، حيث يمكنك التحكم في كيفية التعامل مع الطرق Methods بناءا على الباراميترات التي تمرر الى الطريقة، اعادة التعريف للمعاملات يمكنك من اعادة تعريف للمعاملات، مثل + او – او / او * أو ++ او -- ...الخ. يمكنك من اعادة تعريف هذه العوامل للتعامل مع الانواع الخاصة user-defined types او الفئات. لاوضح لكم الفكرة، تخيلوا ان لدينا فئة للتعامل مع الفواتير وليكن اسمها Cinvoice، وقمت بعمل نسختين من الفاتورة كل نسخة تحتوى على اصناف لاتحتويها الفاتورة الاخرى، يمكنك تعريف معامل الجمع + بحيث انك لو كتبت السطر التالي:
| CODE: |
0001
0002
0003
|
newinvoice = invoice1 + invoice2;
|
سيكون الناتج فاتورة جديدة تحتوى على جميع الاصناف الموجودة في الفاتورتين. كما يمكنك مثلا تعريف معامل الطرح – بحث انك لو طرحت عدد من الفاتورة، كالتالي مثلا:
| CODE: |
0001
0002
0003
|
invoice1 = invoice1 – 1;
|
سيتم حينها خصم 1 من القيمة الاجمالية للفاتورة.
في الاحوال العادية اذا لم تستخدم الاعادة التعريف للعوامل سيقوم المترجم باصدار رسالة خطا عند محاولتك لجمع الفاتورتين كما في المثال السابق، عملية الاعادة التعريف للمعاملات سهلة وتتطلب منك اضافة اجراء مشابه للاجراء التالي:
| CODE: |
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
|
public static theType operator+ (theType A, theType B)
{
// اجراء العملية الحسابية
// ارجاع القيمة الجديدة
// الناتجة عن عملية التحميل
return theReturn;
}
|
الشيفرة واضحة، في السطر الاول قمنا بعملية تعريف للاعادة التعريف، لاحظ الكلمات التي باللون الاحمر، theType تكون من نفس نوع الفئة عادة، والمعامل + يمكنك تغييره الى المعامل الذي تريده مثل الطرح – او القسمة / او الضرب * ...الخ. ثم تبدا في عمل العملية الحسابية او المنطقية التي تريدها اذا حصل التحميل. حتى توضح الفكرة بشكل اكبر سأسرد مثال متكامل يوضح فكرة الاعادة التعريف.
مثال حول الاعادة التعريف للعوامل
في المثال التالي ساقوم بعمل فئة الفواتير التي تحدثت عنها قبل قليل. الفئة التالية مهمتها حفظ الاصناف واسعارها، كما تحتوى على وظيفة بسيطة لطباعة شكل يحاكي الفاتورة على الشاشة. المهم في المثال هو الاعادة التعريف، فعند القيام بعملية جمع لفاتورتين من الفئة هذه.. الفاتورة الناتجة ستحتوى على اصناف الفاتورتين التي تم جمعهما :
| CODE: |
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
0025
0026
0027
0028
0029
0030
0031
0032
0033
0034
0035
0036
0037
0038
0039
0040
0041
0042
0043
|
using System;
using System.Collections;
public class OperatorOverloading
{
public static void Main()
{
/****************
سنقوم بانشاء فاتورتين واضافة بعض
الاصناف اليها، ثم نقوم بجمع الفاتورتين
بواسطة معامل الجمع + ونطبع الفواتير
****************/
Cinvoice inv1 = new Cinvoice();
inv1.MyinvDetails.Add
(new invoiceItem(\"ball pen\",12.5));
inv1.MyinvDetails.Add
(new invoiceItem(\"Eraser\",1.45));
inv1.Showinvoice();
Cinvoice inv2 = new Cinvoice();
inv2.MyinvDetails.Add
(new invoiceItem(\"Pencil\",2.95));
inv2.MyinvDetails.Add
(new invoiceItem(\"Bag\",35.95));
inv2.Showinvoice();
Cinvoice inv3;
inv3 = inv1 + inv2;
inv3.Showinvoice();
Console.Read();
}
}
|
/***********************
الفئة التالية بسيطة وهي عبارة عن
فئة تحمل قيمة اسم الصنف والسعر
ليتم استخدامها في الفئة الرئيسية
***********************/
| CODE: |
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
|
class invoiceItem
{
string itemname;
double itemprice;
public invoiceItem
(string _item, double _price)
{
this.itemname = _item;
this.itemprice = _price;
}
public string ItemName
{
get{return itemname;}
set{itemname = value;}
}
public double ItemPrice
{
get {return itemprice;}
set {itemprice = value;}
}
}
|
/***********************
هذه الفئة الرئيسة التي تحتوي على مصفوفة
من الفئة السابقة لتكون فاتورة
لاحظ ان الاعادة التعريف تم تطبيقه في
هذه الفئة
***********************/
| CODE: |
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
0025
0026
0027
0028
0029
0030
0031
0032
0033
0034
0035
0036
0037
0038
0039
0040
0041
0042
0043
0044
0045
0046
0047
0048
0049
0050
0051
0052
0053
0054
0055
0056
0057
0058
0059
|
class Cinvoice
{
public ArrayList MyinvDetails;
public Cinvoice()
{
MyinvDetails = new ArrayList();
}
// امر عرض الفاتورة
public void Showinvoice()
{
Console.WriteLine(\"Line Item Price\");
int i = 1;
double total = 0;
foreach(invoiceItem detailLine in MyinvDetails)
{
Console.WriteLine(\"{0} {1} {2:c}\",
i++, detailLine.ItemName,
detailLine.ItemPrice);
total += detailLine.ItemPrice;
}
Console.WriteLine(\"=============\");
Console.WriteLine(\"Total: {0:c}
\", total);
}
// تطبيق الاعادة التعريف على المعامل +
public static Cinvoice operator+
(Cinvoice invoice1, Cinvoice invoice2)
{
Cinvoice returninvoice = new Cinvoice();
foreach (invoiceItem detailLine in
invoice1.MyinvDetails)
{
returninvoice.MyinvDetails.Add(detailLine);
}
foreach(invoiceItem detailLine in
invoice2.MyinvDetails)
{
returninvoice.MyinvDetails.Add(detailLine);
}
// عند الجمع، نقوم بجمع الاصناف
// من الفاتورتين في فاتورة مؤقتة
// ثم نرجع قيمتها
return returninvoice;
}
}
|
الان عند تنفيذ المثال السابق سيكون الناتج كالتالي:
| CODE: |
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
|
Line Item Price
1 ball pen $12.50
2 Eraser $1.45
=============
Total: $13.95
Line Item Price
1 Pencil $2.95
2 Bag $35.95
=============
Total: $38.90
Line Item Price
1 ball pen $12.50
2 Eraser $1.45
3 Pencil $2.95
4 Bag $35.95
=============
Total: $52.85
|
كما تلاحظ، تم جمع الصنفين في الفاتورة الاولى بالصنفين في الفاتورة الثانية، والنتيجة في الفاتورة الثالثة هو احتوائها على اربعة اصناف وهو ناتج جمع الفاتورتين الاولى والثانية.
متى نستخدم اعادة التعريف للعوامل ؟
نصيحتي لك، لاتستخدم الاعادة التعريف للعوامل من باب فرد العضلات، او من باب تطبيق فكرة اعادة التعريف للعوامل وفقط ! .. استخدم اعادة التعريف للعوامل فقط في حالة ان المستخدم العادي يستطيع ان يفهم المعاملات التي قمت بتحميلها، فمثلا، مثال الفاتورة السابق يعتبر فكرة جيدة لاستخدام الاعادة التعريف، بينما مثلا، اذا استخدمت التحميل في معامل الجمع ++ للقيام بعملية على نص مثلا، كجعل الحروف تزيد بمقدار واحد اذا استخدمت المعامل، هذه فكرة خاطئة.. حاول دائما ان تكتب شيفرة يستطيع اي مبرمج اخر فهمها بالاضافة الى الاهتمام بفئاتك وجعلها سهلة للمستخدمين الذين يستخدموا الفئة من بعدك... ونصيحة اخرى، اذا رأيت ان استخدام الاعادة التعريف للمعامل قد يسبب تشتيت للمبرمجين، لا تستخدم التحميل واستخدم الطرق Methods بدلا من التحميل للقيام بالعمليات.. ففي الاخير هي تعتبر اسهل واوضح.
خاتمة
اعادة التعريف للطرق Method overloading يعتبر ميزة رائعة، لذلك احرص على ان تطبق الفكرة في كل مرة ترى ان الفكرة قد تزيد كفائة فئاتك، بالنسبة للاعادة التعريف للعوامل فهو مفيد بشكل خيالي في مثير من الاحيان، ولكنه في نفس الوقت قد يؤدي الى التشويش على المبرمجين الذين يستخدموا فئاتك، لذلك احرص على اختيار المكان المناسب لتطبيقها. واخيرا تمنياتي لك ببرمجة سعيدة !
|