多數教學上來就講 class、__init__、self,看完還是一頭霧水——那是因為你沒搞懂「為何要有 OOP」。這份教材反過來:先讓你寫沒有 OOP 的程式,痛一次,再讓 OOP 出來救你。用 iPhone 當主角,貫穿到底。
OOP 不是憑空冒出來的,它是為了解決真實的痛點而生。要懂 OOP 的價值,最快的方法是先看一段沒有它的程式碼——當你看完覺得「這也太慘」,OOP 對你就會像救命稻草。
你要寫一個程式,管理辦公室裡 3 支 iPhone:Akira 的、Mei 的、Tom 的。每支手機都有「型號、顏色、電量、擁有者」,並且要能「打電話、拍照、查電量」。聽起來很簡單對吧?但沒有 OOP 的世界,這事情會很噁心。
用 struct 把資料湊在一起,但每個函式都要手動傳入是哪一支手機。資料和操作完全分家,函式飄在 struct 外面,誰都管不到誰。
變數爆炸:每支手機 6 個變數,3 支就 18 個。函式參數一大堆,回傳值還要存回對的變數——一不小心就傳成 phone1_owner 配 phone2_battery,bug 找不到。
表面上整齊了一點,但更深的問題藏不住:字串 key 打錯不報錯、沒人保證每個 dict 都有相同 key、函式還是飄在外面沒有歸屬。核心病根:資料和能對它做的事,根本沒綁在一起。
phone1_battery、phone2_battery... 加到第 50 支時你會想離職
每個函式都要傳一大堆參數,順序錯一個就完蛋,IDE 也救不了你
手機資料在這、能對它做的事在那,沒人告訴你「手機這個東西能做什麼」
多加一個屬性,所有 phoneN_* 變數都要新增,所有相關函式都要改
這就是物件導向(Object-Oriented Programming,OOP)誕生的核心動機。1960 年代的 Simula 67、1970 年代的 Smalltalk,到後來的 C++、Java、Python——都是在實踐同一個簡單想法:把屬於同一個東西的「資料」和「行為」打包在一起,當作一個整體來處理。
既然如此,何不寫一個叫 iPhone 的「類別」,把這些東西全部裝進去?以後想要一支新手機,就用這個類別「造」一個出來。它自帶屬性、自帶行為——你不用再記「哪個變數要配哪個函式」。
把屬於一起的東西包成一包。手機的所有資料 + 行為,都在 iPhone 類別裡。
外面的人不用知道內部怎麼運作,只要會用「方法」就好。phone.make_call() 就這麼簡單。
寫一次 iPhone 類別,可以造出無限支實例。新增一支不再需要複製 6 行變數。
這兩組詞會貫穿整份教材。如果搞混了,後面看程式碼會覺得每個字都認識、整句卻不知道在說什麼。花 3 分鐘把它釐清,後面就一路通暢。
類別裡的東西,基本上只分這兩種:
self.xxx 形式存在。self.model = "iPhone 15"self.color = "黑色"self.battery = 80self.owner = "Akira"def 定義,第一個參數是 self。make_call() — 打電話take_photo() — 拍照charge() — 充電install_app() — 安裝 App很多人一直搞混這兩個字——它們長得幾乎一樣,但身分完全不同:
function(...) 是函式;obj.method(...)(前面有一個物件加點號)就是方法。len("abc") 是 函式(直接呼叫);"abc".upper() 是 方法("abc" 這個字串物件呼叫自己的 upper 方法)。本質上,方法就是「綁在物件身上」的函式——它知道是「誰」在呼叫自己(透過 self)。
Apple 總部裡有一張設計圖(class),上面寫著 iPhone 是什麼、能做什麼。富士康工廠看著這張設計圖,造出無數支實際的 iPhone(instances)。每一支都有相同的能力(打電話、拍照),但每一支的狀態都不一樣(不同擁有者、不同電量、不同顏色)。
下面這段是「iPhone 設計圖」的真實 Python 程式碼。點任何一行,會跳出該行在做什麼、為什麼這樣寫。常見元件:class__init__selfattributemethod
__init__(出廠時要做的事)和兩個方法(make_call、charge)。第 20-22 行示範怎麼使用——同一張設計圖,造出兩支獨立的手機。光講理論沒用,直接看程式碼最有感。下面這個任務在左右兩種寫法下會長什麼樣,請仔細看,特別注意:呼叫的時候有多麻煩、加新手機要動多少地方、整段程式碼有沒有「歸屬感」。
p1.make_call("0912"),p1 自己知道自己的電量iPhone. 一打 IDE 就提示所有功能__init__ 一處,全部實例自動更新規格看完前面,你可能想:「太棒了,從今天起所有程式都要用 OOP!」慢著。OOP 是為了解決「複雜系統」的問題,但複雜系統的工具用在簡單事情上,反而是負擔。誠實面對 OOP 的代價,才是真的學會它——也才知道什麼時候不要用它。
同樣是「算個三角形面積」這種簡單任務,硬要用 OOP 會變成什麼樣:
真實的 OOP 不只有好處,這四件事你應該知道:
本來 3 行就解決的事,硬包成 class 會變 30 行。簡單工具寫成 OOP,反而難讀、難改。剛學 OOP 的人最容易犯這個錯——什麼都用 class 包起來,整份程式變成一棟棟空房子。
每個實例都會在記憶體開一塊空間存自己的屬性。一萬個實例 = 一萬份屬性。方法呼叫也比直接呼叫函式多一層查詢開銷(在 Python 微小、在 C++ / 嵌入式 / 即時系統可能很重要)。對效能敏感的場景要慎用。
OOP 有一整套詞彙:class、instance、self、attribute、method、繼承、多型、封裝……。同樣是「印出 hello」,OOP 版本要先看懂這些觀念才能讀懂。新手友善度低於 procedural——所以函式式入門通常比 OOP 入門快。
OOP 的強項是「物件帶著狀態走」。但純粹的數學運算、資料轉換(map、filter、reduce、單純的 if/else)用函數式風格更乾淨。Python 支援 OOP 但也支援函數式,不是非用 class 不可——很多工具用函式寫反而簡潔得多。
這是大多數初學者卡關的地方。__init__ 不是你呼叫的,是 Python 偷偷幫你呼叫;self 也不是你傳的,是 Python 偷偷塞進去。把整個過程拆成 5 個步驟,慢動作看一遍,就不再神秘。看懂這節,後面玩工廠遊戲時你會清楚每按一次按鈕背後究竟發生什麼事。
上面 step ⑤ 講到「Python 把新物件交回給你」——這個「交回」要被接住才有意義。記憶體就像 Apple 的倉庫,空間有限。每支造出來的手機,都需要一個「名字」(變數名)指向它的記憶體位置,倉庫管理員才知道這支屬於誰、要保留下來。
變數 = ClassName(...)。這個變數既是這個物件的「名字」,也是它存活的證明。少了它,你剛蓋好的手機就是一隻看不見、馬上消失的鬼魂。
變數不是物件本身,而是「指向物件的標籤」。同一個變數可以「改貼到」其他物件上:
所以「同一個變數名重複使用」會讓前一個物件失去 reference 而消失。要同時保留多支手機,就要用不同的變數名(akira_phone、mei_phone、tom_phone...),或用 list / dict 一起裝起來。
把剛剛學的全部串起來。你填的每個欄位就是傳給 __init__ 的「參數」。按下「製造」,左邊的程式碼會即時生成,右邊倉庫會多一支獨立的手機——每支都是同一個類別的不同實例。試試造 5 支看看記憶體裡的樣子。
填寫規格表 → 呼叫 iPhone 類別 → 產生實例
初學者最大的誤解:「同一個類別的實例,會不會互相影響?」不會。每個實例都有自己的記憶體空間。下面有 3 支同樣由 iPhone 類別造出來的手機,去操作它們——你會發現它們各玩各的,互不干涉。每個按鈕都是呼叫一個「方法」。
所有操作都是「phone.method()」的形式,方法只會影響呼叫它的那支手機。執行紀錄會顯示 Python 實際做的事。
akira_phone.charge(20),只有 Akira 的電量增加,Mei 和 Tom 的完全沒變化。每支手機的 self 都指向自己——這就是「實例獨立」的真正意義。如果是純變數版本,你要記住「呼叫 charge 時要傳 phone1 的 battery」,OOP 版本:「phone.charge(),它自己知道自己是誰」。
最後給你一個彩蛋:把學到的 OOP 思維套到完全不同的情境——建立 Stand 類別產生你自己的替身。一樣的概念(class、instance、__init__、attribute),不同的故事。這就是 OOP 的威力:一套思維可以套用在任何東西上——iPhone、替身、員工、訂單、銀行帳戶、遊戲角色,全部都是 class + instance。
📖 同一個 Stand 類別,可以產出無數個獨立的替身實例。試試把 Star Platinum 改成你自己的 Stand。
混合「為什麼有 OOP」、「class vs instance」、「self 是什麼」、「method vs function」、「實例獨立性」、「何時不該用 OOP」、「跨情境應用」。答錯會立刻解釋。重點不是分數,是有沒有真的看懂每題的觀念。