Home > Blog > tech

Design Patterns คืออะไร? สอน 10 Design Patterns ที่ Developer ต้องรู้ พร้อมตัวอย่างโค้ด 2026

design patterns guide
Design Patterns Guide 2026
2026-04-08 | tech | 3500 words

ในโลกของการพัฒนาซอฟต์แวร์ ปัญหาหลายอย่างเกิดซ้ำแล้วซ้ำเล่า ไม่ว่าจะเป็นการสร้าง Object ที่ซับซ้อน การจัดการ Dependencies ระหว่าง Module หรือการออกแบบระบบที่ต้องรองรับการเปลี่ยนแปลงในอนาคต Design Patterns คือชุดของวิธีแก้ปัญหาที่ถูกพิสูจน์แล้วว่าใช้งานได้จริง ถูกรวบรวมและจัดหมวดหมู่เพื่อให้นักพัฒนาสามารถนำไปใช้ได้ทันทีโดยไม่ต้องเริ่มต้นจากศูนย์

บทความนี้จะพาคุณทำความรู้จัก Design Patterns ตั้งแต่ที่มาประวัติศาสตร์ หลักการแบ่งหมวดหมู่ทั้ง 3 กลุ่ม และสอน 10 Design Patterns ที่สำคัญที่สุดพร้อมตัวอย่างโค้ดทั้ง Python และ TypeScript รวมถึงกรณีที่ควรใช้และไม่ควรใช้ เพื่อให้คุณสามารถนำไปประยุกต์ใช้ในงานจริงได้อย่างมั่นใจ

Design Patterns คืออะไร? ทำไมต้องรู้?

Design Patterns คือแนวทางการแก้ปัญหาที่เกิดขึ้นบ่อยในการออกแบบซอฟต์แวร์ ไม่ใช่โค้ดสำเร็จรูปที่ Copy-Paste ได้ แต่เป็น Template หรือ Blueprint ที่บอกว่า "เมื่อเจอปัญหาแบบนี้ ให้ออกแบบโครงสร้างโค้ดแบบนี้" คล้ายกับสูตรอาหารที่บอกขั้นตอนและส่วนผสม แต่คุณสามารถปรับเปลี่ยนรสชาติตามความชอบได้

เหตุผลที่ Developer ต้องรู้ Design Patterns ได้แก่:

ประวัติ Gang of Four (GoF)

แนวคิด Design Patterns ในซอฟต์แวร์ได้รับแรงบันดาลใจจากสถาปัตยกรรมศาสตร์ โดย Christopher Alexander ได้เขียนหนังสือเกี่ยวกับ Pattern Language ในการออกแบบอาคารและเมืองในปี 1977 แนวคิดนี้ถูกนำมาประยุกต์ใช้กับซอฟต์แวร์ จนในปี 1994 นักวิทยาศาสตร์คอมพิวเตอร์ 4 คน ได้แก่ Erich Gamma, Richard Helm, Ralph Johnson และ John Vlissides ได้ร่วมกันเขียนหนังสือชื่อ "Design Patterns: Elements of Reusable Object-Oriented Software" หนังสือเล่มนี้กลายเป็นตำราคลาสสิกที่ Developer ทั่วโลกอ้างอิง และผู้เขียนทั้ง 4 คนถูกเรียกว่า "Gang of Four" หรือ GoF

หนังสือ GoF ได้จัดหมวดหมู่ Design Patterns เป็น 23 รูปแบบ แบ่งออกเป็น 3 กลุ่มหลัก ซึ่งเป็นรากฐานที่ใช้มาจนถึงทุกวันนี้

3 หมวดหมู่ของ Design Patterns

1. Creational Patterns (สร้าง Object)

เกี่ยวกับกระบวนการสร้าง Object ช่วยให้ระบบไม่ผูกติดกับวิธีการสร้าง Object โดยตรง ตัวอย่าง: Singleton, Factory Method, Abstract Factory, Builder, Prototype

2. Structural Patterns (จัดโครงสร้าง)

เกี่ยวกับการจัดระเบียบความสัมพันธ์ระหว่าง Class และ Object เพื่อสร้างโครงสร้างที่ใหญ่ขึ้นอย่างยืดหยุ่น ตัวอย่าง: Adapter, Decorator, Facade, Proxy, Composite, Bridge, Flyweight

3. Behavioral Patterns (จัดการพฤติกรรม)

เกี่ยวกับการสื่อสารและแบ่งหน้าที่ระหว่าง Object ช่วยให้ Object ทำงานร่วมกันอย่างมีระเบียบ ตัวอย่าง: Observer, Strategy, Command, Template Method, Iterator, State, Mediator, Chain of Responsibility

หมวดหมู่วัตถุประสงค์Pattern ที่จะสอน
Creationalสร้าง Object อย่างยืดหยุ่นSingleton, Factory Method, Abstract Factory, Builder
Structuralจัดโครงสร้าง ClassAdapter, Decorator
BehavioralจัดการพฤติกรรมการทำงานObserver, Strategy, Command, Template Method

1. Singleton Pattern

เปรียบเทียบง่ายๆ: เหมือนประธานาธิบดีของประเทศ — มีได้แค่คนเดียวเท่านั้นในเวลาเดียวกัน ใครจะติดต่อก็ได้คนเดียวกัน

Singleton ทำให้มั่นใจว่า Class จะมี Instance เพียงหนึ่งเดียวตลอดทั้งโปรแกรม และมีจุดเข้าถึงแบบ Global สำหรับ Instance นั้น ใช้บ่อยกับ Database Connection Pool, Logger, Configuration Manager หรือ Cache Manager ที่ต้องการให้ทั้งระบบใช้ Resource เดียวกัน

Python:

class DatabaseConnection:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.connection = cls._connect()
        return cls._instance

    @staticmethod
    def _connect():
        print("Creating new database connection...")
        return "db_connection_object"

    def query(self, sql: str):
        return f"Executing: {sql} on {self.connection}"

# ใช้งาน
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2)  # True — เป็น Instance เดียวกัน
print(db1.query("SELECT * FROM users"))

TypeScript:

class Logger {
    private static instance: Logger;
    private logs: string[] = [];

    private constructor() {}

    static getInstance(): Logger {
        if (!Logger.instance) {
            Logger.instance = new Logger();
        }
        return Logger.instance;
    }

    log(message: string): void {
        const timestamp = new Date().toISOString();
        this.logs.push(`[${timestamp}] ${message}`);
        console.log(`[${timestamp}] ${message}`);
    }

    getLogs(): string[] {
        return [...this.logs];
    }
}

// ใช้งาน
const logger1 = Logger.getInstance();
const logger2 = Logger.getInstance();
console.log(logger1 === logger2); // true
logger1.log("Application started");
เมื่อไหร่ควรใช้: เมื่อต้องการ Instance เดียวของ Resource ที่แชร์ทั้งระบบ เช่น DB Connection Pool, Logger
เมื่อไหร่ไม่ควรใช้: เมื่อ Class ต้องมีหลาย Instance ที่แตกต่างกัน หรือเมื่อต้องการ Test แบบ Isolated — Singleton ทำให้ Unit Testing ยากขึ้น

2. Factory Method Pattern

เปรียบเทียบง่ายๆ: เหมือนร้านพิซซ่าที่มีสาขาหลายแห่ง — สาขา New York ทำพิซซ่าแบบบาง สาขา Chicago ทำแบบหนา แต่ทุกสาขาใช้กระบวนการสั่งซื้อ-จัดทำ-ส่งมอบเหมือนกัน ลูกค้าไม่ต้องรู้ว่าครัวทำพิซซ่าอย่างไร แค่สั่งแล้วได้รับพิซซ่า

Factory Method กำหนด Interface สำหรับการสร้าง Object แต่ให้ Subclass เป็นคนตัดสินใจว่าจะสร้าง Class ไหน ช่วยแยกโค้ดที่สร้าง Object ออกจากโค้ดที่ใช้ Object ทำให้เพิ่มประเภทใหม่ได้ง่ายโดยไม่ต้องแก้โค้ดเดิม

Python:

from abc import ABC, abstractmethod

class Notification(ABC):
    @abstractmethod
    def send(self, message: str) -> str:
        pass

class EmailNotification(Notification):
    def send(self, message: str) -> str:
        return f"Sending EMAIL: {message}"

class SMSNotification(Notification):
    def send(self, message: str) -> str:
        return f"Sending SMS: {message}"

class PushNotification(Notification):
    def send(self, message: str) -> str:
        return f"Sending PUSH: {message}"

class NotificationFactory:
    @staticmethod
    def create(channel: str) -> Notification:
        factories = {
            "email": EmailNotification,
            "sms": SMSNotification,
            "push": PushNotification,
        }
        if channel not in factories:
            raise ValueError(f"Unknown channel: {channel}")
        return factories[channel]()

# ใช้งาน
notification = NotificationFactory.create("email")
print(notification.send("Hello World"))

notification = NotificationFactory.create("sms")
print(notification.send("Your OTP is 123456"))

TypeScript:

interface Payment {
    process(amount: number): string;
}

class CreditCardPayment implements Payment {
    process(amount: number): string {
        return `Processing credit card payment: ${amount} THB`;
    }
}

class BankTransferPayment implements Payment {
    process(amount: number): string {
        return `Processing bank transfer: ${amount} THB`;
    }
}

class PromptPayPayment implements Payment {
    process(amount: number): string {
        return `Processing PromptPay: ${amount} THB`;
    }
}

class PaymentFactory {
    static create(method: string): Payment {
        switch (method) {
            case "credit_card": return new CreditCardPayment();
            case "bank_transfer": return new BankTransferPayment();
            case "promptpay": return new PromptPayPayment();
            default: throw new Error(`Unknown payment: ${method}`);
        }
    }
}

// ใช้งาน
const payment = PaymentFactory.create("promptpay");
console.log(payment.process(1500)); // Processing PromptPay: 1500 THB
ควรใช้เมื่อ: ไม่รู้ล่วงหน้าว่าจะสร้าง Object ประเภทไหน หรือต้องการเพิ่มประเภทใหม่ได้ง่ายในอนาคต
ไม่ควรใช้เมื่อ: มีแค่ 1-2 ประเภทที่ไม่น่าจะเพิ่มอีก — การสร้าง Factory จะเป็น Over-engineering

3. Abstract Factory Pattern

เปรียบเทียบง่ายๆ: เหมือนโรงงานผลิตเฟอร์นิเจอร์ที่มีหลายสไตล์ — สั่งแบบ Modern ได้โต๊ะ Modern + เก้าอี้ Modern + ตู้ Modern สั่งแบบ Vintage ได้โต๊ะ Vintage + เก้าอี้ Vintage + ตู้ Vintage ทุกอย่างในชุดเดียวกันเข้ากัน

Abstract Factory สร้างกลุ่มของ Object ที่เกี่ยวข้องกัน (Product Family) โดยไม่ต้องระบุ Concrete Class เหมาะสำหรับระบบที่ต้องรองรับหลาย Platform หรือหลาย Theme

Python:

from abc import ABC, abstractmethod

class Button(ABC):
    @abstractmethod
    def render(self) -> str: pass

class Input(ABC):
    @abstractmethod
    def render(self) -> str: pass

class LightButton(Button):
    def render(self) -> str:
        return "<button class='light-btn'>Click</button>"

class DarkButton(Button):
    def render(self) -> str:
        return "<button class='dark-btn'>Click</button>"

class LightInput(Input):
    def render(self) -> str:
        return "<input class='light-input'/>"

class DarkInput(Input):
    def render(self) -> str:
        return "<input class='dark-input'/>"

class UIFactory(ABC):
    @abstractmethod
    def create_button(self) -> Button: pass
    @abstractmethod
    def create_input(self) -> Input: pass

class LightThemeFactory(UIFactory):
    def create_button(self) -> Button:
        return LightButton()
    def create_input(self) -> Input:
        return LightInput()

class DarkThemeFactory(UIFactory):
    def create_button(self) -> Button:
        return DarkButton()
    def create_input(self) -> Input:
        return DarkInput()

# ใช้งาน — เปลี่ยน Theme ได้โดยไม่แก้โค้ดที่เหลือ
def build_ui(factory: UIFactory):
    btn = factory.create_button()
    inp = factory.create_input()
    print(btn.render())
    print(inp.render())

build_ui(DarkThemeFactory())
# <button class='dark-btn'>Click</button>
# <input class='dark-input'/>
ควรใช้เมื่อ: ต้องสร้าง Object หลายตัวที่ต้องเข้ากัน (Product Family) เช่น UI Components ที่ต้องเป็น Theme เดียวกัน
ไม่ควรใช้เมื่อ: มีแค่ Object ประเภทเดียว — ใช้ Factory Method ธรรมดาก็พอ

4. Builder Pattern

เปรียบเทียบง่ายๆ: เหมือนสั่งเบอร์เกอร์ที่ Subway — คุณเลือกขนมปัง เลือกเนื้อ เลือกผัก เลือกซอส ทีละขั้น สุดท้ายได้เบอร์เกอร์ตามที่คุณต้องการ ไม่ใช่รับแบบสำเร็จรูปที่เปลี่ยนอะไรไม่ได้

Builder Pattern แยกกระบวนการสร้าง Object ที่ซับซ้อนออกจากตัว Object เอง ทำให้สร้าง Object ที่มี Configuration หลากหลายได้โดยใช้กระบวนการเดียวกัน เหมาะกับ Object ที่มี Constructor Parameter มากเกิน 4-5 ตัว

Python:

class QueryBuilder:
    def __init__(self):
        self._table = ""
        self._conditions = []
        self._columns = ["*"]
        self._order_by = None
        self._limit = None

    def table(self, name: str) -> "QueryBuilder":
        self._table = name
        return self

    def select(self, *columns: str) -> "QueryBuilder":
        self._columns = list(columns)
        return self

    def where(self, condition: str) -> "QueryBuilder":
        self._conditions.append(condition)
        return self

    def order_by(self, column: str, direction: str = "ASC") -> "QueryBuilder":
        self._order_by = f"{column} {direction}"
        return self

    def limit(self, n: int) -> "QueryBuilder":
        self._limit = n
        return self

    def build(self) -> str:
        cols = ", ".join(self._columns)
        sql = f"SELECT {cols} FROM {self._table}"
        if self._conditions:
            sql += " WHERE " + " AND ".join(self._conditions)
        if self._order_by:
            sql += f" ORDER BY {self._order_by}"
        if self._limit:
            sql += f" LIMIT {self._limit}"
        return sql

# ใช้งาน — อ่านง่ายมาก
query = (
    QueryBuilder()
    .table("users")
    .select("id", "name", "email")
    .where("age > 18")
    .where("status = 'active'")
    .order_by("name")
    .limit(10)
    .build()
)
print(query)
# SELECT id, name, email FROM users WHERE age > 18 AND status = 'active' ORDER BY name ASC LIMIT 10

TypeScript:

class HttpRequest {
    method: string = "GET";
    url: string = "";
    headers: Record<string, string> = {};
    body: string | null = null;
    timeout: number = 30000;
}

class HttpRequestBuilder {
    private request: HttpRequest;

    constructor(url: string) {
        this.request = new HttpRequest();
        this.request.url = url;
    }

    method(m: string): this {
        this.request.method = m;
        return this;
    }

    header(key: string, value: string): this {
        this.request.headers[key] = value;
        return this;
    }

    body(data: object): this {
        this.request.body = JSON.stringify(data);
        this.request.headers["Content-Type"] = "application/json";
        return this;
    }

    timeout(ms: number): this {
        this.request.timeout = ms;
        return this;
    }

    build(): HttpRequest {
        return { ...this.request };
    }
}

// ใช้งาน
const req = new HttpRequestBuilder("https://api.example.com/users")
    .method("POST")
    .header("Authorization", "Bearer token123")
    .body({ name: "John", email: "john@example.com" })
    .timeout(5000)
    .build();

console.log(req);
ควรใช้เมื่อ: สร้าง Object ที่มี Parameter มาก หรือต้องการ Fluent API ที่อ่านง่าย
ไม่ควรใช้เมื่อ: Object มี Parameter แค่ 2-3 ตัว — Constructor ธรรมดาก็เพียงพอ

5. Observer Pattern

เปรียบเทียบง่ายๆ: เหมือนช่อง YouTube — เมื่อคุณกด Subscribe ทุกครั้งที่ช่องลงวิดีโอใหม่ คุณจะได้รับแจ้งเตือนอัตโนมัติ ถ้ายกเลิก Subscribe ก็หยุดรับแจ้งเตือน โดยช่องไม่ต้องรู้จักผู้ Subscribe แต่ละคน

Observer Pattern สร้างกลไก Subscription ที่ให้หลาย Object (Observers) ติดตามการเปลี่ยนแปลงของ Object อื่น (Subject) เมื่อ Subject เปลี่ยนสถานะ Observer ทุกตัวจะได้รับการแจ้งเตือนอัตโนมัติ ใช้บ่อยมากใน Event System, UI Framework และ Reactive Programming

Python:

from abc import ABC, abstractmethod
from typing import List

class EventListener(ABC):
    @abstractmethod
    def update(self, event: str, data: dict) -> None:
        pass

class EventEmitter:
    def __init__(self):
        self._listeners: dict[str, List[EventListener]] = {}

    def on(self, event: str, listener: EventListener):
        if event not in self._listeners:
            self._listeners[event] = []
        self._listeners[event].append(listener)

    def off(self, event: str, listener: EventListener):
        if event in self._listeners:
            self._listeners[event].remove(listener)

    def emit(self, event: str, data: dict = None):
        for listener in self._listeners.get(event, []):
            listener.update(event, data or {})

class EmailAlert(EventListener):
    def update(self, event: str, data: dict):
        print(f"Email Alert: {event} -> {data}")

class SlackNotifier(EventListener):
    def update(self, event: str, data: dict):
        print(f"Slack: {event} -> {data}")

class AuditLogger(EventListener):
    def update(self, event: str, data: dict):
        print(f"Audit Log: {event} -> {data}")

# ใช้งาน
order_system = EventEmitter()
order_system.on("order.created", EmailAlert())
order_system.on("order.created", SlackNotifier())
order_system.on("order.created", AuditLogger())

order_system.emit("order.created", {"order_id": 123, "amount": 1500})
# Email Alert: order.created -> {'order_id': 123, 'amount': 1500}
# Slack: order.created -> {'order_id': 123, 'amount': 1500}
# Audit Log: order.created -> {'order_id': 123, 'amount': 1500}

TypeScript:

type Listener = (data: any) => void;

class EventBus {
    private events: Map<string, Listener[]> = new Map();

    on(event: string, listener: Listener): void {
        const listeners = this.events.get(event) || [];
        listeners.push(listener);
        this.events.set(event, listeners);
    }

    off(event: string, listener: Listener): void {
        const listeners = this.events.get(event) || [];
        this.events.set(event, listeners.filter(l => l !== listener));
    }

    emit(event: string, data?: any): void {
        const listeners = this.events.get(event) || [];
        listeners.forEach(listener => listener(data));
    }
}

// ใช้งาน
const bus = new EventBus();
bus.on("user.login", (data) => console.log(`Welcome ${data.name}`));
bus.on("user.login", (data) => console.log(`Audit: ${data.name} logged in`));
bus.emit("user.login", { name: "Somchai" });
// Welcome Somchai
// Audit: Somchai logged in
ควรใช้เมื่อ: การเปลี่ยนแปลงใน Object หนึ่งต้องส่งผลกระทบต่อ Object อื่นๆ โดยไม่รู้ล่วงหน้าว่ามีกี่ตัว
ไม่ควรใช้เมื่อ: มี Observer แค่ตัวเดียวที่ไม่เปลี่ยน — เรียกตรงก็พอ ไม่ต้องสร้าง Event System

6. Strategy Pattern

เปรียบเทียบง่ายๆ: เหมือนแอป Google Maps ที่ให้เลือกวิธีเดินทาง — รถยนต์ รถไฟฟ้า จักรยาน หรือเดิน แต่ละวิธีมี Algorithm คำนวณเส้นทางต่างกัน แต่ผลลัพธ์คือ "บอกเส้นทางจาก A ไป B" เหมือนกัน

Strategy Pattern กำหนดกลุ่มของ Algorithm แยกแต่ละตัวเป็น Class ของตัวเอง และทำให้สามารถสลับ Algorithm ได้ตอน Runtime โดยไม่ต้องแก้โค้ดที่เรียกใช้ ช่วยกำจัด if-else ที่ยาวเหยียดและทำให้เพิ่ม Algorithm ใหม่ได้ง่าย

Python:

from abc import ABC, abstractmethod

class CompressionStrategy(ABC):
    @abstractmethod
    def compress(self, data: bytes) -> bytes:
        pass

    @abstractmethod
    def name(self) -> str:
        pass

class GzipCompression(CompressionStrategy):
    def compress(self, data: bytes) -> bytes:
        import gzip
        return gzip.compress(data)
    def name(self) -> str:
        return "gzip"

class Bzip2Compression(CompressionStrategy):
    def compress(self, data: bytes) -> bytes:
        import bz2
        return bz2.compress(data)
    def name(self) -> str:
        return "bzip2"

class NoCompression(CompressionStrategy):
    def compress(self, data: bytes) -> bytes:
        return data
    def name(self) -> str:
        return "none"

class FileProcessor:
    def __init__(self, strategy: CompressionStrategy):
        self._strategy = strategy

    def set_strategy(self, strategy: CompressionStrategy):
        self._strategy = strategy

    def process(self, data: bytes) -> bytes:
        print(f"Compressing with {self._strategy.name()}...")
        result = self._strategy.compress(data)
        ratio = len(result) / len(data) * 100
        print(f"   {len(data)} -> {len(result)} bytes ({ratio:.1f}%)")
        return result

# ใช้งาน
processor = FileProcessor(GzipCompression())
data = b"Hello World! " * 1000
processor.process(data)

# เปลี่ยน Strategy ได้ตอน Runtime
processor.set_strategy(Bzip2Compression())
processor.process(data)
ควรใช้เมื่อ: มีหลาย Algorithm ที่ทำหน้าที่เดียวกันแต่ต่างวิธี และต้องสลับได้ตอน Runtime
ไม่ควรใช้เมื่อ: Algorithm ไม่เคยเปลี่ยน หรือมีแค่วิธีเดียว — สร้าง Strategy Interface เป็น Over-engineering

7. Adapter Pattern

เปรียบเทียบง่ายๆ: เหมือนปลั๊กแปลงไฟ — เครื่องใช้ไฟฟ้าจากอเมริกาไม่สามารถเสียบกับปลั๊กไทยได้โดยตรง ต้องใช้ Adapter แปลงให้เข้ากันได้ โดยไม่ต้องเปลี่ยนเครื่องใช้ไฟฟ้าหรือปลั๊กผนัง

Adapter Pattern เชื่อมต่อ Interface ที่ไม่เข้ากัน ทำให้ Class ที่มี Interface ต่างกันสามารถทำงานร่วมกันได้ ใช้บ่อยเมื่อต้องใช้ Library ของคนอื่นที่มี Interface ไม่ตรงกับระบบเรา หรือเมื่อต้อง Integrate ระบบเก่ากับระบบใหม่

Python:

# Legacy XML system (ไม่สามารถแก้ได้)
class LegacyXMLParser:
    def parse_xml(self, xml_string: str) -> dict:
        # จำลองการ Parse XML
        return {"format": "xml", "data": xml_string[:50]}

# ระบบใหม่ต้องการ JSON interface
class DataProcessor:
    def process(self, parser, json_string: str):
        result = parser.parse_json(json_string)
        print(f"Processed: {result}")

# Adapter — ทำให้ XML Parser ใช้งานเหมือน JSON Parser ได้
class XMLtoJSONAdapter:
    def __init__(self, xml_parser: LegacyXMLParser):
        self._xml_parser = xml_parser

    def parse_json(self, json_string: str) -> dict:
        # แปลง JSON เป็น XML แล้วใช้ Legacy Parser
        xml_result = self._xml_parser.parse_xml(json_string)
        return {"format": "json_adapted", "original": xml_result}

# ใช้งาน
legacy = LegacyXMLParser()
adapter = XMLtoJSONAdapter(legacy)
processor = DataProcessor()
processor.process(adapter, '{"name": "test"}')
# Processed: {'format': 'json_adapted', 'original': {'format': 'xml', ...}}

TypeScript:

// Third-party payment library (แก้ไม่ได้)
class StripePaymentSDK {
    createStripeCharge(amountInCents: number, currency: string): string {
        return `Stripe charge: ${amountInCents} ${currency}`;
    }
}

// ระบบเราใช้ Interface นี้
interface PaymentGateway {
    charge(amountInBaht: number): string;
}

// Adapter
class StripeAdapter implements PaymentGateway {
    private stripe: StripePaymentSDK;

    constructor() {
        this.stripe = new StripePaymentSDK();
    }

    charge(amountInBaht: number): string {
        const cents = amountInBaht * 100;
        return this.stripe.createStripeCharge(cents, "THB");
    }
}

// ใช้งาน — ระบบเราไม่ต้องรู้จัก Stripe
const gateway: PaymentGateway = new StripeAdapter();
console.log(gateway.charge(1500)); // Stripe charge: 150000 THB
ควรใช้เมื่อ: ต้อง Integrate กับ Library หรือระบบเก่าที่มี Interface ไม่ตรงกับระบบเรา
ไม่ควรใช้เมื่อ: สามารถแก้ไข Interface ของ Class ต้นทางได้โดยตรง — การแก้ตรงง่ายกว่าสร้าง Adapter

8. Decorator Pattern

เปรียบเทียบง่ายๆ: เหมือนการสั่งกาแฟที่ Starbucks — เริ่มจากกาแฟดำ แล้วเพิ่มนม เพิ่มน้ำตาล เพิ่มวิปครีม เพิ่มคาราเมล แต่ละอย่างเป็น "เลเยอร์" ที่ครอบกาแฟเดิม คุณเพิ่มหรือลดได้อย่างอิสระ

Decorator Pattern เพิ่มความสามารถให้ Object ที่มีอยู่แล้วโดยไม่ต้องแก้ไข Class เดิม ทำโดยการ "ห่อ" Object ด้วย Decorator ซ้อนทับกันเป็นชั้นๆ แต่ละ Decorator เพิ่มพฤติกรรมใหม่แล้วส่งต่อไปยัง Object ข้างใน

Python:

from abc import ABC, abstractmethod

class DataSource(ABC):
    @abstractmethod
    def write(self, data: str) -> str:
        pass

    @abstractmethod
    def read(self) -> str:
        pass

class FileDataSource(DataSource):
    def __init__(self):
        self._data = ""

    def write(self, data: str) -> str:
        self._data = data
        return f"Written: {len(data)} chars"

    def read(self) -> str:
        return self._data

class DataSourceDecorator(DataSource):
    def __init__(self, source: DataSource):
        self._source = source

    def write(self, data: str) -> str:
        return self._source.write(data)

    def read(self) -> str:
        return self._source.read()

class EncryptionDecorator(DataSourceDecorator):
    def write(self, data: str) -> str:
        encrypted = data[::-1]  # Simple reverse as encryption
        print(f"   Encrypting data...")
        return super().write(encrypted)

    def read(self) -> str:
        data = super().read()
        return data[::-1]  # Decrypt

class CompressionDecorator(DataSourceDecorator):
    def write(self, data: str) -> str:
        compressed = data.replace("  ", " ")
        print(f"   Compressing data...")
        return super().write(compressed)

class LoggingDecorator(DataSourceDecorator):
    def write(self, data: str) -> str:
        print(f"   LOG: Writing {len(data)} chars at now")
        return super().write(data)

# ใช้งาน — ซ้อน Decorator ได้ตามต้องการ
source = LoggingDecorator(
    EncryptionDecorator(
        CompressionDecorator(
            FileDataSource()
        )
    )
)
source.write("Hello World! This is secret data.")
print(f"Read back: {source.read()}")
ควรใช้เมื่อ: ต้องเพิ่มความสามารถให้ Object แบบยืดหยุ่น สลับเปิด/ปิดได้ตอน Runtime
ไม่ควรใช้เมื่อ: ความสามารถเพิ่มเติมไม่เคยเปลี่ยน — สร้าง Subclass ธรรมดาง่ายกว่า

9. Command Pattern

เปรียบเทียบง่ายๆ: เหมือนการสั่งอาหารในร้านอาหาร — คุณไม่ได้ไปบอกพ่อครัวโดยตรง แต่เขียนออเดอร์ลงกระดาษ (Command Object) ส่งให้พนักงานเสิร์ฟ พนักงานเสิร์ฟนำออเดอร์ไปให้ครัว ออเดอร์สามารถเก็บไว้ ยกเลิก หรือทำซ้ำได้

Command Pattern ห่อ Request เป็น Object ทำให้สามารถส่งผ่าน Command เป็น Parameter จัดคิว ทำ Undo/Redo หรือบันทึก Log ได้ ใช้บ่อยในระบบ Undo ของ Text Editor, Transaction System และ Task Queue

Python:

from abc import ABC, abstractmethod
from typing import List

class Command(ABC):
    @abstractmethod
    def execute(self) -> None:
        pass

    @abstractmethod
    def undo(self) -> None:
        pass

class TextEditor:
    def __init__(self):
        self.content = ""

    def __str__(self):
        return self.content or "(empty)"

class InsertTextCommand(Command):
    def __init__(self, editor: TextEditor, text: str, position: int):
        self.editor = editor
        self.text = text
        self.position = position

    def execute(self) -> None:
        self.editor.content = (
            self.editor.content[:self.position]
            + self.text
            + self.editor.content[self.position:]
        )

    def undo(self) -> None:
        self.editor.content = (
            self.editor.content[:self.position]
            + self.editor.content[self.position + len(self.text):]
        )

class DeleteTextCommand(Command):
    def __init__(self, editor: TextEditor, position: int, length: int):
        self.editor = editor
        self.position = position
        self.length = length
        self.deleted_text = ""

    def execute(self) -> None:
        self.deleted_text = self.editor.content[self.position:self.position + self.length]
        self.editor.content = (
            self.editor.content[:self.position]
            + self.editor.content[self.position + self.length:]
        )

    def undo(self) -> None:
        self.editor.content = (
            self.editor.content[:self.position]
            + self.deleted_text
            + self.editor.content[self.position:]
        )

class CommandHistory:
    def __init__(self):
        self._history: List[Command] = []

    def execute(self, command: Command):
        command.execute()
        self._history.append(command)

    def undo(self):
        if self._history:
            cmd = self._history.pop()
            cmd.undo()

# ใช้งาน
editor = TextEditor()
history = CommandHistory()

history.execute(InsertTextCommand(editor, "Hello World", 0))
print(f"After insert: {editor}")  # Hello World

history.execute(InsertTextCommand(editor, ", Developer", 11))
print(f"After insert: {editor}")  # Hello World, Developer

history.undo()
print(f"After undo: {editor}")  # Hello World

history.undo()
print(f"After undo: {editor}")  # (empty)
ควรใช้เมื่อ: ต้องการ Undo/Redo, Command Queue, Transaction Log หรือ Macro Recording
ไม่ควรใช้เมื่อ: Action เป็นแบบ Fire-and-forget ที่ไม่ต้อง Undo และไม่ต้องจัดคิว

10. Template Method Pattern

เปรียบเทียบง่ายๆ: เหมือนกระบวนการสร้างบ้าน — ทุกบ้านต้องทำตามขั้นตอน: วางฐานราก สร้างโครงสร้าง ติดผนัง ติดหลังคา ตกแต่ง แต่วัสดุและรายละเอียดแต่ละขั้นตอนต่างกันตามแบบบ้าน กระบวนการโดยรวมเหมือนกัน แต่รายละเอียดแตกต่าง

Template Method Pattern กำหนดโครงร่างของ Algorithm ใน Base Class แล้วให้ Subclass Override เฉพาะบางขั้นตอนโดยไม่เปลี่ยนโครงสร้างรวม เหมาะกับกระบวนการที่มีขั้นตอนคงที่แต่รายละเอียดแต่ละขั้นต้องปรับเปลี่ยนได้

Python:

from abc import ABC, abstractmethod

class DataMiner(ABC):
    # Template Method — กำหนดโครงร่างที่ไม่เปลี่ยน
    def mine(self, path: str):
        raw_data = self.extract(path)
        parsed = self.parse(raw_data)
        analyzed = self.analyze(parsed)
        report = self.format_report(analyzed)
        self.send_report(report)

    @abstractmethod
    def extract(self, path: str) -> str:
        pass

    @abstractmethod
    def parse(self, data: str) -> list:
        pass

    def analyze(self, data: list) -> dict:
        # Default implementation — Subclass override ได้
        return {"count": len(data), "data": data}

    def format_report(self, analysis: dict) -> str:
        return f"Report: {analysis['count']} records processed"

    def send_report(self, report: str):
        print(f"Sending: {report}")

class CSVDataMiner(DataMiner):
    def extract(self, path: str) -> str:
        print(f"Reading CSV file: {path}")
        return "name,age\nJohn,30\nJane,25"

    def parse(self, data: str) -> list:
        lines = data.strip().split("\n")
        headers = lines[0].split(",")
        return [dict(zip(headers, line.split(","))) for line in lines[1:]]

class JSONDataMiner(DataMiner):
    def extract(self, path: str) -> str:
        print(f"Reading JSON file: {path}")
        return '[{"name":"John","age":30},{"name":"Jane","age":25}]'

    def parse(self, data: str) -> list:
        import json
        return json.loads(data)

class APIDataMiner(DataMiner):
    def extract(self, path: str) -> str:
        print(f"Fetching API: {path}")
        return '[{"name":"John","age":30}]'

    def parse(self, data: str) -> list:
        import json
        return json.loads(data)

    def analyze(self, data: list) -> dict:
        result = super().analyze(data)
        result["source"] = "api"
        return result

# ใช้งาน
CSVDataMiner().mine("data.csv")
JSONDataMiner().mine("data.json")
APIDataMiner().mine("https://api.example.com/data")
ควรใช้เมื่อ: หลาย Class มีขั้นตอนเดียวกันแต่รายละเอียดต่างกัน ต้องการ "กรอบ" ที่บังคับขั้นตอน
ไม่ควรใช้เมื่อ: ขั้นตอนแต่ละ Class ต่างกันมากจนไม่มีโครงสร้างร่วม

Anti-Patterns — รูปแบบที่ควรหลีกเลี่ยง

การรู้จัก Anti-Patterns สำคัญพอๆ กับการรู้จัก Design Patterns เพราะช่วยให้คุณหลีกเลี่ยงกับดักที่พบบ่อยในการเขียนโค้ด

Anti-Patternปัญหาวิธีแก้
God ObjectClass เดียวทำทุกอย่าง มี Method และ Property มากเกินไปแยก Responsibility ตาม Single Responsibility Principle
Spaghetti Codeโค้ดพันกันยุ่งเหยิง ไม่มีโครงสร้าง ไม่มี Function แยกใช้ Design Patterns และ Refactor อย่างสม่ำเสมอ
Golden Hammerใช้ Pattern เดิมกับทุกปัญหา "เมื่อมีค้อน ทุกอย่างเป็นตะปู"เลือก Pattern ตามปัญหา ไม่ใช่ตามความชอบ
Copy-Paste ProgrammingCopy โค้ดซ้ำแทนที่จะ Refactor เป็น Reusable Functionใช้ Template Method, Strategy หรือ Abstract ออกมา
Premature Optimizationปรับ Performance ก่อนที่จะรู้ว่ามีปัญหาจริงMeasure ก่อน Optimize
Singleton Abuseใช้ Singleton กับทุก Class ทำให้ Testing ยากและเกิด Hidden Dependenciesใช้ Dependency Injection แทน ยกเว้นกรณีจำเป็นจริงๆ

SOLID Principles กับ Design Patterns

SOLID Principles เป็นหลักการออกแบบ OOP ที่เป็นรากฐานของ Design Patterns ทั้งหมด การเข้าใจ SOLID จะทำให้เข้าใจว่าทำไม Pattern แต่ละตัวถึงออกแบบมาแบบนั้น

S — Single Responsibility Principle (SRP)

Class หนึ่งควรมีเหตุผลในการเปลี่ยนแปลงเพียงหนึ่งเดียว หรือพูดง่ายๆ คือ "ทำหน้าที่เดียว ทำให้ดีที่สุด" Pattern ที่เกี่ยว: Command, Observer ช่วยแยก Concern ออกจากกัน ทำให้แต่ละ Class มีหน้าที่ชัดเจน

O — Open/Closed Principle (OCP)

Class ควรเปิดสำหรับการขยาย (Extension) แต่ปิดสำหรับการแก้ไข (Modification) คือเพิ่มความสามารถใหม่ได้โดยไม่ต้องแก้โค้ดเดิม Pattern ที่เกี่ยว: Strategy, Decorator ช่วยเพิ่มพฤติกรรมใหม่โดยไม่แก้ Class เดิม

L — Liskov Substitution Principle (LSP)

Object ของ Subclass ต้องสามารถใช้แทน Object ของ Parent Class ได้โดยไม่ทำให้โปรแกรมพัง Pattern ที่เกี่ยว: Factory Method, Template Method ต้องมั่นใจว่า Subclass ทำงานได้เหมือน Parent

I — Interface Segregation Principle (ISP)

อย่าบังคับ Client ให้พึ่งพา Interface ที่ไม่ได้ใช้ ควรแยก Interface ใหญ่เป็น Interface เล็กๆ หลายตัว Pattern ที่เกี่ยว: Adapter ช่วยแปลง Interface ที่ใหญ่เกินให้เหลือเฉพาะที่ต้องใช้

D — Dependency Inversion Principle (DIP)

Module ระดับสูงไม่ควรพึ่งพา Module ระดับต่ำโดยตรง ทั้งคู่ควรพึ่งพา Abstraction เช่น Interface หรือ Abstract Class Pattern ที่เกี่ยว: Abstract Factory, Strategy ทำงานผ่าน Interface ไม่ใช่ Concrete Class

Design Patterns ใน Framework ยอดนิยม

Framework ที่ Developer ใช้ทุกวันล้วนสร้างบน Design Patterns การรู้ Pattern ช่วยให้เข้าใจ Framework ลึกขึ้น

React (Frontend)

Django (Python Web Framework)

Spring (Java Framework)

แนวทางเลือกใช้ Design Pattern

การเลือก Design Pattern ที่เหมาะสมเป็นทักษะที่ต้องฝึกฝน ไม่ใช่ท่องจำ ต่อไปนี้คือแนวทางที่จะช่วยให้ตัดสินใจได้ง่ายขึ้น:

สถานการณ์Pattern ที่เหมาะ
ต้องการ Instance เดียวทั้งระบบSingleton
สร้าง Object หลายประเภทจาก InputFactory Method
สร้างกลุ่ม Object ที่ต้องเข้ากันAbstract Factory
Object มี Configuration ซับซ้อนBuilder
แจ้งเตือนหลาย Object เมื่อเกิดเหตุการณ์Observer
เปลี่ยน Algorithm ตอน RuntimeStrategy
เชื่อมต่อ Interface ที่ไม่เข้ากันAdapter
เพิ่มความสามารถแบบยืดหยุ่นDecorator
ต้องการ Undo/Redo หรือ Task QueueCommand
กระบวนการเดียวกัน รายละเอียดต่างกันTemplate Method
กฎสำคัญ: อย่าใช้ Design Pattern เพียงเพราะมันเท่หรือเพราะเรียนมา ใช้เมื่อมันแก้ปัญหาจริงของคุณ โค้ดที่ง่ายที่สุดที่ทำงานได้ คือโค้ดที่ดีที่สุด "Simple is better than complex" — Zen of Python

สรุป

Design Patterns เป็นเครื่องมือสำคัญในคลังอาวุธของ Developer ทุกคน ไม่ใช่กฎตายตัวที่ต้องทำตาม แต่เป็นแนวทางที่พิสูจน์แล้วว่าใช้งานได้จริง การเข้าใจ 10 Pattern ที่สอนในบทความนี้ ได้แก่ Singleton, Factory Method, Abstract Factory, Builder, Observer, Strategy, Adapter, Decorator, Command และ Template Method จะช่วยให้คุณออกแบบซอฟต์แวร์ที่ดูแลง่าย ขยายได้ง่าย และทำงานร่วมกับทีมได้อย่างมีประสิทธิภาพ

สิ่งสำคัญที่สุดคือการฝึกใช้จริง ลองนำ Pattern ไปใช้ในโปรเจกต์ส่วนตัว อ่านโค้ดของ Open Source Framework และสังเกตว่าพวกเขาใช้ Pattern ไหนบ้าง เมื่อเวลาผ่านไป คุณจะเลือก Pattern ได้อย่างเป็นธรรมชาติโดยไม่ต้องคิดนาน เพราะมันจะกลายเป็นส่วนหนึ่งของวิธีคิดในการแก้ปัญหาซอฟต์แวร์ของคุณ


Back to Blog | iCafe Forex | SiamLanCard | Siam2R