物件導向程式設計 (object-oriented programming) 有三大基本特性,分別是封裝 (encapsulation) 、繼承 (inheritance) 及多型 (polymorphism)
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 。 |
相關教學影片