บทนำ: ปัญหาของการแทนสถานะ
ในการเขียนโปรแกรมจริง มักมีสถานการณ์ที่ 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 มากขึ้น
