السلام عليكــم ورحمـة الله وبركاتــه ،،
هذه كلاس استعملها لتنفيد الخصائص في برامج سي++ كما هو الحال بالنسبة للغات الأخرى كلغات الدوت نت. الشيء المهم في هذه الكلاس هو امكانية كتابة كود يبدو و كأنه يتحكم في متغيرة, مثال :
Test t; t.value = 5; Test tt; tt.value = t.value;
بدل الكود العادي لـ سي++
Test t; t.SetValue(5); Test tt; tt.SetValue(t.GetValue());
هذه الكلاس تستطيع التعامل مع كلتا طريقتي الكتابة أعلاه لتمكن كل واحد من التعامل مع الكلاس المنفدة للخصائص بالطريقة التي يريد.
هذه الكلاس هي إلى حد ما نوع من أنواع smart pointers حيث الكلاس تسير متغيرة لا يمكن للعناصر الخارجية الوصول إليها بطريقة مباشرة. و من هنا يمكن لنا التحكم في شروط قبول و رفض تغيير محتوى الخاصية و حتى رفض أو قبول قراءة محتواها. و الأهم من هذا هو القيام بتنفيد مهام بمجرد تغير في محتوى خاصية.
لإعلان خاصية :
property<C, T> name; // حيث C هو نوع الكلاس التي تحتوي على الخاصية و T هو نوع الخاصية و name هو اسم العنصر الحاوي للخاصية
بعد إعلان الخاصية يتوجب علينا تهيئتها للإستعمال و ذلك بزيادة دالتين الأولى تتكلف بقراءة محتوى الخاصية و الثانية تتكلف بتغيير محتواها. محتوى الخاصية ماهو إلا متغيرة من نوع T تتم قراءتها و كتابتها عن طريق الدالتين السابقتين. مثال
class Test
{
private :
// هذه هي المتغيرة التي بداخلها محتوى الخاصية
int i;
public :
Test(void)
{
// تهيئة الخاصية
value.Init(this, &Test::GetValue, &Test::SetValue);
}
// هذه هي الدالة التي تقبل أو ترفض تغيير محتوى الخاصية
int &SetValue(const int &n)
{
// العلم وقت تغير محتوى الخاصية
cout << "Propertie value Setter called.." << endl;
if((n > 0) && (n < 10)) return i = 0; // إمكانية التحكم في محتوى الخصائص
return i = n;
}
// هذه هي الدالة التي تقبل أو ترفض إعطائنا محتوى الخاصية
int &GetValue(void)
{
//العلم وقت قراءة محتوى الخاصية
cout << "Propertie value Getter called.." << endl;
return i;
}
public :
// كيفية إنشاءو إعلان الخاصية
property<Test, int> value;
};الدالة Init هي التي تهيء الخاصية لتكون مقروءة فقط, مكتوبة فقط أم مقروءة و مكتوبة.
لإنشاء خاصية مقروءة فقط :
value.Init(this, &Test::GetValue, NULL);
لإنشاء خاصية مكتوبة فقط :
value.Init(this, NULL, &Test::SetValue);
لإنشاء خاصية مكتوبة و مقروءة في نفس الوقت :
value.Init(this, &Test::GetValue, &Test::SetValue);
بالطبع لا يمكن إنشاء خاصية لا مقروءة و لا مكتوبة.
للوصول إلى محتويات عنصر ممثل بخاصية نستعمل المعامل <- خصوصا إذا كان العنصر يمثل كلاس نريد الوصول إلى أحد العناصر المشتركة فيها.
للوصول مباشرة إلى محتوى الخاصية نستعمل المعامل *
هذا مثال مبسط لطريقة استعمال الخصائص يعتمد على الكلاس Test التي أنشأناها في المثال أعلاه :
void main(void)
{
Test t, tt;
t.value = 5;
// إحالة محتوى خاصية لخاصية أخرى
int i = tt.value = t.value;
i = t.value;
// مقارنة خاصيتين
bool b = t.value == tt.value;
b = t.value != tt.value;
t.value = tt.value * 2;
// مقارنة خاصية بمحتوى خاصية أخرى
b = t.value == *tt.value;
b = t.value != *tt.value;
// طريقة التعامل العادية لـ سي++
i = t.GetValue(); // تعادل i = t.value;
tt.SetValue(i); // تعادل tt.value = i;
t.SetValue(tt.GetValue()); // تعادل t.value = tt.value;
}وفي الأخير, هذا الكود للكلاس التي تمكننا من تنفيد خصائص مشابهة للغات من نوع RAD ك لغات دوت نت و غيرها...
//كلاس الحاوية للخاصية
// C : نوع الكلاس الحاوية لـ property.
// T : نوع محتوى الخاصية
template <class C, typename T>
class property
{
public :
// نوع المؤشر الذي ينادي على الدالة التي ترجع محتوى الخاصية
typedef T &(C::*PGETTER)(void);
// نوع المؤشر الذي ينادي على الدالة التي تغير محتوى الخاصية
typedef T &(C::*PSETTER)(const T &value);
private :
// مؤشر العنصر الحاوي للخاصية
C *m_pObject;
// مؤشر الدالة التي ترجع محتوى الخاصية
PSETTER pSetter;
// مؤشر الدالة التي تغير محتوى الخاصية
PGETTER pGetter;
// لضبط أن الخاصية لن تهيئ إلا مرة واحدة
bool canInit;
public:
// المنشئ الإفتراضي
property(void)
{
m_pObject = NULL;
pSetter = NULL;
pGetter = NULL;
canInit = true;
}
// تهيئة الخاصية
// pObject : العنصر الحاوي للخاصية
// pGetter : مؤشر الدالة التي ترجع محتوى الخاصية
// pSetter : مؤشر الدالة التي تغير محتوى الخاصية
void Init(C *pObject, PGETTER pGetFunction, PSETTER pSetFunction)
{
if(!canInit) throw "Second initialisation exception!..";
if(pObject == NULL) throw "Argument NULL : pObject";
if(!pGetFunction && !pSetFunction) throw "Can't create non read/write property!..";
m_pObject = pObject;
this->pSetter = pSetFunction;
this->pGetter = pGetFunction;
canInit = false;
}
// هل يمكننا قراءة الخاصية
bool CanRead(void)
{
return (this->pGetter != NULL);
}
// هل يمكننا كتابة الخاصية
bool CanWrite(void)
{
return (this->pSetter != NULL);
}
// إرجاع محتوى الخاصية
operator T()
{
// TODO : Assertion de m_pObject != NULL
if(this->pGetter == NULL) throw "Read only propety!..";
return (m_pObject->*pGetter)();
}
// إحالة معلومة إلى الخاصية
T &operator =(const T& value)
{
// TODO : Assertion de m_pObject != NULL
if(this->pSetter == NULL) throw "Read only Property!";
return (m_pObject->*pSetter)(value);
}
// إحالة محتوى خاصية إلى خاصية أخرى
T &operator =(const property<C, T> &prop)
{
if(this->pSetter == NULL) throw "Read only Property!";
return (m_pObject->*pSetter)((prop.m_pObject->*pGetter)());
}
// مقارنة مساواة محتوى الخاصية بمتغير آخر
bool operator==(const T& value)
{
// TODO : Assertion de m_pObject != NULL
if(this->pGetter == NULL) throw "Write only property!..";
return value == (m_pObject->*pGetter)();
}
// مقارنة مخالفة محتوى الخاصية بمتغير آخر
bool operator!=(const T& value)
{
if(this->pGetter == NULL) throw "Write only property!..";
return value != (m_pObject->*pGetter)();
}
// الوصول إلى محتوى الخاصية خصوصا إذا كانت حاوية لعنصر لكلاس
T *operator->()
{
// TODO : Assertion de m_pObject != NULL
if(this->pGetter == NULL) throw "Propriété en écriture seule!..";
return (m_pObject->*pGetter)();
}
T &operator *(void)
{
if(this->pGetter == NULL) throw "Write only property!..";
return (m_pObject->*pGetter)();
}
};لمعرفة إذا كانت الخاصية تقبل القراءة ننادي على الدالة CanRead()
لمعرفة إذا كانت الخاصية تقبل الكتابة أي تغيير المحتوى ننادي على الدالة CanWrite()
أرجو أن أكون قد تمكنت من شرح كيفية استعمال الكلاس property لتنفيد الخصائص في لغة سي++. و أرجو منكم الإبلاغ عن أي خطأ أو ثغرة كنت فد نسيت تصحيحها.
تحياتي...