Design Patterns in Ruby: State Pattern Abram Hindle [email protected] Department of Computing Science University of Alberta http://softwareprocess.es/

(C) 2013 Abram Hindle Licensed under Creative Commons CC-BY-SA 3.0 Unported

Design Patterns ●





Just because you have duck-typing doesn't mean you can ignore common OO idioms! Design patterns communicate intent, so it is best if we have a similar understanding. OO is hard :(

State ●







State is often the current snapshot of values in a system. State machines are a modelling methodology that try to simplify complicate control flows and protocols State is often encoded as attributes and variables. State Machines are often implemented imperatively.

State Pattern ●







Intent: Change an object's behaviour dynamically (runtime). Intent: Model a state-machine following OO rules. Avoid cluttering a class with all the different behaviours, avoid subclassing for changing behaviour. Use the type system to ensure state transition safety.

State Machines? Press button Off

On Press button

A

B

(A|B)B*

State Pattern UML (Abstract) Context

State

state

StateOne

StateTwo

State Pattern UML (Concrete) Price

VideoRental state

getPrice(days)

getPrice(days) return state.getPrice(days)

NewRelease StateOne Price: 4.99 getPrice(days)

OldMovie BasePrice: 1.99 getPrice(days)

State Pattern UML (Concrete) Gear

BullDozer trackGearLeft trackGearRight

moveTrack(dozer)

move()

Forward StateOne

Reverse

moveTrack(dozer)

moveTrack(dozer)

State Pattern UML (Concrete) PedalState

StompPedal state

Filter( audio)

filter()

On StateOne Filter( audio)

Off Filter( audio) Does nothing to the audio

State Method Example

Remember Street Fighter II special moves?

SFII: HaDouKen Fireball

DOWN

DOWN-FORWARD

FORWARD + PUNCH

Our Player: Ryu class RyuPlayer   def initialize()     @moves = [WhirlwindKickMove.new(), FireballMove.new(), DragonUppercutMove.new()]     @todo = [ ]   end   def execute( move )     @todo = [ move ]   end   def move( move, action )     @todo = [ ]

We run 3 seperate state machines for 3 special moves. The state machines RETURN the next state, and thus we must replace the states when we execute a new move.

    @moves = @moves.map {|specialMove| specialMove.nextState( self, move, action ) }     executeMove()   end   def executeMove()     if (@todo.length > 0)       puts(@todo[0].say())     end   end end

Fireball Without State Pattern class FireballMoveConditional def initialize() @state = :none end def nextState( context, move, action ) def say() if (@state == :none && move.down()) then return "HaDoKen!" @state = :ha end elsif (@state == :ha && move.down()) then # this example has a # state value as a guard @state = :ha elsif (@state == :ha && move.downForward()) then # but if I mess up the @state = :do # conditional then all elsif (@state == :do && move.forward() && action.punch()) then # hell breaks loose # the body of the state machine is put into these conditional blocks context.execute(FireballMove.new()) @state = :none else # the general default behaviour is here but sometimes different # states have default behaviour too @state = :none end return self end end

FireBall with State Pattern

● ●

● ● ● ●

class FireballMove class FireballDo   def say()   def nextState( context, move, action )     return "HaDoKen!"     if (move.forward() && action.punch()) then   end   def nextState( context, move, action )       context.execute(FireballMove.new())     end     if (move.down()) then     return FireballMove.new()       return FireballHa.new()   end     end end     return FireballMove.new()   end end class FireballHa   def nextState( context, move, action ) State encoded as a Class     if (move.downForward()) then Only relevant triggers for that       return FireballDo.new() state need to be handled.     elsif (move.down()) then Code is better organized.       return FireballHa.new() Responsibilities seperated.     end No crazy conditionals     return FireballMove.new() Precedence or order of   end conditionals is explicit and clear. end

Dragon Uppercut State Machine class DragonUppercutSho class DragonUppercutMove   def nextState( context, move, action )   def say()     if (move.downBackward()) then     return "ShoRyuKen!"       return DragonUppercutRyu.new()   end     end   def nextState( context, move, action )     if (move.forward()) then     return DragonUppercutMove.new()   end       return DragonUppercutSho.new() end     end     return DragonUppercutMove.new()   end end class DragonUppercutRyu   def nextState( context, move, action )     if (move.forward() && action.punch()) then       context.execute(DragonUppercutMove.new())     end     return DragonUppercutMove.new()   end end

Whirlwind Kick State Machine class WhirlwindKickMove   def say()     return "TaTsunaki!"   end   def nextState( context, move, action )     if (move.up()) then       return WhirlwindKickTa.new()     end     return WhirlwindKickMove.new()   end end class WhirlwindKickTsun   def nextState( context, move, action )     if (move.downBackward()) then       return WhirlwindKickAki.new()     end     return WhirlwindKickMove.new()   end end

class WhirlwindKickTa   def nextState( context, move, action )     if (move.down()) then       return WhirlwindKickTsun.new()     end     return WhirlwindKickMove.new()   end end

class WhirlwindKickAki   def nextState( context, move, action )     if (move.backward() && action.kick()) then       context.execute(WhirlwindKickMove.new())     end     return WhirlwindKickMove.new()   end end

Trying Some Moves Out!     player = RyuPlayer.new()     puts("# crouching")     player.move( Down.new(), Action.new() )     player.move( Down.new(), Action.new() )     player.move( Down.new(), Action.new() )     puts("# starting fireball")     player.move( Down.new(), Action.new() )     player.move( DownForward.new(), Action.new() )     player.move( Forward.new(), Punch.new() ) #fireball     puts("# converting to dragon uppercut")     player.move( DownBackward.new(), Action.new() )     player.move( Forward.new(), Punch.new() ) #DragonUppercut     puts("# charging whirlwind kick")     player.move( Up.new(), Action.new() )     player.move( Down.new(), Action.new() )     player.move( DownBackward.new(), Action.new() )     player.move( Backward.new(), Kick.new() ) #whirlwind

State Pattern Conclusions ●

Use 1: Dynamic Change/Delegation of Behaviour



Use 2: Type-Safe State Machines



State Pattern is quite relevant to Ruby







State can be treated as a role or responsibility and thus belongs in its own class. Handling State via conditionals will bite your butt very quickly Running multiple state machines is easier than unioning/flattening out state machines.

State Pattern Resources ●

C2 on State Pattern http://c2.com/cgi/wiki?StatePattern



Wikipedia on State Pattern http://en.wikipedia.org/wiki/State_pattern





Source Making on State pattern http://sourcemaking.com/design_patterns/state State Pattern Gem http://rubygems.org/gems/state_pattern https://github.com/dcadenas/state_pattern







The Delegate module can be used to implement

State pattern http://www.ruby-doc.org/stdlib-1.9.3/libdoc/delegate/rdoc/Delegator.htm and http://www.rubydoc.org/docs/ProgrammingRuby/html/lib_patterns.html

Design Patterns in Ruby: State Pattern - GitHub

State Method Example. Remember Street. Fighter II special moves? .... State pattern http://www.ruby-doc.org/stdlib-1.9.3/libdoc/delegate/rdoc/Delegator.html.

2MB Sizes 78 Downloads 272 Views

Recommend Documents

The Ruby Programming Language - GitHub
You'll find a guide to the structure and organization of this book in Chapter 1. ..... Determine US generation name based on birth year ...... curly braces: "360 degrees=#{2*Math::PI} radians" # "360 degrees=6.28318530717959 radians" ...... of comput

101 Ruby Code Factoids - GitHub
You can add the current directory to your load path with: .... Apple.chew. # => "munch munch" def Apple.cut. "chop chop" end. Apple.cut ..... 61) Method#owner.

Why's (Poignant) Guide to Ruby - GitHub
This PDF edition of _why's Poignant Guide to Ruby is distributed under the Creative. Commons ... 5 Them What Make the Rules and Them What Live the Dream. 73 ..... A method may require more information in order to perform its action. ..... If plastic_

Design Patterns Design Patterns
concurrency, by using asynchronous method invocation and a scheduler for ... The reactor design pattern is a concurrent programming pattern for handling.

Pro Scala: Monadic Design Patterns for the Web - GitHub
where AGILE methodologies rightfully demand a justification thread running from ...... In addition to structural equivalence of terms (which is a bi-directional ...

design patterns in php pdf
There was a problem previewing this document. Retrying... Download. Connect more apps... Try one of the apps below to open or edit this item. design patterns ...

Design Patterns in PHP.pdf
Sign in. Loading… Whoops! There was a problem loading more pages. Whoops! There was a problem previewing this document. Retrying... Download. Connect ...

PACE: Pattern Accurate Computationally Efficient ... - GitHub
More generally, disclosure of software vulnerabilities falls into three categories: ... and “off-the-shelf” tools are used to identify and classify vulnerability entities.

Pluralsight design pattern
Infirmof purpose. Give methe daggers thesleeping and the dead Are but has pictures. 'tis theeye ofchildhood That fearsa painted devil. Late night drunks.Recovery jamesarthur. ... Onefootatatime, I step onto thecoarse block..506539936392720973 Batch d

Polyamine patterns in the cerebrospinal fluid of patients - GitHub
Jun 1, 2010 - ... of Molecular Science and Technology, Ajou University School, Suwon, ..... suggest that altered PAs are related to development of MSA or PD.

design patterns - cs164
sections labs design reviews, code reviews, office hours alphas new release cycle. Page 5. new release cycle. Page 6. workload. Page 7. project 1. Page 8 ...

/Users/mattetti/.rvm/rubies/ruby-1.9.3-p125/bin/ruby; 362 ... - GitHub
/Users/mattetti/.rvm/rubies/ruby-1.9.3-p125/bin/ruby. Total samples: 362. Focusing on: 362. Dropped nodes with

FINAL Why You'll Love Ruby On Rails - GitHub
I started my programming career using. Smalltalk, a language in which everything was an object. And here was Ruby with exactly the same thing—everything is ...

toward modularity in synthetic biology: design pattern ...
Box 355061, Seattle, WA 98195-5061, U.S.A.. Abstract. Modularity is ..... with I the current and Vout the voltage drop across the capacitor. The current is equal to ...