面向物件程式設計基礎
把一組資料結構和處理它們的方法組成物件(object),把相同行為的物件歸納為類(class),通過類的封裝(encapsulation)隱藏內部細節,通過繼承(inheritance)實現類的特化(specialization)和泛化(generalization),通過多型(polymorphism)實現基於物件型別的動態分派。
面向物件思想有三大要素:封裝、繼承和多型
封裝:封裝是一個概念,它的含義是把方法、屬性、事件集中到一個統一的類中,並對使用者遮蔽其中的細節問題。資料被保護在抽象資料型別的內部,儘可能地隱藏內部的細節,只保留一些對外介面使之與外部發生聯絡。
繼承:如果兩個類存在繼承關係,則子類會自動繼承父類的方法和變數,在子類中可以呼叫父類的方法和變數,如果想要在子類裡面做一系列事情,應該放在父類無參構造器裡面。在java中,只允許單繼承,也就是說一個類最多隻能顯示地繼承於一個父類。但是一個類卻可以被多個類繼承,也就是說一個類可以擁有多個子類。
多型:多型性是指相同的操作可作用於多種型別的物件上並獲得不同的結果。不同的物件,收到同一訊息可以產生不同的結果,這種現象稱為多型性。
類和物件
簡單的說,類是物件的藍圖和模板,而物件是類的例項。這個解釋雖然有點像用概念在解釋概念,但是從這句話我們至少可以看出,類是抽象的概念,而物件是具體的東西。在面向物件程式設計的世界中,一切皆為物件,物件都有屬性和行為,每個物件都是獨一無二的,而且物件一定屬於某個類(型)。當我們把一大堆擁有共同特徵的物件的靜態特徵(屬性)和動態特徵(行為)都抽取出來後,就可以定義出一個叫做“類”的東西。
定義類
在Python中可以使用class關鍵字定義類,然後在類中通過之前學習過的函式來定義方法,這樣就可以將物件的動態特徵描述出來,程式碼如下所示。
class Student(object): # __init__是一個特殊方法用於在建立物件時進行初始化操作 # 通過這個方法我們可以為學生物件繫結name和age兩個屬性 def __init__(self,name,age): self.name=name self.age=age def study(self,course_name): print('%s正在學習%s' %(self.name,course_name)) # PEP 8要求識別符號的名字用全小寫多個單詞用下劃線連線 # 但是很多程式員和公司更傾向於使用駝峰命名法(駝峰標識) def wacht_av(self): if self.age < 18: print('%s只能觀看《熊出沒》' %self.name) else: print('%s正在觀看島國愛情動作片.' % self.name)
說明:寫在類中的函式,我們通常稱之為(物件的)方法,這些方法就是物件可以接收的訊息。
建立和使用物件
當我們定義好一個類之後,可以通過下面的方式來建立物件並給物件發訊息。
def main(): stu1=Student('張蘇',38) stu1.study('python程式設計') stu1.wacht_av() stu2=Student('張三',15) stu2.study('思想品德') stu2.wacht_av() if __name__ == '__main__': main()
訪問可見性問題
對於上面的程式碼,有C++、Java、C#等程式設計經驗的程式設計師可能會問,我們給Student物件繫結的name和age屬性到底具有怎樣的訪問許可權(也稱為可見性)。因為在很多面向物件程式語言中,我們通常會將物件的屬性設定為私有的(private)或受保護的(protected),簡單的說就是不允許外界訪問,而物件的方法通常都是公開的(public),因為公開的方法就是物件能夠接受的訊息。在Python中,屬性和方法的訪問許可權只有兩種,也就是公開的和私有的,如果希望屬性是私有的,在給屬性命名時可以用兩個下劃線作為開頭,下面的程式碼可以驗證這一點。
class Test: def __init__(self, foo): self.__foo = foo def __bar(self): print(self.__foo) print('__bar') def main(): test = Test('hello') # AttributeError: 'Test' object has no attribute '__bar' test.__bar() # AttributeError: 'Test' object has no attribute '__foo' print(test.__foo) if __name__ == "__main__": main()
但是,Python並沒有從語法上嚴格保證私有屬性或方法的私密性,它只是給私有的屬性和方法換了一個名字來“妨礙”對它們的訪問,事實上如果你知道更換名字的規則仍然可以訪問到它們,下面的程式碼就可以驗證這一點。之所以這樣設定,可以用這樣一句名言加以解釋,就是“We are all consenting adults here”。因為絕大多數程式設計師都認為開放比封閉要好,而且程式設計師要自己為自己的行為負責。
class Test: def __init__(self, foo): self.__foo = foo def __bar(self): print(self.__foo) print('__bar') def main(): test = Test('hello') test._Test__bar() print(test._Test__foo) if __name__ == "__main__": main()
強化練習
練習1:設計一個Circle(圓)類
包括圓心位置,半徑、顏色等屬性。編寫構造方法和其他方法,計算周長和麵積
from math import pi class Circle: def __init__(self,color,point,radius): self.color=color self.point=point self.radius=radius def area(self): return pi*self.radius*self.radius def perimeter(self): return2*pi*self.radius circle=Circle(color="red",point="18",radius=10) area1=circle.area() per1=circle.perimeter() color1=circle.color point1=circle.point print(area1,per1,color1,point1)
練習2:定義一個類描述數字時鐘
fromtime import sleep class Clock(object): def __init__(self,hour=0,minute=0,second=0): self.hour=hour self.minute=minute self.second=second def run(self): self.second+=1 if self.second==60: self.second=0 self.minute+=1 if self.minute==60: self.minute=0 self.hour+=1 if self.hour == 24: self.hour=0 def __str__(self): return '%02d:%02d:%02d' %(self.hour,self.minute,self.second) def main(): clock=Clock(23,59,58) while True: print(clock) sleep(1) clock.run() if __name__ == '__main__': main()
練習3:定義和使用矩形類
class Rect(object): def __init__(self,width=0,height=0): self.__width=width self.__height=height def perimeter(self): return (self.__height+self.__width)*2 def area(self): return (self.__width*self.__height) def __str__(self): return '矩形[%f,%f]' %(self.__width,self.__height) def __del__(self): print('銷燬矩形物件') if __name__ == '__main__': rect1=Rect() print(rect1) print(rect1.perimeter()) print(rect1.area()) rect2=Rect(4,5) print(rect2) print(rect2.perimeter()) print(rect2.area())