Kahibaro
Discord Login Register

کپسوله‌سازی

مفهوم کپسوله‌سازی در عمل

کپسوله‌سازی (Encapsulation) یکی از ایده‌های مهم در برنامه‌نویسی شی‌گرا است که روی «پنهان کردن جزئیات داخلی» و «کنترل دسترسی» به داده‌ها تمرکز دارد. در این بخش فرض می‌کنیم با مفهوم کلی کلاس، شیء، ویژگی و متد آشنا هستید و فقط روی خود کپسوله‌سازی تمرکز می‌کنیم.

ایدهٔ اصلی کپسوله‌سازی

کپسوله‌سازی یعنی:

به زبان ساده: «کاربر کلاس باید بداند چطور از کلاس استفاده کند، نه این‌که داخل آن دقیقاً چه می‌گذرد».

در پایتون سیستم «دسترسی سخت‌گیرانه» مثل بعضی زبان‌ها (مثل جاوا) ندارد، اما الگوها و قراردادهایی دارد که با استفاده از آن‌ها می‌توانیم به کپسوله‌سازی نزدیک شویم.

نام‌گذاری ویژگی‌ها برای کنترل دسترسی

پایتون سطح دسترسی رسمی مثل public و private ندارد، اما با نام‌گذاری می‌توانیم منظور خودمان را منتقل کنیم.

۱. ویژگی‌های عمومی (Public)

نام عادی بدون خط زیر (_) در ابتدا:

python
class Person:
    def __init__(self, name, age):
        self.name = name      # عمومی
        self.age = age        # عمومی
python
  p = Person("Ali", 20)
  print(p.name)
  p.age = 21

۲. ویژگی‌های «داخلی» (با یک زیرخط `_`)

پیش‌وند یک خط زیر: _
این یعنی «این ویژگی برای استفادهٔ داخلی کلاس است، نه برای استفادهٔ مستقیم بیرون».

python
class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner        # عمومی
        self._balance = balance   # داخلی (protected به‌صورت قراردادی)
python
  acc = BankAccount("Sara", 1000)
  print(acc._balance)   # از نظر فنی می‌شود، اما توصیه نمی‌شود

۳. ویژگی‌های «تقریباً خصوصی» (با دو زیرخط `__`)

پیش‌وند دو خط زیر: __
این کار باعث «name mangling» می‌شود؛ یعنی نام ویژگی در داخل کلاس تغییر داده می‌شود تا به‌طور تصادفی از بیرون به آن دسترسی پیدا نشود.

python
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 تبدیل می‌کند.
از بیرون می‌توان با این نام طولانی به آن رسید، اما این کار اصلاً توصیه نمی‌شود و فقط برای درک مکانیزم مهم است.

python
print(acc._BankAccount__balance)  # از لحاظ فنی ممکن است، ولی کار درستی نیست

هدف: جلوگیری از دسترسی اتفاقی، نه قفل کردن کامل.

چرا بهتر است داده‌ها را مستقیماً در دسترس نگذاریم؟

فرض کنید کلاس BankAccount داشته باشیم:

python
class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self.balance = balance

از بیرون:

python
acc = BankAccount("Ali", 1000)
acc.balance = -5000  # منطقی نیست، ولی امکان‌پذیر است!

اینجا:

با کپسوله‌سازی، دادهٔ «حساس» را مخفی می‌کنیم و فقط از طریق متدهای کنترل‌شده اجازهٔ تغییر می‌دهیم.

کپسوله‌سازی با متدهای کنترل‌شده (getter / setter ساده)

روش متداول: ویژگی را مخفی می‌کنیم و متدهایی برای خواندن و تغییر آن می‌نویسیم.

python
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

استفاده:

python
acc = BankAccount("Ali", 1000)
acc.deposit(500)          # افزایش کنترل‌شده
print(acc.get_balance())  # خواندن موجودی
acc.withdraw(3000)        # پیام خطا، بدون خراب کردن وضعیت داخلی

مزیت‌ها:

استفاده از `property` برای دسترسی شبیه ویژگی، با کنترل داخلی

بعضی وقت‌ها نمی‌خواهیم همیشه get_... و set_... صدا بزنیم؛ در عین حال می‌خواهیم کنترل داشته باشیم.
در پایتون می‌توانیم از @property استفاده کنیم.

یک مثال ساده با `property`

python
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

استفاده:

python
p = Person("Sara", 20)
print(p.age)   # در ظاهر شبیه ویژگی است، در واقع متد @property صدا زده می‌شود
p.age = 25     # در ظاهر انتساب عادی است، در واقع age.setter اجرا می‌شود
p.age = -5     # پیام خطا، مقدار تغییر نمی‌کند

اینجا:

کپسوله‌سازی در سطح متدها

تا این‌جا بیشتر دربارهٔ «ویژگی‌ها» صحبت کردیم، اما متدها هم می‌توانند «داخلی» باشند.

متدهای داخلی (با `_` و `__`)

گاهی متدی فقط برای کمک به بقیهٔ متدهای کلاس است و قرار نیست مستقیماً از بیرون فراخوانی شود.

python
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()
python
  tp = TextProcessor("  Salam   DonyA  ")
  print(tp.clean())       # استفاده از متد عمومی

اگر بخواهیم از name mangling استفاده کنیم:

python
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()

از بیرون:

python
tp = TextProcessor("  Salam   DonyA  ")
print(tp.clean())
# tp.__normalize_spaces("  hi  ")  # خطا

این کار دسترسی «اتفاقی» از بیرون را سخت‌تر می‌کند.

مزیت‌های عملی کپسوله‌سازی

خلاصهٔ مزیت‌ها در کد واقعی:

  1. جلوگیری از خراب شدن داده‌ها
    • با متدهای کنترل‌شده، جلوی مقدارهای نامعتبر را می‌گیریم (مثل سن منفی، موجودی منفی و ...).
  2. تغییر راحت‌تر و بدون شکستن کدهای دیگر
    • اگر منطق داخلی عوض شود (مثل فرمول محاسبهٔ مالیات)، لازم نیست همهٔ جاهایی که از کلاس استفاده می‌کنند را تغییر دهیم، چون آن‌ها فقط از متدهای عمومی استفاده می‌کنند.
  3. استفادهٔ ساده‌تر برای دیگران
    • کاربران کلاس فقط متدها و ویژگی‌های عمومی را می‌بینند و لازم نیست درگیر جزئیات داخلی شوند.
  4. ساخت «رابط» (Interface) تمیز
    • کلاس می‌تواند چند متد عمومی و واضح داشته باشد که نشان می‌دهد «با من چطور کار کن»، و بقیهٔ متدها/ویژگی‌ها پنهان بمانند.

یک مثال جمع‌بندی: کلاس دما با کنترل واحد

در این مثال، دما را داخلی به صورت سلسیوس نگه می‌داریم، اما اجازه می‌دهیم کاربر با سلسیوس یا فارنهایت کار کند، بدون این‌که بداند داخل چه می‌شود.

python
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

استفاده:

python
t = Temperature(0)
print(t.celsius)      # 0
print(t.fahrenheit)   # 32.0
t.fahrenheit = 212    # تنظیم با فارنهایت
print(t.celsius)      # 100.0

در این‌جا:

نکاتی برای تمرین کپسوله‌سازی

برای تمرین:

در حین طراحی، همیشه بپرسید:

«کدام قسمت‌ها باید برای بقیهٔ برنامه قابل دیدن باشد و کدام‌ها فقط برای استفادهٔ داخلی این کلاس است؟»

پاسخ به این سؤال، شما را به سمت کپسوله‌سازی خوب راهنمایی می‌کند.

Views: 5

Comments

Please login to add a comment.

Don't have an account? Register now!