التحقق من البيانات في الـ Setters
جزء من قسم Object Oriented Programming في رحلة C على Coddy — الدرس 20 من 61.
في الدرس السابق، قمنا بإنشاء دوال تعيين (setters) أساسية تقوم ببساطة بتعيين القيم. ولكن هذا يغفل فائدة رئيسية للتغليف (encapsulation): القدرة على حماية بياناتك عن طريق إضافة منطق التحقق من الصحة (validation logic).
ضع في اعتبارك Counter لا ينبغي أبداً أن يصبح سالباً. بدون التغليف (encapsulation)، يمكن لأي شخص كتابة c->value = -50 وإفساد الحالة. باستخدام setter، يمكنك التحكم في القيم المسموح بها:
// counter.c
void counter_set_value(Counter *c, int new_value) {
if (new_value < 0) {
return; // رفض المدخلات غير الصالحة بصمت
}
c->value = new_value;
}الآن، يتم تجاهل القيم السالبة ببساطة.
قد تختار أيضًا إرجاع رمز حالة للإشارة إلى النجاح أو الفشل:
int counter_set_value(Counter *c, int new_value) {
if (new_value < 0) {
return 0; // فشل
}
c->value = new_value;
return 1; // نجاح
}يتيح هذا النمط للمستدعي معرفة ما إذا كانت العملية قد نجحت أم لا. تكمن الفكرة الجوهرية في أن دوال التعيين (setters) لا تقتصر فقط على عملية الإسناد، بل هي بمثابة حراس يفرضون القواعد المتعلقة بما يمكن أن تكون عليه حالة الكائن الخاص بك. هذه هي القوة الحقيقية للتغليف (encapsulation): حيث يحافظ الكائن الخاص بك على سلامته الخاصة، بغض النظر عما يحاول الكود الخارجي القيام به.
التحدي
سهللنقم ببناء وحدة Temperature تحمي بياناتها باستخدام التحقق من الصحة في الـ setter. ستقوم بإنشاء متتبع لدرجة الحرارة يقبل فقط القيم ضمن نطاق صالح - مما يوضح كيف تعمل الـ setters كحراس لبوابة حالة الكائن الخاص بك.
ستقوم بإنشاء ثلاثة ملفات:
temperature.h: واجهتك العامة باستخدام نمط المؤشر المعتم (opaque pointer pattern). قم بالتصريح عن نوعTemperatureباستخدام تصريح مسبق (forward declaration) فقط. قم بالتصريح عن هذه الدوال:create_temperature— تأخذ عدداً صحيحاً للدرجات الأولية وتعيد مؤشر Temperature.free_temperature— تحرر الذاكرة المخصصة.temp_get_degrees— دالة getter تعيد قيمة درجة الحرارة الحالية.temp_set_degrees— دالة setter تعيدint(1 للنجاح، 0 للفشل).
TEMPERATURE_H.temperature.c: قم بتعريفstruct Temperatureالفعلي مع عضوint degreesمخفي. قم بتنفيذ جميع الدوال. المفتاح هو الـ setter الخاص بك - يجب أن يقبل فقط القيم بين -50 و 150 (شاملة). إذا كانت القيمة خارج هذا النطاق، فقم بإرجاع 0 واترك درجة الحرارة دون تغيير. إذا كانت صالحة، فقم بتحديث القيمة وأرجع 1.main.c: قم بتوضيح التحقق من الصحة من خلال محاولة تعيين درجات حرارة صالحة وغير صالحة، مما يوضح أن القيم غير الصالحة يتم رفضها بينما يتم قبول القيم الصالحة.
ستتلقى ثلاثة مدخلات: درجة الحرارة الأولية، و درجة حرارة صالحة لتعيينها (ضمن النطاق)، و درجة حرارة غير صالحة لمحاولة تعيينها (خارج النطاق).
في ملفك الرئيسي، قم بإنشاء Temperature بالقيمة الأولية واطبعها. ثم حاول تعيين درجة الحرارة غير الصالحة - وبما أنه يجب رفضها، اطبع النتيجة وأظهر أن القيمة تظل دون تغيير. أخيراً، قم بتعيين درجة الحرارة الصالحة، واطبع نتيجة النجاح، وأظهر القيمة المحدثة. قم بتحرير الذاكرة وتأكيد عملية التنظيف.
اطبع المخرجات بهذا التنسيق:
Initial: {degrees}
Set {invalid}: {0 or 1}
After invalid: {degrees}
Set {valid}: {0 or 1}
After valid: {degrees}
Freedعلى سبيل المثال، مع المدخلات 25، 100، و 200، ستكون المخرجات:
Initial: 25
Set 200: 0
After invalid: 25
Set 100: 1
After valid: 100
Freedيوضح هذا القوة الحقيقية للتغليف (encapsulation) - حيث يفرض الـ setter الخاص بك القواعد المتعلقة بما يشكل درجة حرارة صالحة، مما يحمي سلامة الكائن بغض النظر عن القيم التي يحاول الكود الخارجي تعيينها.
ورقة مرجعية
يمكن أن تتضمن دوال التعيين (Setters) منطق تحقق لحماية سلامة البيانات. فبدلاً من مجرد تعيين القيم، تعمل هذه الدوال كـ حراس يفرضون القواعد المتعلقة بحالات الكائنات الصالحة.
مثال بسيط على التحقق - رفض المدخلات غير الصالحة بصمت:
void counter_set_value(Counter *c, int new_value) {
if (new_value < 0) {
return; // رفض المدخلات غير الصالحة
}
c->value = new_value;
}إرجاع رمز حالة للإشارة إلى النجاح أو الفشل:
int counter_set_value(Counter *c, int new_value) {
if (new_value < 0) {
return 0; // فشل
}
c->value = new_value;
return 1; // نجاح
}يسمح هذا النمط للمستدعين بالتحقق مما إذا كانت العملية قد نجحت، مما يمكّن الكائنات من الحفاظ على سلامتها بغض النظر عن محاولات الكود الخارجي.
جرّب بنفسك
#include <stdio.h>
#include "temperature.h"
int main() {
// قراءة المدخلات
int initial, valid_temp, invalid_temp;
scanf("%d", &initial);
scanf("%d", &valid_temp);
scanf("%d", &invalid_temp);
// TODO: إنشاء Temperature بالقيمة الأولية
// TODO: طباعة درجة الحرارة الأولية
// التنسيق: "Initial: {degrees}"
// TODO: محاولة تعيين درجة الحرارة غير الصالحة
// طباعة النتيجة وإظهار أن القيمة تظل دون تغيير
// التنسيق: "Set {invalid}: {0 or 1}"
// التنسيق: "After invalid: {degrees}"
// TODO: تعيين درجة الحرارة الصالحة
// طباعة نتيجة النجاح وإظهار القيمة المحدثة
// التنسيق: "Set {valid}: {0 or 1}"
// التنسيق: "After valid: {degrees}"
// TODO: تحرير الذاكرة وطباعة "Freed"
return 0;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات البرمجة التركيبية
ملفات الترويسةحواجز التضمينملفات المصدرالدوال الساكنةمراجعة: آلة حاسبة تركيبية4التغليف (Encapsulation)
مفهوم الـ Opaque Pointersتعريف الـ Opaque Structsالـ Getters والـ Settersالتحقق من البيانات في الـ Settersملخص: الصندوق السري2الكائنات والأساليب
الـ Structs ككائناتمؤشر 'Self'صحة استخدام Constالمؤشر مقابل القيمةالأساليب المساعدةملخص: Point Manager5مشروع: حساب بنكي بسيط
إعداد المشروعتنفيذ الحساب3دورة حياة الكائن
نمط المنشئ (Constructor Pattern)نمط الهادم (Destructor Pattern)تهيئة الـ Stackالنسخ العميق (Deep Copy)مراجعة: String Wrapper6الوراثة عبر التركيب
تضمين الـ Structقاعدة العضو الأولالوصول إلى أعضاء الأبعملية الـ Upcastingمراجعة: هرمية الأشكال9مشروع: رسّام الأشكال
نظرة عامة على المشروعتنفيذ الدائرة