Composition OOP
Composition OOP Has-a Relationship Composition over Inheritance Dependency Injection Design Patterns Loose Coupling Flexibility Runtime Behavior Strategy Decorator
| Concept | Relationship | Coupling | Flexibility | เหมาะกับ |
|---|---|---|---|---|
| Inheritance | Is-a | Tight | ต่ำ | True Hierarchy |
| Composition | Has-a | Loose | สูง | ทั่วไป |
| Aggregation | Has-a (weak) | Loose | สูง | Optional Parts |
| Interface | Can-do | Very Loose | สูงมาก | Contract |
| Mixin | Mixed-in | ปานกลาง | ปานกลาง | Shared Behavior |
Composition vs Inheritance
# === Composition vs Inheritance ===
# BAD: Deep Inheritance Hierarchy
# class Animal:
# def eat(self): ...
#
# class Mammal(Animal):
# def breathe(self): ...
#
# class Dog(Mammal):
# def bark(self): ...
#
# class GuideDog(Dog):
# def guide(self): ...
#
# Problem: GuideDog สืบทอด 4 ชั้น เปลี่ยน Animal กระทบทุก Class
# GOOD: Composition
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Protocol
# Define Behaviors as Protocols (Interfaces)
class Movable(Protocol):
def move(self) -> str: ...
class Soundable(Protocol):
def make_sound(self) -> str: ...
class Trainable(Protocol):
def perform_task(self, task: str) -> str: ...
# Implement Behaviors as separate classes
class WalkBehavior:
def move(self) -> str:
return "Walking on 4 legs"
class FlyBehavior:
def move(self) -> str:
return "Flying with wings"
class SwimBehavior:
def move(self) -> str:
return "Swimming with fins"
class BarkSound:
def make_sound(self) -> str:
return "Woof!"
class MeowSound:
def make_sound(self) -> str:
return "Meow!"
class GuideTraining:
def perform_task(self, task: str) -> str:
return f"Guiding: {task}"
class SearchTraining:
def perform_task(self, task: str) -> str:
return f"Searching for: {task}"
# Compose behaviors into Animal
@dataclass
class Animal:
name: str
movement: Movable
sound: Soundable
training: Trainable = None
def describe(self):
desc = f"{self.name}: {self.movement.move()} | {self.sound.make_sound()}"
if self.training:
desc += f" | Can: {self.training.perform_task('demo')}"
return desc
# Create animals with different compositions
dog = Animal("Dog", WalkBehavior(), BarkSound())
guide_dog = Animal("Guide Dog", WalkBehavior(), BarkSound(), GuideTraining())
search_dog = Animal("Search Dog", WalkBehavior(), BarkSound(), SearchTraining())
cat = Animal("Cat", WalkBehavior(), MeowSound())
animals = [dog, guide_dog, search_dog, cat]
print("=== Composition Example ===")
for a in animals:
print(f" {a.describe()}")
# Runtime behavior change!
print("\n Dog learns to swim:")
dog.movement = SwimBehavior()
print(f" {dog.describe()}")
Design Patterns
# === Strategy Pattern (Composition) ===
class PaymentStrategy(Protocol):
def pay(self, amount: float) -> str: ...
class CreditCardPayment:
def __init__(self, card_number: str):
self.card = card_number[-4:]
def pay(self, amount: float) -> str:
return f"Paid with Credit Card ****{self.card}"
class PayPalPayment:
def __init__(self, email: str):
self.email = email
def pay(self, amount: float) -> str:
return f"Paid via PayPal ({self.email})"
class CryptoPayment:
def __init__(self, wallet: str):
self.wallet = wallet[:8]
def pay(self, amount: float) -> str:
return f"Paid with Crypto ({self.wallet}...)"
@dataclass
class ShoppingCart:
items: list = field(default_factory=list)
payment: PaymentStrategy = None
def add_item(self, name: str, price: float):
self.items.append({"name": name, "price": price})
def total(self) -> float:
return sum(item["price"] for item in self.items)
def checkout(self) -> str:
if not self.payment:
return "Error: No payment method set"
return self.payment.pay(self.total())
cart = ShoppingCart()
cart.add_item("Laptop", 999.99)
cart.add_item("Mouse", 29.99)
# Strategy: เปลี่ยน Payment Method ตอน Runtime
cart.payment = CreditCardPayment("4111111111111234")
print(f"\n=== Strategy Pattern ===")
print(f" {cart.checkout()}")
cart.payment = PayPalPayment("user@example.com")
print(f" {cart.checkout()}")
cart.payment = CryptoPayment("0x1234567890abcdef")
print(f" {cart.checkout()}")
# === Decorator Pattern (Composition) ===
class Logger:
def __init__(self, wrapped):
self._wrapped = wrapped
def pay(self, amount: float) -> str:
print(f" [LOG] Processing payment of ")
result = self._wrapped.pay(amount)
print(f" [LOG] Payment result: {result}")
return result
print(f"\n=== Decorator Pattern ===")
logged_payment = Logger(CreditCardPayment("9876543210001234"))
logged_payment.pay(1029.98)
Dependency Injection
# === Dependency Injection with Composition ===
# Database Repository (Interface)
class UserRepository(Protocol):
def find_by_id(self, user_id: int) -> dict: ...
def save(self, user: dict) -> bool: ...
# Concrete Implementations
class PostgresUserRepo:
def find_by_id(self, user_id: int) -> dict:
return {"id": user_id, "name": "John", "source": "PostgreSQL"}
def save(self, user: dict) -> bool:
return True
class MongoUserRepo:
def find_by_id(self, user_id: int) -> dict:
return {"id": user_id, "name": "John", "source": "MongoDB"}
def save(self, user: dict) -> bool:
return True
class InMemoryUserRepo:
"""For testing"""
def __init__(self):
self.users = {}
def find_by_id(self, user_id: int) -> dict:
return self.users.get(user_id, {})
def save(self, user: dict) -> bool:
self.users[user["id"]] = user
return True
# Service uses Composition + DI
@dataclass
class UserService:
repo: UserRepository # Injected dependency
def get_user(self, user_id: int) -> dict:
return self.repo.find_by_id(user_id)
def update_user(self, user_id: int, name: str) -> bool:
user = self.repo.find_by_id(user_id)
user["name"] = name
return self.repo.save(user)
# Production: ใช้ Postgres
prod_service = UserService(repo=PostgresUserRepo())
print(f"\n=== Dependency Injection ===")
print(f" Production: {prod_service.get_user(1)}")
# Testing: ใช้ InMemory
test_repo = InMemoryUserRepo()
test_repo.save({"id": 1, "name": "Test User"})
test_service = UserService(repo=test_repo)
print(f" Testing: {test_service.get_user(1)}")
# Switch to Mongo: เปลี่ยน Implementation ไม่ต้องแก้ Service
mongo_service = UserService(repo=MongoUserRepo())
print(f" MongoDB: {mongo_service.get_user(1)}")
# Benefits Summary
benefits = {
"Loose Coupling": "Service ไม่ผูกกับ Database ใดๆ",
"Testability": "Mock/InMemory Repository สำหรับ Test",
"Flexibility": "เปลี่ยน Database ไม่ต้องแก้ Business Logic",
"Single Responsibility": "แต่ละ Class ทำหน้าที่เดียว",
"Open/Closed": "เพิ่ม Implementation ใหม่ไม่ต้องแก้ Code เดิม",
}
print(f"\n Composition Benefits:")
for k, v in benefits.items():
print(f" [{k}]: {v}")
เคล็ดลับ
- Favor Composition: เลือก Composition ก่อน ใช้ Inheritance เมื่อจำเป็นจริงๆ
- Protocol: ใช้ Protocol/Interface กำหนด Contract
- DI: Inject Dependencies ผ่าน Constructor
- Small Classes: สร้าง Class เล็กๆ ทำหน้าที่เดียว แล้วรวมกัน
- Test: Composition ทำให้ Mock ง่าย Test ง่าย
Composition ใน OOP คืออะไร
สร้าง Object รวม Object อื่น Has-a Relationship แทน Inheritance ยืดหยุ่น เปลี่ยน Runtime Loose Coupling Test ง่าย Mock ง่าย
Composition ต่างจาก Inheritance อย่างไร
Inheritance Is-a Tight Coupling Parent กระทบ Child Composition Has-a Loose Coupling เปลี่ยนอิสระ Swap Runtime Test ง่าย Code มากกว่า
เมื่อไหร่ควรใช้ Composition เมื่อไหร่ใช้ Inheritance
Inheritance True Is-a ไม่เปลี่ยน Runtime Composition Flexibility Cross-cutting Multiple Behaviors ส่วนใหญ่ Composition ดีกว่า
Design Pattern ที่ใช้ Composition มีอะไรบ้าง
Strategy เปลี่ยน Algorithm Decorator เพิ่ม Behavior Observer แจ้งเตือน Composite Tree Command Action Adapter Interface Builder Object
สรุป
Composition OOP Has-a Relationship Composition over Inheritance Strategy Decorator Dependency Injection Protocol Interface Loose Coupling Testability Production Code
