State-based Behavior II SWEN-261 Introduction to Software Engineering Department of Software Engineering Rochester Institute of Technology
There are a number of benefits for explicitly modeling state-based behavior. Explicitly defining this state-based behavior provides a common specification for the team. Allowing the state behavior to evolve implicitly creates a situation where every team member may have a different model of the behavior. You can model state-based behavior in many areas of your software system. • Web application interface • User interface • Individual class-behavior Today's discussion 2
With class behavior modeled, you may decide to have the states be implicit in the implementation. Allow the state definition to implicitly evolve • Standard approach as behavior develops • Maintain information in a collection of attributes • State is defined as logical combinations of values Consider that you are implementing scoring for bowling. What information do you need to use? • Balls thrown • Previous strike or spare • Pins knocked down • Previous frame score • Frame number How many combinations are there? 3
Here is what one Intro to SE team turned that approach into. if (pe.pinsDownOnThisThrow() >= 0) { if(curScore[i+1] != -1) { if (frameNumber == 9) { if (curScore[i+2] != -1){ if (pe.totalPinsDown() == 10) { if( curScore[i+2] != -2){ if(pe.getThrowNumber() == 1) { if( curScore[i+3] != -2){ if ((pe.totalPinsDown() != 10) && if ( i/2 > 0 ){ (pe.getThrowNumber() == 2 && if (curScore[i+3] != -1){ tenthFrameStrike == false)) { if( curScore[i+3] != -2){ if (pe.getThrowNumber() == 3) { if( i%2 == 0 && i < 18){ if (pe.pinsDownOnThisThrow() == 10) { if ( i/2 == 0 ) { } else if (pe.getThrowNumber() == 2) { if(curScore[i] != -2){ } else if (pe.getThrowNumber() == 3) { } else if (i/2 != 9){ if( i%2 == 1 && curScore[i - 1] + if(curScore[i] != -2){ curScore[i] == 10 && } else if (i < 18){ i < current - 1 && i < 19){ if(curScore[i] != -1 && i > 2){ if (i > 1) { if(curScore[i] != -2){ } else if( i < current && i%2 == 0 && if (i/2 == 9){ curScore[i] == 10 && i < 18){ if (i == 18){ if (curScore[i+2] != -1) { if(curScore[i] != -2){ if(curScore[i+3] != -1) { } else if (i/2 == 10) { } else if(curScore[i+4] != -1) { if(curScore[i] != -2){ if (strikeballs == 2){ 38 if statements in a file of 621 lines of code. 4
You should not be afraid to implement state-based behavior using explicit states. It may seem more complex but in the end it often is • Easier to implement • Easier to expand • Easier to understand • Easier to maintain Alan Skorkin gives the following reason for developers not using states explicitly. … early on you don't feel like your objects' state machine behaviour is complex enough to warrant a " full-blown " state machine ( YAGNI and all that jazz ), but later on – when it IS complex enough – you feel like you've invested too much time/effort to replace it with something that has equivalent functionality. It's a bit of a catch-22. It's overkill and by the time it's not, it's too late . Alan Skorkin Why Developers Never Use State Machines 5
Explicitly using states in the implementation provides an easier model to code translation. There are multiple approaches that you can use for an implementation with explicit states. • Switch-based implementation • State-transition table (2 dimensions: states x events) • State design pattern These are discussed in the book chapter listed in the resources for this lesson. Today's discussion 6
You need to decide how you will implement the core aspects of a state machine. States Transitions • State attribute in class • Setting state attribute • Separate class for each • Transition class state Guards Events • If statement tests • Calling a method • Explicit guard methods • Event dispatch Actions • Interrupt • Statement execution • Time out • Explicit action methods • End of an activity. Simplest approach for switch-based implementation. 7
Here are the mechanics for the simplest to understand switch-based implementation. Use an enum to define the states. • Current state is stored in a class attribute. • Transition to a new state by changing current state. Every method on the interface will be implemented with a switch on the current state. Events will be method calls. Guards will be if statement conditions. Actions will be statements executed in the event method. Use private helper methods as needed. 8
We will go back and consider the state-based behavior of the Guessing Game sample webapp. What states does a GuessGame object have? • There is more than one possible set of states. Here is one possible statechart for its behavior. 9
In the original implementation, the states are encoded implicitly in combinations of attributes. The game is lost public synchronized boolean hasMoreGuesses() { return howManyGuessesLeft > 0; } The game is won – detected as return value from makeGuess() return myGuess == numberToGuess; Waiting for another guess public synchronized boolean hasMoreGuesses() { return howManyGuessesLeft > 0; } 10
You start with a definition of the states and an attribute to hold the current state. /** * States for the game in a state-based implementation. */ private enum State { WAIT_FOR_GUESS , GAME_WON , GAME_LOST } /** * The current state the object is in. */ private State currentState = State. WAIT_FOR_GUESS ; 11
With the state-based implementation the states are explicitly coded. public synchronized boolean makeGuess( final int myGuess) { boolean validGuess = false ; This state machine has only one event — a call to makeGuess(). switch (currentState) { case WAIT_FOR_GUESS : if (myGuess >= 0 && myGuess < UPPER_BOUND ){ howManyGuessesLeft--; You need to implement code for each state where this event triggers a transition. if (myGuess == numberToGuess) { currentState = State. GAME_WON ; } You typically implement guards as else if (howManyGuessesLeft <= 0) { the conditional of an if statement. currentState = State. GAME_LOST ; } The transition is completed by setting validGuess = true ; the current state to the next state. } break ; // Guesses are expected only when the game is in WAIT_FOR_GUESS. default : throw new IllegalStateException("No more guesses allowed."); What you do if the event is received when you } return validGuess; do not expect it, is application dependent. } 12
For objects with complex behavior, a state machine is often the only design that is maintainable. Add state machines to your toolbox for defining software behavior • Even without implementing the states explicitly, you gain by having a clear definition of the behavior For more complex systems, implementation techniques other than switch-based are better. • State design pattern separates concerns for each state into a separate class • State-machine frameworks provide polymorphic design of state machine • Dispatch mechanism sends events through system • Tools provide auto-coding of state machine operation directly from the statechart definition 13
Recommend
More recommend