OOP + จัดระเบียบโค้ด
สัปดาห์นี้สอน OOP "ผ่านการเลี้ยง Pet ดิจิทัล" 🐰 — เพราะมันมี state (หิว/เบื่อ/อายุ) ที่เปลี่ยนได้ · มีหลาย instance (Mochi vs Tofu vs Mango) · และมี behavior (feed/play/sleep) — ตัวอย่างที่สอน OOP ได้สมบูรณ์แบบที่สุด · เริ่มเลย!
เป้าหมายสัปดาห์นี้
- เข้าใจ
class,method,__init__,self— ผ่าน Pet 🐰 - เห็นว่า "หลาย instance = หลาย state แยกกัน"
- แยก code เป็นหลาย module (file)
- รู้ "เมื่อไหร่ใช้ OOP / เมื่อไหร่ใช้แค่ function"
- จับ AI เมื่อมัน over-engineer code ของคุณ
🐰 Meet Mochi — Pet ตัวแรกของเรา
ก่อนเรียน "theory" ของ OOP · มารู้จัก Mochi ก่อน — Pet กระต่ายดิจิทัลของเรา · Mochi มี state ที่เปลี่ยนตามเวลา (ความหิว ความสุข อายุ) และเราสามารถดูแลด้วย method 3 อย่าง (feed/play/sleep)
🎮 ลองเลี้ยง Mochi เลย!
ลองรัน Pet class ด้านล่าง · แก้บรรทัด "ใช้งาน" เพื่อ feed/play/sleep หลาย ๆ ครั้ง · ดู state เปลี่ยน
💡 ลองเพิ่ม mochi.play() 5 ครั้ง → ดูว่า hunger จะถึง 10 มั้ย (เล่นเยอะแล้วหิว!)
🤔 ทำไมไม่ใช้ dict?
ถ้าแค่ pet ตัวเดียว · ใช้ dict ก็ได้ — แต่พอมี 3 ตัว · code เริ่ม "ปวดหัว":
- ผูก data + function เข้าด้วยกัน ·
mochi.feed()>feed(mochi) - field กำหนดใน
__init__ที่เดียว · ไม่ต้องจำว่ามี field อะไร - เปลี่ยน field ใน 1 ที่ · IDE ช่วยเช็คทุกที่ที่ใช้
🧩 องค์ประกอบของ class — กายวิภาคของ Mochi
มาแยก code ของ Pet ออกเป็น "ชิ้นส่วน" ที่ตั้งชื่อให้รู้จัก:
class Pet: # ① class definition
def __init__(self, name, species): # ② constructor
self.name = name # ③ attribute (instance data)
self.species = species
self.hunger = 5 # ④ default value
self.happy = 5
def feed(self, food): # ⑤ method
self.hunger -= 3 # ⑥ self = "ตัวกระต่ายตัวนี้"
print(f"{self.name} กิน {food}")
mochi = Pet("Mochi", "🐰") # ⑦ create instance
mochi.feed("แครอท") # ⑧ call method
print(mochi.hunger) # ⑨ access attribute
| หมายเลข | ชื่อ | ความหมายในแบบ "เลี้ยงสัตว์" |
|---|---|---|
| ① | class Pet: | "พิมพ์เขียว" ของ pet — บอกว่า pet ทุกตัวมีอะไร / ทำอะไรได้ |
| ② | __init__ | "พิธีต้อนรับน้องใหม่" — ทำงานครั้งเดียวตอนเกิด |
| ③ | self.name | "แบบฟอร์มประจำตัว" ของ pet ตัวนี้ |
| ④ | self.hunger = 5 | "ค่าเริ่มต้น" ตอนเกิด — ทุกตัวเริ่มที่ 5 |
| ⑤ | def feed(self): | "ความสามารถ" ของ pet — ทุกตัวมี method นี้ |
| ⑥ | self | "ตัวฉัน" — pet ตัวนี้ของฉัน · ไม่ใช่ตัวอื่น |
| ⑦ | Pet(...) | "เลี้ยงตัวใหม่ขึ้นมา" — เกิด instance |
| ⑧ | mochi.feed(...) | "ให้อาหารกระต่ายชื่อ Mochi" |
| ⑨ | mochi.hunger | "ดูว่า Mochi หิวแค่ไหน" |
📐 Class Diagram (Mermaid)
🎯 หลาย Pet = หลาย State แยกกัน
"ทำไมไม่ใช้ global variable?" — เพราะ pet หลายตัวต้องมี state ของตัวเอง · class ให้คุณสร้าง หลาย instance โดยแต่ละตัวมี state ไม่ปะปนกัน
class Pet:
def __init__(self, name):
self.name = name
self.hp = 100
def attack(self, other, dmg):
other.hp -= dmg
a = Pet("Mochi")
b = Pet("Tofu")
a.attack(b, 30)
a.attack(b, 20)
print(a.hp, b.hp)
100 50
a.attack(b, 30) ลด b.hp เป็น 70 · a.attack(b, 20) ลดอีก เป็น 50 ·
a.hp ไม่เปลี่ยน — เพราะ method ทำกับ other (=b) · นี่คือพลังของ self
ที่แยกระหว่าง 2 instance
✅ เมื่อไหร่ "ควรใช้" OOP
| สัญญาณ | ตัวอย่าง | ใช้ class ดีกว่า function |
|---|---|---|
| มีหลาย instance ที่มี structure เหมือนกัน | pet หลายตัว · sensor หลายตัว · booking หลายรายการ | ✅ |
| มี state ที่เก็บ + เปลี่ยน | Cart (เพิ่ม/ลบของ) · BankAccount (deposit/withdraw) | ✅ |
| operation หลายอันที่ทำกับข้อมูลชุดเดียว | player.attack(), player.move(), player.heal() | ✅ |
| ต้องการ encapsulation — ซ่อน detail | internal counter, cache, lazy loading | ✅ |
🚫 เมื่อไหร่ "ไม่ควรใช้" OOP
| สัญญาณ | ตัวอย่าง | ใช้ function ก็พอ |
|---|---|---|
| Script สั้น ๆ ที่ทำครั้งเดียว | "แปลง CSV เป็น JSON ทีเดียว" | ✅ |
| มี instance เดียว | config app, global logger | ✅ ใช้ module + function |
| Data analysis pipeline | groupby/merge/plot | ✅ ใช้ pandas DataFrame ก็พอ |
| Utility ไม่มี state | def calc_grade(score) · def hash_id(s) | ✅ |
🤖 AI Over-Engineer Detector — ฉากตลก ๆ ที่เจอจริง
AI ชอบ "show off" ทุกอย่างที่เรียน — รวมทั้ง design patterns ที่ไม่ต้องใช้ · นี่คือฉากที่นักศึกษาเจอบ่อย:
- ใช้
ABC,Protocol,@abstractmethod— concepts ปี 4 - 15 บรรทัดแทนที่จะใช้ 2 บรรทัด
- ทุกอย่างคือ "flexibility ที่ไม่มีใครต้องการ" — YAGNI principle
- ถ้าจริง ๆ แล้ว project ต้องการ flex — รอ "เมื่อต้องการครั้งที่ 2" ค่อยเพิ่ม
🚩 5 สัญญาณว่า AI Over-Engineer
| สัญญาณ | ตัวอย่าง | วิธี reject |
|---|---|---|
| 1 ฟังก์ชัน → 3+ class | "add" → CalculatorEngine + AddOp + AbstractOperation | "เขียนเป็น function อันเดียวพอ" |
| import แปลก ๆ | abc, Protocol, metaclass | "ไม่ใช้ abc · ใช้ basic Python เท่านั้น" |
| มี Factory / Builder / Strategy pattern | class TodoFactory · class StoreBuilder | "ลบ pattern ทั้งหมด · เขียน straightforward" |
| ทุก method แค่ wrap call ของ method อื่น | class A → call B → call C → return value เดิม | "flatten · ลบ class ที่ไม่มี state" |
| เพิ่ม generic / typing เกินจำเป็น | Generic[T, U, V] ทั้งที่ใช้ int | "ใช้ type ปกติ · ไม่ต้อง generic" |
📁 แยก code เป็น module (file) — เลี้ยง Pet เริ่มใหญ่
ตอนนี้ Pet class โต — มี save/load, มี shop, มี battle · ถ้าใส่ทุกอย่างใน main.py
→ 500+ บรรทัด หา function ไม่เจอ · ทางออก: แยก concern เป็นไฟล์
pet_app/
├── main.py ← entry point — รันจากที่นี่
├── pet.py ← class Pet (data + behavior หลัก)
├── store.py ← save_pets(), load_pets() — JSON I/O
├── shop.py ← buy_food(), buy_toy() — ระบบเศรษฐกิจ
└── ui.py ← show_menu(), print_status() — display
pet.py
class Pet:
def __init__(self, name, species="🐰"):
self.name = name
self.species = species
self.hunger = 5
self.happy = 5
def feed(self, food):
self.hunger = max(0, self.hunger - 3)
def status(self):
return f"{self.species} {self.name}: หิว={self.hunger}"
store.py
import json
from pet import Pet
def save_pets(pets, filename="pets.json"):
data = [{"name": p.name, "species": p.species,
"hunger": p.hunger, "happy": p.happy} for p in pets]
with open(filename, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
def load_pets(filename="pets.json"):
with open(filename, encoding="utf-8") as f:
data = json.load(f)
pets = []
for d in data:
p = Pet(d["name"], d["species"])
p.hunger = d["hunger"]
p.happy = d["happy"]
pets.append(p)
return pets
main.py
from pet import Pet
from store import save_pets, load_pets
def main():
mochi = Pet("Mochi", "🐰")
tofu = Pet("Tofu", "🐱")
mochi.feed("แครอท")
save_pets([mochi, tofu])
print("เซฟแล้ว!")
if __name__ == "__main__":
main()
everything.py
🔄 Refactor — แตก 1 ไฟล์ใหญ่
เอา todo.py จาก W07 (TODO Manager) มา refactor:
todo_v2/
├── main.py ← parse argv, call commands
├── todo.py ← class Todo (id, text, done)
├── store.py ← load_todos(), save_todos()
└── commands.py ← add_command(), list_command(), done_command(), delete_command()
ขอ AI ช่วย refactor — แต่ ทีละไฟล์:
spec: refactor todo.py เดิม (paste code) ให้แตกเป็น 4 ไฟล์ตาม structure ด้านบน
ทำทีละไฟล์ — เริ่มที่ todo.py · ต้องเก็บ behavior เดิมไว้ทุกประการ
อย่าเพิ่ม feature ที่ไม่อยู่ในเวอร์ชันเดิม
🐱 Bonus: Inheritance — Cat สืบทอดจาก Pet
"แล้วถ้า Cat กับ Dog ทำงานต่างกัน?" — Cat ชอบกินปลา · Dog ชอบเล่นเยอะกว่า · ใช้ inheritance ให้ Cat / Dog "สืบทอด" จาก Pet โดยเปลี่ยนแค่ส่วนที่ต่าง
- Pet = ปู่ย่า · กำหนดสิ่งที่ทุกสัตว์มี (hunger, happy, feed)
- Cat / Dog = ลูกหลาน · สืบทอดของเดิม + เพิ่ม/เปลี่ยนตามชนิด
super().feed()= "เรียกของพ่อแม่" — ทำของพื้นฐานก่อนค่อยเพิ่ม
📐 Mermaid — Inheritance Tree
🎨 Type hints — สำคัญในยุค AI
ใส่ type ใน function signature — AI อ่านง่ายขึ้น 5 เท่า · IDE auto-complete ฉลาดขึ้น · เครื่องมือเช็คได้
__init__ · type hint = "contract" ที่บอกว่าใช้ class นี้ยังไง ·
AI ใช้ type hint เพื่อแก้ code ได้แม่นขึ้น
🧰 Standard structure ของ Python project
my_project/
├── README.md ← อธิบายโปรเจกต์
├── requirements.txt ← list ของ library ที่ต้องลง
├── .gitignore ← บอก git ว่าไฟล์ไหนไม่เก็บ
├── .env ← secrets (ไม่ commit!)
├── main.py ← entry point
├── src/ ← code หลัก
│ ├── __init__.py
│ ├── student.py
│ └── grading.py
├── tests/ ← unit tests (W13)
│ └── test_grading.py
└── data/ ← ข้อมูล (มัก gitignore)
└── students.csv
requirements.txt
pandas==2.1.0
numpy>=1.26
matplotlib>=3.8
requests
วิธีใช้: pip install -r requirements.txt
.gitignore
__pycache__/
*.pyc
.venv/
.env
*.csv
*.db
🌍 Virtual environment (.venv)
เก็บ library ของแต่ละ project ไว้ในห้องของตัวเอง · ไม่ปนกัน
# สร้าง .venv ใน project folder
python -m venv .venv
# Activate
# Windows:
.venv\Scripts\activate
# Mac/Linux:
source .venv/bin/activate
# ตอนนี้ pip install จะลงใน .venv เฉพาะ project นี้
pip install pandas
# ปิด
deactivate
🧪 Workshop — Refactor TODO Manager + ออกแบบ Pet ของตัวเอง
Part A: Refactor TODO Manager เป็น Module-based
-
Copy todo.py จาก W07 มาวางใน folder ใหม่
todo_v2/ - วาด class diagram (Mermaid) — มีอะไรเป็น class · มีอะไรเป็น function อิสระ
- แตกเป็นไฟล์ ตาม structure ด้านบน · refactor ทีละไฟล์
- ใส่ type hints ทุก function/method
- เซ็ต virtual environment + requirements.txt
- ทดสอบ ว่าทุก command ที่เคยทำงานได้ใน v1 ยังทำงานได้ใน v2 — regression test (W05)
- commit + push GitHub — README อธิบายว่า "ทำไม refactor + structure ใหม่"
Part B: 🐰 Adopt Your Own Pet — ออกแบบ class จากศูนย์
เลือก 1 ตัว ที่ "คุณอยากเลี้ยง" · ออกแบบ class + เขียน + commit
| ตัวเลือก | State (attributes) | Behaviors (methods) |
|---|---|---|
| 🌱 Plant (เลี้ยงต้นไม้) | name, height, water_level, sunlight, age | water(), give_sun(), trim(), grow() |
| 🤖 Robot (Mechatronics) | battery, position_x, position_y, payload | move(), turn(), pickup(), recharge() |
| 🎮 Player (RPG) | name, hp, level, xp, inventory | attack(), heal(), level_up(), pick_up() |
| 🚗 Car (Engineering sim) | fuel, speed, position, gear | accelerate(), brake(), refuel(), shift() |
| ☕ Drink (สั่งกาแฟ) | name, size, sweet_level, milk, price | add_topping(), customize(), price() |
| 📦 Order (ก๋วยเตี๋ยว) | items, total, status, table | add(), remove(), checkout(), receipt() |
- เลือก 1 อัน · เขียน spec ใน Spec Scorecard — ระบุ attributes + methods + invariants (e.g. "battery 0-100")
- วาด class diagram (Mermaid) — ดูตัวอย่าง Pet ด้านบน
- เขียน class + รัน + ทดสอบ 5 scenarios — ใช้ Python Runner ด้านบนเป็น template
- สร้าง 3 instances · ทดสอบว่า state แยกกัน
- เพิ่ม subclass — เช่น Cactus extends Plant (น้ำน้อย) · CleanRobot extends Robot
- commit + push
ข้อผิดที่พบบ่อย
def add(a, b) เป็น class Calculator · ถามตัวเอง:
"จะมีหลาย instance ของสิ่งนี้มั้ย?" · ตอบไม่ = ไม่ต้องใช้ class ·
เป็น function ก็ "คนรัก AI ไม่ใช้ class เก่งกว่าคนที่ใส่ class ทุกที่"
self หน้า attribute
def feed(self): hunger -= 3 ← ไม่มี self. = Python หา hunger ใน scope ของ method ไม่เจอ → NameError ·
ทุก attribute ต้อง self.hunger
from x import *)
ทำให้ไม่รู้ว่าชื่อมาจากไหน · IDE auto-complete ไม่รู้ · เพื่อนอ่าน code งง · ใช้
from x import name1, name2 เสมอ
class Cart(Pet) — Cart ไม่ใช่ Pet · นี่คือ misuse inheritance ·
ตรวจ: "X is a Y?" — ถ้าตอบไม่ได้ ใช้ composition แทน
ส่งงานสัปดาห์นี้
- 📁
todo_v2/ที่ refactor เสร็จแล้ว · มี structure ตามมาตรฐาน - 📊 Mermaid class diagram ของ todo_v2 ใน
diagrams.md - ✅ test results — v2 ทำทุก command ได้เหมือน v1
- 🐰 My Own Pet (Part B) — class + subclass + 5 scenarios ทดสอบ + Mermaid diagram
- 📝 commit log อธิบาย refactor steps
Reference จาก slide เดิม
ครอบคลุม Topic 9 — OOP and Matplotlib ส่วน OOP + ขยายไปการจัด module + type hints + virtual env ตามมาตรฐานยุค AI