State Machine DSL revamp


lørdag 16. januar 2010 Ruby DSL

I min forrige blogpost gikk jeg gjennom hvordan jeg laget en enkel tilstandsmaskin i Ruby. Jeg har tenkt litt mer på den, og her kommer en liten forbedring..

Orginalt la jeg til rette for to ulike måter å konfigurere tilstandsmaskinen på. Først lagde jeg en enkel add_transition metode som jeg kunne kalle slik som dette:

   9         @state_machine.add_transition(:locked, :coin, :unlocked) { @unlock_called = true }

Ulempen med denne er at den ikke kommuniserer så godt – god kode skrevet for mennesker bør kommunisere mer enn det der. Jeg lagde derfor et flytende grensesnitt, ala det jeg tidligere hadde laget i C#.

   72         @state_machine.configure do |sm|

   73             sm.given(:locked).when(:coin).then_set_state(:unlocked).and_run { @unlock_called = true }

   74             sm.given(:locked).when(:pass).then_set_state(:locked).and_run { @alarm_called = true }

   75             sm.given(:unlocked).when(:coin).then_set_state(:unlocked).and_run { @thank_you_called = true }

   76             sm.given(:unlocked).when(:pass).then_set_state(:locked).and_run { @lock_action_called = true }

   77         end

Etter å ha tenkt meg litt om har jeg kommet opp med et grensesnitt jeg liker bedre. Det ligner mer på andre ting jeg har sett i Ruby, og er på en form som Rubyister gjerne refererer til som DSL-metoder. Ved å bruke en Hash (dotnet'ere kan kalle dette Dictionary) som parameter kommuniserer koden enda bedre. Det blir litt mer tekst å skrive, men på den andre siden er det også mere fleksibelt (om man f.eks. vil legge til flere parametre). Det nye grensesnittet brukes på denne måten:

   137         @state_machine.transition :if_state_is => :locked,  

   138             :when_event => :coin, :then_set_state => :unlocked do

   139             @unlock_called = true

   140         end

   141         @state_machine.transition :if_state_is => :locked,  

   142             :when_event => :pass, :then_set_state => :locked do

   143             @alarm_called = true

   144         end

   145         @state_machine.transition :if_state_is => :unlocked,  

   146             :when_event => :coin, :then_set_state => :unlocked do

   147             @thank_you_called = true

   148         end

   149         @state_machine.transition :if_state_is => :unlocked,  

   150             :when_event => :pass, :then_set_state => :locked do

   151             @lock_action_called = true

   152         end

Mellom 'do' og 'end' putter man selvsagt all koden som skal trigges av tilstandsendringen – på samme måte som man kunne gjøre mellom { og } i de foregående eksemplene (dette er to ulike måter å gjøre det samme på i Ruby, hvor klammene normalt brukes når kodeblokken er på samme linje som metodekallet, og bare inneholder én statement.

Selve transition-metoden er enkel å implementere. Den plukker ut verdiene fra Hash'en og sender dem til den tidligere definerte add_transition-metoden.

   185 class GenericStateMachine #open class definition to add another dsl method

   186     def transition args, &and_run

   187         add_transition args[:if_state_is], args[:when_event], args[:then_set_state], &and_run

   188     end

   189 end


comments powered by Disqus