Skip to content
Skip to breadcrumbs
Skip to header menu
Skip to action menu
Skip to quick search
Quick Search
Browse
Pages
Blog
Labels
Attachments
Mail
Advanced
What’s New
Space Directory
Feed Builder
Keyboard Shortcuts
Confluence Gadgets
Log In
Dashboard
Groovy
Copy Page
You are not logged in. Any changes you make will be marked as
anonymous
. You may want to
Log In
if you already have an account. You can also
Sign Up
for a new account.
This page is being edited by
.
Paragraph
Paragraph
Heading 1
Heading 2
Heading 3
Heading 4
Heading 5
Heading 6
Preformatted
Quote
Bold
Italic
Underline
More colours
Strikethrough
Subscript
Superscript
Monospace
Clear Formatting
Bullet list
Numbered list
Outdent
Indent
Align left
Align center
Align right
Link
Table
Insert
Insert Content
Image
Link
Attachment
Symbol
Emoticon
Wiki Markup
Horizontal rule
tinymce.confluence.insert_menu.macro_desc
Info
JIRA Issue
Status
Gallery
Tasklist
Table of Contents
Other Macros
Page Layout
No Layout
Two column (simple)
Two column (simple, left sidebar)
Two column (simple, right sidebar)
Three column (simple)
Two column
Two column (left sidebar)
Two column (right sidebar)
Three column
Three column (left and right sidebars)
Undo
Redo
Find/Replace
Keyboard Shortcuts Help
<p>The <a href="http://en.wikipedia.org/wiki/State_pattern">State Pattern</a> provides a structured approach to partitioning the behaviour within complex systems. The overall behaviour of a system is partitioned into well-defined states. Typically, each state is implemented by a class. The overall system behaviour can be determined firstly by knowing the <em>current state</em> of the system; secondly, by understanding the behaviour possible while in that state (as embodied in the methods of the class corresponding to that state).</p> <h3>Example</h3> <p>Here is an example:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> class Client { def context = new Context() def connect() { context.state.connect() } def disconnect() { context.state.disconnect() } def send_message(message) { context.state.send_message(message) } def receive_message() { context.state.receive_message() } } class Context { def state = new Offline(this) } class ClientState { def context ClientState(context) { this.context = context inform() } } class Offline extends ClientState { Offline(context) { super(context) } def inform() { println "offline" } def connect() { context.state = new Online(context) } def disconnect() { println "error: not connected" } def send_message(message) { println "error: not connected" } def receive_message() { println "error: not connected" } } class Online extends ClientState { Online(context) { super(context) } def inform() { println "connected" } def connect() { println "error: already connected" } def disconnect() { context.state = new Offline(context) } def send_message(message) { println "\"$message\" sent" } def receive_message() { println "message received" } } client = new Client() client.send_message("Hello") client.connect() client.send_message("Hello") client.connect() client.receive_message() client.disconnect() </pre></td></tr></table> <p><br class="atl-forced-newline" /></p> <p>Here is the output:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> offline error: not connected connected "Hello" sent error: already connected message received offline </pre></td></tr></table> <p><br class="atl-forced-newline" /></p> <p>This example was inspired from a similar <a href="http://wiki.rubygarden.org/Ruby/page/show/StatePattern">Ruby Example</a>. One of the great things about a dynamic language like Groovy though is that we can take this example and express it in many different ways depending on our particular needs. Some potential variations for this example are shown below.</p> <h3>Variation 1: Leveraging Interface-Oriented Design</h3> <p>One approach we could take is to leverage <a href="http://www.pragmaticprogrammer.com/titles/kpiod/index.html">Interface-Oriented Design</a>. To do this, we could introduce the following interface:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> interface State { def connect() def disconnect() def send_message(message) def receive_message() } </pre></td></tr></table> <p><br class="atl-forced-newline" /></p> <p>Then our <code>Client</code>, <code>Online</code> and <code>Offline</code> classes could be modified to implement that interface, e.g.:<br /> <table class="wysiwyg-macro" data-macro-name="panel" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e3BhbmVsfQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="RICH_TEXT"><tr><td class="wysiwyg-macro-body"> <p> class Client <strong>implements State</strong> { <br class="atl-forced-newline" /> // ... as before ... <br class="atl-forced-newline" /> }</p></td></tr></table><br class="atl-forced-newline" /> <table class="wysiwyg-macro" data-macro-name="panel" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e3BhbmVsfQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="RICH_TEXT"><tr><td class="wysiwyg-macro-body"> <p> class Online <strong>implements State</strong> { <br class="atl-forced-newline" /> // ... as before ... <br class="atl-forced-newline" /> }</p></td></tr></table><br class="atl-forced-newline" /></p> <p>You might ask: Haven't we just introduced additional boilerplate code? Can't we rely on duck-typing for this? The answer is 'yes' and 'no'. We can get away with duck-typing but one of the key intentions of the state pattern is to partition complexity. If we know that the <em>client</em> class and each <em>state</em> class all satisfy one interface, then we have placed some key boundaries around the complexity. We can look at any state class in isolation and know the bounds of behaviour possible for that state.</p> <p>We don't have to use interfaces for this, but it helps express the intent of this particular style of partitioning and it helps reduce the size of our unit tests (we would have to have additional tests in place to express this intent in languages which have less support for interface-oriented design).</p> <h3>Variation 2: Extract State Pattern Logic</h3> <p>Alternatively, or in combination with other variations, we might decide to extract some of our State Pattern logic into helper classes. For example, we could define the following classes in a state pattern package/jar/script:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> abstract class InstanceProvider { static def registry = GroovySystem.metaClassRegistry static def create(objectClass, param) { registry.getMetaClass(objectClass).invokeConstructor([param] as Object[]) } } abstract class Context { private context protected setContext(context) { this.context = context } def invokeMethod(String name, Object arg) { context.invokeMethod(name, arg) } def startFrom(initialState) { setContext(InstanceProvider.create(initialState, this)) } } abstract class State { private client State(client) { this.client = client } def transitionTo(nextState) { client.setContext(InstanceProvider.create(nextState, client)) } } </pre></td></tr></table> <p><br class="atl-forced-newline" /></p> <p>This is all quite generic and can be used wherever we want to introduce the state pattern. Here is what our code would look like now:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> class Client extends Context { Client() { startFrom(Offline) } } class Offline extends State { Offline(client) { super(client) println "offline" } def connect() { transitionTo(Online) } def disconnect() { println "error: not connected" } def send_message(message) { println "error: not connected" } def receive_message() { println "error: not connected" } } class Online extends State { Online(client) { super(client) println "connected" } def connect(){ println "error: already connected" } def disconnect(){ transitionTo(Offline) } def send_message(message) { println "\"$message\" sent" } def receive_message(){ println "message received" } } client = new Client() client.send_message("Hello") client.connect() client.send_message("Hello") client.connect() client.receive_message() client.disconnect() </pre></td></tr></table> <p><br class="atl-forced-newline" /></p> <p>You can see here the <code>startFrom</code> and <code>transitionTo</code> methods begin to give our example code a DSL feel.</p> <h3>Variation 3: Bring on the DSL</h3> <p>Alternatively, or in combination with other variations, we might decide to fully embrace a Domain Specific Language (DSL) approach to this example.</p> <p>We can define the following generic helper functions (first discussed <a href="http://www.bytemycode.com/snippets/snippet/640/">here</a>):</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> class Grammar { def fsm def event def fromState def toState Grammar(a_fsm) { fsm = a_fsm } def on(a_event) { event = a_event this } def on(a_event, a_transitioner) { on(a_event) a_transitioner.delegate = this a_transitioner.call() this } def from(a_fromState) { fromState = a_fromState this } def to(a_toState) { assert a_toState, "Invalid toState: $a_toState" toState = a_toState fsm.registerTransition(this) this } def isValid() { event && fromState && toState } public String toString() { "$event: $fromState=>$toState" } } </pre></td></tr></table> <p><br class="atl-forced-newline" /></p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> class FiniteStateMachine { def transitions = [:] def initialState def currentState FiniteStateMachine(a_initialState) { assert a_initialState, "You need to provide an initial state" initialState = a_initialState currentState = a_initialState } def record() { Grammar.newInstance(this) } def reset() { currentState = initialState } def isState(a_state) { currentState == a_state } def registerTransition(a_grammar) { assert a_grammar.isValid(), "Invalid transition ($a_grammar)" def transition def event = a_grammar.event def fromState = a_grammar.fromState def toState = a_grammar.toState if (!transitions[event]) { transitions[event] = [:] } transition = transitions[event] assert !transition[fromState], "Duplicate fromState $fromState for transition $a_grammar" transition[fromState] = toState } def fire(a_event) { assert currentState, "Invalid current state '$currentState': passed into constructor" assert transitions.containsKey(a_event), "Invalid event '$a_event', should be one of ${transitions.keySet()}" def transition = transitions[a_event] def nextState = transition[currentState] assert nextState, "There is no transition from '$currentState' to any other state" currentState = nextState currentState } } </pre></td></tr></table> <p><br class="atl-forced-newline" /></p> <p>Now we can define and test our state machine like this:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> class StatePatternDslTest extends GroovyTestCase { private fsm protected void setUp() { fsm = FiniteStateMachine.newInstance('offline') def recorder = fsm.record() recorder.on('connect').from('offline').to('online') recorder.on('disconnect').from('online').to('offline') recorder.on('send_message').from('online').to('online') recorder.on('receive_message').from('online').to('online') } void testInitialState() { assert fsm.isState('offline') } void testOfflineState() { shouldFail{ fsm.fire('send_message') } shouldFail{ fsm.fire('receive_message') } shouldFail{ fsm.fire('disconnect') } assert 'online' == fsm.fire('connect') } void testOnlineState() { fsm.fire('connect') fsm.fire('send_message') fsm.fire('receive_message') shouldFail{ fsm.fire('connect') } assert 'offline' == fsm.fire('disconnect') } } </pre></td></tr></table> <p><br class="atl-forced-newline" /></p> <p>This example isn't an exact equivalent of the others. It doesn't use predefined <code>Online</code> and <code>Offline</code> classes. Instead it defines the entire state machine on the fly as needed. See the <a href="http://www.bytemycode.com/snippets/snippet/640/">previous reference</a> for more elaborate examples of this style.</p> <p>See also: <a class="confluence-link" href="/display/GROOVY/Model-based+testing+using+ModelJUnit" data-linked-resource-id="33161311" data-linked-resource-type="page" data-linked-resource-default-alias="Model-based testing using ModelJUnit" data-base-url="http://docs.codehaus.org">Model-based testing using ModelJUnit</a></p>
Please type the word appearing in the picture.
Attachments
Labels
Location
Watch this page
< Edit
Preview >
Loading…
Save
Cancel
Next hint
search
attachments
weblink
advanced