# Python OOP (物件導向)
###### tags: `Python`
* **OOP(Object-oriented programming, 物件導向程式設計):**
它是一個具有 **物件(Object)** 概念的開發方式,能夠提高軟體的**重用性、擴充性及維護性**
* OOP三大特性: `封裝`、`繼承`、`多型`
### 封裝(encapsulation)
>簡單來說:隱藏程式實現細節只保留下接口,使程式容易模組化。
簡單例子,無須知道work這個函式內部實作的細節,會用就好。
```python
class Worker:
def __init__(self, name):
self.name = name
def work(self):
print("I'm working now!! Don't bother me.")
w = Worker('Madi')
w.work() # I'm working now!! Don't bother me.
```
變數/函式前面加上雙底線,ex: `__sleep`,會變成private member。
### 繼承(inheritance)
```python
class Worker:
def __init__(self):
self.salary = 100
class Madi(Worker): # Madi繼承
def __init__(self):
self.salary = 200 # 子類別的變數
super().__init__() # super()代表父類別,__init__()會覆蓋掉子類別的self
def withdraw(self):
print(self.salary) # 父類別的變數,因為self被覆蓋掉
md = Madi()
md.withdraw() #100
```
### 多型
>呼叫同名的方法時,會得到不同的結果。
```python
class Employee:
def work(self):
print('Employee work')
class Andy(Employee):
def work(self):
print('Andy work')
class Joy(Employee):
def work(self):
print('Joy work')
w = Employee()
w1 = Andy()
w2 = Joy()
w.work() # Employee work
w1.work() # Andy work
w2.work() # Joy work
```
## Method(方法):
* **實例方法(Instance Method)**: 帶有`self`(實例)為參數的方法
* **類別方法(Class Method)**: 帶有`cls`(類別)為參數的方法
* **靜態方法(Static Method)**: 不帶有`self`、`cls`為參數的方法
* **抽象方法(Abstract Method)**: 尚未被實作且 children class 一定要用 override來實作之,類同Java裏頭的interface(介面)。
以下整理至 [Python進階技巧 (2) — Static/Class/Abstract Methods之實現](https://medium.com/citycoddee/python%E9%80%B2%E9%9A%8E%E6%8A%80%E5%B7%A7-2-static-class-abstract-methods%E4%B9%8B%E5%AF%A6%E7%8F%BE-1e3b3998bccf)
### 實例方法(Instance Method)
>在Python裡的instance method **必須傳入`self`參數**
以下為一個例子,建立一個Person的類別,裏頭有introduce這個instance method。
```Python
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# instance method
def introduce(self): # 必須傳入self
print(f"My name is {self.name}, I'm {self.age} old.")
p = Person('Madi', 25)
p.introduce() # My name is Madi, I'm 25 old.
Person.introduce() # Error! 無法使用class call instance method
```
instance method 只能instance call,不能class call。
### 靜態方法(Static Method)
#### Q: 何謂 Static :
>**一個 member 被宣告為 static 代表該 member 是在 Class Initialization 階段就被寫在一塊記憶體上**,因為是發生在 Instance Initialization 前,所以理所當然不會帶有 instance,且該 static member 之參數必須也是 static的(不然根本拿不到)。而**該 class 所生之所有 instances 皆可以使用該 static member**。
此外,Static 也可以在設計上與記憶體使用上帶來好處:
1. 將某個**member**設成static,**代表該 member獨立於instance,與instance無關,不帶instance為參數,使member間的關係乾淨俐落。**
2. 將某個**variable**設成static,**代表該class所生之所有instance都共用相同值的變數,作為instance之間的溝通。**
3. 承上,設成static的variable或member都**只會使用到一塊記憶體,不隨instance增加的數量而增加,減少記憶體使用。**
>在Python裡使用`@staticmethod`這個decorator來實作static method。
同樣的例子,再增加一個static method叫breath,注意的是**不能傳入`self`參數。**
```Python
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
print(f"My name is {self.name}, I'm {self.age} old.")
@staticmethod
def breath(): # 不能加self參數
print('breath...')
p = Person('Madi', 25)
Person.breath() # breath...
p.breath() # breath...
```
static method 可以instance call,也可以class call。
### 類別方法(Class Method)
>在Python裡使用`@classmethod`這個decorator來實作class method。
同樣的例子,上述的Person這個類別再加一個get_weather的class method,使用class裏頭定義的weather這個變數,須注意的是,**必須傳入`cls`參數。**
```python
class Person:
weather = 'cloud' # cls裏頭的變數
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
print(f"My name is {self.name}, I'm {self.age} old.")
@staticmethod
def breath():
print('breath...')
@classmethod
def get_weather(cls): # 必須傳入cls參數
print(f"Today's weather is {cls.weather}.") # cls.變數名稱
p = Person('Madi', 25)
Person.get_weather() # Today's weather is cloud.
p.get_weather() # Today's weather is cloud.
```
1. class method 可以instance call,也可以class call。
2. 與static method不同的是,class method可以用`cls`去access class內部的member。
### 抽象方法(Abstract Method)
>在Python裡要先import `abc`,再使用`@abc.abstractmethod`這個decorator來實作abstract method。
```python
import abc # 載入Abstract method(內建)
class Person(metaclass=abc.ABCMeta): # 設定metaclass
weather = 'cloud'
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
print(f"My name is {self.name}, I'm {self.age} old.")
@staticmethod
def breath():
print('breath...')
@classmethod
def get_weather(cls):
print(f"Today's weather is {cls.weather}.")
@abc.abstractmethod
def get_gender(self):
return NotImplemented # 尚未實作,留給子類別實作,且子類別必須實作
# Man繼承Person
class Man(Person):
def get_gender(self): # override父類別的abstract method
print("I'm a man.")
m = Man('Eric', 25)
Man('Eric', 25).get_gender() # I'm a man.
m.get_gender() # I'm a man.
```
abstract method 可以instance call,也可以class call。