# Day 29 : Object Oriented Programming - Python ## 前言 要瞭解Golang如何實現物件導向,就要先瞭解什麼是物件導向,而要如何開始進入這個主題呢,我想就先就自己在WeHelp最先接觸的兩種語言Python、JavaScript開始說起好了 ## 物件導向 物件導向是一種程式撰寫的典範(programming paradigm),某些程式語言會遵循此風格來做設計,最經典的莫過於Java和C++。其核心概念為Class(類別)和Object(物件),目標是透過Object來模擬真實事件的事物,且物件還會包含Attribute(屬性)和Method(方法),屬性為該事物有關的特質或是相關資訊,方法則是讓我們用來操作事務。 Class就像個設計圖,主要用透過自行定義的**屬性**和**方法**來描述某件事情,可以看成一種規格,你滿足了這些規格才能做出設計圖所要的產品;Object則為透過Class裡面描述的規格所實現出來的東西, 先列舉一些生活的例子,再進入程式碼實作階段 | Class | Object | Attribute | Method | |:----------:|:--------------------:|:---------------------:|:----------------------:| | 建築設計圖 | 各種實體建築物 | 價格、面積、房齡 | 裝修、拆除 | | 車子 | Mercedes, BMW, Tesla | 品牌、加速度、馬力 | 加速、煞車、迴轉 | | 銀行 | 富邦、中信、台新 | 名字、營業額、地址 | 存款、取款、轉帳 | | 電腦 | ASUS、Acer、Mac | 顯示器、CPU、硬碟大小 | 播放音樂、顯示網頁畫面 | | 寵物 | 狗、貓、烏龜 | 品種、年齡、顏色 | 跑、叫、吃 | ## 定義 在Python裡面所有資料類型都是物件,而軟體工程師也可以自己定義資料類型,這種自創的資料類型就稱為類別(Class)。 這邊當然先以我最喜歡的狗狗貓貓為例,如果我們想要得到一個貓的物件,就必須先訂一個寵物的設計圖(Class)。裡面會定義品種、顏色、年齡三個屬性,以及run、speak、eat三個方法,最後在第14行透過實體化物件得到一個貓貓,而變成物件後,就可以使用原本在Class裡面定義的屬性和方法了。 ```python= class Pet(): # 定義屬性 breed="英短" color="橘色" age=7 # 定義方法 def run(self): print("I am running") def speak(self): print("I am speaking") def eat(self): print("I am eating") # 實體化 cat = Pet() # 物件就可以使用自訂義的方法屬性 cat.run() # I am running print(cat.age) # 7 ``` 接著可能會覺得有點奇怪,Pet這個class裡面的屬性都已經固定,這個不太像寵物設計圖吧,比較像產生一個貓的設計圖,所以要做些修改,讓它可以產生各種不同類的寵物。 我們可以在class裡面建立一個初始化的方法,當要實體化這個類別的物件時,`__init__()`這個方法就會自動執行,如在這裡有定義參數參數,之後在實體化的時候就要傳遞三個參數,如第12行 ```python= class Pet(): # constructor def __init__(self,breed,color,age): # 屬性 self.petBreed=breed self.petColor=color self.petAge=age def run(self): print("I am running") cat = Pet("英短","橘色",7) cat.run() print(cat.petAge) ``` 介紹Python裡面最基本的class和object的使用後,也需要瞭解OOP四個特性,分別為繼承(Inheritance)、封裝(Encapsulation)、多型(Polymorphism)、抽象化(Abstaction) ## 繼承(Inheritance) 繼承就像字面上的意思一樣,你可以繼承你爸爸的東西。在物件導向的設計裡面,類別是可以繼承的,被繼承的類別稱為父類別(parent class),繼承的類別稱為子類別(child class),其特點就是子類別會擁有父類別**公開的屬性和方法**。 這邊就先放棄常用的寵物,利用祖父、父與子可能比較有感覺 我們定義兩個類別,一個是祖父一個是父親,其中父親繼承了祖父的類別,也就是可以使用祖父公開的屬性和方法,所以當我們實體化一個jason物件,儘管類別是父親,但可以使用祖父的**屬性(lastName)**和方法**live()**。 ```python= class Grandfaher(): lastName = "Wu" def live(self): print("I live in Taipei") def work(self): print("I am tired") class Father(Grandfaher): age = 46 def work (self): print("I am an enginner") jason =Father() jason.live() # I live in Taipe print(jason.lastName) # Wu ``` 說明完何謂繼承,再分享三個繼承的特性,多層繼承、多重繼承與方法(屬性)複寫 ### Multi-Level Inheritance 先多新增一個Son的Class,Father類會繼承Grandfather類,Son類又會繼承Father類,這一層一層的繼承關係就是所謂的多層繼承,但使用多層繼層需要注意有什麼可能會造成屬性或方法不合邏輯,例如兒子john的年紀和他的工作就怪怪的,這時候可以用overridding的特型來解決。 ```python= class Grandfaher(): lastName = "Wu" age = 70 def live(self): print("I live in Taipei") def work(self): print("I am tired") class Father(Grandfaher): age = 46 def work (self): print("I am an enginner") class Son(Father): def play(self): print("I play baseball in MLB") john=Son() john.live() # I live in Taipei john.work() # I am an enginner print(john.lastName) # Wu print(john.age) # 46 ``` ### Method (Attribute) overriding 在繼承的時候如果有名字一樣的方法或是屬性,會覆蓋掉上一層的規定,例如我在原本Son的類別新增一樣的age屬性,和work方法,就會覆蓋掉原本在Father類別裡面的規定,得到的結果也比較合乎邏輯。 ```python= class Son(Father): age =24 def work (self): print("I am a pitcher") def play(self): print("I play baseball in MLB") john.live() # I live in Taipei john.work() # I am a pitcher print(john.lastName) # Wu print(john.age) # 24 ``` 這時候大家誤會john了,他不想單純當一個投手,就像捷克隊的球員不只打經典賽還可以當醫師、工程師,john想要繼承他爸爸的工作,當一個工程師,則可以使用super(),來使用父類別的方法,修改如下 ```python= class Son(Father): age =24 def work (self): super().work() def play(self): print("I play baseball in MLB") john.live() # I live in Taipei john.work() # I am an enginner print(john.lastName) # Wu print(john.age) # 24 ``` ### Multiple Inheritance 多重繼承代表一個類別可以繼承兩個類別,但如果是同樣的方法或是屬性,會以先繼承的為主,如果兒子比較喜歡當老師,他會想先繼承媽媽的職業,因為把媽媽的類別方在第一個,最後使用work方法得到的結果就會跟媽媽一樣。 ```python= class Father(Grandfaher): age = 46 def work (self): print("I am a enginner") class Mother(): age = 43 def work (self): print("I am a teacher") class Son(Mother,Father): age =24 john=Son() john.work() # I am a teacher print(john.age) # 24 ``` ## 封裝(Encapsulation) 如果有個方法或屬性只想在類別裡面被呼叫,不想被物件使用,可以在名稱前面加上兩個底線,定義為私有屬性和私有方法避免被操作,而這個概念就叫做封裝。 假設有個女兒的類別,她剛離職也不想讓別人知道她的年紀,儘管透過實體化,lily這個物件不會得到女兒現在的年齡和工作,只會得到她繼承爸爸的資訊。 ```python= class Father(Grandfaher): age = 46 def work(self): print("I am a enginner") class Daughter(Father): __age = 27 def __work(self): print("I'm preparing for a test") lily=Daughter() lily.work() # I am a enginner print(lily.age) # 46 print(lily.lastName) # Wu ``` ## 多型(Polymorphism) 同一個類別被具體化為物件後,儘管是呼叫同一個屬性或是同一個方法,但因為是來自不同的物件所以不會受影響。 假如多年之後,john發現他爸爸有個私生兒子,他年紀也比他大,同時也在MLB打球,但跟john不同,tony是個打者,可以發現儘管在tony這個物件修改了屬性和方法,但仍然不影響john本身。 ```python= class Son(Father): age = 24 def work(self): print("I am a pitcher") def play(self): print("I play baseball in MLB") john = Son() tony = Son() tony.age=30 tony.work = lambda: print("I am a hitter") print(tony.age) # 30 print(john.age) # 24 tony.work() # I am a hitter john.work() # I am a pitcher ``` ## 抽象 (Abstraction) 這個特性目前感覺就是跟著文章去瞭解,但還沒有很清楚其優點,如果剛好有讀者看到再麻煩指點指點。 有時候定義一個類別的時候,雖然有定義方法,但並不一定要去實作細節,因為可能不同子類別會有不同實踐的方法,因此我們可以再父類別先做抽象化就好。 而在Python裡面,想要定義一個抽象類別,要透過繼承標準套件 abc 中的 ABC 類別(顧名思義就是 ABstract Class)與 @abstractmethod 裝飾器,來實現一個包含抽象方法、並且**不能初始化**的類別,有抽象方法的類別稱為抽象類別,且要抽象類別不能實體化為物件(如第20行),實作程式碼如下, ```python= from abc import ABC, abstractmethod class Father(ABC): lastName = "wu" age = 46 @abstractmethod def work(self): pass class John(Father): age = 24 def work(self): print("I am a pitcher") def play(self): print("I play baseball in MLB") jose = Father() # TypeError john = John() john.work() # I am a pitcher print(john.lastName) # Wu ``` ## References 1. [什麼是物件導向程式設計 (Object-oriented programming)](https://ithelp.ithome.com.tw/articles/10265795) 2. [Object-oriented Programming in 7 minutes | Mosh](https://www.youtube.com/watch?v=pTB0EiLXUC8) 3. [物件導向程式設計四大支柱之三與四:抽象與多型](https://datainpoint.substack.com/p/7e4) 4. [Python多型(Polymorphism)實用教學](https://www.learncodewithmike.com/2020/01/python-polymorphism.html) 5. [30天把自己榨好榨滿的四週四語言大挑戰!- Day13談談抽象這件事](https://ithelp.ithome.com.tw/articles/10223079) ###### tags: `About Python`