Python 入門指南 V2.00 - 單元 10 - 物件導向與封裝




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



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


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

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


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


001class 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
009d = Demo()
010d.set_att()
011print(d.do_something())
012d.a = 5
013print(d.do_something())
014 
015# 檔名: class_demo4.py 
016# 作者: Kaiching Chang 
017# 時間: July, 2014

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


012d.a = 5

執行結果如下



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


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


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


001class 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
009d = Demo()
010d.set_att()
011print(d.do_something())
012d.__a = 5
013print(d.__a)
014print(d.do_something())
015 
016# 檔名: class_demo5.py 
017# 作者: Kaiching Chang 
018# 時間: July, 2014

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


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

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


012d.__a = 5
013print(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

重點整理


  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

the end

沒有留言: