更新於 2024/12/12閱讀時間約 20 分鐘

物件導向(OOP)與程式設計的風格

這個單元我一直很想學習,物件導向 Object Oriented Programming 聽起來是個很酷的功能,而且是只有 Python 可以使用,沒想到事實是:

物件導向是一種程式設計的概念,而Python 是一種物件導向語言是「不正確」的說法

學校老師沒教你 OOP 物件導向的秘密 Why is Java NOT an OOP Language? 【電腦說人話】(CC字幕)

簡單來說,物件導向程式設計就像創立一個「烤餅乾的模板」(Class),而每個用這個模板烤出來的餅乾就是「物件」(Instance)。餅乾裡的內餡可以依照需求調整,但是外型是根據 Class 為基底創造,這代表每個物件都可以有不同的屬性。

更精確地來說是 OOP 的核心是 State and message 跟物件 Object 這個詞沒有直接關係。State(狀態) 指的是物件的屬性值(Attributes),用來描述物件(例如某學生)當前的數據或狀態。再透過Message(訊息)物件之間的溝通方式,透過方法呼叫(Method Call) 傳送訊息,改變物件的狀態或要求物件執行某些行為(例如更新成績)。

物件導向的設計適合用於建立遊戲角色,都會有基本的名字、年齡、技能。如果我不用物件導向的方式設計程式,我就要在每個角色都複製貼上名字、年齡、技能,再更改數值。


1. Class & Method (類別與方法)

Class 是用來定義物件的模板,裡面可以包含屬性(特性)和方法(功能)。

  • 屬性(Attributes):物件的特性,如名字、年齡等。
  • 方法(Methods):物件的行為或功能,如走路、說話等。方法是需要被「呼叫」而不是像屬性一開始就建立好。我覺得用「行動」來解釋會比方法

接下來,我們來看如何建構「模板」Class 吧!


2. __init__ (建構子方法)、self

  • 建構子方法(Initializer)
    • __init__ 是 Python 中的建構子方法,也叫「初始化方法」。
    • 它會在建立物件的瞬間自動執行,用來設定物件的屬性(資料)就會馬上帶入姓名、學號。例如我建立一個學生的基本資訊會有姓名、學號,當我建立學生 A,只要依序輸入「bicky, 10530」,程式就知道我輸入的是姓名和學號。
    • 小提醒:字要拼對,我一開始寫 initi 所以一直跑不出來
  • self 是物件本身
    • self 代表這個物件的自己,就像你在補習班的個人資料表。
    • 任何屬性都要寫成 self.屬性名稱 = 值,這樣物件才能記住你的資料。
  • 自動呼叫,無需手動啟動
    • 當你用 物件 = 類別(參數...) 來創造物件時,__init__ 方法就會自動被呼叫
    • 你只需要把必要的參數傳進去,程式會幫你完成初始化的設定。

Q1:一定要有 self 嗎?

A:是的,self 是 Python 的規範,代表物件自己,要去接住之後創立的實體。不

Q2:如果沒寫 __init__ 會怎樣?

A:物件還是可以被創造,但沒有任何屬性被初始化,等於一個「空白資料表」。

程式碼範例:創造補習班學生名單

程式解釋

  1. student1 = Student("小明", "高三", "數學") 被執行時:
    • Python 自動呼叫 __init__,幫 student1 設定:self.name = "小明"self.grade = "高三"self.course = "數學"
  2. 當我們呼叫 student1.show_info() 時(我把"."想成發動某功能),物件會記住這些屬性並輸出:
    姓名: 小明, 年級: 高三, 課程: 數學
# 定義一個學生類別
class Student:
# 初始化方法 (告訴程式,第一個第二第三個值個別是什麼」
def __init__(self, name, grade, course):
self.name = name # 記錄學生名字. 可以解釋成「的」
self.grade = grade # 記錄學生年級
self.course = course # 記錄報名的課程

# 定義一個方法:顯示學生資料
def show_info(self):
print(f"姓名: {self.name}, 年級: {self.grade}, 課程: {self.course}")

# 創建學生物件
student1 = Student("小明", "高三", "數學")
student2 = Student("老楊", "高二", "英文")

# 顯示學生資料
student1.show_info()
student2.show_info()

3. Abstraction (抽象化)

抽象化 是指隱藏不必要的細節,僅保留物件的核心功能。

抽象化的核心概念

  • 隱藏細節,展示功能
  • 使用者不需要知道內部的運作,只要知道怎麼用即可。
  • 程式設計應用場景
    • 設計抽象類別,規範子類別的行為,不需要在抽象類別中實作功能
    • 子類別根據具體需求實作抽象方法。

在程式中,如果我們要設計一個「動物」的模板,但不想指定每種動物的具體叫聲(因為每個動物的叫聲不同),就可以使用抽象化(Abstraction)

例子:建立不同動物的行為 (不用知道這個聲音怎麼發出來)

此程式碼由 ChatGPT 產生

# 引入抽象類別模組
from abc import ABC, abstractmethod

# 抽象類別: Animal
class Animal(ABC):
# 抽象方法 (沒有實作)
@abstractmethod
def make_sound(self):
pass # 這裡什麼都不做,等子類別來實作

# 繼承抽象類別
class Dog(Animal):
def make_sound(self):
print("汪汪!") # 實作叫聲方法

class Cat(Animal):
def make_sound(self):
print("喵喵!") # 實作叫聲方法

# 創建物件並呼叫方法
dog = Dog()
cat = Cat()

dog.make_sound() # 輸出: 汪汪!
cat.make_sound() # 輸出: 喵喵!

Inheritance 的三大重點:

我會用徒弟承襲師傅的武功,又可以更改部分內容創造自己的武功,再傳給自己的徒弟。

  1. 繼承功能,避免重複程式碼
    子類別可以直接使用父類別的屬性與方法,不必重新寫一遍。
  2. 擴展功能(方法覆寫)
    子類別可以重新定義(覆寫)父類別的方法,修改其行為。
  3. 多層繼承(進階概念)
    子類別還能再被其他類別繼承,形成「多層繼承」結構。

程式範例:動物與寵物 (Inheritance)

1. 設計父類別:Animal

# 定義父類別 (父母)
class Animal:
def __init__(self, name):
self.name = name

def eat(self): #method吃東西
print(f"{self.name} 正在吃東西。")

2. 設計子類別:DogCat

# 定義子類別 (繼承父類別 Animal)
class Dog(Animal):
def bark(self):
print(f"{self.name} 說:汪汪!")

class Cat(Animal):
def meow(self):
print(f"{self.name} 說:喵喵!")

3. 使用物件:創造狗和貓的實例

# 創建物件
dog = Dog("小黑")
cat = Cat("小白")

# 呼叫繼承的屬性與方法
dog.eat() # 繼承自 Animal 類別
dog.bark() # 子類別的方法

cat.eat() # 繼承自 Animal 類別
cat.meow() # 子類別的方法

輸出結果:

小黑 正在吃東西。
小黑 說:汪汪!
小白 正在吃東西。
小白 說:喵喵!

解釋發生了什麼?

  1. 定義父類別 Animal:
    • 它擁有 name 屬性,還有一個方法 eat()。
  2. 繼承父類別:
    • Dog 和 Cat 繼承了 Animal,因此不需要再次定義 eat() 方法。
  3. 擴展功能:
    • Dog 類別擁有一個 bark() 方法,專屬於狗。
    • Cat 類別擁有一個 meow() 方法,專屬於貓。
  4. 創建物件並使用:
    • 創建 dog 和 cat 物件後,可以使用從 Animal 繼承來的 eat() 方法,以及各自的新功能 bark() 和 meow()。

進階概念:覆寫 (Method Overriding)

有時候,子類別可能需要改寫(覆寫)父類別的方法,例如重新設計行為:

範例:覆寫父類別方法

class Animal:
def __init__(self, name):
self.name = name

def sound(self):
print("動物發出聲音。")

# 子類別覆寫父類別的方法
class Dog(Animal):
def sound(self): #sound改成直接說汪汪
print(f"{self.name} 說:汪汪!")

class Cat(Animal):
def sound(self):
print(f"{self.name} 說:喵喵!")

# 創建物件並測試
dog = Dog("小黑")
cat = Cat("小白")

dog.sound() # 輸出: 小黑 說:汪汪!
cat.sound() # 輸出: 小白 說:喵喵!

5. Encapsulation (封裝)

在程式設計中,封裝 就是隱藏物件的內部資料,只讓外界透過受控的方式存取和修改

白話文的比喻是 封裝(Encapsulation) 就像老師會保管學生的個人成績,不讓其他學生直接查看,但允許合法查詢,例如家長來問時,老師會提供「經過篩選的資料」。


補習班的比喻:成績資料庫

  • 學生成績存在學校的「成績資料庫」中,屬於隱私資料
  • 學生或家長不能直接改成績,因為這樣不安全。
  • 如果學生想知道自己的成績,必須合法查詢,如:
    • 查詢接口(Getter):詢問成績,老師會讀取並回報。
    • 修改接口(Setter):如果成績有錯誤,家長提出申訴,老師經過核對後可以更改。

封裝的三大重點:

  1. 隱藏資料(資料保護)
    • 透過私有屬性 (private attributes),不讓外界直接存取物件內部資料。
  2. 受控存取(安全操作)
    • 提供Getter(讀取資料)和Setter(修改資料)來管理資料存取。
  3. 避免錯誤(資料驗證)
    • 可以在 Setter 中加入條件檢查,確保資料正確。

程式範例:學生成績管理 (Encapsulation)

1. 定義學生類別 Student

class Student:
def __init__(self, name, score):
self.name = name # 公開屬性
self.__score = score # 私有屬性(封裝)在屬性前面加雙底線__

# Getter 方法(讀取分數)
def get_score(self):
return self.__score #return後不能賦值操作

# Setter 方法(修改分數,並檢查分數是否合法)
def set_score(self, score):
if 0 <= score <= 100: # 確保分數範圍在 0~100
self.__score = score
else:
print("分數必須在 0 到 100 之間!")

# 顯示學生資訊
def show_info(self):
print(f"學生: {self.name}, 分數: {self.__score}")

2. 測試物件:建立與操作 Student

# 建立學生物件
student1 = Student("小明", 85)
student1.show_info() # 輸出: 學生: 小明, 分數: 85

# 嘗試直接修改私有屬性(錯誤操作)
student1.__score = 95 # 沒有效果,因為 __score 被封裝了
student1.show_info() # 輸出仍為: 學生: 小明, 分數: 85

# 使用 Setter 方法合法修改
student1.set_score(95) # 修改成功
student1.show_info() # 輸出: 學生: 小明, 分數: 95

# 嘗試輸入無效分數
student1.set_score(150) # 輸出: 分數必須在 0 到 100 之間!

解釋發生了什麼?

  1. 資料保護:
    • self.__score 被定義為私有屬性(前面加上雙底線 __),外部無法直接存取和更改。
  2. 受控存取:
    • get_score() 是一個Getter,用來讀取分數。
    • set_score() 是一個Setter,用來修改分數,並檢查分數範圍是否正確。
  3. 錯誤預防:
    • 如果分數不在 0 到 100 之間,set_score() 會顯示錯誤訊息,防止無效資料。

6. Polymorphism (多型)

多型 表示不同的類別可以有相同的方法名稱,但表現不同的行為。例如,狗和貓都能發出聲音,但聲音不同。

程式碼範例:

class Dog:
def speak(self):
print("汪汪!")

class Cat:
def speak(self):
print("喵喵!")

# 多型實作
def make_sound(animal):
animal.speak()

make_sound(Dog()) # 汪汪!
make_sound(Cat()) # 喵喵!

總結:

物件導向程式設計幫助我們組織程式碼,更容易維護和擴展。關鍵概念包括:

  • 類別與物件:建立資料模型。
  • 方法與屬性:定義物件行為與特性。
  • 建構子與 self:自動初始化物件屬性。
  • 抽象化、繼承、封裝與多型:設計更彈性、更易於管理的應用程式

小比較:

  • Abstraction(抽象化)是告訴你功能怎麼用,細節不需要知道。
  • Encapsulation(封裝)是保護資料,不該讓外部亂改,資料只能安全存取。

既然提到程式風格,我再補充一些常聽到的程式設計觀念:

常見的程式設計觀念

Imperative vs Declarative Programming

1. Imperative Programming (命令式程式設計)

什麼是命令式程式設計?

重點:描述「如何做」,一步步執行每個操作。

簡單比喻:煮泡麵的命令式操作

  1. 煮水
  2. 放入泡麵
  3. 等待 3 分鐘
  4. 倒入調味包
  5. 攪拌均勻

命令式程式碼範例(Python)

# 計算從 1 到 5 的總和(命令式)
total = 0
for i in range(1, 6):
total += i
print(f"總和是: {total}")

2. Declarative Programming (聲明式程式設計)

什麼是聲明式程式設計?

你只需要描述想要的結果,不必關心怎麼實現。

簡單比喻:外送點餐的聲明式操作

  • 點餐 App:「我要一個珍珠奶茶。」
  • 不需要知道如何準備珍奶,外送員直接幫你送過來。

聲明式程式碼範例(Python - 使用 sum()

# 計算從 1 到 5 的總和(聲明式)
total = sum(range(1, 6))
print(f"總和是: {total}")

3. Procedural Programming (程序式程式設計)


什麼是程序式程式設計?

程序式程式設計就像補習班每天上課的流程

  1. 進入教室
  2. 開始點名
  3. 上課教學
  4. 下課擦黑板

重點: 把程式分成具體步驟的程序或功能重複使用


程序式程式碼範例(Python)

# 程序式設計:計算圓的面積
import math

# 定義一個函數(程序)
def calculate_area(radius):
return math.pi * radius ** 2

# 使用程序
area = calculate_area(5)
print(f"圓的面積是: {area:.2f}")

4. Functional Programming (函數式程式設計)

什麼是函數式程式設計?

簡單比喻:點飲料機(功能固定)

  • 飲料機只專注於輸入與輸出:
    • 投幣 + 選擇飲料 → 出飲料(結果總是一樣)。

函數式程式碼範例(Python - 使用 map()

# 使用函數式程式設計(map)
numbers = [1, 2, 3, 4, 5]

# 定義一個函數,將數字平方
def square(n):
return n ** 2

# 使用函數式程式設計應用
squared_numbers = list(map(square, numbers))
print(squared_numbers)

重點總結:

  • Imperative (命令式):一步步指示,描述怎麼做
  • Declarative (聲明式):只描述想要的結果,不關心怎麼實現。
  • Procedural (程序式):按固定程序與功能模組執行。
  • Functional (函數式):專注純函數運算,沒有副作用。



分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.