مقدمه
کپسولهسازی (Encapsulation) یکی از ستونهای اصلی برنامهنویسی شیءگرا (OOP)
است. این اصل به معنای بستهبندی دادهها (فیلدها) و متدهایی که روی آن دادهها کار میکنند، در یک
واحد به نام کلاس است. اما بخش مهمتر کپسولهسازی، مخفیسازی دادهها (Data
Hiding) است؛ یعنی محدود کردن دسترسی مستقیم به وضعیت داخلی یک شیء از دنیای خارج. این
کار به ما اجازه میدهد تا از یکپارچگی دادهها محافظت کرده و تضمین کنیم که شیء همیشه در یک وضعیت
معتبر باقی میماند. در این درس، با ابزارهایی که C# برای پیادهسازی این اصل قدرتمند در
اختیار ما قرار میدهد، یعنی سطوح دسترسی و پراپرتیها، آشنا
میشویم.
مشکل فیلدهای عمومی (Public Fields)
در درسهای قبل، برای سادگی، فیلدهای کلاسهایمان را به صورت public
تعریف کردیم. این کار به کدهای خارج از کلاس اجازه میدهد تا مقادیر این فیلدها را مستقیماً بخوانند
و تغییر دهند. هرچند این روش ساده به نظر میرسد، اما یک مشکل اساسی دارد: کلاس هیچ کنترلی بر روی
مقادیری که به فیلدهایش اختصاص داده میشود، ندارد.
Program.cs
public class Employee
{
public string Name;
public int Age;
}
Employee emp = new Employee();
emp.Name = "John";
emp.Age = -50;
Console.WriteLine($"{emp.Name} is {emp.Age} years old.");
در مثال بالا، کد خارج از کلاس توانست یک مقدار نامعتبر (-50) را به فیلد Age اختصاص دهد. این
کار یکپارچگی شیء emp را از بین میبرد. کلاس Employee هیچ راهی برای جلوگیری از این اتفاق
ندارد. اینجاست که کپسولهسازی با مخفی کردن فیلدها و فراهم کردن یک راه کنترلشده برای دسترسی به
آنها، به ما کمک میکند.
سطوح دسترسی (Access Modifiers)
اولین قدم برای کپسولهسازی، استفاده از سطوح دسترسی است. این کلمات کلیدی مشخص میکنند که یک عضو
(فیلد، متد، پراپرتی و ...) از کجا قابل مشاهده و استفاده است. رایجترین سطوح دسترسی عبارتند از:
- public: عضو از هر کدی، چه در داخل و چه در خارج از اسمبلی
(پروژه)، قابل دسترسی است.
- private: عضو فقط از داخل همان کلاسی که در آن
تعریف شده، قابل دسترسی است. این سطح دسترسی، پیشفرض اعضای کلاس است.
یک اصل مهم در طراحی شیءگرا این است: فیلدها همیشه باید private باشند. با خصوصی
کردن فیلدها، ما دسترسی مستقیم از خارج را مسدود میکنیم و کنترل کامل دادهها را در دست کلاس قرار
میدهیم.
Program.cs
public class Employee
{
private string _name;
private int _age;
}
Employee emp = new Employee();
emp._age = 30;
در این کد، با private کردن فیلد _age (استفاده از `_` در ابتدای نام فیلدهای خصوصی یک قرارداد
رایج است)، دیگر نمیتوان از خارج کلاس به آن دسترسی داشت. حالا سؤال این است: چگونه مقادیر را
بخوانیم یا تغییر دهیم؟ پاسخ در پراپرتیها نهفته است.
پیادهسازی کپسولهسازی با پراپرتیها
پراپرتیها (Properties) اعضای ویژهای هستند که یک راه انعطافپذیر برای خواندن، نوشتن یا محاسبهی
مقدار یک فیلد خصوصی فراهم میکنند. از دید کدی که از کلاس استفاده میکند، پراپرتیها شبیه فیلدهای
عمومی به نظر میرسند، اما در داخل کلاس، آنها مانند متدهایی به نام اکسسور
(accessor) پیادهسازی میشوند.
اکسسرهای get و set
یک پراپرتی میتواند دارای دو اکسسور باشد:
- اکسسور get: زمانی اجرا میشود که مقدار پراپرتی خوانده میشود. این اکسسور
باید مقداری از نوع پراپرتی را برگرداند.
- اکسسور set: زمانی اجرا میشود که یک مقدار جدید به پراپرتی اختصاص داده
میشود. این اکسسور به یک پارامتر ضمنی به نام value دسترسی دارد
که حاوی مقدار جدید است.
بیایید کلاس Employee را با استفاده از یک پراپرتی برای فیلد _age بازنویسی کنیم و در اکسسور
set، منطق اعتبارسنجی را اضافه کنیم.
Program.cs
public class Employee
{
private int _age;
public int Age
{
get
{
return _age;
}
set
{
if (value > 0 && value < 120)
{
_age = value;
}
else
{
Console.WriteLine("Invalid age specified!");
}
}
}
}
Employee emp = new Employee();
emp.Age = 35;
Console.WriteLine($"Employee's age: {emp.Age}");
emp.Age = -50;
Console.WriteLine($"Employee's age is still: {emp.Age}");
اکنون کلاس Employee کنترل کاملی بر روی فیلد _age دارد. هرگاه کدی تلاش کند مقدار Age را
تنظیم کند، اکسسور set اجرا شده و مقدار ورودی (value) را اعتبارسنجی میکند. اگر مقدار معتبر
باشد، به فیلد خصوصی _age اختصاص داده میشود؛ در غیر این صورت، یک پیام خطا نمایش داده شده و
مقدار فیلد بدون تغییر باقی میماند. این قدرت واقعی کپسولهسازی است.
پراپرتیهای فقط-خواندنی و محاسباتی
شما میتوانید با حذف اکسسور set، یک پراپرتی فقط-خواندنی (Read-Only) ایجاد
کنید. این کار برای مقادیری که نباید از خارج کلاس تغییر کنند یا برای مقادیر محاسباتی بسیار مفید
است.
Program.cs
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName
{
get
{
return $"{FirstName} {LastName}";
}
}
}
Person p = new Person();
p.FirstName = "Sara";
p.LastName = "Jones";
Console.WriteLine(p.FullName);
در این مثال، پراپرتی FullName یک فیلد پشتیبان ندارد. در عوض، مقدار آن هر بار که خوانده میشود،
با ترکیب FirstName و LastName محاسبه میشود. چون اکسسور set برای آن تعریف نشده، این
پراپرتی فقط-خواندنی است. در درس بعدی با روش خلاصهتری برای تعریف پراپرتیها آشنا خواهیم شد.