Table of Contents
مفهوم کپسولهسازی در عمل
کپسولهسازی (Encapsulation) یکی از ایدههای مهم در برنامهنویسی شیگرا است که روی «پنهان کردن جزئیات داخلی» و «کنترل دسترسی» به دادهها تمرکز دارد. در این بخش فرض میکنیم با مفهوم کلی کلاس، شیء، ویژگی و متد آشنا هستید و فقط روی خود کپسولهسازی تمرکز میکنیم.
ایدهٔ اصلی کپسولهسازی
کپسولهسازی یعنی:
- دادهها (ویژگیها) و رفتارها (متدها) در یک کلاس کنار هم قرار بگیرند.
- از بیرون کلاس، فقط بخشهای لازم قابل دسترسی باشند.
- تغییرات داخلی کلاس، کمترین اثر را روی بقیهٔ برنامه بگذارد.
به زبان ساده: «کاربر کلاس باید بداند چطور از کلاس استفاده کند، نه اینکه داخل آن دقیقاً چه میگذرد».
در پایتون سیستم «دسترسی سختگیرانه» مثل بعضی زبانها (مثل جاوا) ندارد، اما الگوها و قراردادهایی دارد که با استفاده از آنها میتوانیم به کپسولهسازی نزدیک شویم.
نامگذاری ویژگیها برای کنترل دسترسی
پایتون سطح دسترسی رسمی مثل public و private ندارد، اما با نامگذاری میتوانیم منظور خودمان را منتقل کنیم.
۱. ویژگیهای عمومی (Public)
نام عادی بدون خط زیر (_) در ابتدا:
class Person:
def __init__(self, name, age):
self.name = name # عمومی
self.age = age # عمومی- انتظار میرود از بیرون کلاس مستقیماً به آنها دسترسی داشته باشیم:
p = Person("Ali", 20)
print(p.name)
p.age = 21۲. ویژگیهای «داخلی» (با یک زیرخط `_`)
پیشوند یک خط زیر: _
این یعنی «این ویژگی برای استفادهٔ داخلی کلاس است، نه برای استفادهٔ مستقیم بیرون».
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner # عمومی
self._balance = balance # داخلی (protected بهصورت قراردادی)- پایتون از لحاظ فنی دسترسی را محدود نمیکند:
acc = BankAccount("Sara", 1000)
print(acc._balance) # از نظر فنی میشود، اما توصیه نمیشود- این یک «قرارداد» بین برنامهنویسان است؛ یعنی: اگر
_دیدی، مستقیم دست نزن مگر اینکه دقیقاً بدانی چه میکنی.
۳. ویژگیهای «تقریباً خصوصی» (با دو زیرخط `__`)
پیشوند دو خط زیر: __
این کار باعث «name mangling» میشود؛ یعنی نام ویژگی در داخل کلاس تغییر داده میشود تا بهطور تصادفی از بیرون به آن دسترسی پیدا نشود.
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
self.__balance = balance # تقریباً خصوصی
acc = BankAccount("Sara", 1000)
print(acc.owner) # مشکلی نیست
# print(acc.__balance) # خطا میدهد
پایتون نام __balance را داخل کلاس به چیزی شبیه _BankAccount__balance تبدیل میکند.
از بیرون میتوان با این نام طولانی به آن رسید، اما این کار اصلاً توصیه نمیشود و فقط برای درک مکانیزم مهم است.
print(acc._BankAccount__balance) # از لحاظ فنی ممکن است، ولی کار درستی نیستهدف: جلوگیری از دسترسی اتفاقی، نه قفل کردن کامل.
چرا بهتر است دادهها را مستقیماً در دسترس نگذاریم؟
فرض کنید کلاس BankAccount داشته باشیم:
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
self.balance = balanceاز بیرون:
acc = BankAccount("Ali", 1000)
acc.balance = -5000 # منطقی نیست، ولی امکانپذیر است!اینجا:
- هیچ کنترلی روی مقدار
balanceنداریم. - هر کسی میتواند هر مقداری بگذارد، حتی مقدار نامعتبر.
با کپسولهسازی، دادهٔ «حساس» را مخفی میکنیم و فقط از طریق متدهای کنترلشده اجازهٔ تغییر میدهیم.
کپسولهسازی با متدهای کنترلشده (getter / setter ساده)
روش متداول: ویژگی را مخفی میکنیم و متدهایی برای خواندن و تغییر آن مینویسیم.
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
self.__balance = balance # مخفی
def get_balance(self):
return self.__balance
def deposit(self, amount):
if amount <= 0:
print("مبلغ واریزی باید مثبت باشد.")
return
self.__balance += amount
def withdraw(self, amount):
if amount <= 0:
print("مبلغ برداشت باید مثبت باشد.")
return
if amount > self.__balance:
print("موجودی کافی نیست.")
return
self.__balance -= amountاستفاده:
acc = BankAccount("Ali", 1000)
acc.deposit(500) # افزایش کنترلشده
print(acc.get_balance()) # خواندن موجودی
acc.withdraw(3000) # پیام خطا، بدون خراب کردن وضعیت داخلیمزیتها:
- نمیتوانیم بدون کنترل، موجودی را تغییر دهیم.
- قوانین کسبوکار (مثلاً جلو گیری از موجودی منفی) داخل کلاس «کپسوله» شدهاند.
- اگر بعداً بخواهیم نحوهٔ محاسبهٔ موجودی را عوض کنیم، بقیهٔ کد نیازی به تغییر ندارد؛ فقط داخل کلاس را ویرایش میکنیم.
استفاده از `property` برای دسترسی شبیه ویژگی، با کنترل داخلی
بعضی وقتها نمیخواهیم همیشه get_... و set_... صدا بزنیم؛ در عین حال میخواهیم کنترل داشته باشیم.
در پایتون میتوانیم از @property استفاده کنیم.
یک مثال ساده با `property`
class Person:
def __init__(self, name, age):
self.name = name # عمومی
self.__age = age # مخفی
@property
def age(self):
# این متد مثل یک ویژگی خوانده میشود
return self.__age
@age.setter
def age(self, value):
# این متد هنگام تنظیم مقدار فراخوانی میشود
if value < 0:
print("سن نمیتواند منفی باشد.")
else:
self.__age = valueاستفاده:
p = Person("Sara", 20)
print(p.age) # در ظاهر شبیه ویژگی است، در واقع متد @property صدا زده میشود
p.age = 25 # در ظاهر انتساب عادی است، در واقع age.setter اجرا میشود
p.age = -5 # پیام خطا، مقدار تغییر نمیکنداینجا:
- کد بیرون کلاس، مثل قبل و ساده از
p.ageاستفاده میکند. - اما در پشت صحنه، ما کنترل کامل روی مقدار داریم.
- اگر بعداً بخواهیم منطق را عوض کنیم (مثلاً تبدیل سال به ماه و ...)، میتوانیم فقط داخل کلاس را تغییر دهیم، بدون اینکه کد جاهای دیگر را دست بزنیم.
کپسولهسازی در سطح متدها
تا اینجا بیشتر دربارهٔ «ویژگیها» صحبت کردیم، اما متدها هم میتوانند «داخلی» باشند.
متدهای داخلی (با `_` و `__`)
گاهی متدی فقط برای کمک به بقیهٔ متدهای کلاس است و قرار نیست مستقیماً از بیرون فراخوانی شود.
class TextProcessor:
def __init__(self, text):
self.text = text
def _normalize_spaces(self, s):
# متد داخلی کمکی
return " ".join(s.split())
def clean(self):
# متد عمومی
cleaned = self._normalize_spaces(self.text)
return cleaned.strip().lower()- فراخوانی پیشنهادی از بیرون:
tp = TextProcessor(" Salam DonyA ")
print(tp.clean()) # استفاده از متد عمومی- هرچند میشود
tp._normalize_spaces(...)را صدا زد، اما طبق قرارداد «نباید» این کار را بکنیم، چون این متد برای استفادهٔ داخلی است.
اگر بخواهیم از name mangling استفاده کنیم:
class TextProcessor:
def __init__(self, text):
self.text = text
def __normalize_spaces(self, s):
return " ".join(s.split())
def clean(self):
cleaned = self.__normalize_spaces(self.text)
return cleaned.strip().lower()از بیرون:
tp = TextProcessor(" Salam DonyA ")
print(tp.clean())
# tp.__normalize_spaces(" hi ") # خطااین کار دسترسی «اتفاقی» از بیرون را سختتر میکند.
مزیتهای عملی کپسولهسازی
خلاصهٔ مزیتها در کد واقعی:
- جلوگیری از خراب شدن دادهها
- با متدهای کنترلشده، جلوی مقدارهای نامعتبر را میگیریم (مثل سن منفی، موجودی منفی و ...).
- تغییر راحتتر و بدون شکستن کدهای دیگر
- اگر منطق داخلی عوض شود (مثل فرمول محاسبهٔ مالیات)، لازم نیست همهٔ جاهایی که از کلاس استفاده میکنند را تغییر دهیم، چون آنها فقط از متدهای عمومی استفاده میکنند.
- استفادهٔ سادهتر برای دیگران
- کاربران کلاس فقط متدها و ویژگیهای عمومی را میبینند و لازم نیست درگیر جزئیات داخلی شوند.
- ساخت «رابط» (Interface) تمیز
- کلاس میتواند چند متد عمومی و واضح داشته باشد که نشان میدهد «با من چطور کار کن»، و بقیهٔ متدها/ویژگیها پنهان بمانند.
یک مثال جمعبندی: کلاس دما با کنترل واحد
در این مثال، دما را داخلی به صورت سلسیوس نگه میداریم، اما اجازه میدهیم کاربر با سلسیوس یا فارنهایت کار کند، بدون اینکه بداند داخل چه میشود.
class Temperature:
def __init__(self, celsius):
self.__celsius = celsius # دمای داخلی همیشه در سلسیوس است
@property
def celsius(self):
return self.__celsius
@celsius.setter
def celsius(self, value):
self.__celsius = value
@property
def fahrenheit(self):
# تبدیل سلسیوس به فارنهایت
return self.__celsius * 9 / 5 + 32
@fahrenheit.setter
def fahrenheit(self, value):
# تبدیل فارنهایت به سلسیوس
self.__celsius = (value - 32) * 5 / 9استفاده:
t = Temperature(0)
print(t.celsius) # 0
print(t.fahrenheit) # 32.0
t.fahrenheit = 212 # تنظیم با فارنهایت
print(t.celsius) # 100.0در اینجا:
- دادهٔ واقعی (سلسیوس) در کلاس کپسوله شده است.
- کاربر میتواند با دو «رابط» (
celsiusوfahrenheit) کار کند، بدون دانستن جزئیات تبدیل.
نکاتی برای تمرین کپسولهسازی
برای تمرین:
- کلاسی بسازید که اطلاعات دانشجو را نگه دارد:
- نام (عمومی)
- شمارهٔ دانشجویی (عمومی)
- نمرهها (مخفی)
- متدی برای افزودن نمره با کنترل اینکه بین ۰ و ۲۰ باشد.
- متدی برای محاسبهٔ میانگین.
- کلاس «محصول فروشگاه» بسازید:
- قیمت را بهصورت مخفی نگه دارید.
- فقط قیمتهای مثبت قبول کنید.
- ویژگیای (
property) برای نمایش قیمت با مالیات (مثلاً ۹٪) اضافه کنید.
در حین طراحی، همیشه بپرسید:
«کدام قسمتها باید برای بقیهٔ برنامه قابل دیدن باشد و کدامها فقط برای استفادهٔ داخلی این کلاس است؟»
پاسخ به این سؤال، شما را به سمت کپسولهسازی خوب راهنمایی میکند.