Python 入門指南

單元 10 - 物件導向與封裝

本書已有新版,請參考 Python 入門指南 5.00 - 目錄

~~學習進度表~~

物件導向程式設計 (object-oriented programming) 有三大基本特性,分別是封裝 (encapsulation)繼承 (inheritance)多型 (polymorphism)

Encapsulation
    Inheritance
        Polymorphism

繼承的目的是讓類別 (class) 具有像是親屬的垂直關係(父母子女),子類別 (subclass) 可以擁有父類別 (superclass) 的成員 (member) ,而多型像是親屬的平行關係(兄弟姊妹),多個子類別繼承自單一父類別之時,這些子類別就可以用父類別代替,父類別如同家族裡的「姓」,子類別則是「名」。

繼承的英文原文 inherit ,中文意思泛指從什麼得到什麼,生物學上的遺傳也是用這個詞。

至於封裝的意思就是把屬性 (attribute) 封在類別中,這還牽涉到程式設計中另一個重要的概念             資訊隱藏 (information hiding) ,主要就是不讓外界隨意存取類別的屬性,也就是說,只讓類別屬性給同個類別的方法 (method) 存取。

我們先來看看可由外部程式存取類別屬性的例子

class Demo:
  def set_att(self, a=22, b=33):
    self.a = a
    self.b = b
    
  def do_something(self):
    return self.a + self.b
    
d = Demo()
d.set_att()
print(d.do_something())
d.a = 5
print(d.do_something())

#《程式語言教學誌》的範例程式
# http://kaiching.org/
# 檔名:class_demo4.py
# 功能:示範 Python 中的類別
# 作者:張凱慶 */

類別定義外要存取實體屬性 (instance attribute) 同樣用小數點運算子,這裡將 a 重新設定為 5

d.a = 5

執行結果如下

$ python class_demo4.py
55
38
$

由於 a 的值重新被設定為 5 ,導致第二次呼叫 do_something() 的結果變成 38

這樣子有什麼不好呢?簡單說,屬性值本來是依物件的需求而設定,像是一個描述車子的 Car 類別Car 類別有長、寬等屬性,長、寬在 Car 類別中都設定成合理的數字,若無對外部程式防堵修改屬性值,那有可能長、寬被修改成不合理的數字,那就會造成奇怪的執行結果了。

資訊隱藏的意義就在這裡,物件屬性不應該給外部程式隨意變動,屬性要進行封裝的話,就是在屬性前加上連續兩個底線,我們將上例改寫如下

class Demo:
  def set_att(self, a=22, b=33):
    self.__a = a
    self.__b = b
    
  def do_something(self):
    return self.__a + self.__b
    
d = Demo()
d.set_att()
print(d.do_something())
d.__a = 5
print(d.__a)
print(d.do_something())

#《程式語言教學誌》的範例程式
# http://kaiching.org/
# 檔名:class_demo5.py
# 功能:示範 Python 中的類別
# 作者:張凱慶 */

set_att() 中兩個實體屬性都已加上兩個底線

def set_att(self, a=22, b=33):
  self.__a = a
  self.__b = b

下面執行程式修改 __a 後,加上印出 __a 的動作

d.__a = 5
print(d.__a)

來執行看看結果囉

$ python class_demo5.py
55
5
55
$

__a 重新被設定為 5 也印出 5 來,可是 do_something()回傳值仍是 55 ,程式順利執行結束沒有出錯,這是為什麼呢?

簡單說, Python 直譯器 (interpreter) 允許程式執行時直接給物件添加屬性,基於物件類別的作用域 (scope) 不同,所謂的作用域也就是變數 (variable) 或識別字 (identifier) 的效力範圍,上例 Demo 中的 __a 由於已被封裝,因此效力僅限於 Demo 中,外界無法再存取 Demo 專屬的 __a

至於下面 d 設定 __a 的值,這是當前執行環境作用域 __main__d 擁有的 __a , Python 直譯器把這個 __a 當成 d 公開的屬性,對 d__a 有任何的修改都不會影響 Demo 中定義的 self.__a

好不好懂呢?類別中的屬性要不要封裝取決於程式設計師自己的決定囉!可是這樣設定屬性還得額外呼叫 set_att() ,我們希望建立變數時就能直接設定,這就需要設定 __init__() 方法了。

中英文術語對照
屬性attribute
類別class
封裝encapsulation
識別字identifier
資訊隱藏information hiding
繼承inheritance
實體屬性instance attribute
直譯器interpreter
成員member
方法method
物件導向程式設計object-oriented programming
多型polymorphism
作用域scope
子類別subclass
父類別superclass
變數variable
重點整理
1. 物件導向程式設計有封裝、繼承及多型等三大基本特性。
2. 繼承像是親屬的重直關係(父母子女),多型則像是親屬的平行關係(兄弟姊妹)。
3. 封裝連帶的觀念就是資訊隱藏,類別的屬性只能在類別中處理,要在實體屬性前加上兩個斜線。
4. 外界要存取封裝過的實體屬性要透過 getter 方法,或用 setter 方法進行設定。
問題與討論
1. 物件導向程式設計有哪三大基本特性?
2. 什麼是封裝?為什麼要做封裝?
3. 什麼是繼承?繼承機制有什麼優點?
4. 什麼是多型?可以用什麼方式比擬嗎?
練習
1. 寫一個程式 exercise1001.py ,利用 exercise0905.py 設計的 IntegerDemo ,封裝實體屬性 value
2. 寫一個程式 exercise1002.py ,利用 exercise0906.py 計算階層值的類別,封裝實體屬性 value
3. 寫一個程式 exercise1003.py ,利用 exercise0907.py 計算費氏數列的類別,封裝實體屬性 value

相關教學影片

上一頁 單元 9 - 類別
回 Python 入門指南首頁
下一頁 單元 11 - __init__() 方法
回 Python 教材首頁
回程式語言教材首頁