物件導向程式設計 (object-oriented programming) 有三大基本特性,分別是封裝 (encapsulation) 、繼承 (inheritance) 及多型 (polymorphism)
繼承的目的是讓類別 (class) 具有像是親屬的垂直關係(父母子女),子類別 (subclass) 可以擁有父類別 (superclass) 的成員 (member) ,而多型像是親屬的平行關係(兄弟姊妹),多個子類別繼承自單一父類別之時,這些子類別就可以用父類別代替,父類別如同家族裡的「姓」,子類別則是「名」。
繼承的英文原文 inherit ,中文意思泛指從什麼得到什麼,生物學上的遺傳也是用這個詞。
至於封裝的意思就是把屬性 (attribute) 封在類別中,這還牽涉到程式設計中另一個重要的概念 資訊隱藏 (information hiding) ,主要就是不讓外界隨意存取類別的屬性,也就是說,只讓類別的屬性給同個類別的方法 (method) 存取。
我們先來看看可由外部程式存取類別中屬性的例子
001 | class Demo: |
002 | def set_att(self, a=22, b=33): |
003 | self.a = a |
004 | self.b = b |
005 | |
006 | def do_something(self): |
007 | return self.a + self.b |
008 | |
009 | d = Demo() |
010 | d.set_att() |
011 | print(d.do_something()) |
012 | d.a = 5 |
013 | print(d.do_something()) |
014 | |
015 | # 檔名: class_demo4.py |
016 | # 作者: Kaiching Chang |
017 | # 時間: July, 2014 |
類別定義外要存取實體屬性 (instance attribute) 同樣用小數點運算子,這裡將 a 重新設定為 5
012 | d.a = 5 |
執行結果如下
由於 a 的值重新被設定為 5 ,導致第二次呼叫 do_something() 的結果變成 38 。
這樣子有什麼不好呢?簡單說,屬性值本來是依物件的需求而設定,像是一個描述車子的 Car 類別有長、寬等屬性,長、寬在 Car 類別中都設定成合理的數字,若無對外部程式防堵修改屬性值,那有可能長、寬被修改成不合理的數字,那就會造成奇怪的執行結果了。
資訊隱藏的意義就在這裡,物件的屬性不應該給外部程式隨意變動,屬性要進行封裝的話,就是在屬性前加上連續兩個底線,我們將上例改寫如下
001 | class Demo: |
002 | def set_att(self, a=22, b=33): |
003 | self.__a = a |
004 | self.__b = b |
005 | |
006 | def do_something(self): |
007 | return self.__a + self.__b |
008 | |
009 | d = Demo() |
010 | d.set_att() |
011 | print(d.do_something()) |
012 | d.__a = 5 |
013 | print(d.__a) |
014 | print(d.do_something()) |
015 | |
016 | # 檔名: class_demo5.py |
017 | # 作者: Kaiching Chang |
018 | # 時間: July, 2014 |
set_att() 中兩個實體屬性都已加上兩個底線
002 | def set_att(self, a=22, b=33): |
003 | self.__a = a |
004 | self.__b = b |
下面執行程式修改 __a 後,加上印出 __a 的動作
012 | d.__a = 5 |
013 | print(d.__a) |
來執行看看結果囉
__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__() 方法了。
中英文術語對照
物件導向程式設計 | object-oriented programming |
封裝 | encapsulation |
繼承 | inheritance |
多型 | polymorphism |
類別 | class |
子類別 | subclass |
父類別 | superclass |
成員 | member |
屬性 | attribute |
資訊隱藏 | information hiding |
方法 | method |
實體屬性 | instance attribute |
直譯器 | interpreter |
作用域 | scope |
變數 | variable |
識別字 | identifier |
重點整理
- 物件導向程式設計有封裝、繼承及多型等三大基本特性。
- 繼承像是親屬的重直關係(父母子女),多型則像是親屬的平行關係(兄弟姊妹)。
- 封裝連帶的觀念就是資訊隱藏,類別的屬性只能在類別中處理,要在實體屬性前加上兩個斜線。
- 外界要存取封裝過的實體屬性要透過 getter 方法,或用 setter 方法進行設定。
問題與討論
- 物件導向程式設計有哪三大基本特性?
- 什麼是封裝?為什麼要做封裝?
- 什麼是繼承?繼承機制有什麼優點?
- 什麼是多型?可以用什麼方式比擬嗎?
練習
- 寫一個程式 exercise1001.py ,利用 exercise0905.py 設計的 IntegerDemo ,封裝實體屬性 value 。
- 寫一個程式 exercise1002.py ,利用 exercise0906.py 計算階層值的類別,封裝實體屬性 value 。
- 寫一個程式 exercise1003.py ,利用 exercise0907.py 計算費氏數列的類別,封裝實體屬性 value 。
the end
沒有留言:
張貼留言