---
# System prepended metadata

title: 第一章 物件導向基礎概念

---

## 第一章 物件導向基礎概念
### 為甚麼需要物件導向
先給大家看看沒有物件導向的話，原本的程式會長怎樣。
想像我們現在要寫一個為學校每個學生都建立一個儲存資料的程式。
那麼沒有物件導向的程式就會是這樣寫
```python
# 學生姓名
student_names = ["小明", "小華", "小美"]
# 分數
student_scores = [95, 88, 92]
# 年齡
student_ages = [18, 19, 17]
# 身高
student_height = [170, 165, 160]
# 體重
student_weight = [60, 55, 50]

# 需要新增一個學生的話，就需要一次改動全部的list

student_names.append("小強")
student_scores.append(85)
student_ages.append(20)
student_height.append(175)
student_weight.append(70)
```
這種「分散式」的資料管理方式有幾個明顯的缺點：
容易出錯：新增或刪除資料時，必須同步修改所有列表，只要漏掉一個，資料就對不上了。
* 可讀性差：student_scores[0] 到底屬於哪個學生？你需要對照 student_names[0] 才能知道，程式碼變得難以理解。
* 難以擴充：如果要增加「體重」屬性，就必須再建立一個全新的列表。
* 核心問題：資料和操作資料的邏輯是分開的。我們希望將屬於「小明」的所有資料（姓名、分數、年齡）打包在一起，讓它成為一個獨立、完整的個體。

這就是物件導向（Object-Oriented Programming, OOP）要解決的核心問題：將相關的資料（屬性）和操作這些資料的函式（方法）打包在一起，形成一個「物件」。
### 類別是什麼
**類別**是一種定義物件的藍圖，你可以透過這個藍圖，寫出各種有不同屬性的物件出來。
例如在python裡面，numbers及list本身就是一個類別，你可以透過a = 15創造一個類別為int的物件a出來。
你可以想像int double list這些都是官方的型態，而你現在可以自己做自己想要的型態出來，class就是這樣的一個角色，他可以讓你從頭定義一個屬於自己的型態出來。
像是下列程式碼

```python=
class Student:
    def __init__(self, name, age, score, height, weight):
        self.name = name
        self.age = age
        self.score = score
        self.height = height
        self.weight = weight


students = [
    Student("小明", 18, 95, 170, 60),
    Student("小華", 19, 88, 165, 55),
    Student("小美", 17, 92, 160, 50)
]
```

上面的程式碼當中改寫了一開始舉的例子，我們將name age這些學生的屬性都包裝成一個叫做Student的類別，在後面我們需要儲存學生的列表的話，就只需要一種列表，且所有資訊都濃縮在一個類別當中。

```python=
student4 = Student("小強", 20, 85, 175, 70)
students.append(student4)
```

這樣後面要多加一個學生進來，也就只需要一次更改一種列表即可。
### 成員變數是什麼
**成員變數（屬性）** 用來儲存物件狀態的變數，每個物件可以擁有自己的成員變數值，這些值定義了物件的特性或狀態。
```python=
class Car:
    def __init__(self, year, color):
        self.year = year
        self.color = color

toyota = Car(2020, "red")
honda = Car(2021, "blue")
```
像上面的程式碼中，定義了兩個不同的物件，來自於同個類別（Car），而他們透過建構式設定了不同的屬性。
**toyota**的年份是2020，顏色為red。
**honda**的年份是2021，顏色為blue。

### 成員函式是什麼
**成員函式（方法）** 是類別內部定義的函式，通常用來操作物件的成員變數或執行與物件相關的邏輯。
```python=
class Animal:
    def __init__(self, postion, speed):
        self.position = postion
        self.speed = speed

    def forward(self):
        self.position += self.speed

people = Animal(0, 10)
turtle = Animal(0, 1)

people.forward()
turtle.forward()

print(people.position)
# 10
print(turtle.position)
# 1
```
在上面的程式碼當中，定義了兩個不同的動物，一個是people另一個是turtle。
當中烏龜的移動速度較慢所以我們定義他的速度為1，而人則為10，兩個的初始位置皆為0。
同時調用他們兩個的forward函式，讓他們往前走一步，最後印出來的結果是他們的位置都改變了，並且還會根據當初定義的速度有不同距離的變化。
### 建構式是什麼
**建構式（Constructor）** 是類別中的一種特殊方法，用來在創建物件時初始化該物件的狀態。
```python=
class Animal:
    def __init__(self, postion, speed):
        self.position = postion
        self.speed = speed
```
例如上面的程式碼中的建構式告訴我們如果要建立一個Animal類別的物件出來的話，第一個參數為position，第二個為speed，而成員變數在python中是定義在建構式內部的。
### self是什麼
**self**在python裡面是一個約定成俗的參數名字，用來代表當前物件的參數名稱。
### 為什麼成員函式前面都要加self
因為python在呼叫成員函式時，會將物件自己本身當作第一個參數傳入函式，所以我們需要有一個參數用來代表進行函式時這個物件本身，這個參數名字就是self。

以下例子為python呼叫成員函式時會做的事
```python=
people.forward()
# python會將其變成Animal.forward(people)然後進行呼叫。
```
```python=
# 進來函式後
def forward(self):
# 在這裡people傳進來變成了參數self，因此我們可以透過self.speed調用他的成員變數
# 就像是之前的people.speed一樣。
```


### python官方範例
Every variable in Python is an object
在python程式語言當中，所有定義的變數實際上都是object，每一個都是一個類別的instance。
例如my_list = []，my_list實際上是的型態是List，而他可以呼叫的方法有my_list.clear()等等。

### 類別變數、類別函式
相較於成員變數、成員函式是屬於每個根據這個**類別**打造出來的**物件**所擁有，類別變數及類別函式屬於整個類別。直接用例子看會比較清楚。

```python=
class A:
    count = 0;
    def __init__(self):
        A.count += 1
        self.value = 0

    @classmethod
    def get_count(cls):
        return cls.count

a1 = A()
a2 = A()
a3 = A()

print(A.get_count())  # Output: 3
print(a1.value)  # Output: 0
print(a2.value)  # Output: 0
print(a3.value)  # Output: 0
```

類別變數是放在任何一個函式外面，而類別函式需要加上裝飾器來表示他是一個類別函式，並且第一個參數是代表這個類別他本身(與self相同概念)，上面的程式碼我們用來讓他呼叫到類別變數count。
在python裡面還有靜態函式:static method。
```python=
class A:
    count = 0;
    def __init__(self):
        A.count += 1

    @classmethod
    def get_count(cls):
        return cls.count

    @staticmethod
    def static_method():
        return "This is a static method."

a1 = A()
a2 = A()
a3 = A()

print(A.get_count())  # Output: 3
print(A.static_method())  # Output: This is a static method.
```
這個就沒有任何的參數，所以也不會用來呼叫類別變數(count)。

#### 有甚麼用處?
```python=
class Car:
    def __init__(self, color, year):
        self.color = color
        self.year = year
    
    @classmethod
    def from_string(cls, car_string):
        color, year = car_string.split(',')
        return cls(color.strip(), int(year.strip()))
    
civic = Car.from_string("red, 2020")
print(civic.color)  # Output: red
print(civic.year)   # Output: 2020
```
這個是一種類別方法的使用方式，有一個屬於Car類別的類別方法叫做from_string，可以先不管他的實作方式，他的作用是接收一個字串以後，可以將其轉換成一個Car物件，並且回傳。
```python=
civic = Car.from_string("red, 2020")
print(civic.color)  # Output: red
print(civic.year)   # Output: 2020
```
#### 靜態函式可以拿來做甚麼?
如果想要創建一些公用函式並且這些函式都有共同點，那麼可以把他們包裝成一個類別，這個類別就是這些公用函式的集合，這樣可以更好的分類程式碼。
```python=
class StringUtils:
    @staticmethod
    def is_palindrome(s: str):
        """檢查一個字串是否為迴文"""
        s = ''.join(filter(str.isalnum, s)).lower()
        return s == s[::-1]

    @staticmethod
    def count_vowels(s: str):
        """計算字串中的母音數量"""
        count = 0
        for char in s.lower():
            if char in "aeiou":
                count += 1
        return count
```
像是上面的程式碼就可以放到Util.py檔案中，並且歸類到StringUtils類別中。