အခန်း 19 · animation 2 ခု

Chapter 18: OOP – Encapsulation (အတွင်းရေးဖုံးကွယ်ခြင်း)

808 Coder မှ တင်ဆက်သည်

OOP ရဲ့ တိုင်ကြီး ၄ ခုထဲက ပထမတစ်ခုကို အခု အသေးစိတ် လေ့လာမယ် — Encapsulation။

ဆေးတောင့် (capsule) ကို စဉ်းစားကြည့်။ အထဲမှာ ဆေးမှုန့်တွေ ရှိတယ် — ဒါပေမယ့် နင်က ဆေးတောင့်ကို မြိုချရုံပဲ၊ အထဲက မှုန့်ကို လက်နဲ့ ဆွဲကိုင်စရာ မလိုဘူး။ အထဲရေးကို ဖုံးကွယ်ထားပြီး၊ သုံးဖို့လိုတဲ့ အပိုင်း (မြိုချတာ) ကိုပဲ ဖွင့်ပေးထားတာ။

Encapsulation ဆိုတာ ဒီသဘောပဲ — object ရဲ့ အတွင်းအချက်အလက်တွေကို ဖုံးကွယ်ထားပြီး၊ လိုအပ်တဲ့အပိုင်းကိုပဲ public interface (အများသုံးမျက်နှာစာ) ကနေ ဖွင့်ပေးတာ။ နောက်တစ်နည်း ပြောရရင် — data (variable) နဲ့ အဲဒီ data ပေါ် လုပ်တဲ့ code (method) တွေကို class တစ်ခုတည်းမှာ ထုပ်ပိုးစုစည်းထားတာ၊ ပြီးတော့ ဘယ်သူက ဘာကို ထိကိုင်ခွင့်ရှိလဲ ထိန်းချုပ်တာ။

ဒီအခန်းပြီးရင်...

  • Encapsulation ဆိုတာ ဘာလဲ၊ ဘာကြောင့် အရေးကြီးလဲ ရှင်းပြနိုင်မယ်
  • Access modifier ၃ မျိုး (public, protected, private) ကို ခွဲခြားသုံးနိုင်မယ်
  • Private attribute တွေကို getter/setter နဲ့ လုံခြုံစွာ စီမံနိုင်မယ် (validation နဲ့အတူ)
  • @property decorator သုံးပြီး pythonic ဆန်ဆန် getter/setter ရေးနိုင်မယ်
  • PIN နဲ့ balance ကို ကာကွယ်ထားတဲ့ Bank Account System ရေးနိုင်မယ်

Encapsulation ဆိုတာဘာလဲ

အဓိပ္ပာယ် ၂ ထပ် ရှိတယ် —

  1. ထုပ်ပိုးခြင်း (Bundling) — data နဲ့ method တွေကို class တစ်ခုတည်းမှာ စုစည်းထားတာ။ အရင်အခန်းတွေမှာ လုပ်ခဲ့တာတွေအားလုံး ဒါပဲ (Wallet class မှာ balance data နဲ့ deposit() method အတူရှိတာ)။
  2. ဖုံးကွယ်ခြင်း (Hiding) — အတွင်းရေးကို အပြင်ကနေ တိုက်ရိုက်ထိကိုင်ခွင့် မပေးဘဲ၊ method တွေကတဆင့်ပဲ ထိကိုင်ခွင့်ပေးတာ။

ဘာကြောင့် ဖုံးကွယ်ရတာလဲ? ဥပမာ — ဘဏ်အကောင့်ရဲ့ balance ကို ဘယ်သူမဆို တိုက်ရိုက်ပြောင်းလို့ရနေရင် (acc.balance = -999999) ပြဿနာတက်ပြီ။ Encapsulation နဲ့ balance ကို ဖုံးထားပြီး deposit()/withdraw() method တွေကတဆင့်ပဲ ပြောင်းခွင့်ပေးရင် — ငွေပမာဏ မှန်မမှန်၊ လက်ကျန် လောက်မလောက် စစ်ဆေးပြီးမှ လုပ်လို့ရတယ်။ Data ကို ကာကွယ်တာ၊ စည်းမျဉ်းနဲ့ ထိန်းချုပ်တာ — ဒါဟာ encapsulation ရဲ့ အဓိကအကျိုးပါ။

မှတ်ချက်

Encapsulation ကောင်းကောင်းလုပ်ထားတဲ့ class ကို သုံးတဲ့သူက အတွင်းပိုင်း ဘယ်လိုအလုပ်လုပ်လဲ သိစရာ မလိုဘူး — ဘယ် method ခေါ်ရင် ဘာဖြစ်မလဲ သိရင်ရပြီ။ ဒါကို abstraction နဲ့ တွဲပြီး မှတ်ထားပါ။

Encapsulation ကို အဆင့် ၃ ဆင့်နဲ့ တည်ဆောက်လေ့ရှိတယ် — ပထမ အတွင်းရေးကို private လုပ်ပြီး ဖုံးကွယ်တယ်၊ ဒုတိယ လိုအပ်တဲ့ လုပ်ဆောင်ချက်တွေကို public method အဖြစ် ဖွင့်ပေးတယ်၊ တတိယ အဲဒီ method တွေထဲမှာ validation နဲ့ စည်းမျဉ်းတွေ ထည့်တယ်။ ဒီအခန်းမှာ ဒီအဆင့် ၃ ဆင့်လုံးကို တစ်ခုချင်း လေ့လာမယ်။

Reference

Encapsulation မရှိရင် ဘာဖြစ်မလဲ

Attribute တွေ အားလုံး public ဖြစ်နေရင် ဘယ်သူမဆို ဘာမဆို လုပ်နိုင်တယ် —

class UnsafeAccount:
    def __init__(self, balance):
        self.balance = balance  # public — ဘယ်သူမဆို ထိနိုင်တယ်

a = UnsafeAccount(5000)
a.balance = -99999   # ဘယ်သူမှ တားမထားဘူး!
print(a.balance)
-99999

လက်ကျန် အနှုတ် ၉၉၉၉၉ ဖြစ်သွားတယ် — လက်တွေ့မှာ မဖြစ်သင့်တဲ့ အခြေအနေ။ ဘယ်သူက ဘယ်အချိန်မှာ ပြောင်းသွားလဲ လိုက်ရှာရလည်း ခက်မယ်။ Encapsulation က ဒီလိုမျိုး "တိုက်ရိုက်ထိခွင့်" ကို ပိတ်ပြီး၊ method တွေကတဆင့် စည်းမျဉ်းနဲ့ ဝင်ခွင့်ပေးတယ်။ အမှားနည်းတယ်၊ debug လုပ်ရလွယ်တယ်၊ စနစ်စိတ်ချရတယ်။ အထူးသဖြင့် အဖွဲ့လိုက်ရေးတဲ့ project တွေမှာ တစ်ယောက်ရဲ့ code က နောက်တစ်ယောက်ရဲ့ data ကို မတော်တဆ ဖျက်ဆီးမိတာ ကာကွယ်ပေးတယ်။

Access Modifier များ

Python မှာ attribute တစ်ခုကို ဘယ်လောက်အထိ ဖွင့်ပေးမလဲ ဆိုတာ နာမည်ရဲ့ အစမှာ underscore ခံပြီး ဖော်ပြတယ်။ ၃ မျိုး ရှိတယ် —

အမျိုးအစား ရေးနည်း အဓိပ္ပာယ်
Public self.name နေရာတိုင်းကနေ တိုက်ရိုက်သုံးလို့ရတယ်။ စံသတ်မှတ်ချက် (default)။
Protected self._name "အတွင်းပိုင်းသုံးဖို့" ဆိုတဲ့ သဘောတူညီချက် (convention)။ Class အပြင်က တိုက်ရိုက်မသုံးသင့်ဘူး၊ ဒါပေမယ့် subclass တွေက သုံးနိုင်တယ်။
Private self.__name Class အပြင်ကနေ တိုက်ရိုက်သုံးလို့ မရဘူး။ Python က နာမည်ကို ပြောင်းပစ်တယ် (name mangling)။

တစ်ခုချင်း ကြည့်ရအောင် —

class Staff:
    def __init__(self, name, department, salary):
        self.name = name            # public
        self._department = department  # protected (convention)
        self.__salary = salary      # private

s = Staff("Ko Aung", "Sales", 800000)
print(s.name)          # ရတယ် — public
print(s._department)   # အလုပ်လုပ်တယ်၊ ဒါပေမယ့် မလုပ်သင့်ဘူး (convention ချိုးဖောက်တာ)
print(s.__salary)      # AttributeError တက်မယ်!
Ko Aung
Sales
AttributeError: 'Staff' object has no attribute '__salary'

__salary ကို အပြင်ကနေ တိုက်ရိုက်ခေါ်ရင် Python က ရှာမတွေ့ဘူး။ တကယ်တော့ Python က သူ့ကို _Staff__salary လို့ နာမည်ပြောင်းပြီး သိမ်းထားတာ — ဒါကို name mangling လို့ ခေါ်တယ်။ Class အထဲက method တွေကတော့ self.__salary လို့ ပုံမှန်သုံးလို့ရတယ်။

သတိပြုရန်

Python ရဲ့ private/protected ဟာ တခြား ဘာသာစကားတွေလို တင်းကျပ်တဲ့ တားမြစ်ချက် မဟုတ်ဘူး — သဘောတူညီချက် (convention) နဲ့ name mangling သာဖြစ်တယ်။ _Staff__salary လို့ သိရင် အပြင်ကနေလည်း ဝင်ယူလို့ရတယ်။ ဒါကြောင့် "မထိသင့်ဘူး" ဆိုတဲ့ စည်းကမ်းကို လိုက်နာဖို့က programmer ရဲ့ တာဝန်ပါ။

Protected (_) ကို subclass မှာ ဘယ်လိုသုံးလဲ ကြည့်ရအောင် —

class Person:
    def __init__(self, name):
        self._name = name  # protected — subclass သုံးဖို့ ရည်ရွယ်

class Employee(Person):
    def show(self):
        print("Employee:", self._name)  # subclass ကနေ သုံးလို့ရ

e = Employee("Daw Hla")
e.show()
Employee: Daw Hla

Protected ရဲ့ သဘောတူညီချက်က — "class အပြင်လူ မထိနဲ့၊ ဒါပေမယ့် အမွေဆက်ခံတဲ့ subclass တွေက သုံးနိုင်တယ်"။ Private (__) ကတော့ subclass ကနေတောင် တိုက်ရိုက်မရဘူး။

Reference

Private Attribute နှင့် Getter/Setter

Private attribute ကို အပြင်က တိုက်ရိုက်မထိနိုင်ဘူး — ဒါဆို ဘယ်လို ဖတ်/ပြောင်းမလဲ? Getter (ဖတ်တဲ့ method) နဲ့ Setter (ပြောင်းတဲ့ method) ဆိုတဲ့ public method တွေကနေ တဆင့် လုပ်တယ်။ Setter ရဲ့ အကောင်းဆုံးအချက်က — တန်ဖိုးမပြောင်းခင် validation (စစ်ဆေးမှု) ထည့်လို့ရတာ။

class Customer:
    def __init__(self, name, credit):
        self.__name = name      # private
        self.__credit = credit  # private

    def get_name(self):
        return self.__name

    def get_credit(self):
        return self.__credit

    def set_credit(self, credit):
        if credit >= 0:
            self.__credit = credit
        else:
            print("Credit must not be negative.")


c = Customer("Ma Ei", 10000)
print(c.get_name())
print(c.get_credit())

c.set_credit(15000)
print(c.get_credit())

c.set_credit(-500)      # မမှန်တဲ့ တန်ဖိုး — ငြင်းပယ်ခံရမယ်
print(c.get_credit())   # အရင်တန်ဖိုးပဲ ကျန်မယ်
Ma Ei
10000
15000
Credit must not be negative.
15000

-500 လို့ ပေးလိုက်တာ setter က စစ်ပြီး ငြင်းလိုက်တယ် — __credit က အရင် 15000 အတိုင်း ကျန်နေတယ်။ __credit ကို public ထားခဲ့ရင် c.__credit = -500 လို့ ဘယ်သူမဆို တိုက်ရိုက်ဖျက်ဆီးလို့ရမှာ။ Setter က တံခါးစောင့်လိုပဲ — မမှန်တဲ့ data အဝင်မခံဘူး။

နောက်ထပ် ဥပမာ — ဝန်ထမ်းလစာ validation နဲ့ —

class Employee:
    def __init__(self, name, salary):
        self.__name = name
        self.__salary = salary

    def get_name(self):
        return self.__name

    def set_name(self, name):
        self.__name = name

    def get_salary(self):
        return self.__salary

    def set_salary(self, salary):
        if salary >= 0:
            self.__salary = salary
        else:
            print("Invalid salary!")


emp = Employee("U Kyaw", 500000)
print(emp.get_name())
emp.set_salary(600000)
print(emp.get_salary())
emp.set_salary(-1000)
print(emp.get_salary())
U Kyaw
600000
Invalid salary!
600000
Encapsulation: private data, public doors class Employee __salary private: locked inside get_salary() set_salary() validation check outside code X outside code must use the doors, never touch __salary directly
encapsulation — private data ကို getter/setter တံခါးတွေကနေပဲ ရောက်ပုံ

Reference

@property Decorator — Python ဆန်ဆန် နည်း

Getter/setter ရေးတာ အလုပ်ဖြစ်တယ် — ဒါပေမယ့် get_salary(), set_salary() လို့ method အဖြစ် ခေါ်ရတာ Python ဆန်မဆန်ဘူး။ Python မှာ ပိုသပ်ရပ်တဲ့နည်း ရှိတယ် — @property decorator။

@property သုံးရင် attribute လိုမျိုး တိုက်ရိုက်သုံးလို့ရတယ် (p.price) — ဒါပေမယ့် နောက်ကွယ်မှာ getter/setter အလုပ်လုပ်နေတာ။

class Product:
    def __init__(self, price):
        self.__price = price

    @property
    def price(self):        # getter အဖြစ် လုပ်ဆောင်တယ်
        return self.__price

    @price.setter
    def price(self, value):  # setter အဖြစ် လုပ်ဆောင်တယ်
        if value > 0:
            self.__price = value
        else:
            print("Price must be positive.")


p = Product(100)
print(p.price)    # getter ခေါ်တာ — method လို () မလိုဘူး

p.price = 150     # setter ခေါ်တာ — attribute လို ပေးလိုက်ရုံပဲ
print(p.price)

p.price = -50     # setter က ငြင်းမယ်
print(p.price)
100
150
Price must be positive.
150

သတိထားပါ — class ကို အရင် အပြည့်အစုံ ရေးပြီးမှ အောက်မှာ သုံးတဲ့ code (p = Product(100) စတာ) ရေးရတယ်။ p.price လို့ ဖတ်ရင် getter အလုပ်လုပ်၊ p.price = 150 လို့ ပေးရင် setter အလုပ်လုပ် — အပြင်လူက method ခေါ်နေမှန်း မသိဘူး။ သန့်ရှင်းတယ်၊ ဖတ်ရလွယ်တယ်၊ validation လည်း ပါသေးတယ်။

မှတ်ချက်

@property နဲ့ getter ပဲ ရေးပြီး setter မရေးရင် အဲဒီ attribute က read-only ဖြစ်သွားမယ် — ဖတ်လို့ပဲရမယ်၊ ပြောင်းလို့မရဘူး။ ပြောင်းဖို့ ကြိုးစားရင် AttributeError တက်မယ်။

Read-only property ဥပမာ —

class Circle:
    def __init__(self, radius):
        self.__radius = radius

    @property
    def radius(self):
        return self.__radius

    @property
    def area(self):
        return 3.14159 * self.__radius ** 2

c = Circle(5)
print(c.radius)
print(round(c.area, 2))
5
78.54

radius နဲ့ area ကို ဖတ်လို့ပဲရမယ် — c.radius = 10 လို့ ပြောင်းဖို့ကြိုးစားရင် AttributeError တက်မယ်။ တွက်ချက်ထားတဲ့ တန်ဖိုး (area) ကို attribute လိုမျိုး သန့်သန့်ရှင်းရှင်း ဖော်ပြနိုင်တာ @property ရဲ့ နောက်ထပ် အသုံးဝင်ပုံပဲ။

Reference

Private ကို ဘယ်အချိန်သုံးသင့်လဲ

Attribute တိုင်းကို private လုပ်ပစ်ရမလား? မဟုတ်ဘူး။ စည်းမျဉ်းအတိုချုပ် —

  • Private (__) — အပြင်က တိုက်ရိုက်ထိရင် အန္တရာယ်ရှိတဲ့ data: password, PIN, balance, အတွင်း state။ Validation လိုတဲ့ data မှန်သမျှ။
  • Public — တိုက်ရိုက်ဖတ်/ပြောင်းလို့ ပြဿနာမရှိတဲ့ data: name, title, color။

Python ရဲ့ ဒဿနက "we're all consenting adults" — programmer တွေ စည်းကမ်းလိုက်နာမယ်လို့ ယုံကြည်တယ်။ ဒါကြောင့် Python ရဲ့ private က "လုံးဝတားမြစ်ချက်" မဟုတ်ဘဲ "မထိသင့်ဘူး" ဆိုတဲ့ အချက်ပြသဘောပါ။ Java/C++ လို တင်းကျပ်တာ မဟုတ်ဘူး — ဒါကို အားနည်းချက်လို့ မမြင်ဘဲ Python ရဲ့ ရိုးရှင်းမှု (simplicity) လို့ မြင်ပါ။ စည်းကမ်းလိုက်နာဖို့က ကိုယ့်တာဝန်။

@property ရဲ့ လျှို့ဝှက်အစွမ်း: နောက်မှ ပြောင်းလဲလို့ရတာ

@property ရဲ့ အကောင်းဆုံးအချက်တစ်ခု — public attribute အဖြစ် စခဲ့တဲ့ code ကို နောက်မှ validation ထည့်ချင်ရင်၊ သုံးနေတဲ့ code တွေကို လုံးဝ မပြင်ဘဲ ပြောင်းလို့ရတယ် —

class Account:
    def __init__(self):
        self.__balance = 0

    @property
    def balance(self):
        return self.__balance

    @balance.setter
    def balance(self, value):
        if value >= 0:
            self.__balance = value
        else:
            print("Balance cannot be negative.")

a = Account()
a.balance = 5000   # အရင်ကလို သုံးလို့ရတုန်းပဲ — ဘာမှ မကွဲဘူး
print(a.balance)
a.balance = -100   # အခု validation ပါပြီ
print(a.balance)
5000
Balance cannot be negative.
5000

အပြင်ကနေ a.balance လို့ သုံးတဲ့ code မှန်သမျှ ဆက်အလုပ်လုပ်တုန်းပဲ — ဒါပေမယ့် နောက်ကွယ်မှာ အခု validation ရှိနေပြီ။ ဒါကြောင့် @property ကို "နောင်တချိန် တင်းကျပ်ချင်လာရင် ပြင်ရလွယ်အောင် ကြိုတင်ပြင်ဆင်ထားတဲ့ တံခါး" လို့ မှတ်ထားပါ။

Encapsulation နဲ့ Abstraction — ညီအစ်ကိုနှစ်

တိုင် ၄ ခုထဲက ၂ ခု (encapsulation နဲ့ abstraction) ကို ရောထွေးတတ်တယ်။ ခွဲခြားနည်း —

  • Encapsulation = "ဘယ်လို" ကာကွယ်မလဲ — အတွင်းရေးကို ဖုံးကွယ်တဲ့ ယန္တရား (private, getter/setter, @property)။
  • Abstraction = "ဘာ" ပြမလဲ — အသုံးပြုသူ မြင်ရတဲ့ ရိုးရှင်းတဲ့ မျက်နှာစာ (method နာမည်တွေက ရှင်းနေတာ၊ အတွင်းပိုင်း ဘယ်လိုအလုပ်လုပ်လဲ သိစရာ မလိုတာ)။

TV remote ကို စဉ်းစားကြည့် — ခလုတ်နှိပ်ရုံနဲ့ လိုင်းပြောင်းတယ် (abstraction: ရိုးရှင်းတဲ့ မျက်နှာစာ)။ အထဲက ဆားကစ်ပြားကို ဖွင့်ကြည့်ခွင့် မပေးဘူး (encapsulation: အတွင်းရေး ဖုံးကွယ်ခြင်း)။ တစ်ခုက ယန္တရား၊ တစ်ခုက ရလဒ် — နှစ်ခုတွဲမှ class ကောင်းတစ်ခု ဖြစ်တယ်။

မှတ်ချက်

Name mangling ဖြစ်သွားတဲ့ နာမည်ကို dir() နဲ့ ကြည့်နိုင်တယ် — dir(s) ထဲမှာ _Staff__salary ဆိုတာ တွေ့ရမယ်။ ဒါက "ဖုံးကွယ်ထားတယ်" ဆိုတာ သက်သေပဲ — ဒါပေမယ့် debug လုပ်ဖို့အတွက်ပဲ ကြည့်ပါ၊ သာမန် code မှာ အဲဒီနာမည်နဲ့ ဝင်မသုံးပါနဲ့။

Reference

Mini Project: Bank Account Management System

Encapsulation ကို လက်တွေ့သုံးထားတဲ့ ဘဏ်အကောင့် စနစ်။ __pin နဲ့ __balance ကို private လုပ်ထားတယ် — PIN မှန်မှ ငွေထုတ်/လက်ကျန်ကြည့်ခွင့်ရမယ်။

class BankAccount:
    def __init__(self, holder, pin, balance=0):
        self.__holder = holder
        self.__pin = pin
        self.__balance = balance

    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
            print(str(amount) + " deposited successfully.")
        else:
            print("Invalid deposit amount.")

    def withdraw(self, amount, pin):
        if pin == self.__pin:
            if 0 < amount <= self.__balance:
                self.__balance -= amount
                print(str(amount) + " withdrawn successfully.")
            else:
                print("Insufficient balance.")
        else:
            print("Incorrect PIN. Access denied.")

    def get_balance(self, pin):
        if pin == self.__pin:
            return self.__balance
        return "Incorrect PIN. Access denied."

    def get_holder(self):
        return self.__holder


account = BankAccount("Daw Mya", 2468, 5000)

account.deposit(1000)
account.withdraw(2000, 2468)   # PIN မှန် — အောင်မြင်
account.withdraw(500, 9999)    # PIN မှား — ငြင်းပယ်

print("Balance:", account.get_balance(2468))  # PIN မှန် — ပြမယ်
print("Balance:", account.get_balance(9999))  # PIN မှား — မပြဘူး
1000 deposited successfully.
2000 withdrawn successfully.
Incorrect PIN. Access denied.
Balance: 4000
Balance: Incorrect PIN. Access denied.

__balance ကို အပြင်ကနေ account.__balance လို့ တိုက်ရိုက်ထိလို့ မရဘူး — deposit(), withdraw(), get_balance() ဆိုတဲ့ public method တွေကတဆင့်ပဲ ကိုင်တွယ်ရတယ်။ ပြီးတော့ အရေးကြီးတဲ့ လုပ်ဆောင်ချက် ၂ ခု (withdraw, get_balance) က PIN စစ်ဆေးမှု ခံထားရတယ်။ ဒါဟာ encapsulation ရဲ့ လက်တွေ့တန်ဖိုးပဲ — အတွင်းရေးကို ဖုံးကွယ်ပြီး၊ စည်းမျဉ်းနဲ့ ဝင်ပေါက်ကို ထိန်းချုပ်ထားတာ။

BankAccount: a vault with guarded doors class BankAccount __pin __balance locked inside the vault deposit() withdraw(pin) get_balance(pin) get_holder() open window (no guard) every door checks the PIN except the open window
BankAccount ကို safe အဖြစ် ပုံဖော်ခြင်း

Reference

Encapsulation မလုပ်ရင် ဖြစ်တတ်တဲ့ bug ၃ မျိုး

  1. မမှန်တဲ့ data ဝင်လာတာ — အသက် -5၊ လစာ -1000၊ လက်ကျန် -99999။ Public attribute ဆို ဘယ်သူမှ တားမထားဘူး။ Setter validation က ဒါကို အဝင်ဝမှာ တားတယ်။
  2. Data နဲ့ logic ကွဲသွားတာ — ဥပမာ is_borrowed ကို အပြင်ကနေ တိုက်ရိုက်ပြောင်းလိုက်ရင် စာအုပ်အခြေအနေ မှန်ပေမယ့် ငှားရမ်းမှတ်တမ်း (log) မရေးဖြစ်ဘူး။ Method ကတဆင့်ပဲ ပြောင်းခွင့်ပေးရင် log ရေးတာပါ တစ်ပါတည်း လုပ်လို့ရတယ်။
  3. ဘယ်သူ့လက်ချက်လဲ မသိတာ — public attribute ကို program ရဲ့ နေရာအနှံ့က ပြောင်းနေရင် bug ရှာရတာ အိပ်မက်ဆိုး။ Private + method ဆို ပြောင်းတဲ့နေရာ (setter/method) တစ်ခုတည်းမှာ breakpoint ထောက်ပြီး ရှာလို့ရတယ်။

လက်တွေ့ဥပမာ: အစီရင်ခံစာကတ် (Report Card)

Private data + validation + @property တို့ကို တစ်ခုတည်း ပေါင်းသုံးထားတဲ့ ဥပမာ —

class ReportCard:
    def __init__(self, name):
        self.__name = name
        self.__marks = {}

    def add_mark(self, subject, mark):
        if 0 <= mark <= 100:
            self.__marks[subject] = mark
        else:
            print(f"Invalid mark for {subject}: must be 0-100.")

    @property
    def average(self):
        if not self.__marks:
            return 0
        return sum(self.__marks.values()) / len(self.__marks)

    def show(self):
        print(f"Report card for {self.__name}:")
        for subject, mark in self.__marks.items():
            print(f"  {subject}: {mark}")
        print(f"  Average: {self.average:.1f}")


r = ReportCard("Aung Khant")
r.add_mark("Myanmar", 85)
r.add_mark("English", 72)
r.add_mark("Maths", 105)  # မမှန်တဲ့ အမှတ် — ငြင်းပယ်ခံရမယ်
r.show()
Invalid mark for Maths: must be 0-100.
Report card for Aung Khant:
  Myanmar: 85
  English: 72
  Average: 78.5

__marks ကို အပြင်ကနေ တိုက်ရိုက်ထိလို့ မရဘူး — add_mark() ကတဆင့်ပဲ ထည့်ရပြီး 0–100 validation ခံရတယ်။ average က @property နဲ့ တွက်ချက်ပေးတဲ့ read-only attribute — သိမ်းထားစရာ မလိုဘဲ လိုအပ်တိုင်း အလိုအလျောက် တွက်ပေးတယ်။ ဒါဟာ encapsulation ရဲ့ အစုံအလင် ပုံစံပဲ။

__init__ မှာ validation ထည့်တာ

Setter မှာတင် မဟုတ်ဘူး — object စထုတ်ချင်းမှာတင် မမှန်တဲ့ data မဝင်အောင် __init__ မှာလည်း စစ်လို့ရတယ် —

class Student:
    def __init__(self, name, age):
        self.__name = name
        if age > 0:
            self.__age = age
        else:
            print("Age must be positive. Set to 0.")
            self.__age = 0

    @property
    def age(self):
        return self.__age

s1 = Student("Ma Thiri", 20)
s2 = Student("Ko Aung", -5)
print(s1.age)
print(s2.age)
Age must be positive. Set to 0.
20
0

Object မွေးဖွားလာကတည်းက data မှန်ကန်ဖို့ __init__ မှာ စစ်ဆေးတာ — "အစကောင်းမှ အနှောင်းသေချာ" ဆိုတဲ့ သဘောပဲ။ Setter validation နဲ့ တွဲလိုက်ရင် object ရဲ့ ဘဝတစ်လျှောက်လုံး data မှန်ကန်နေမယ်။

Encapsulation Checklist — ကိုယ့် class ကို ပြန်စစ်ဖို့

Class တစ်ခု ရေးပြီးတိုင်း ဒီမေးခွန်း ၄ ခု မေးကြည့်ပါ —

  1. အတွင်းရေးတွေ ဖုံးထားပြီလား? — အပြင်က တိုက်ရိုက်မထိသင့်တဲ့ attribute တွေကို __ (private) လုပ်ထားလား?
  2. ဝင်ပေါက်တွေ စည်းမျဉ်းနဲ့လား? — Setter တွေမှာ validation ပါလား? (အနှုတ်၊ အလွတ်၊ မမှန်တဲ့ အမျိုးအစား ဝင်မလာနိုင်ဘူးလား?)
  3. Public interface ရှင်းလား? — သုံးတဲ့သူက ဘယ် method ခေါ်ရမယ် ဆိုတာ ချက်ချင်းသိလား?
  4. Python ဆန်လား? — Getter/setter အစား @property သုံးသင့်တဲ့နေရာ သုံးထားလား?

၄ ခုလုံး "ဟုတ်ကဲ့" ဆိုရင် encapsulation ကောင်းကောင်းလုပ်ထားတဲ့ class ဖြစ်ပြီ။ ဒီအခန်းမှာ သင်ခဲ့တဲ့ ၄ တိုင်ထဲက ပထမတိုင် ပြီးသွားပြီ — နောက်အခန်းတွေမှာ ကျန်တဲ့ တိုင်တွေနဲ့ ဘယ်လိုတွဲဖက်သုံးရမလဲ ဆက်လေ့လာမယ်။

အနှစ်ချုပ်

ဒီအခန်းမှာ OOP တိုင် ၄ ခုထဲက ပထမတိုင်ကို အသေးစိတ် လေ့လာခဲ့ပြီးပြီ။ Encapsulation ရဲ့ အဓိကက "ဖုံးကွယ်ပြီး ထိန်းချုပ်တာ" — အတွင်းရေးကို private နဲ့ ဖုံးကွယ်တယ်၊ public method တွေကနေ စည်းမျဉ်းနဲ့ ဝင်ခွင့်ပေးတယ်၊ setter တွေမှာ validation ထည့်တယ်၊ @property နဲ့ pythonic ဆန်ဆန် ရေးတယ်။ ဒီအကျင့်က data ကို ကာကွယ်ပေးတယ်၊ bug နည်းစေတယ်၊ code ကို ရေရှည်ထိန်းသိမ်းရလွယ်စေတယ်။

  • Encapsulation ဆိုတာ object ရဲ့ အတွင်းရေးကို ဖုံးကွယ်ပြီး လိုအပ်တဲ့အပိုင်းကိုပဲ public interface ကနေ ဖွင့်ပေးတာ။ အဓိပ္ပာယ် ၂ ထပ်: ထုပ်ပိုးခြင်း (bundling) + ဖုံးကွယ်ခြင်း (hiding)။
  • Access modifier ၃ မျိုး — public (self.name), protected (self._name, convention), private (self.__name, name mangling)။
  • Getter/setter က private data ကို လုံခြုံစွာ ဖတ်/ပြောင်းပေးတယ်။ Setter မှာ validation ထည့်တာ အရေးအကြီးဆုံး အကျိုး။
  • @property က pythonic ဆန်ဆန် getter/setter — attribute လိုမျိုး သုံးလို့ရပြီး နောက်ကွယ်မှာ validation အလုပ်လုပ်တယ်။
  • Private ကို အန္တရာယ်ရှိတဲ့ data (PIN, balance, password) အတွက် သုံး — အရာတိုင်းကို private လုပ်ပစ်စရာ မလိုဘူး။

ဒီအနှစ်ချုပ်နဲ့ checklist ကို class ရေးတိုင်း ပြန်စစ်ဖို့ သုံးပါ — အကျင့်ဖြစ်သွားရင် encapsulation က အလိုအလျောက် ပါလာမယ်။ တစ်ချက်ချင်းစီကို ကိုယ့်ဘာသာ ဥပမာနဲ့ ရှင်းပြနိုင်ရင် ဒီအခန်း ကျွမ်းကျင်ပြီလို့ သတ်မှတ်နိုင်တယ်။

အမှားများတတ်တဲ့ အချက်များ

  1. Underscore တစ်ချောင်း/နှစ်ချောင်း ရောတာ — _name (protected, convention) နဲ့ __name (private, mangling) မတူဘူး။
  2. @property setter ထဲမှာ infinite recursion — setter ထဲမှာ self.price = value လို့ ပြန်ရေးမိရင် setter က သူ့ကိုယ်သူ အဆုံးမသတ် ခေါ်နေပြီး RecursionError တက်မယ်။ self.__price = value လို့ private attribute ကို တိုက်ရိုက် ပေးရတယ်။
  3. Private attribute ကို subclass က တိုက်ရိုက်သုံးဖို့ ကြိုးစားတာ — name mangling ကြောင့် subclass က self.__salary လို့ ခေါ်ရင် သူ့ကိုယ်ပိုင် _Subclass__salary ကို ရှာမယ်၊ parent ရဲ့ _Parent__salary ကို မရဘူး။
  4. Validation မေ့တာ — setter ရေးပြီး if စစ်ဆေးမှု မထည့်ရင် getter/setter ရေးရကျိုး တစ်ဝက်ပဲ ရမယ်။

လေ့ကျင့်ခန်းများ

အောက်က လေ့ကျင့်ခန်းတွေကို ကိုယ်တိုင် အရင်ကြိုးစားရေးကြည့်ပါ — မှားမှာကို မကြောက်ပါနဲ့၊ မှားမှ သင်ယူရတာ။

၁။ Student class မှာ __name (private) နဲ့ __score (private) ထားပါ။ get_name(), get_score(), set_score(score) (0–100 အတွင်းမှ လက်ခံပါ) တို့ကို ရေးပါ။ Score 150 ပေးကြည့်ပြီး ငြင်းပယ်ခံရတာ စမ်းပြပါ။

၂။ Access modifier table ကို ကိုယ်တိုင်စမ်းပါ — Laptop class မှာ brand (public), _model (protected), __serial (private) ထားပြီး၊ object အပြင်ကနေ ၃ ခုလုံး တိုက်ရိုက်ဖတ်ကြည့်ပါ။ ဘယ်ဟာတွေ အလုပ်လုပ်/မလုပ်လဲ မှတ်သားပါ။

၃။ Temperature class ကို @property နဲ့ ရေးပါ — celsius attribute ကို -273.15 (absolute zero) အောက် မရောက်အောင် setter မှာ စစ်ပါ။

၄။ Bank Account project ကို တိုးချဲ့ပါ — change_pin(old_pin, new_pin) method ထည့်ပါ။ PIN အဟောင်း မှန်မှ အသစ်ပြောင်းခွင့်ပေးရမယ်။

၅။ (စိန်ခေါ်မှု) — __password ကို private လုပ်ထားတဲ့ User class ရေးပါ။ login(password) method က ပေးတဲ့ password မှန်ရင် "Login successful", မှားရင် "Login failed" ပြန်ပေးပါ။ Password ကို အပြင်ကနေ တိုက်ရိုက်ဖတ်လို့ မရတာ သက်သေပြပါ။