# 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`