Hôm nay bạn sẽ học về state machine (máy trạng thái), cách chúng hoạt động & cách dùng chúng trong dự án Ruby với gem AASM.
Bây giờ:
Hãy tưởng tượng đèn giao thông…
Nó có thể là đỏ, xanh hoặc vàng.
Khi nó đổi màu, màu tiếp theo dựa trên màu hiện tại.

Giả sử đây là loại phát âm thanh cho người khiếm thị để họ biết khi nào có thể qua đường.
Bây giờ:
Bạn đang viết phần mềm cho thứ này.
Làm thế nào bạn sẽ biết phát âm thanh gì mỗi lần & màu nào nên là tiếp theo?
Bạn có thể viết một vài câu if như sau:
if @light.state == "green" @light.play_green_sound end if @light.state == "green" @light.change_to_yellow end # ...
Code kiểm tra state này sẽ rải rác khắp nơi!
Làm thế nào để cải thiện điều này?
Nếu bạn áp dụng nguyên lý Object-Oriented Design, bạn sẽ khám phá lại State Design Pattern (Pattern Trạng thái).
State Design Pattern là gì?
State Design Pattern là một cách triển khai state machine.
Bạn cần 3 thành phần:
- Class
Context, class này biết state hiện tại là gì - Class
State, class này định nghĩa phương thức mà các state riêng lẻ nên triển khai - Một class cho mỗi state. Các class này sẽ kế thừa từ class
State
Trong ví dụ đèn giao thông, context là chính TrafficLight.
Và các state là Green, Red & Yellow.
Mọi state sẽ biết phải làm gì.
Lợi ích lớn?
Mọi state tự biết về chính nó nên không cần kiểm tra state hiện tại. Điều này dẫn đến ít câu điều kiện hơn, thường là nguồn của sự phức tạp.
Triển khai Traffic Light (Đèn giao thông)
Hãy xem code cho triển khai thực tế của pattern này.
Đây là TrafficLight:
class TrafficLight
def initialize
@state = nil
end
def next_state(klass = Green)
@state = klass.new(self)
@state.beep
@state.start_timer
end
end
Đây là base State:
class State
def initialize(light)
@light = light
end
def beep
end
def next_state
end
def start_timer
end
end
Đúng, 3 phương thức này rỗng.
Đây là quy ước phổ biến trong ngôn ngữ khác (như Java) để định nghĩa "interface" này, nhưng nó không phổ biến trong Ruby.
Nó ở đây cho mục đích demo.
Tuy nhiên, chúng ta vẫn muốn chia sẻ phương thức initialize giữa tất cả state vì tất cả đều cần context (object TrafficLight) để báo hiệu thay đổi state.
Bây giờ:
3 state trông rất giống nhau, nên tôi sẽ chỉ cho code của một trong số chúng.
Đây là state 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
Mọi state biết cách & khi nào chuyển sang state tiếp theo.
Ví dụ Game AI
Bạn có thể dùng state machine để giải game phụ thuộc vào state hiện tại, như RubyWarrior.
Trong RubyWarrior, bạn được cho object player & board.
Mục tiêu là:
- Đánh bại tất cả enemy trên board
- Đến exit trong khi giữ HP trên 0
Bạn có thể làm một nước đi tại một thời điểm & bạn phải chọn tốt nếu muốn hoàn thành level.
Nhìn vào state hiện tại giúp bạn làm lựa chọn đó.
Đó là lý do state machine là giải pháp tốt.
Ví dụ:
class Attacking < State
def play(warrior)
warrior.attack!
@player.set_state(Healing) unless enemy_found?(warrior)
end
end
Đây là một trong các state warrior có thể ở, khi không có enemy trong tầm nhìn, chúng ta chuyển sang state Healing để hồi phục sau tổn thương chiến đấu.
Dùng Gem AASM
Nếu bạn muốn theo dõi state hiện tại trong khi đảm bảo các transition (chuyển đổi) hợp lệ, bạn có thể dùng state machine gem như AASM.
Gem này xây dựng quanh ý tưởng events (như bật tắt công tắc) kích hoạt transition sang state khác.
Ví dụ:
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
Cách dùng class này:
light = Light.new p light.on? # true light.switch p light.on? # false
Dùng state machine này, bạn chỉ có thể chuyển sang state "on" nếu state hiện tại là "off". Bạn cũng có thể có nhiều callback (before/after) để chạy code cụ thể khi chuyển state.
Các callback này có thể bao gồm:
- Gửi email
- Ghi log thay đổi state
- Cập nhật dashboard giám sát trực tiếp
Ngoài ra, AASM có tùy chọn lưu state hiện tại vào database dùng ActiveRecord.
Video AASM Gem
Tóm tắt
Bạn đã học về state machine, state design pattern & gem AASM! Hãy tiếp tục học bằng cách đăng ký newsletter Ruby của tôi (7000+ subscribers) để bạn không bỏ lỡ bài mới & mẹo Ruby độc quyền cho subscriber.
Bây giờ là lúc thực hành với ý tưởng mới này 🙂
Cảm ơn bạn đã đọc!