Enum & State Machine

บทนำ: ปัญหาของการแทนสถานะ

ในการเขียนโปรแกรมจริง มักมีสถานการณ์ที่ object มีหลายสถานะ (state) และพฤติกรรมเปลี่ยนไปตามสถานะ ตัวอย่างเช่น:

  • หากจำหน่ายออนไลน์: order อาจมีสถานะเป็น “Pending”, “Processing”, “Shipped”, “Delivered”
  • ระบบควบคุมประตูอัจฉริยะ: ประตูอาจมีสถานะ “Locked”, “Unlocked”, “Opening”, “Closing”
  • เกม: ตัวละครอาจมีสถานะ “Idle”, “Running”, “Jumping”, “Attacking”

ตัวอย่างอื่น ก็คือสีไฟจราจร ที่มี 3 สถานะเท่านั้น: แดง เขียว เหลือง และแต่ละสถานะมีพฤติกรรมที่ชัดเจน

วิธีดั้งเดิม เราอาจใช้ String หรือ int เพื่อแทนสถานะ:

javapublic class Order {
    private String status;  // "pending", "processing", "shipped", "delivered"
    
    public void process() {
        if (status.equals("pending")) {
            // ประมวลผล
            status = "processing";
        } else if (status.equals("processing")) {
            status = "shipped";
        }
    }
}

ปัญหาของวิธีนี้:

  • ไม่ปลอดภัย – สามารถ set status เป็นค่าใดก็ได้ เช่น status = "invalid"
  • ยากต่อการ maintain – ต้องจำชื่อสถานะทั้งหมด และตรวจสอบ string ซ้ำๆ
  • ไม่ชัดเจน – ไม่รู้ว่า valid state มีอะไรบ้าง
  • ยากต่อการ extend – เพิ่มสถานะใหม่ต้องแก้ code หลายที่

Enum คือวิธีแก้ปัญหานี้ที่ยอดเยี่ยม มันให้เรากำหนด set ของ constants ที่เป็นไปได้ เท่านั้น


Enum: ข้อมูลประเภทพิเศษ

ความเข้าใจพื้นฐาน

Enum คือ data type พิเศษที่ใช้กำหนดชุด constants ที่เป็นไปได้ เช่น สี, ทิศทาง, สถานะ เป็นต้น

ตัวอย่างเบื้องต้น เราสร้าง Enum สำหรับสีไฟจราจร:

javapublic enum TrafficLight {
    RED,     // แดง
    YELLOW,  // เหลือง
    GREEN    // เขียว
}

ตอนนี้เราสามารถใช้ Enum แทนที่ String:

javapublic class TrafficController {
    private TrafficLight currentLight;
    
    public TrafficController() {
        currentLight = TrafficLight.RED;
    }
    
    public void changeLight() {
        if (currentLight == TrafficLight.RED) {
            currentLight = TrafficLight.GREEN;
        } else if (currentLight == TrafficLight.GREEN) {
            currentLight = TrafficLight.YELLOW;
        } else if (currentLight == TrafficLight.YELLOW) {
            currentLight = TrafficLight.RED;
        }
    }
    
    public TrafficLight getCurrentLight() {
        return currentLight;
    }
}

ข้อดีของการใช้ Enum:

  • Type-safe – Compiler รู้ว่า valid values มีอะไรบ้าง ไม่สามารถ set ค่า invalid
  • ชัดเจน – อ่าน code แล้ว รู้ว่ามี state ไหนบ้าง
  • ไม่มี typos – IDE auto-complete ช่วยหลีกเลี่ยง typos

ตัวอย่างที่ 1: Order Status Management

ตัวอย่างจริง: Enum สำหรับ Order

ลองสร้าง order system ที่มีหลายสถานะ:

java// สร้าง Enum สำหรับสถานะ order
public enum OrderStatus {
    PENDING("รอการยืนยัน"),
    CONFIRMED("ยืนยันแล้ว"),
    PROCESSING("กำลังเตรียม"),
    SHIPPED("ส่งแล้ว"),
    DELIVERED("ได้รับแล้ว"),
    CANCELLED("ยกเลิก");
    
    private String description;  // ← Enum สามารถมี field ได้
    
    OrderStatus(String description) {  // ← Enum มี constructor ได้
        this.description = description;
    }
    
    public String getDescription() {
        return description;
    }
}

// ใช้ Enum ใน Order class
public class Order {
    private String orderId;
    private OrderStatus status;
    private double amount;
    
    public Order(String orderId, double amount) {
        this.orderId = orderId;
        this.amount = amount;
        this.status = OrderStatus.PENDING;  // ← สถานะเริ่มต้น
    }
    
    public void confirm() {
        // transition จาก PENDING ไป CONFIRMED
        if (status == OrderStatus.PENDING) {
            status = OrderStatus.CONFIRMED;
            System.out.println("Order confirmed: " + orderId);
        } else {
            System.out.println("Cannot confirm order in state: " + status);
        }
    }
    
    public void process() {
        if (status == OrderStatus.CONFIRMED) {
            status = OrderStatus.PROCESSING;
            System.out.println("Order processing: " + orderId);
        }
    }
    
    public void ship() {
        if (status == OrderStatus.PROCESSING) {
            status = OrderStatus.SHIPPED;
            System.out.println("Order shipped: " + orderId);
        }
    }
    
    public void deliver() {
        if (status == OrderStatus.SHIPPED) {
            status = OrderStatus.DELIVERED;
            System.out.println("Order delivered: " + orderId);
        }
    }
    
    public void cancel() {
        if (status != OrderStatus.DELIVERED && status != OrderStatus.CANCELLED) {
            status = OrderStatus.CANCELLED;
            System.out.println("Order cancelled: " + orderId);
        }
    }
    
    public OrderStatus getStatus() {
        return status;
    }
    
    public void printInfo() {
        System.out.println("Order: " + orderId + 
                         ", Status: " + status + 
                         " (" + status.getDescription() + ")" +
                         ", Amount: $" + amount);
    }
}

// ใช้งาน
public class OrderDemo {
    public static void main(String[] args) {
        Order order = new Order("ORD-001", 999.99);
        order.printInfo();
        
        order.confirm();
        order.printInfo();
        
        order.process();
        order.printInfo();
        
        order.ship();
        order.printInfo();
        
        order.deliver();
        order.printInfo();
    }
}

// OUTPUT:
// Order: ORD-001, Status: PENDING (รอการยืนยัน), Amount: $999.99
// Order confirmed: ORD-001
// Order: ORD-001, Status: CONFIRMED (ยืนยันแล้ว), Amount: $999.99
// Order processing: ORD-001
// Order: ORD-001, Status: PROCESSING (กำลังเตรียม), Amount: $999.99
// Order shipped: ORD-001
// Order: ORD-001, Status: SHIPPED (ส่งแล้ว), Amount: $999.99
// Order delivered: ORD-001
// Order: ORD-001, Status: DELIVERED (ได้รับแล้ว), Amount: $999.99

ในตัวอย่างนี้ Enum ไม่ได้เป็นแค่ constants แต่เป็น full-fledged object ที่มี field (description) และ method (getDescription())


State Machine: ควบคุมการ Transition

แนวคิด State Machine

State Machine (เครื่องสถานะ) คือ concept ที่บรรยาย object มีหลายสถานะและ rules ว่า transition (การเปลี่ยนสถานะ) ได้อย่างไร

ในการพัฒนาซอฟต์แวร์ State Machine ช่วยให้เราคิด logic ของ workflow อย่างชัดเจน

ตัวอย่าง: ประตูระบบปลดล็อค

text          insert card
    +──────────────────+
    │                  ▼
   LOCKED ◄─── VALIDATING ──► UNLOCKED
    ▲                              │
    │                              │
    +──────── timeout ─────────────+

ตัวอย่างที่ 2: Door Lock State Machine

java// Enum สำหรับสถานะประตู
public enum DoorState {
    LOCKED("ล็อกอยู่"),
    VALIDATING("กำลังยืนยัน"),
    UNLOCKED("เปิดแล้ว");
    
    private String description;
    
    DoorState(String description) {
        this.description = description;
    }
    
    public String getDescription() {
        return description;
    }
}

// Enum สำหรับ events
public enum DoorEvent {
    INSERT_CARD,
    VALIDATE_SUCCESS,
    VALIDATE_FAILURE,
    TIMEOUT
}

// Smart door lock ที่ implement state machine
public class SmartDoor {
    private DoorState currentState;
    
    public SmartDoor() {
        currentState = DoorState.LOCKED;
        System.out.println("Door initialized: " + currentState.getDescription());
    }
    
    // Handle events และ transition state
    public void handleEvent(DoorEvent event) {
        DoorState nextState = getNextState(currentState, event);
        
        if (nextState != null && nextState != currentState) {
            System.out.println("Transition: " + currentState + " → " + nextState + 
                             " (event: " + event + ")");
            currentState = nextState;
        } else {
            System.out.println("Invalid event " + event + " in state " + currentState);
        }
    }
    
    // State transition rules
    private DoorState getNextState(DoorState state, DoorEvent event) {
        switch (state) {
            case LOCKED:
                if (event == DoorEvent.INSERT_CARD) {
                    return DoorState.VALIDATING;
                }
                break;
                
            case VALIDATING:
                if (event == DoorEvent.VALIDATE_SUCCESS) {
                    return DoorState.UNLOCKED;
                } else if (event == DoorEvent.VALIDATE_FAILURE || 
                          event == DoorEvent.TIMEOUT) {
                    return DoorState.LOCKED;
                }
                break;
                
            case UNLOCKED:
                if (event == DoorEvent.TIMEOUT) {
                    return DoorState.LOCKED;
                }
                break;
        }
        
        return null;  // ไม่มี valid transition
    }
    
    public DoorState getCurrentState() {
        return currentState;
    }
}

// ใช้งาน
public class SmartDoorDemo {
    public static void main(String[] args) {
        SmartDoor door = new SmartDoor();
        
        // ลำดับเหตุการณ์ที่ถูกต้อง
        System.out.println("\n--- Correct sequence ---");
        door.handleEvent(DoorEvent.INSERT_CARD);
        door.handleEvent(DoorEvent.VALIDATE_SUCCESS);
        door.handleEvent(DoorEvent.TIMEOUT);
        
        // ลำดับเหตุการณ์ที่ผิด
        System.out.println("\n--- Incorrect sequence ---");
        door.handleEvent(DoorEvent.VALIDATE_SUCCESS);  // ← ผิด เพราะอยู่ LOCKED
    }
}

// OUTPUT:
// Door initialized: ล็อกอยู่
// 
// --- Correct sequence ---
// Transition: LOCKED → VALIDATING (event: INSERT_CARD)
// Transition: VALIDATING → UNLOCKED (event: VALIDATE_SUCCESS)
// Transition: UNLOCKED → LOCKED (event: TIMEOUT)
// 
// --- Incorrect sequence ---
// Invalid event VALIDATE_SUCCESS in state LOCKED

ข้อดีของ State Machine approach:

  • ชัดเจน – transition rules อยู่ใน getNextState() ทำให้ดูง่าย
  • ปลอดภัย – ป้องกัน invalid transitions
  • ไม่มี bugs – ไม่มี edge cases ที่ลืมคิด เพราะ rules อยู่ตรงนี้
  • Maintainable – เพิ่ม state ใหม่เพียงแค่ add case ใน switch

Advanced Enum: Methods และ Abstract Methods

Enum ที่มี Behavior

Enum สามารถมี method มากไปกว่าเพียงแค่ getter:

java// Enum สำหรับการคำนวณเงินเดือน
public enum EmployeeType {
    
    // Employee types ต่างมี salary calculation ต่างกัน
    FULL_TIME("พนักงานประจำ") {
        @Override
        public double calculateSalary(double baseSalary, int daysWorked) {
            return baseSalary;  // ได้เต็มเดือน
        }
    },
    
    PART_TIME("พนักงานชั่วคราว") {
        @Override
        public double calculateSalary(double baseSalary, int daysWorked) {
            return baseSalary * daysWorked / 30;  // คำนวณตามวันที่ทำงาน
        }
    },
    
    CONTRACT("พนักงานสัญญา") {
        @Override
        public double calculateSalary(double baseSalary, int daysWorked) {
            return baseSalary * 1.2;  // มีค่าจ้างพิเศษ 20%
        }
    };
    
    private String description;
    
    EmployeeType(String description) {
        this.description = description;
    }
    
    // Abstract method - แต่ละ type ต้อง implement
    public abstract double calculateSalary(double baseSalary, int daysWorked);
    
    public String getDescription() {
        return description;
    }
}

// ใช้งาน
public class Employee {
    private String name;
    private EmployeeType type;
    private double baseSalary;
    private int daysWorked;
    
    public Employee(String name, EmployeeType type, double baseSalary, int daysWorked) {
        this.name = name;
        this.type = type;
        this.baseSalary = baseSalary;
        this.daysWorked = daysWorked;
    }
    
    public double getSalary() {
        // ← เรียก method ของ enum type
        return type.calculateSalary(baseSalary, daysWorked);
    }
    
    public void printInfo() {
        System.out.println(name + " (" + type.getDescription() + ") " +
                         "-> Salary: $" + getSalary());
    }
}

// ใช้งาน
public class EmployeeDemo {
    public static void main(String[] args) {
        new Employee("Alice", EmployeeType.FULL_TIME, 3000, 25).printInfo();
        new Employee("Bob", EmployeeType.PART_TIME, 3000, 15).printInfo();
        new Employee("Charlie", EmployeeType.CONTRACT, 3000, 20).printInfo();
    }
}

// OUTPUT:
// Alice (พนักงานประจำ) -> Salary: $3000.0
// Bob (พนักงานชั่วคราว) -> Salary: $1500.0
// Charlie (พนักงานสัญญา) -> Salary: $3600.0

แนวทางนี้เรียกว่า Strategy Pattern ที่เอาไปใช้กับ Enum แต่ละ enum constant มี behavior ของตัวเอง ทำให้ไม่ต้องใช้ if-else ยาวๆ เพื่อ check type


Best Practices: Enum & State Machine

เมื่อใช้ Enum:

ใช้ Enum เมื่อ:

  • มี set ของค่าที่เป็นไปได้ที่ fixed และ known
  • ค่าเหล่านั้นแทนสถานะ หรือ categories ที่ชัดเจน
  • ต้องการให้ code อ่านง่ายและปลอดภัยด้าน type
  • ต้องการให้ IDE auto-complete ช่วยหลีกเลี่ยง typos

หลีกเลี่ยง:

  • ไม่ใช้ Enum แทนที่ String แบบ random ที่ไม่เกี่ยวข้องกัน
  • ไม่ใช้ Enum ถ้าค่าต้องเปลี่ยนระหว่าง runtime

เมื่อใช้ State Machine:

ใช้ State Machine เมื่อ:

  • Object มี workflow ที่มีหลายสถานะ
  • มี rules ชัดเจนว่า transition ได้อย่างไร
  • ต้อง handle events และเปลี่ยนพฤติกรรมตามสถานะ
  • Logic ซับซ้อนและต้องป้องกัน invalid states

สรุป

Enum & State Machine ให้วิธีที่มีประสิทธิภาพในการแทนและจัดการสถานะ:

Enum ให้ประโยชน์:

  • Type-safe – Compiler ช่วยหลีกเลี่ยง invalid values
  • ชัดเจน – Valid states ปรากฏชัดเจนใน code
  • Flexible – Enum สามารถมี fields, methods, abstract methods ได้

State Machine ให้ประโยชน์:

  • Organized – Logic ของ transitions อยู่ตรงนี้
  • Safe – ป้องกัน invalid state transitions
  • Maintainable – เพิ่มสถานะใหม่หรือ transitions ได้ง่าย

ตัวอย่างการใช้จริง:

  • Order status management – Pending → Processing → Shipped → Delivered
  • Game state – Menu → Playing → Paused → GameOver
  • User status – Active → Inactive → Banned
  • Network connection – Disconnected → Connecting → Connected → Error

Enum และ State Machine เป็น pattern ที่ simple แต่ powerful มันช่วยให้ code ชัดเจน ปลอดภัย และ maintainable มากขึ้น