---
# System prepended metadata

title: Python OOP (物件導向)
tags: [Python]

---

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