OOP คืออะไรและทำไมต้องใช้
OOP (Object-Oriented Programming) คือแนวคิดการเขียนโปรแกรมที่จัดโครงสร้างโค้ดเป็น objects ซึ่งรวม data (attributes) และ behavior (methods) ไว้ด้วยกัน แทนที่จะเขียนโค้ดเป็นลำดับขั้นตอนจากบนลงล่างแบบ procedural programming OOP จัดการโค้ดเป็นกลุ่มของ objects ที่มีปฏิสัมพันธ์กัน
ข้อดีหลักของ OOP คือ Reusability ที่สามารถนำ class ไปใช้ซ้ำได้หลายที่ Maintainability ที่แก้ไขได้ง่ายเพราะโค้ดถูกจัดกลุ่มอย่างมีระเบียบ Scalability ที่ขยายระบบได้ง่ายโดยเพิ่ม class ใหม่ Modularity ที่แบ่งปัญหาใหญ่เป็นส่วนเล็กๆที่จัดการง่าย และ Collaboration ที่ทีมสามารถทำงานคนละ class ได้โดยไม่กระทบกัน
ภาษาที่รองรับ OOP ได้แก่ Python, Java, C++, C#, TypeScript, Ruby, PHP, Kotlin, Swift, Go (บางส่วน) และ Rust (บางส่วน) แต่ละภาษามีวิธี implement OOP แตกต่างกัน บางภาษาเป็น pure OOP เช่น Java ที่ทุกอย่างต้องอยู่ใน class บางภาษาเป็น multi-paradigm เช่น Python ที่ใช้ OOP หรือ functional ก็ได้
OOP มี 4 หลักการพื้นฐาน (Four Pillars) คือ Encapsulation, Abstraction, Inheritance และ Polymorphism ทั้ง 4 หลักการนี้ทำงานร่วมกันเพื่อสร้างโค้ดที่มีคุณภาพดี
4 หลักการพื้นฐานของ OOP
อธิบายหลักการแต่ละข้อพร้อมตัวอย่างโค้ด
# 4 Pillars of OOP — ตัวอย่างด้วย Python
#
# === 1. Encapsulation ===
# ซ่อนรายละเอียดภายในและเปิดเผยเฉพาะส่วนที่จำเป็น
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner # public
self._account_id = "ACC001" # protected (convention)
self.__balance = balance # private (name mangling)
def deposit(self, amount):
if amount <= 0:
raise ValueError("Amount must be positive")
self.__balance += amount
return self.__balance
def withdraw(self, amount):
if amount > self.__balance:
raise ValueError("Insufficient funds")
self.__balance -= amount
return self.__balance
@property
def balance(self):
return self.__balance
# === 2. Abstraction ===
# ซ่อนความซับซ้อน แสดงเฉพาะ interface ที่จำเป็น
from abc import ABC, abstractmethod
class PaymentGateway(ABC):
@abstractmethod
def process_payment(self, amount):
pass
@abstractmethod
def refund(self, transaction_id):
pass
class StripeGateway(PaymentGateway):
def process_payment(self, amount):
return {"status": "success", "provider": "stripe", "amount": amount}
def refund(self, transaction_id):
return {"status": "refunded", "tx_id": transaction_id}
# === 3. Inheritance ===
# สืบทอด properties และ methods จาก parent class
class Animal:
def __init__(self, name, sound):
self.name = name
self.sound = sound
def speak(self):
return f"{self.name} says {self.sound}"
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name, "Woof")
self.breed = breed
def fetch(self, item):
return f"{self.name} fetches the {item}"
class Cat(Animal):
def __init__(self, name):
super().__init__(name, "Meow")
# === 4. Polymorphism ===
# Object ต่างชนิดตอบสนองต่อ method เดียวกันต่างวิธี
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
def perimeter(self):
return 2 * 3.14159 * self.radius
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
# Polymorphism in action
shapes = [Circle(5), Rectangle(4, 6), Circle(3)]
for s in shapes:
print(f"{s.__class__.__name__}: area={s.area():.2f}")
Encapsulation และ Abstraction ด้วย Python
ตัวอย่างเชิงลึกของ Encapsulation และ Abstraction ในโปรเจกต์จริง
#!/usr/bin/env python3
# ecommerce_oop.py — E-commerce System ด้วย OOP
from abc import ABC, abstractmethod
from datetime import datetime
from typing import Optional
from dataclasses import dataclass, field
# --- Encapsulation: Product class ---
@dataclass
class Product:
name: str
price: float
stock: int
_id: str = field(default_factory=lambda: f"PRD-{datetime.now().strftime('%H%M%S')}")
def update_price(self, new_price: float):
if new_price < 0:
raise ValueError("Price cannot be negative")
self.price = new_price
def reduce_stock(self, qty: int):
if qty > self.stock:
raise ValueError(f"Only {self.stock} items available")
self.stock -= qty
# --- Abstraction: Payment interface ---
class PaymentProcessor(ABC):
@abstractmethod
def charge(self, amount: float, token: str) -> dict:
pass
@abstractmethod
def refund(self, tx_id: str, amount: float) -> dict:
pass
class StripeProcessor(PaymentProcessor):
def __init__(self, api_key: str):
self.__api_key = api_key # Encapsulated
def charge(self, amount: float, token: str) -> dict:
# Stripe API call (simplified)
return {"tx_id": f"ch_{token[:8]}", "amount": amount, "status": "paid"}
def refund(self, tx_id: str, amount: float) -> dict:
return {"tx_id": tx_id, "refunded": amount, "status": "refunded"}
class PayPalProcessor(PaymentProcessor):
def __init__(self, client_id: str, secret: str):
self.__client_id = client_id
self.__secret = secret
def charge(self, amount: float, token: str) -> dict:
return {"tx_id": f"pp_{token[:8]}", "amount": amount, "status": "paid"}
def refund(self, tx_id: str, amount: float) -> dict:
return {"tx_id": tx_id, "refunded": amount, "status": "refunded"}
# --- Encapsulation: Shopping Cart ---
class ShoppingCart:
def __init__(self):
self.__items: list[tuple[Product, int]] = []
def add_item(self, product: Product, qty: int = 1):
if qty > product.stock:
raise ValueError(f"Only {product.stock} {product.name} available")
# Check if already in cart
for i, (p, q) in enumerate(self.__items):
if p._id == product._id:
self.__items[i] = (p, q + qty)
return
self.__items.append((product, qty))
def remove_item(self, product_id: str):
self.__items = [(p, q) for p, q in self.__items if p._id != product_id]
@property
def total(self) -> float:
return sum(p.price * q for p, q in self.__items)
@property
def item_count(self) -> int:
return sum(q for _, q in self.__items)
def checkout(self, processor: PaymentProcessor, token: str) -> dict:
if not self.__items:
raise ValueError("Cart is empty")
# Reduce stock
for product, qty in self.__items:
product.reduce_stock(qty)
# Process payment (Polymorphism)
result = processor.charge(self.total, token)
order = {
"items": [(p.name, q, p.price) for p, q in self.__items],
"total": self.total,
"payment": result,
"date": datetime.now().isoformat(),
}
self.__items.clear()
return order
# ใช้งาน
laptop = Product("MacBook Pro", 59900, 10)
mouse = Product("MX Master 3S", 3490, 50)
cart = ShoppingCart()
cart.add_item(laptop, 1)
cart.add_item(mouse, 2)
print(f"Cart: {cart.item_count} items, Total: {cart.total:,.0f} THB")
stripe = StripeProcessor("sk_test_xxx")
order = cart.checkout(stripe, "tok_visa_4242")
print(f"Order: {order['payment']['status']}")
Inheritance และ Polymorphism ในทางปฏิบัติ
ตัวอย่าง Inheritance หลายแบบและ Polymorphism
#!/usr/bin/env python3
# inheritance_demo.py — Inheritance Patterns
# === Single Inheritance ===
class Vehicle:
def __init__(self, brand, model, year):
self.brand = brand
self.model = model
self.year = year
self._mileage = 0
def drive(self, km):
self._mileage += km
return f"Drove {km}km. Total: {self._mileage}km"
def __str__(self):
return f"{self.year} {self.brand} {self.model}"
class ElectricCar(Vehicle):
def __init__(self, brand, model, year, battery_kwh):
super().__init__(brand, model, year)
self.battery_kwh = battery_kwh
self.charge_level = 100
def drive(self, km):
consumption = km * 0.15 # kWh per km
if consumption > self.charge_level * self.battery_kwh / 100:
raise ValueError("Not enough charge")
self.charge_level -= consumption / self.battery_kwh * 100
return super().drive(km) + f" (Charge: {self.charge_level:.0f}%)"
def charge(self, percent=100):
self.charge_level = min(100, self.charge_level + percent)
# === Multiple Inheritance (Mixin Pattern) ===
class GPSMixin:
def get_location(self):
return {"lat": 13.7563, "lng": 100.5018}
def navigate(self, destination):
return f"Navigating to {destination}"
class BluetoothMixin:
def connect_device(self, device_name):
return f"Connected to {device_name}"
def play_music(self, song):
return f"Playing: {song}"
class SmartCar(ElectricCar, GPSMixin, BluetoothMixin):
def __init__(self, brand, model, year, battery_kwh):
super().__init__(brand, model, year, battery_kwh)
self.autopilot = False
def enable_autopilot(self):
self.autopilot = True
location = self.get_location()
return f"Autopilot ON at {location}"
# === Composition over Inheritance ===
class Engine:
def __init__(self, horsepower, fuel_type):
self.horsepower = horsepower
self.fuel_type = fuel_type
def start(self):
return f"{self.fuel_type} engine ({self.horsepower}hp) started"
class Transmission:
def __init__(self, type_name, gears):
self.type_name = type_name
self.gears = gears
self.current_gear = 0
def shift(self, gear):
if 0 <= gear <= self.gears:
self.current_gear = gear
return f"Shifted to gear {gear}"
class Car:
def __init__(self, brand, engine: Engine, transmission: Transmission):
self.brand = brand
self.engine = engine # Composition
self.transmission = transmission # Composition
def start_drive(self):
self.engine.start()
self.transmission.shift(1)
return f"{self.brand} ready to drive"
# === Method Resolution Order (MRO) ===
print(SmartCar.__mro__)
# SmartCar -> ElectricCar -> Vehicle -> GPSMixin -> BluetoothMixin -> object
# ใช้งาน
tesla = SmartCar("Tesla", "Model 3", 2024, 75)
print(tesla.drive(50))
print(tesla.navigate("Central World"))
print(tesla.connect_device("AirPods"))
print(tesla.enable_autopilot())
SOLID Principles สำหรับ OOP ที่ดี
5 หลักการ SOLID ที่ช่วยให้เขียน OOP ได้ดีขึ้น
#!/usr/bin/env python3
# solid_principles.py — SOLID Principles Examples
# === S — Single Responsibility Principle ===
# แต่ละ class ควรมีหน้าที่เดียว
# BAD: class ทำหลายหน้าที่
class UserBad:
def save_to_db(self): pass
def send_email(self): pass
def generate_report(self): pass
# GOOD: แยก responsibilities
class User:
def __init__(self, name, email):
self.name = name
self.email = email
class UserRepository:
def save(self, user: User): pass
def find(self, user_id: str): pass
class EmailService:
def send(self, to: str, subject: str, body: str): pass
# === O — Open/Closed Principle ===
# เปิดสำหรับ extension, ปิดสำหรับ modification
from abc import ABC, abstractmethod
class Discount(ABC):
@abstractmethod
def calculate(self, price: float) -> float:
pass
class PercentageDiscount(Discount):
def __init__(self, percent: float):
self.percent = percent
def calculate(self, price: float) -> float:
return price * (1 - self.percent / 100)
class FixedDiscount(Discount):
def __init__(self, amount: float):
self.amount = amount
def calculate(self, price: float) -> float:
return max(0, price - self.amount)
class BuyOneGetOneFree(Discount):
def calculate(self, price: float) -> float:
return price / 2
# === L — Liskov Substitution Principle ===
# Subclass ต้องแทนที่ parent ได้โดยไม่เกิดปัญหา
class Bird(ABC):
@abstractmethod
def move(self): pass
class FlyingBird(Bird):
def move(self):
return "Flying"
class SwimmingBird(Bird):
def move(self):
return "Swimming"
# === I — Interface Segregation Principle ===
# แยก interface ให้เล็กและเฉพาะเจาะจง
class Printable(ABC):
@abstractmethod
def print_document(self): pass
class Scannable(ABC):
@abstractmethod
def scan_document(self): pass
class Faxable(ABC):
@abstractmethod
def fax_document(self): pass
class ModernPrinter(Printable, Scannable):
def print_document(self): return "Printing"
def scan_document(self): return "Scanning"
class BasicPrinter(Printable):
def print_document(self): return "Printing"
# === D — Dependency Inversion Principle ===
# ขึ้นอยู่กับ abstraction ไม่ใช่ implementation
class Database(ABC):
@abstractmethod
def query(self, sql: str): pass
class PostgreSQL(Database):
def query(self, sql: str):
return f"PostgreSQL: {sql}"
class MySQL(Database):
def query(self, sql: str):
return f"MySQL: {sql}"
class UserService:
def __init__(self, db: Database): # ขึ้นอยู่กับ abstraction
self.db = db
def get_user(self, user_id: str):
return self.db.query(f"SELECT * FROM users WHERE id='{user_id}'")
# เปลี่ยน database ได้ง่าย
service_pg = UserService(PostgreSQL())
service_mysql = UserService(MySQL())
Design Patterns ที่ใช้บ่อยใน OOP
ตัวอย่าง Design Patterns ที่ใช้บ่อยในการพัฒนาซอฟต์แวร์
#!/usr/bin/env python3
# design_patterns.py — Common Design Patterns
# === Singleton Pattern ===
class DatabaseConnection:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.connection = "Connected to DB"
return cls._instance
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2) # True
# === Factory Pattern ===
class NotificationFactory:
@staticmethod
def create(channel: str, **kwargs):
if channel == "email":
return EmailNotification(**kwargs)
elif channel == "sms":
return SMSNotification(**kwargs)
elif channel == "push":
return PushNotification(**kwargs)
raise ValueError(f"Unknown channel: {channel}")
class EmailNotification:
def __init__(self, to, subject="", body=""):
self.to = to
self.subject = subject
self.body = body
def send(self):
return f"Email sent to {self.to}"
class SMSNotification:
def __init__(self, phone, message=""):
self.phone = phone
self.message = message
def send(self):
return f"SMS sent to {self.phone}"
class PushNotification:
def __init__(self, device_token, title="", body=""):
self.device_token = device_token
def send(self):
return f"Push sent to {self.device_token[:8]}..."
# === Observer Pattern ===
class EventEmitter:
def __init__(self):
self._listeners = {}
def on(self, event: str, callback):
if event not in self._listeners:
self._listeners[event] = []
self._listeners[event].append(callback)
def emit(self, event: str, *args, **kwargs):
for callback in self._listeners.get(event, []):
callback(*args, **kwargs)
class OrderSystem(EventEmitter):
def place_order(self, order_id, items, total):
# Process order
self.emit("order_placed", order_id=order_id, total=total)
def cancel_order(self, order_id):
self.emit("order_cancelled", order_id=order_id)
# Usage
orders = OrderSystem()
orders.on("order_placed", lambda **kw: print(f"Email: Order {kw['order_id']} confirmed"))
orders.on("order_placed", lambda **kw: print(f"Inventory: Reduce stock for {kw['order_id']}"))
orders.on("order_cancelled", lambda **kw: print(f"Refund: Process refund for {kw['order_id']}"))
orders.place_order("ORD-001", ["laptop"], 59900)
# === Strategy Pattern ===
class SortStrategy(ABC):
@abstractmethod
def sort(self, data: list) -> list:
pass
class QuickSort(SortStrategy):
def sort(self, data: list) -> list:
if len(data) <= 1: return data
pivot = data[len(data) // 2]
left = [x for x in data if x < pivot]
middle = [x for x in data if x == pivot]
right = [x for x in data if x > pivot]
return self.sort(left) + middle + self.sort(right)
class BubbleSort(SortStrategy):
def sort(self, data: list) -> list:
arr = data.copy()
for i in range(len(arr)):
for j in range(len(arr) - i - 1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
return arr
class Sorter:
def __init__(self, strategy: SortStrategy):
self.strategy = strategy
def sort(self, data):
return self.strategy.sort(data)
FAQ คำถามที่พบบ่อย
Q: OOP กับ Functional Programming ต่างกันอย่างไร?
A: OOP จัดโครงสร้างโค้ดเป็น objects ที่รวม data และ behavior เน้น mutable state และ side effects ที่ควบคุมผ่าน encapsulation ส่วน FP เน้น pure functions ที่ไม่เปลี่ยน state ใช้ immutable data และ function composition ภาษาสมัยใหม่เช่น Python, TypeScript, Kotlin รองรับทั้งสองแนวทาง เลือกใช้ตามความเหมาะสมของปัญหา
Q: Composition over Inheritance หมายความว่าอย่างไร?
A: หมายความว่าควรใช้ composition (มี object เป็น property) แทน inheritance (สืบทอดจาก class) เมื่อทำได้ เพราะ inheritance สร้าง tight coupling ระหว่าง parent-child classes ถ้า parent เปลี่ยน child ได้รับผลกระทบ ส่วน composition ยืดหยุ่นกว่า เปลี่ยน component ได้ง่าย Go ภาษาตัดสินใจไม่มี inheritance เลย ใช้ composition อย่างเดียว
Q: Abstract Class กับ Interface ต่างกันอย่างไร?
A: Abstract class สามารถมีทั้ง abstract methods และ concrete methods (methods ที่มี implementation) และมี instance variables ได้ ส่วน Interface (ใน Java/TypeScript) มีแค่ method signatures ไม่มี implementation class สามารถ implement หลาย interfaces ได้แต่ inherit ได้แค่ abstract class เดียว (ยกเว้น Python ที่รองรับ multiple inheritance)
Q: เมื่อไหร่ไม่ควรใช้ OOP?
A: ไม่ควรใช้ OOP เมื่อโปรเจกต์เล็กมากเช่น scripts ไม่กี่บรรทัด เมื่อปัญหาเป็น data transformation pipeline ที่ FP เหมาะกว่า เมื่อ performance เป็นสิ่งสำคัญมากเช่น embedded systems หรือ game engines ที่ OOP overhead อาจเป็นปัญหา และเมื่อทีมไม่คุ้นเคยกับ OOP ควรเรียนรู้ก่อนนำไปใช้กับโปรเจกต์จริง
Q: Python รองรับ private variables จริงหรือ?
A: Python ไม่มี true private เหมือน Java แต่ใช้ name mangling ด้วย double underscore (เช่น __balance จะกลายเป็น _ClassName__balance) ทำให้เข้าถึงจากภายนอกยากขึ้น single underscore (เช่น _balance) เป็นแค่ convention บอกว่าเป็น protected แต่ยังเข้าถึงได้ Python เชื่อในหลัก "we are all consenting adults"
