## 第一章 物件導向基礎概念
### 為甚麼需要物件導向
先給大家看看沒有物件導向的話,原本的程式會長怎樣。
想像我們現在要寫一個為學校每個學生都建立一個儲存資料的程式。
那麼沒有物件導向的程式就會是這樣寫
```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類別中。