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