+1

Giới thiệu về Gem State Machine

Giới thiệu

  1. State Machine là gì? Nó là một thuật ngữ dược dùng để miêu tả kỹ thuật tương tác và chuyển đổi của đối tượng và các trạng thái (state) thông qua thuộc tính. Một đối tượng sẽ có hữu hạn các trạng thái, để chuyển từ trạng thái này sang trạng thái khác thì cần có những sự kiện trạng thái. Ví dụ: Cột đèn tín hiệu giao thông chẳng hạn chẳng hạn, có ba đèn với các trạng thái khác nhau (màu). Tại bất kỳ thời điểm nào tín hiệu đèn có thể đang ở một trạng thái red, green, yellow. Nó chỉ có thể chuyển đổi từ trạng thái này sang trạng thái khác. State Machine Diagram Với các sự kiện chuyển đổi trạng thái:
  • red -> green (được phép đi)
  • green -> yellow (chuẩn bị đi)
  • yellow -> red (dừng lại) Một tín hiệu đèn giao thông không thể đi từ xanh sang đỏ, bỏ qua màu vàng, hoặc màu vàng trở lại màu xanh lá cây. (hữu hạn trạng thái của đối tượng).. => Các hành vi của đối tượng được thực hiện thông qua các trạng thái và sự chuyển đổi trạng thái của chúng.
  1. Tại sao nên sử dụng State Machine?
  • State Machine giúp chúng ta định nghĩa các hành vi của một đối tượng một cách đơn giản và hiệu quả.
  • Khi chuyển đổi trạng thái, khởi tạo trạng thái của đối tượng sẽ được kích hoạt tại mỗi sự kiện. Tại mỗi sự kiện đối tượng đều được xác định trạng thái cho phép thì mới được thực hiện.
  • Dùng State Machine sẽ quản lý được toàn bộ các trạng thái và chuyển đổi trạng thái đối tượng chỉ bằng một machine duy nhất. => Vậy làm thế nào để sử dụng machine state? Có rất nhiều gem giúp chúng ta sử dụng state machine trong ruby. Chúng ta sẽ thực hành qua một ví dụ cụ thể trong rails với gem "state_machine" - một state machines trong ruby.
  1. Khi nào thì sử dụng State Machine?
  • Khi thuộc tính của đối tượng có nhiều trạng thái khác nhau
  • Với mỗi sự kiện chuyển đổi trạng thái của đối tượng có kèm theo các tiến trình theo sau đó. (bởi State machine còn hỗ trợ Transaction và các hàm callback)

Cài đặt

Thêm gem vào Gemfile của ứng dụng của bạn:

gem 'state_machine' gem 'ruby-graphviz', :require => 'graphviz'

Và sau đó thực hiện:

$ bundle

Hoặc tự cài đặt bằng:

$ gem install state_machines

Tạo Model

$ rails generate model name_model state:string rake db:migrate

Ví dụ: so sánh

Chúng ta sẽ tạo model cho một cuộc gọi cho ứng dụng điện thoại. Một cuộc gọi sẽ có trạng thái có trả lời hay không. Trước hết chúng ta sẽ sử dụng boolean answered để trả về kết quả trạng thái.

class Call
    attr_accessor :answered

    def initialize
        self.answered = false
    end
end

Cách xử lý trường hợp không trả lời được hoặc cuộc gọi bị lỗi

class Call
    attr_reader :answered, :failed, :failure_reason

    def initialize
        @answered = false
        @failed = false
    end

    def answer
        @answered = true
    end

    def fail(reason)
        @failed = true
        @failure_reason = reason
    end
end

Nhưng khi chúng ta muốn theo dõi xem cuộc gọi đang quay số hay đang trong cuộc nói chuyện hay muốn biết thời gian cuộc nói chuyện, .... Khi đó chúng cần định nghĩa thêm các phương thức với mỗi phương thức lại cần phải thêm các điều kiện để kiểm tra cho các boolean khác nhau. => Model Call sẽ trở lên rất phức tạp khi chúng ta thêm nhiều thuộc tính và trạng thái. Thay thế với State Machine Ở mỗi một thời điểm Call sẽ có trạng thái khác nhau:

  • dialing
  • in_progress
  • completed
  • failed Quy định sự thay đổi trạng thái:
  • dialing -> in_progress or failed
  • in_progress -> completed Phần xử lý với mỗi bối cảnh:
  • failure_reason thông báo lỗi nếu cuộc gọi fail
  • duration tính thời gian nếu cuộc gọi thành công Tất cả các hành vi của Call đều thể dễ dàng mô tả bằng cách sử dụng một state_machine thay vì quản lý thủ công nhiều booleans và flags:
# Using the state_machine gem.
class Call
    state_machine :state, :initial => :dialing do
        event :answered do
            transition :dialing => :in_progress
        end

        event :failure do
            transition :dialing => :failed
        end

        event :hangup do
            transition :in_progress => :completed
        end

        state :completed do
            def duration
                @end_time - @start_time
            end
        end

        state :failed do
            attr_accessor :failure_reason
        end

        before_transition :dialing => :in_progress, :do => :rec_start_time
        after_transition :in_progress => :completed, :do => :rec_end_time
    end

    def answered?
        in_progress? || completed?
    end

    private

    def rec_start_time
        @start_time = Time.now
    end

    def rec_end_time
        @end_time = Time.now
    end
end

**Note: cách sử dụng **

Để sử dụng được State Machine cho đối tượng bạn cần gọi state_machine

state_machine :state, :initial => :dialing do
end
  • state là thuộc tính của đối tượng
  • state_machine định nghĩa giá trị default cho đối tượng khi khởi tạo
  • Trong quá trình tạo dữ liệu nếu bạn muốn thay đổi giá trị default của state bạn cần gọi state_machine khác.
call = Call.new           # => #<Call:0xb7cf4eac @state="dialing">
call.state                   # => "dialing"
  • Khi chuyển đổi trạng thái của đối tượng bạn sẽ gọi đến event
  • Tại event sẽ kiểm tra trạng thái và chuyển đổi trạng thái đối tượng đó
    event :answered do
        transition :dialing => :in_progress
    end
//rails c
call.dialing? # => true
call.in_progress? # => false
call.answered  # => true
call.state = 'in_progress'

Tài liệu tham khảo

https://github.com/pluginaweek/state_machine https://github.com/state-machines/state_machines http://www.rubydoc.info/github/pluginaweek/state_machine/StateMachine/AllMatcher


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.