แผนการเรียน
/

การเขียนโปรแกรมเชิงวัตถุ
ทบทวนเนื้อหาและเตรียมทำโปรเจกต์

สัปดาห์ที่ 9: สรุปแนวคิด OOP, UML และ GitHub สำหรับการพัฒนา

วัตถุประสงค์การเรียนรู้

1
ทบทวนความเข้าใจในหลักการ 4 ประการของ OOP
2
ทบทวนความสัมพันธ์ระหว่างคลาสและแผนภาพ UML
3
เตรียมโครงสร้างโปรเจกต์โดยใช้ GitHub

พื้นฐานสำคัญ: Class vs. Object

Class (แม่พิมพ์)

  • นิยามเชิงตรรกะหรือต้นแบบ (Template)
  • กำหนด คุณลักษณะ (attributes) และ พฤติกรรม (methods)
  • ยังไม่ใช้หน่วยความจำจนกว่าจะถูกสร้าง (Instantiate)
  • ตัวอย่าง: `class Dog:`

Object (วัตถุ)

  • สิ่งที่จับต้องได้ (Concrete Entity) ที่สร้างจากคลาส
  • มี สถานะ (state) และ เอกลักษณ์ (identity) เฉพาะตัว
  • ใช้หน่วยความจำขณะรันโปรแกรม (Runtime)
  • ตัวอย่าง: `my_dog = Dog("Buddy")`

หลักการ 4 ประการของ OOP

1. Encapsulation (การห่อหุ้ม)

การรวมข้อมูลและเมธอดไว้ด้วยกัน และจำกัดการเข้าถึงข้อมูลภายในจากภายนอกโดยตรง (เช่น การใช้ private variables)

2. Inheritance (การสืบทอด)

การสร้างคลาสใหม่จากคลาสที่มีอยู่เดิมเพื่อนำโค้ดกลับมาใช้ใหม่ (ความสัมพันธ์แบบ "Is-A")

3. Polymorphism (การพ้องรูป)

ความสามารถของวัตถุต่างชนิดในการตอบสนองต่อการเรียกเมธอดเดียวกันในรูปแบบที่ต่างกัน (เช่น Method Overriding)

4. Abstraction (นามธรรม)

การซ่อนรายละเอียดการทำงานที่ซับซ้อน และแสดงเฉพาะสิ่งที่จำเป็นต้องใช้งาน (เช่น Abstract Classes)

การออกแบบความสัมพันธ์:
Inheritance vs. Composition

Inheritance

ความสัมพันธ์แบบ "Is-A" (เป็น)

ผูกพันกันแน่นแฟ้น (Tightly coupled) ใช้เมื่อคลาสลูกเป็นรูปแบบเฉพาะของคลาสแม่

class Car(Vehicle): 
   pass
          
Composition

ความสัมพันธ์แบบ "Has-A" (มี)

ยืดหยุ่นกว่า (Loosely coupled) ใช้เมื่อวัตถุที่ซับซ้อนประกอบขึ้นจากวัตถุอื่น ๆ

class Car: 
   def __init__(self):
      self.engine = Engine()
         

เตรียมพร้อมโปรเจกต์
Version Control

สิ่งที่ต้องทำในสัปดาห์ที่ 9

GitHub Repo: สร้าง Repository ชื่อ OOP682 เป็น public และตั้งค่าไฟล์ .gitignore สำหรับ Python
Structure: จัดโครงสร้างชุดคำสั่งตามสัปดาห์เช่นน project/, week09/, week10/
Folder Environment: เพิ่มไฟล์ `requirements.txt` เพื่อบอก package ที่จำเป็นหรับ folder นั้น ๆ เมื่อใช้ `pip`
ใช้ uv เครื่องมือจัดการโครงงาน ด้วยคำสั่ง uv init --no-workspace

หลักการที่ 1: Encapsulation

การรวมข้อมูลและเมธอดเข้าด้วยกันเป็นหน่วยเดียว และจำกัดการเข้าถึงสถานะภายในของวัตถุ (Information Hiding)

ระดับการเข้าถึงใน Python

  • public เข้าถึงได้จากทุกที่ (ค่าเริ่มต้น)
  • _protected เป็นข้อตกลงร่วมกัน ควรใช้แค่ภายในคลาสหรือคลาสลูก
  • __private ถูกเปลี่ยนชื่อ (Mangling) เป็น _Class__var
class BankAccount:
    def __init__(self, balance):
        self._balance = balance  # Protected
        self.__secret = "Key"    # Private

    def deposit(self, amount):
        if amount > 0:
            self._balance += amount

account = BankAccount(100)
# account.__secret  <-- AttributeError
# account._BankAccount__secret  <-- เข้าถึงได้ถ้ารู้ชื่อ

วิถีแห่ง Python: @property

หลีกเลี่ยง Getter/Setter แบบ Java ให้ใช้ Property เพื่อเพิ่มลอจิกในการเข้าถึงข้อมูล

The Getter

เรียกใช้เมธอดเสมือนเป็น Attribute ตัวหนึ่ง

@property
def radius(self):
  return self._radius

The Setter

เพิ่มลอจิกตรวจสอบความถูกต้องเมื่อมีการกำหนดค่า

@radius.setter
def radius(self, value):
  if value < 0: raise ValueError
  self._radius = value
obj.radius = 10  ⟶  เรียกใช้ Setter อัตโนมัติ

หลักการที่ 2: Inheritance

การใช้โค้ดซ้ำ ("Is-A")

คลาสลูก (Child) รับคุณลักษณะและเมธอดมาจากคลาสแม่ (Parent) สามารถ ขยาย (extend) หรือ เขียนทับ (override) การทำงานได้

ฟังก์ชัน super()

ส่งคืนออบเจกต์ตัวแทนเพื่อเรียกใช้เมธอดของคลาสแม่ จำเป็นมากสำหรับการเรียกใช้เมธอดที่ถูก Override

class Animal:
    def speak(self):
        print("เสียงสัตว์ทั่วไป")

class Dog(Animal):  # สืบทอด
    def speak(self):
        # เขียนทับ (Override)
        print("โฮ่ง!")

class SuperDog(Dog):
    def speak(self):
        # เรียกใช้ speak() ของคลาสแม่
        super().speak() 
        print("...และบินได้ด้วย!")

การสืบทอดหลายทาง & MRO

Python รองรับการสืบทอดจากหลายคลาสแม่ ลำดับมีความสำคัญเพื่อหลีกเลี่ยง "Diamond Problem"

Method Resolution Order (MRO)

อัลกอริทึม (C3 Linearization) ที่ Python ใช้กำหนดลำดับในการค้นหาเมธอด

>>> ClassName.__mro__
(Child, Parent1, Parent2, object)

Python ค้นหาจาก ซ้ายไปขวา, ลึกไปตื้น (โดยส่วนใหญ่)

class A:
    def do_task(self): print("A")

class B(A): pass

class C(A):
    def do_task(self): print("C")

# Multiple Inheritance
class D(B, C): pass

d = D()
d.do_task()  
# ผลลัพธ์: "C" 
# MRO: D -> B -> C -> A

หลักการที่ 3: Polymorphism

"หลายรูปแบบ"

ความสามารถของวัตถุต่างชนิดในการตอบสนองต่อการเรียกเมธอดเดียวกัน ใน Python มักใช้หลักการ Duck Typing

🦆
"ถ้ามันเดินเหมือนเป็ด และร้องเหมือนเป็ด ก็ถือว่ามันเป็นเป็ด"
class PDFReader:
    def read(self):
        return "Reading PDF..."
class WordReader:
    def read(self):
        return "Reading Doc..."
def scan(reader): print(reader.read()) # ทำงานได้ทั้งคู่!

หลักการที่ 4: Abstraction (นามธรรม)

การซ่อนความซับซ้อน

Abstraction ช่วยให้เรากำหนดโครงร่าง (Interface) โดยซ่อนรายละเอียดการทำงานที่ซับซ้อนไว้ บังคับให้คลาสลูกต้องปฏิบัติตามสัญญาที่ระบุไว้

  • ABC: Abstract Base Classes (จากโมดูล abc)
  • @abstractmethod: เมธอดที่ ต้อง ถูกเขียนทับ (Implement) ในคลาสลูก
  • การบังคับใช้: Python จะแจ้งข้อผิดพลาดหากพยายามสร้าง Object จาก Abstract Class ที่ยังไม่สมบูรณ์
from abc import ABC, abstractmethod

class PaymentGateway(ABC):
    @abstractmethod
    def process(self, amount):
        pass

class PayPal(PaymentGateway):
    def process(self, amount):
        print(f"Paid ${amount} via PayPal")

# pg = PaymentGateway() # Error! สร้างไม่ได้
p = PayPal()
p.process(100) # Output: Paid $100 via PayPal

Composition เหนือกว่า Inheritance

Inheritance (เป็น Is-A)

ใช้เมื่อคลาสลูกเป็น "รูปแบบเฉพาะ" ของคลาสแม่

class Manager(Employee):
  ...

ผูกมัดแน่น (Rigid) การเปลี่ยนแปลงในคลาสแม่กระทบลูกทั้งหมด

Composition (มี Has-A)

ใช้สร้างวัตถุที่ซับซ้อนจากส่วนประกอบย่อยๆ ที่เป็นอิสระต่อกัน

class Car:
  def __init__(self):
    self.engine = Engine()

ยืดหยุ่นกว่า (Flexible) สามารถสลับเปลี่ยนส่วนประกอบได้ขณะทำงาน

"จงเลือกใช้ Object Composition มากกว่า Class Inheritance" - Design Patterns (GoF)

การแสดงภาพการออกแบบ: UML Class Diagrams

วางแผนโครงสร้างคลาสก่อนเริ่มเขียนโค้ดจริง

ClassName
+ public_attr
- private_attr
+ method()
+ get_status()
Inheritance (Generalization)
เส้นทึบ หัวลูกศรสามเหลี่ยมโปร่ง "Is-A" (เป็น)
Composition
เส้นทึบ หัวเพชรทึบ "Part-of" (เป็นส่วนหนึ่งที่ขาดไม่ได้ วงจรชีวิตขึ้นต่อกัน)
Aggregation
เส้นทึบ หัวเพชรโปร่ง "Has-A" (มีส่วนประกอบ แต่วงจรชีวิตแยกกันได้)

การจัดโครงสร้าง โปรเจกต์ Python

หลีกเลี่ยงการเขียนโค้ดทั้งหมดในไฟล์เดียว (Monolithic) ควรแบ่งโค้ดเป็นส่วนๆ ตามหน้าที่

  • 📁 Modules: ไฟล์ .py แต่ละไฟล์ที่มีคลาสหรือฟังก์ชันที่เกี่ยวข้องกัน
  • 📦 Packages: โฟลเดอร์ที่มีไฟล์ __init__.py ใช้รวมหลายโมดูล
  • 🚀 Entry Point: ไฟล์หลัก เช่น main.py หรือ app.py สำหรับเริ่มโปรแกรม
my_project/
├── main.py
├── requirements.txt
├── README.md
├── models/
├── __init__.py
├── user.py
└── product.py
├── services/
├── __init__.py
└── database.py
└── tests/

การจัดการเวอร์ชันด้วย GitHub

คำสั่งพื้นฐานที่จำเป็นสำหรับการทำงานร่วมกัน

1. เริ่มต้น (Setup)

git init git remote add origin <url>

2. บันทึกงาน (Staging & Committing)

git add . git commit -m "เพิ่มฟีเจอร์ User class"

3. ซิงค์ข้อมูล (Syncing)

git pull origin main git push origin main

⚠️ ไฟล์ .gitignore

สิ่งที่ไม่ควรอัปโหลด:

  • __pycache__/
  • venv/ หรือไฟล์ .env
  • ไฟล์ระบบ (.DS_Store)

ประเภทเมธอด: Instance vs Class vs Static

Instance Methods

เมธอดปกติ ทำงานกับข้อมูลของออบเจกต์ (State)

  • อาร์กิวเมนต์แรก: self
  • เข้าถึง: ทั้ง Instance และ Class
def work(self):
  self.data = 1

Class Methods

ผูกกับคลาส ไม่ใช่อินแตนซ์ มักใช้สร้าง Factory Methods

  • Decorator: @classmethod
  • อาร์กิวเมนต์แรก: cls
@classmethod
def from_string(cls, s):
  return cls(s)

Static Methods

ฟังก์ชันยูทิลิตี้ที่อยู่ในคลาสแต่ไม่ยุ่งกับข้อมูลภายใน

  • Decorator: @staticmethod
  • อาร์กิวเมนต์แรก: ไม่มี
@staticmethod
def is_valid(x):
  return x > 0

การจัดการแอททริบิวต์ด้วย @property

หลีกเลี่ยงการเขียน get_variable() และ set_variable() แบบ Java ให้ใช้ Property เพื่อตรวจสอบข้อมูลหรือคำนวณค่าโดยไม่ต้องเปลี่ยน Interface เดิม

ประโยชน์

  • Validation: ตรวจสอบค่าก่อนบันทึก (เช่น อายุห้ามติดลบ)
  • Computed Fields: คำนวณค่าสดใหม่เสมอ (เช่น area จาก width * height)
  • Backward Compatibility: เปลี่ยนโค้ดภายในโดยไม่กระทบคนเรียกใช้
class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def diameter(self):
        # แอททริบิวต์แบบคำนวณ
        return self._radius * 2

    @diameter.setter
    def diameter(self, value):
        # ตรวจสอบความถูกต้อง
        if value < 0:
            raise ValueError
        self._radius = value / 2

เมธอดพิเศษ (__dunder__)

กำหนดพฤติกรรมของออบเจกต์เมื่อทำงานร่วมกับตัวดำเนินการ (Operators) และฟังก์ชันของ Python

การแสดงผล (String Representation)

  • __str__(self) สำหรับผู้ใช้ทั่วไป (print)
  • __repr__(self) สำหรับนักพัฒนา (debugging)

ตัวดำเนินการ (Operators)

  • __add__(self, other) obj1 + obj2
  • __eq__(self, other) obj1 == obj2

การเป็นคอนเทนเนอร์

  • __len__(self) len(obj)
  • __getitem__(self, key) obj[key]

Context Managers

  • __enter__(self) with obj:
  • __exit__(self, ...) เมื่อจบการทำงาน

มองไปข้างหน้า: Design Patterns

สัปดาห์หน้า (Week 10) เราจะเปลี่ยนจาก "เขียนคลาสอย่างไร" ไปสู่ "ออกแบบระบบอย่างไร" เราจะเรียนรู้หลักการ SOLID และ Design Patterns มาตรฐาน

S.O.L.I.D.

5 หลักการออกแบบเพื่อให้ซอฟต์แวร์เข้าใจง่าย ยืดหยุ่น และดูแลรักษาง่าย

Design Patterns

วิธีการแก้ปัญหามาตรฐานที่ใช้ซ้ำได้ (เช่น Singleton, Factory, Observer)

การเตรียมตัว: ตรวจสอบ GitHub repository ของคุณให้เป็นปัจจุบัน คุณจะต้องทำการ Refactor โปรเจกต์โดยใช้รูปแบบเหล่านี้ในเร็วๆ นี้

จบการบรรยาย สัปดาห์ที่ 9

มีคำถามเพิ่มเติมไหมครับ?

สรุปเนื้อหา

  • Encapsulation, Inheritance, Polymorphism
  • Composition ดีกว่า Inheritance ในหลายกรณี
  • โครงสร้างโปรเจกต์ Python ที่ดี
  • กระบวนการ Git/GitHub เบื้องต้น

แหล่งข้อมูล

  • 📘 Python 3 Documentation (Classes)
  • 📗 GitHub Guides
  • 📙 เอกสารประกอบรายวิชา (wichit2s)
"Simple is better than complex." — The Zen of Python

ชั่วโมงปฏิบัติการ:
การพัฒนา Python GUI สมัยใหม่

การตั้งค่าสภาพแวดล้อมด้วย Scoop & uv + สร้างเว็บเบราว์เซอร์ด้วย PySide6

วาระการปฏิบัติการ (Agenda)

1
เครื่องมือ (Tooling): ติดตั้ง Scoop, Git และ uv
2
การตั้งค่า (Setup): เริ่มต้น Workspace และติดตั้ง PySide6
3
การพัฒนา (Development): สร้างเว็บเบราว์เซอร์ที่ใช้งานได้จริง

ขั้นตอนที่ 1: การติดตั้ง Scoop

Scoop เป็นคำสั่งติดตั้งซอฟต์แวร์แบบ command-line สำหรับ Windows จะใช้เพื่อจัดการเครื่องมือพัฒนาต่างๆ

ข้อกำหนดเบื้องต้น

เปิด PowerShell (ไม่ใช่ CMD) ในฐานะ Administrator

# 1. กำหนดนโยบายการรันคำสั่ง (Execution Policy)
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
# 2. ติดตั้ง Scoop
irm get.scoop.sh | iex

ขั้นตอนที่ 2: ติดตั้ง Git & uv

เราต้องการ Git สำหรับการจัดการเวอร์ชัน และ uv สำหรับจัดการแพ็คเกจ Python ที่มีความเร็วสูงมาก

คำสั่งติดตั้ง

scoop install git uv
ทำไมต้อง uv?
ใช้ทดแทน pip และ virtualenv เขียนด้วยภาษา Rust ทำงานเร็วกว่าเครื่องมือเดิม 10-100 เท่า
ทำไมต้อง Git?
จำเป็นสำหรับการดาวน์โหลดซอร์สโค้ด (Clone) และจัดการประวัติการแก้ไขงาน

การติดตั้งเครื่องมือบน macOS

1. ติดตั้ง Homebrew

Homebrew เป็นตัวจัดการแพ็คเกจ (Package Manager) ยอดนิยมสำหรับ macOS (คล้าย Scoop ของ Windows)

# เปิด Terminal แล้วรันคำสั่ง:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

*หมายเหตุ: หลังติดตั้งเสร็จ อาจต้องทำตามคำแนะนำบนหน้าจอเพื่อเพิ่ม Homebrew เข้าสู่ PATH

2. ติดตั้ง Git และ uv

ใช้ Homebrew ติดตั้ง Git และ uv (Python Package Manager ความเร็วสูง)

# ติดตั้ง Git และ uv
brew install git uv

3. ตรวจสอบการติดตั้ง

git --version uv --version

ขั้นตอนที่ 3: เริ่มต้น Workspace

เราจะสร้างโฟลเดอร์สำหรับโปรเจกต์และเริ่มต้นสภาพแวดล้อม Python โดยใช้ `uv`

  • สร้างโฟลเดอร์โปรเจกต์
  • เริ่มต้นโปรเจกต์ Python (Initialize)
  • เพิ่มไลบรารี PySide6 (Qt for Python)
# สร้างและเข้าไปในโฟลเดอร์
mkdir browser_lab
cd browser_lab
# เริ่มต้นโปรเจกต์ uv
uv init --no-workspace
# ติดตั้ง PySide6 (สร้าง venv ให้อัตโนมัติ)
uv add pyside6

เป้าหมาย: เว็บเบราว์เซอร์อย่างง่าย

Central Widget (QWebEngineView)

ส่วนประกอบหลัก (Core Components):

  • QMainWindow: โครงหลักของแอปพลิเคชัน
  • QToolBar: แถบนำทางด้านบน (Back, Forward, Reload)
  • QWebEngineView: วิดเจ็ตหลักตรงกลางสำหรับเรนเดอร์หน้าเว็บ HTML

การเขียนโค้ด: โครงสร้างหลัก (Skeleton)

สร้างไฟล์ชื่อ main.py เริ่มต้นด้วยการสืบทอดจาก QMainWindow

import sys
from PySide6.QtWidgets import QApplication, QMainWindow

# 1. สร้างคลาสย่อยของ QMainWindow เพื่อสร้างหน้าต่างแอป
class BrowserWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        
        self.setWindowTitle("My PySide6 Browser")
        self.resize(1024, 768)

# 2. ตั้งค่าลูปการทำงานของแอปพลิเคชัน (Application Loop)
if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = BrowserWindow()
    window.show()
    sys.exit(app.exec())

เพิ่มส่วนแสดงผลเว็บ (Web View)

QWebEngineView เป็นวิดเจ็ตทรงพลังที่ทำงานบนพื้นฐานของ Chromium

ขั้นตอนสำคัญ:

  1. Import QWebEngineView
  2. Import QUrl สำหรับจัดการ URL
  3. กำหนดให้เป็น Central Widget ของหน้าต่าง
from PySide6.QtWebEngineWidgets import QWebEngineView
from PySide6.QtCore import QUrl

class BrowserWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("My PySide6 Browser")

        # สร้าง Web View
        self.browser = QWebEngineView()
        
        # กำหนด URL เริ่มต้น
        self.browser.setUrl(QUrl("http://www.google.com"))
        
        # วางไว้ตรงกลางหน้าต่าง
        self.setCentralWidget(self.browser)

สร้าง แถบนำทาง (Navigation Bar)

เราต้องการแถบเครื่องมือด้านบน ใน Qt เราจะเพิ่ม QAction ลงใน QToolBar

from PySide6.QtWidgets import QToolBar, QStyle

# ภายใน __init__ ...
    # สร้าง Toolbar
    nav_bar = QToolBar()
    self.addToolBar(nav_bar)

    # สร้างปุ่มคำสั่ง (Actions)
    back_btn = QAction("Back", self)
    back_btn.triggered.connect(self.browser.back)
    nav_bar.addAction(back_btn)

    forward_btn = QAction("Forward", self)
    forward_btn.triggered.connect(self.browser.forward)
    nav_bar.addAction(forward_btn)

    reload_btn = QAction("Reload", self)
    reload_btn.triggered.connect(self.browser.reload)
    nav_bar.addAction(reload_btn)
*หมายเหตุ: ต้อง import QAction จาก PySide6.QtGui

ลอจิกของ แถบที่อยู่ (Address Bar)

เราต้องการช่องกรอกข้อความ (QLineEdit) เพื่อพิมพ์ URL และเชื่อมต่อกับเบราว์เซอร์

  • Input: เมื่อกด Enter ให้โหลด URL นั้น
  • Feedback: เมื่อหน้าเว็บเปลี่ยน ให้อัปเดตข้อความในช่อง URL
from PySide6.QtWidgets import QLineEdit

    # ภายใน __init__...
    self.url_bar = QLineEdit()
    self.url_bar.returnPressed.connect(self.navigate_to_url)
    nav_bar.addWidget(self.url_bar)

    # ซิงค์ URL เมื่อเว็บเปลี่ยน
    self.browser.urlChanged.connect(self.update_url)

# สร้างเมธอดใหม่ในคลาส
def navigate_to_url(self):
    url = self.url_bar.text()
    if not url.startswith("http"):
        url = "http://" + url
    self.browser.setUrl(QUrl(url))

def update_url(self, q):
    self.url_bar.setText(q.toString())

จบแล็บ! (Lab Complete)

รันโปรแกรมเพื่อตรวจสอบความถูกต้องของสภาพแวดล้อม

คำสั่งรันโปรแกรม

uv run main.py
โจทย์ท้าทาย 1
เพิ่มปุ่ม "Stop" เพื่อหยุดการโหลดหน้าเว็บ
โจทย์ท้าทาย 2
ตกแต่ง Toolbar ด้วย CSS (StyleSheet)