2026年10月31日 星期六

Python 封裝:為什麼不應任意修改物件資料?

Python 封裝:為什麼不應任意修改物件資料?

Python 封裝(Encapsulation):為什麼不應該讓所有程式都能隨意修改物件資料?

Python 物件導向系列(四)|Class × Inheritance × Polymorphism × Encapsulation

AI 能快速寫出 Python 類別,但真正的設計能力,是知道哪些資料需要檢查、哪些操作應該被允許,以及如何讓物件維持合理的狀態。

前面三篇已介紹類別、繼承與多型。這次繼續以「水井村智慧養殖池」為例,理解封裝(Encapsulation)的用意。封裝不只是把資料藏起來,更是把資料與相關操作組織在一起,提供清楚的存取介面與檢查規則。

一、生活中的封裝:自動販賣機

使用者可以選商品、付款與購買,但不能任意修改販賣機的庫存帳務。操作必須經過規定的介面;程式中的物件也可以如此設計。

二、沒有檢查的公開屬性,會發生什麼事?

class Pond:
    def __init__(self, name, oxygen):
        self.name = name
        self.oxygen = oxygen

pond1 = Pond("水井一號池", 5.8)
pond1.oxygen = -100
print(pond1.oxygen)

執行結果:

-100

溶氧濃度不應為負數,但這段程式仍允許寫入。當感測數據用於警報或控制時,資料錯誤可能引發後續問題。

三、第一步:透過方法更新資料

class Pond:
    def __init__(self, name, oxygen):
        self.name = name
        self.oxygen = 0
        self.set_oxygen(oxygen)

    def set_oxygen(self, value):
        if value >= 0:
            self.oxygen = value
        else:
            print("錯誤:溶氧不能小於 0")

    def show_info(self):
        print(self.name, "溶氧:", self.oxygen)

pond1 = Pond("水井一號池", 5.8)
pond1.set_oxygen(6.2)
pond1.set_oxygen(-100)
pond1.show_info()
錯誤:溶氧不能小於 0
水井一號池 溶氧: 6.2

這個版本可以檢查經過 set_oxygen() 的更新,但外部仍能執行 pond1.oxygen = -100 繞過檢查,因此需要更清楚的屬性介面。

四、Python 的屬性命名與封裝

寫法意義注意事項
self.oxygen公開屬性通常可直接讀寫
self._oxygen非公開慣例提醒使用者避免從外部直接修改
self.__oxygen名稱改寫降低意外存取及子類別名稱衝突,並非絕對私有

Python 的雙底線屬性會發生 Name Mangling(名稱改寫),例如 __oxygen 可能成為 _Pond__oxygen。這不是安全邊界,也不能用來防止惡意程式存取。

五、推薦實作:使用 @property 驗證資料

class Pond:
    def __init__(self, name, oxygen):
        self.name = name
        self.oxygen = oxygen

    @property
    def oxygen(self):
        return self._oxygen

    @oxygen.setter
    def oxygen(self, value):
        if value < 0:
            raise ValueError("溶氧不能小於 0")
        self._oxygen = value

pond1 = Pond("水井一號池", 5.8)
print(pond1.oxygen)
pond1.oxygen = 6.2
print(pond1.oxygen)
5.8
6.2

此時 pond1.oxygen = 6.2 看似直接指定屬性,實際上會呼叫 @oxygen.setter 的檢查邏輯。若執行 pond1.oxygen = -100,則會引發 ValueError。

重點:_oxygen 仍可被直接存取。封裝的目的是建立可靠、清楚的操作介面,並非提供資訊安全上的絕對保護。

六、封裝流程圖:資料如何被檢查?

外部程式pond.oxygen = 值@oxygen.setter檢查 value ≥ 0合理數值更新 _oxygen不合理數值引發 ValueError

使用者只需要知道如何操作公開介面,物件內部則負責檢查與維持資料一致性。

七、所有屬性都要用 Getter 與 Setter 嗎?

不需要。如果只是簡單的學生姓名,直接使用公開屬性就很合理:

class Student:
    def __init__(self, name):
        self.name = name

只有在需要驗證、轉換、唯讀控制或維持特殊規則時,才考慮 @property 或明確的操作方法。好的封裝不是讓程式變長,而是讓規則更明確。

八、No-AI → AI Pair → Challenge AI 實習

No-AI:先發現資料問題

class Product:
    def __init__(self, name, stock):
        self.name = name
        self.stock = stock

product = Product("水井文蛤", 100)
product.stock = -50

請思考:負數庫存有什麼問題?如果直接修改庫存,哪些規則可能被跳過?

AI Pair:與 AI 合作改善

建議提示詞:

請使用 Python 的 @property 改善 Product 類別,讓庫存不能小於零,並提供購買商品的方法。請比較公開屬性與 property 的優缺點,程式保持適合大學生理解。

拿到 AI 程式後,請測試:負數庫存、庫存不足、購買零件商品及正常購買。

Challenge AI:需求突然改變

新規則:「庫存不允許由外部直接設定,只能透過進貨 restock() 與銷售 purchase() 改變。」請設計唯讀的 stock 屬性,並判斷這樣是否比公開 Setter 更符合需求。

九、物件導向四個重要觀念比較

觀念核心問題智慧養殖範例
Class 類別如何組織資料與行為?建立 Pond
Inheritance 繼承如何共用與擴充既有設計?SmartPond(Pond)
Polymorphism 多型如何用相同方式操作不同物件?check_status()
Encapsulation 封裝如何管理資料存取並維持合理狀態?oxygen 的驗證

十、結語:AI 會寫封裝,但誰來決定規則?

AI 可以快速產生 @property、Getter 與 Setter,但只有理解應用情境的人,才能判斷哪些資料不合理、哪些操作應被允許,以及哪些規則需要由物件自己維護。

AI 協助寫程式;學生負責理解問題、設計規則、驗證行為。這是 AI 時代學習 Python 物件導向的重要能力。

Python 物件導向教學系列:(一)Class (二)Inheritance (三)Polymorphism (四)Encapsulation
下一篇建議:整合四大觀念,設計智慧養殖監控系統。

沒有留言:

張貼留言