วันนี้คุณจะได้เรียนรู้เกี่ยวกับ state machines (เครื่องจักรสถานะ) วิธีทำงาน และวิธีใช้ในโปรเจกต์ Ruby ด้วย gem AASM
ตอนนี้:
นึกภาพไฟจราจร...
มันสามารถเป็น แดง เขียว หรือ เหลือง
เมื่อมันเปลี่ยนสี สี ถัดไป จะขึ้นอยู่กับ สี ปัจจุบัน

สมมติว่านี่คือแบบที่ออกเสียงสำหรับคนตาบอด รู้ว่าจะข้ามได้เมื่อไหร่
ตอนนี้:
คุณกำลังเขียนซอฟต์แวร์สำหรับสิ่งนี้
คุณจะรู้ยังไงว่าเล่นเสียงอะไรทุกครั้ง และสีไหนควรเป็นถัดไป?
คุณอาจเขียน if statements แบบนี้:
if @light.state == "green" @light.play_green_sound end if @light.state == "green" @light.change_to_yellow end # ...
โค้ดตรวจสอบสถานะนี้จะกระจายอยู่ทั่วไป!
เราจะปรับปรุงอย่างไร?
ถ้าคุณนำหลักการออกแบบเชิงวัตถุ (Object-Oriented Design principles) ไปใช้ คุณจะค้นพบใหม่ State Design Pattern
State Design Pattern คืออะไร?
State Design Pattern เป็นหนึ่งวิธีในการ implement state machine
คุณต้องการ 3 ส่วนประกอบ:
- คลาส
Contextคลาสนี้รู้ว่าสถานะปัจจุบันคืออะไร - คลาส
Stateคลาสนี้กำหนดเมธอดที่ควร implement โดยสถานะแต่ละตัว - คลาสหนึ่งสำหรับแต่ละสถานะ คลาสเหล่านี้จะสืบทอดจากคลาส
State
ในตัวอย่างไฟจราจร Context คือ TrafficLight เอง
และสถานะคือ Green, Red และ Yellow
ทุกสถานะจะรู้ว่าต้องทำอะไร
ประโยชน์ใหญ่?
ทุกสถานะรู้จักตัวเอง ดังนั้นไม่ต้องตรวจสอบสถานะปัจจุบัน แปลว่า conditional statements น้อยลง ซึ่งมักเป็นแหล่งความซับซ้อน
การ Implement ไฟจราจร
มา看看โค้ดสำหรับการ implement จริงของรูปแบบนี้
นี่คือ TrafficLight:
class TrafficLight
def initialize
@state = nil
end
def next_state(klass = Green)
@state = klass.new(self)
@state.beep
@state.start_timer
end
end
นี่คือ State พื้นฐาน:
class State
def initialize(light)
@light = light
end
def beep
end
def next_state
end
def start_timer
end
end
ใช่ เมธอด 3 ตัวนี้ว่างเปล่า
เป็นข้อตกลงทั่วไปในภาษาโปรแกรมอื่น (อย่าง Java) ที่จะกำหนด "interface" นี้ แตไม่ค่อยนิยมใน Ruby
นี่อยู่ที่นี่เพื่อการสาธิต
อย่างไรก็ตาม เรายังต้องการแชร์เมธอด initialize ระหว่างทุกสถานะ เพราะทุกอย่างต้องการ context (ออบเจ็กต์ TrafficLight) เพื่อส่งสัญญาณ การเปลี่ยนสถานะ
ตอนนี้:
3 สถานะดูคล้ายกันมาก ผมจะแสดงโค้ดสำหรับแค่หนึ่ง
นี่คือสถานะ Green:
class Green < State
def beep
puts "Color is now green"
end
def next_state
@light.next_state(Yellow)
end
def start_timer
sleep 5; next_state
end
end
ทุกสถานะรู้ว่าจะเปลี่ยนไปสถานะไหนและเมื่อไหร่
ตัวอย่าง AI Game
คุณสามารถใช้ state machine เพื่อแก้เกมที่ขึ้นอยู่กับสถานะปัจจุบัน เช่น RubyWarrior
ใน RubyWarrior คุณได้รับออบเจ็กต์ผู้เล่นและกระดาน
เป้าหมายคือ:
- เอาชนะศัตรูทั้งหมดบนกระดาน
- ไปถึงทางออกขณะที่ HP อยู่เหนือ 0
คุณทำการเคลื่อนไหวได้ทีละครั้ง และต้องเลือกที่ดี หากต้องการผ่านด่าน
การดูสถานะปัจจุบันช่วยให้คุณเลือกได้
นั่นคือเหตุผลที่ state machine เป็นโซลูชันที่ดี
ตัวอย่าง:
class Attacking < State
def play(warrior)
warrior.attack!
@player.set_state(Healing) unless enemy_found?(warrior)
end
end
นี่คือหนึ่งในสถานะที่ warrior ของเราอาจอยู่ เมื่อไม่มีศัตรูในสายตา เราเปลี่ยนไปสถานะ Healing เพื่อฟื้นฟูจากความเสียหาย
การใช้ Gem AASM
หากคุณต้องการติดตามสถานะปัจจุบัน ในขณะที่รับรองว่าการเปลี่ยนผ่าน (transitions) ถูกต้อง คุณสามารถใช้ state machine gem อย่าง AASM
Gem นี้สร้างขึ้นรอบไอเดียของ events (เหมือนกดสวิตช์ไฟ) ที่กระตุ้นการเปลี่ยนผ่านไปสถานะอื่น
ตัวอย่าง:
require 'aasm'
class Light
include AASM
aasm do
state :on, :off
event :switch do
transitions :from => :on, :to => :off, :if => :on?
transitions :from => :off, :to => :on, :if => :off?
end
end
end
วิธีใช้คลาสนี้:
light = Light.new p light.on? # true light.switch p light.on? # false
การใช้ state machine นี้ คุณสามารถเปลี่ยนไปสถานะ "on" ได้ก็ต่อเมื่อสถานะปัจจุบันคือ "off" คุณยังสามารถมี callbacks (before/after) หลายตัว เพื่อรันโค้ดเฉพาะระหว่างการเปลี่ยนสถานะ
Callbacks เหล่านี้อาจรวมถึง:
- ส่งอีเมล
- บันทึกการเปลี่ยนสถานะ (logging)
- อัปเดตแดชบอร์ดตรวจสอบแบบเรียลไทม์
นอกจากนี้ AASM มีตัวเลือกในการบันทึกสถานะปัจจุบันลงฐานข้อมูลด้วย ActiveRecord
วิดีโอ AASM Gem
สรุป
คุณได้เรียนรู้เกี่ยวกับ state machines, state design pattern และ gem AASM! เรียนรู้ต่อไปด้วยการสมัคร newsletter Ruby ของผม (7000+ subscribers) เพื่อ ไม่พลาดบทความใหม่และเคล็ดลับ Ruby 獨家
ตอนนี้เวลาฝึกกับแนวคิดใหม่ๆ 🙂
ขอบคุณที่อ่านครับ!