در
مطلب پیشین کلاسی را برای حل بخشی از یک مسئله بزرگ تهیه کردیم. اگر فراموش کردید پیشنهاد میکنم یک بار دیگر آن مطلب را مطالعه کنید. بد نیست بار دیگر نگاهی به آن بیاندازیم.
public class Rectangle
{
public double Width;
public double Height;
public double Area()
{
return Width*Height;
}
public double Perimeter()
{
return 2*(Width + Height);
}
}
کلاس خوبی است اما همان طور که در بخش قبل مطرح شد این کلاس میتواند بهتر هم باشد. در این کلاس برای نگهداری حالت اشیائی که از روی آن ایجاد خواهند شد از متغیرهایی با سطح دسترسی عمومی استفاده شده است. این متغیرهای عمومی فیلد نامیده میشوند. مشکل این است که با این تعریف، اشیاء نمیتوانند هیچ اعتراضی به مقادیر غیر معتبری که ممکن است به آنها اختصاص داده شود، داشته باشند. به عبارت دیگر هیچ کنترلی بر روی مقادیر فیلدها وجود ندارد. مثلاً ممکن است یک مقدار منفی به فیلد طول اختصاص یابد. حال آنکه طول منفی معنایی ندارد.
تذکر: ممکن است این سوال پیش بیاید که خوب ما کلاس را نوشته ایم و خودمان میدانیم چه مقادیری برای فیلدهای آن مناسب است. اما مسئله اینجاست که اولاً ممکن است کلاس تهیه شده توسط برنامه نویس دیگری مورد استفاده قرار گیرد. یا حتی پس از مدتی فراموش کنیم چه مقادیری برای کلاسی که مدتی قبل تهیه کردیم مناسب است. و از همه مهمتر این است که کلاسها و اشیاء به عنوان ابزاری برای حل مسائل هستند و ممکن است مقادیری که به فیلدها اختصاص مییابد در زمان نوشتن برنامه مشخص نباشد و در زمان اجرای برنامه توسط کاربر یا کدهای بخشهای دیگر برنامه تعیین گردد.
به طور کلی هر چه کنترل و نظارت بیشتری بر روی مقادیر انتسابی به اشیاء داشته باشیم برنامه بهتر کار میکند و کمتر دچار خطاهای مهلک و بدتر از آن خطاهای منطقی میگردد. بنابراین باید ساز و کار این نظارت را در کلاس تعریف نماییم.
برای کلاسها یک نوع عضو دیگر هم میتوان تعریف کرد که دارای این ساز و کار نظارتی است. این عضو
Property نام دارد و یک مکانیسم انعطاف پذیر برای خواندن، نوشتن یا حتی محاسبه مقدار یک فیلد خصوصی فراهم مینماید.
تا اینجا باید به این نتیجه رسیده باشید که تعریف یک متغیر با سطح دسترسی عمومی در کلاس روش پسندیده و قابل توصیه ای نیست. بنابراین متغیرها را در سطح کلاس به صورت خصوصی تعریف میکنیم و از طریق تعریف Property امکان استفاده از آنها در بیرون کلاس را ایجاد میکنیم.
حال به چگونگی تعریف Propertyها دقت کنید.
public class Rectangle
{
private double _width = 0;
private double _height = 0;
public double Width
{
get { return _width; }
set { if (value > 0) { _width = value; } }
}
public double Height
{
get { return _height; }
set { if (value > 0) { _height = value; } }
}
public double Area()
{
return _width * _height;
}
public double Perimeter()
{
return 2*(_width + _height);
}
}
به تغییرات ایجاد شده در تعریف کلاس دقت کنید. ابتدا سطح دسترسی دو متغیر خصوصی شده است یعنی فقط اعضای داخل کلاس به آن دسترسی دارند و از بیرون قابل استفاده نیست. نام متغیرهای پیش گفته بر اساس اصول صحیح نامگذاری فیلدهای خصوصی تغییر داده شده است. ببینید اگر اصول نامگذاری را رعایت کنید چقدر زیباست. هر جای برنامه که چشمتان به width_ بخورد فوراً متوجه میشوید یک فیلد خصوصی است و نیازی نیست به محل تعریف آن مراجعه کنید. از طرفی یک مقدار پیش فرض برای این دو فیلد در نظر گرفته شده است که در صورتی که مقدار مناسبی برای آنها تعیین نشد مورد استفاده قرار خواهند گرفت.
دو قسمت اضافه شده دیگر تعریف دو Property مورد نظر است. یکی عرض و دیگری ارتفاع. خط اول تعریف پروپرتی تفاوتی با تعریف فیلد عمومی ندارد. اما همان طور که میبینید هر فیلد دارای یک بدنه است که با {} مشخص میشود. در این بدنه ساز و کار نظارتی تعریف میشود.
نحوه دسترسی به پروپرتیها مشابه فیلدهای عمومی است. اما پروپرتیها در حقیقت متدهای ویژه ای به نام اکسسور (Accessor) هستند که از طرفی سادگی استفاده از متغیرها را به ارمغان میآورند و از طرف دیگر دربردارنده امنیت و انعطاف پذیری متدها هستند. یعنی در عین حال که روشی عمومی برای داد و ستد مقادیر ارایه میدهند، کد پیاده سازی یا وارسی اطلاعات را مخفی نموده و استفاده کننده کلاس را با آن درگیر نمیکنند. قطعه کد زیر چگونگی استفاده از پروپرتی را نشان میدهد.
Rectangle rectangle = new Rectangle();
rectangle.Width = 10;
Console.WriteLine(rectangle.Width);
به خوبی مشخص است برای کد استفاده کننده از شیء که آنرا کد مشتری مینامیم نحوه دسترسی به پروپرتی یا فیلد تفاورتی ندارد. در اینجا خط دوم که مقداری را به یک پروپرتی منتسب کرده سبب فراخوانی اکسسور set میگردد. همچنین مقدار منتسب شده یعنی ۱۰ در داخل بدنه اکسسور از طریق کلمه کلیدی value قابل دسترسی و ارزیابی است. در خط سوم که لازم است مقدار پروپرتی برای چاپ بازیابی یا خوانده شود منجر به فراخوانی اکسسور get میگردد.
تذکر: به دو اکسسور get و set مانند دو متد معمولی نگاه کنید از این نظر که میتوانید در بدنه آنها اعمال دلخواه دیگری بجز ذخیره و بازیابی اطلاعات پروپرتی را نیز انجام دهید.
چند نکته:
- اکسسور get هنگام بازگشت یا خواندن مقدار پروپرتی اجرا میشود و اکسسور set زمان انتساب یک مقدار جدید به پروپرتی فراخوانی میشود. جالب آنکه در صورت لزوم این دو اکسسور میتوانند دارای سطوح دسترسی متفاوتی باشند.
-
داخل اکسسور set کلمه کلیدی value مقدار منتسب شده را در اختیار قرار میدهد تا در صورت لزوم بتوان بر روی آن پردازش لازم را انجام داد.
-
یک پروپرتی میتواند فاقد اکسسور set باشد که در این صورت یک پروپرتی فقط خواندنی ایجاد میگردد. همچنین میتواند فقط شامل اکسسور set باشد که در این صورت فقط امکان انتساب مقادر به آن وجود دارد و امکان دریافت یا خواندن مقدار آن میسر نیست. چنین پروپرتی ای فقط نوشتنی خواهد بود.
-
در بدنه اکسسور set الزامی به انتساب مقدار منتسب توسط کد مشتری نیست. در صورت صلاحدید میتوانید به جای آن هر مقدار دیگری را در نظر بگیرید یا عملیات مورد نظر خود را انجام دهید.
-
در بدنه اکسسور get هم هر مقداری را میتوانید بازگشت دهید. یعنی الزامی وجود ندارد حتماً مقدار فیلد خصوصی متناظر با پروپرتی را بازگشت دهید. حتی الزامی به تعریف فیلد خصوصی برای هر پروپرتی ندارید. به طور مثال ممکن است مقدار بازگشتی اکسسور get حاصل محاسبه و ... باشد.
اکنون مثال دیگری را در نظر بگیرید. فرض کنید در یک پروژه فروشگاهی در حال تهیه کلاسی برای مدیریت محصولات هستید. قصد داریم یک پروپرتی ایجاد کنیم تا نام محصول را نگهداری کند و در حال حاضر هیچ محدودیتی برای نام یک محصول در نظر نداریم. کد زیر را ببینید.
public class Product
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
همانطور که میبینید در بدنه اکسسورهای پروپرتی Name هیچ عملیات نظارتی ای در نظر گرفته نشده است. آیا بهتر نبود بیهوده پروپرتی تعریف نکنیم و خیلی ساده از یک فیلد عمومی که همین کار را انجام میدهد استفاده کنیم؟ خیر. بهتر نبود. مهمترین دلیلی که فعلاً کافی است تا شما را قانع کند تعریف پروپرتی روش پسندیدهتری از فیلد عمومی است را بررسی میکنیم.
فرض کنید پس از مدتی متوجه شدید اگر نام بسیار طولانی ای برای محصول درج شود ظاهر برنامه شما دچار مشکل میشود. پس باید بر روی این مورد نظارت داشته باشید. دیدیم که برای رسیدن به این هدف باید فیلد عمومی را فراموش و به جای آن پروپرتی تعریف کنیم. اما مسئله اینست که تبدیل یک فیلد عمومی به پروپرتی میتواند سبب بروز ناسازگاریهایی در بخشهای دیگر برنامه که از این کلاس و آن فیلد استفاده میکنند شود. پس بهتر آن است که از ابتدا پروپرتی تعریف کنیم هر چند نیازی به عملیات نظارتی خاصی نداریم. در این حالت اگر نیاز به پردازش بیشتر پیدا شد به راحتی میتوانیم کد مورد نظر را در اکسسورهای موجود اضافه کنیم بدون آنکه نیازی به تغییر بخشهای دیگر باشد.
و یک خبر خوب! از سی شارپ ۳ به بعد ویژگی جدیدی در اختیار ما قرار گرفته است که میتوان پروپرتیهایی مانند مثال بالا را که نیازی به عملیات نظارتی ندارند، سادهتر و خواناتر تعریف نمود. این ویژگی جدید پروپرتی اتوماتیک یا Auto-Implemented Property نام دارد. مانند نمونه زیر.
public class Product
{
public string Name { get; set; }
}
این کد مشابه کد پیشین است با این تفاوت که خود کامپایلر یک متغیر خصوصی و بی نام را ایجاد مینماید که فقط داخل اکسسورهای پروپرتی قابل دسترسی است.
البته استفاده از پروپرتی برتری دیگری هم دارد. و آن کنترل سطح دسترسی اکسسورها است. مثال زیر را ببینید.
public class Student
{
public DateTime Birthdate { get; set; }
public double Age { get; private set; }
}
کلاس دانشجو یک پروپرتی به نام تاریخ تولد دارد که قابل خواندن و نوشتن توسط کد مشتری (کد استفاده کننده از کلاس یا اشیاء آن) است. و یک پروپرتی دیگر به نام سن دارد که توسط کد مشتری تنها قابل خواندن است. و تنها توسط سایر اعضای داخل همین کلاس قابل نوشتن است. چون اکسسور set آن به صورت خصوصی تعریف شده است. به این ترتیب بخش دیگری از کلاس سن دانشجو را بر اساس تاریخ تولد او محسابه میکند و در پروپرتی Age قرار میدهد و کد مشتری میتواند آنرا مورد استفاده قرار دهد اما حق دستکاری آنرا ندارد. به همین ترتیب در صورت نیاز اکسسور get را میتوان خصوصی کرد تا پروپرتی از دید کد مشتری فقط نوشتنی باشد. اما حتماً میتوانید حدس بزنید که نمیتوان هر دو اکسسور را خصوصی کرد. چرا؟
تذکر: در هنگام تعریف یک
فیلد میتوان از کلمه کلیدی readonly استفاده کرد تا یک
فیلد فقط خواندنی ایجاد گردد. اما در اینصورت فیلد تعریف شده حتی داخل کلاس هم فقط خواندنی است و فقط در هنگام تعریف یا در متد سازنده کلاس امکان مقدار دهی به آن وجود دارد. در بخشهای بعدی مفهوم سازنده کلاس مورد بررسی خواهد گرفت.