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