Created
October 4, 2016 00:10
-
-
Save bign8/cd520dd13c72c233a0d22b278605df2c to your computer and use it in GitHub Desktop.
ESOF-322 State Pattern Notes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bufio" | |
"flag" | |
"fmt" | |
"math/rand" | |
"os" | |
) | |
var ( | |
count = flag.Int("count", 5, "inital volume of gumball machine") | |
_ State = (*stateSoldOut)(nil) | |
_ State = (*stateNoQuarter)(nil) | |
_ State = (*stateHasQuarter)(nil) | |
_ State = (*stateSold)(nil) | |
_ State = (*stateWinner)(nil) | |
) | |
// State exposes the methods that can be performed on a gumball Machine | |
type State interface { | |
String() string | |
Insert() | |
Eject() | |
Crank() | |
dispense() | |
} | |
// GumballMachine contains the core switching logic for a gumball machine | |
type GumballMachine struct { | |
soldOut State | |
noQuarter State | |
hasQuarter State | |
sold State | |
winner State | |
state State | |
count int | |
} | |
// NewGumballMachine consntructs a new gumball machine | |
func NewGumballMachine(count int) *GumballMachine { | |
gm := &GumballMachine{count: count} | |
gm.soldOut = &stateSoldOut{gm} | |
gm.noQuarter = &stateNoQuarter{gm} | |
gm.hasQuarter = &stateHasQuarter{gm} | |
gm.sold = &stateSold{gm} | |
gm.winner = &stateWinner{gm} | |
if count > 0 { | |
gm.state = gm.noQuarter | |
} else { | |
gm.state = gm.soldOut | |
} | |
return gm | |
} | |
func (gm *GumballMachine) Insert() { gm.state.Insert() } | |
func (gm *GumballMachine) Eject() { gm.state.Eject() } | |
func (gm *GumballMachine) Crank() { | |
gm.state.Crank() | |
gm.state.dispense() | |
} | |
func (gm *GumballMachine) Refil(count int) { | |
gm.count += count | |
if gm.state == gm.soldOut { | |
gm.state = gm.noQuarter | |
} | |
} | |
func (gm *GumballMachine) String() string { | |
return fmt.Sprintf("%s (%d)", gm.state.String(), gm.count) | |
} | |
func (gm *GumballMachine) release() { | |
fmt.Println("A gumball comes rolling out the slot...") | |
if gm.count != 0 { | |
gm.count-- | |
} | |
} | |
type stateSoldOut struct{ gm *GumballMachine } | |
func (s *stateSoldOut) Insert() { fmt.Println("Can't insert") } | |
func (s *stateSoldOut) Eject() { fmt.Println("Can't eject") } | |
func (s *stateSoldOut) Crank() { fmt.Println("Can't crank") } | |
func (s *stateSoldOut) dispense() {} | |
func (s *stateSoldOut) String() string { return "Sold Out" } | |
type stateNoQuarter struct{ gm *GumballMachine } | |
func (s *stateNoQuarter) Insert() { s.gm.state = s.gm.hasQuarter } | |
func (s *stateNoQuarter) Eject() { fmt.Println("Can't eject") } | |
func (s *stateNoQuarter) Crank() { fmt.Println("Can't crank") } | |
func (s *stateNoQuarter) dispense() {} | |
func (s *stateNoQuarter) String() string { return "No Quarter" } | |
type stateHasQuarter struct{ gm *GumballMachine } | |
func (s *stateHasQuarter) Insert() { fmt.Println("You can't insert another quarter") } | |
func (s *stateHasQuarter) Eject() { s.gm.state = s.gm.noQuarter } | |
func (s *stateHasQuarter) Crank() { | |
if rand.Intn(10) == 0 && s.gm.count > 1 { | |
fmt.Println("Your A Winner!!!") | |
s.gm.state = s.gm.winner | |
} else { | |
s.gm.state = s.gm.sold | |
} | |
} | |
func (s *stateHasQuarter) dispense() {} | |
func (s *stateHasQuarter) String() string { return "Has Quarter" } | |
type stateSold struct{ gm *GumballMachine } | |
func (s *stateSold) Insert() { fmt.Println("Can't insert") } | |
func (s *stateSold) Eject() { fmt.Println("Can't eject") } | |
func (s *stateSold) Crank() { fmt.Println("Can't crank") } | |
func (s *stateSold) dispense() { | |
s.gm.release() | |
if s.gm.count > 0 { | |
s.gm.state = s.gm.noQuarter | |
} else { | |
s.gm.state = s.gm.soldOut | |
} | |
} // TODO | |
func (s *stateSold) String() string { return "Sold" } | |
type stateWinner struct{ gm *GumballMachine } | |
func (s *stateWinner) Insert() { fmt.Println("Can't insert") } | |
func (s *stateWinner) Eject() { fmt.Println("Can't eject") } | |
func (s *stateWinner) Crank() { fmt.Println("Can't crank") } | |
func (s *stateWinner) dispense() { | |
for i := 0; i < 2; i++ { | |
s.gm.release() | |
if s.gm.count == 0 { | |
s.gm.state = s.gm.soldOut | |
return | |
} | |
} | |
s.gm.state = s.gm.noQuarter | |
} // TODO | |
func (s *stateWinner) String() string { return "Winner" } | |
func main() { | |
flag.Parse() | |
gm := NewGumballMachine(*count) | |
scan := bufio.NewReader(os.Stdin) | |
for { | |
fmt.Print(gm.String() + "; Insert, Eject, Crank, Refil > ") | |
switch text, _ := scan.ReadString('\n'); text[0] { | |
case 'I': | |
fallthrough | |
case 'i': | |
gm.Insert() | |
case 'E': | |
fallthrough | |
case 'e': | |
gm.Eject() | |
case 'C': | |
fallthrough | |
case 'c': | |
gm.Crank() | |
case 'R': | |
fallthrough | |
case 'r': | |
gm.Refil(*count) | |
case '\n': | |
default: | |
fmt.Printf("Unknown Command: %q\n", text) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Guest Lecture: Nate Woods | |
Class: ESOF-322 | |
Date: Tuesday Oct 4 (12:15pm to 1:30pm) | |
Location: Cheever Hall 215 | |
Topic: The State Pattern | |
*/ | |
// # Gumballs! | |
// Imagine a gumball machine | |
// What can you do with it? | |
// # State Diagrams | |
// TODO: (insert state diagram here) | |
// ## States | |
// - Has Quarter | |
// - No Quarter | |
// - Gumball Sold | |
// - Out Of Gumballs | |
// ## Actions | |
// - Insert Quarter | |
// - Eject Quarter | |
// - Turns Crank | |
// - Dispense (Gumballs Condition) | |
// States are just different configurations of the machine | |
// that behave in a certain way and need some action to take them to another state. | |
// -------------------------- START DEMO -------------------------- | |
public class GumballMachine { | |
// States represented as unique integers | |
final static int SOLD_OUT = 0; // Out Of Gumballs | |
final static int NO_QUARTER = 1; | |
final static int HAS_QUARTER = 2; | |
final static int SOLD = 3; | |
// Current State of the GumballMachine | |
int state = SOLD_OUT; | |
int count = 0; | |
public GumballMachine(int count) { | |
this.count = count; | |
if (count > 0) state = NO_QUARTER; | |
} | |
// For each action we create a method | |
public void insertQuarter() { | |
if (state == HAS_QUARTER) { | |
System.out.println("You can't insert another quarter"); | |
} else if (state == SOLD_OUT) { | |
System.out.println("You can't insert a quarter, the machine is sold out"); | |
} else if (state == SOLD) { | |
System.out.println("Please wait, we're already giving you a gumball"); | |
} else if (state == NO_QUARTER) { | |
state = HAS_QUARTER; | |
System.out.println("You inserted a querter"); | |
} | |
} | |
public void ejectQuarter() { | |
if (state == HAS_QUARTER) { | |
System.out.println("Quarter returned"); | |
state = NO_QUARTER; | |
} else if (state == NO_QUARTER) { | |
System.out.println("You haven't inserted a quarter"); | |
} else if (state == SOLD_OUT) { | |
System.out.println("You can't eject, you haven't inserted a quarter yet"); | |
} else if (state == SOLD) { | |
System.out.println("Sorry, you already turned the crank"); | |
} | |
} | |
public void turnCrank() { | |
if (state == SOLD) { | |
System.out.println("Turning twice doesn't get you another gumball!") | |
} else if (state == NO_QUERTER) { | |
System.out.println("You turned but there's no quarter"); | |
} else if (state == SOLD_OUT) { | |
System.out.println("You turned , but there are no gumballs"); | |
} else if (state == HAS_QUARTER) { | |
System.out.println("You turned..."); | |
state = SOLD; | |
dispense(); | |
} | |
} | |
public void dispense() { | |
if (state == SOLD) { | |
System.out.println("A gumball comes rolling out the slot"); | |
count = count - 1; | |
if (count == 0) { | |
System.out.println("Oops, out of gumballs!"); | |
state = SOLD_OUT; | |
} else { | |
state = NO_QUARTER; | |
} | |
} else if (state == NO_QUARTER) { | |
System.out.println("You need to pay first"); | |
} else if (state == SOLD_OUT) { | |
System.out.println("No gumball dispensed"); | |
} else if (State == HAS_QUARTER) { | |
System.out.println("No gumball dispensed"); | |
} | |
} | |
// other methods like toString() and refill() | |
} | |
public class GumballMachineTestDriver { | |
public static void main(String[] args) { | |
GumballMachine gm = new GumballMachine(5); | |
System.out.println(gm); | |
gm.insertQuarter(); | |
gm.turnCrank(); | |
System.out.println(gm); | |
gm.insertQuarter(); | |
gm.ejectQuarter(); | |
gm.turnCrank(); // no gumball for you | |
System.out.println(gm); | |
gm.insertQuarter(); | |
gm.turnCrank(); // should get a gumball | |
gm.insertQuarter(); | |
gm.turnCrank(); // should get a gumball | |
gm.ejectQuarter(); // shouldn't get a quarter back | |
System.out.println(gm); | |
gm.insertQuarter(); | |
gm.insertQuarter(); // nope | |
gm.turnCrank(); | |
gm.insertQuarter(); // stress testing | |
gm.turnCrank(); | |
gm.insertQuarter(); | |
gm.turnCrank(); | |
System.out.println(gm); | |
} | |
} | |
// -------------------------- END DEMO -------------------------- | |
// # Change: One in ten get a free gumball!!! | |
// 10% of the time when the crank is turned | |
// the customer gets two gumballs! | |
// TODO: insert new state diagram here | |
// See just this addition reveals the awkward design we have | |
public class GumballMachineChanges { | |
final static int SOLD_OUT = 0; | |
final static int NO_QUARTER = 1; | |
final static int HAS_QUARTER = 2; | |
final static int SOLD = 3; | |
// add a new winner state | |
public void insertQuarter() { /* lots of if statements */ } | |
public void ejectQuarter() { /* lots of if statements */ } | |
public void turnCrank() { /* lots of if statements */ } // Add Winner logic | |
public void dispense() { /* lots of if statements */ } | |
} | |
// Pain points | |
// - Open Closed Principle: open for extension / closed for modification | |
// - This code would make a FORTRAN programmer proud | |
// - Not object oriented | |
// - Transitions are burried | |
// - Haven't encapsulated anything that varies here | |
// - Further additions are likely to cause bugs in working code | |
// -------------------------- START DEMO -------------------------- | |
public interface State { | |
public void insertQuarter(); | |
public void ejectQuarter(); | |
public void turnCrank(); | |
public void dispense(); | |
} | |
public class NoQuarterState implements State { | |
GumballMachine gm; | |
public NoQuarterState(GumballMachine gm) { | |
this.gm = gm; | |
} | |
public void insertQuarter() { | |
System.out.println("You inserted a quarter"); | |
gm.setState(gm.getHasQuarterState()); | |
} | |
public void ejectQuarter() { | |
System.out.println("You haven't inserted a quarter"); | |
} | |
public void turnCrank() { | |
System.out.println("You turned, but there's no quarter"); | |
} | |
public void dispense() { | |
System.out.println("You need to pay first"); | |
} | |
} | |
public class GumballMachine { | |
State soldOutState; | |
State noQuarterState; | |
State hasQuarterState; | |
State soldState; | |
State state = soldOutState; | |
int count = 0; | |
public NewGumballMachine(int count) { | |
soldOutState = new SoldOutState(this); | |
noQuarterState = new NoQuarterState(this); | |
hasQuarterState = new HasQuarterState(this); | |
soldState = new SoldState(this); | |
this.count = count; | |
if (count > 0) state = noQuarterState; | |
} | |
public void insertQuarter() { | |
state.insertQuarter(); | |
} | |
public void ejectQuarter() { | |
state.ejectQuarter(); | |
} | |
public void turnCrank() { | |
state.turnCrank(); | |
state.dispense(); | |
} | |
void setState(State state) { | |
this.state = state; | |
} | |
void releaseBall() { | |
System.out.println("A gumball comes rolling out the slot..."); | |
if (count != 0) count = count - 1; | |
} | |
// More methods including getters for each state (getCount(), getNoQuarterState()) | |
} | |
public class HasQuarterState implements State { | |
GumballMachine gm; | |
public HasQuarterState(GumballMachine gm) { | |
this.gm = gm; | |
} | |
public void insertQuarter() { // Invalid action for this state | |
System.out.println("You can't insert another quarter"); | |
} | |
public void ejectQuarter() { | |
System.out.println("Quarter returned"); | |
gm.setState(gm.getNoQuarterState()); | |
} | |
public void turnCrank() { | |
System.out.println("You turned..."); | |
gm.setState(gm.getSoldState()); | |
} | |
public void dispense() { // Invalid action for this state | |
System.out.println("No gumball dispensed"); | |
} | |
} | |
public class SoldState implements State { | |
GumballMachine gm; | |
public HasQuarterState(GumballMachine gm) { | |
this.gm = gm; | |
} | |
public void insertQuarter() { // Invalid action for this state | |
System.out.println("Please wait, we'er already giving you a gumball"); | |
} | |
public void ejectQuarter() { // Invalid action for this state | |
System.out.println("Sorry, you already turned the crank"); | |
} | |
public void turnCrank() { // Invalid action for this state | |
System.out.println("Turning twice doesn't get you another gumball"); | |
} | |
public void dispense() { | |
gm.releaseBall(); | |
if (gm.getCount() > 0) { | |
gm.setState(gm.getNoQuarterState()); | |
} else { | |
System.out.println("Oops, out of gumballs!"); | |
gm.setState(gm.getSoldOutState()); | |
} | |
} | |
} | |
// -------------------------- END DEMO -------------------------- | |
// - Localized the behavior of each state into it's own class | |
// - Removed all teh troublesome if statements that would have been difficult to maintain | |
// - Closed each state for modification, and yet left Gumball Machine open to extension by adding new state clases | |
// - Created a code base and class structure that maps closely to the Mighty Gumball diagram and is easer to read + understand | |
// # The State pattern | |
// Allows an object to alter it's behavior when its internal state changes. | |
// The object will appear to change it's class. | |
// TODO: insert UML diagram here!!! | |
// Note: same as strategy pattern | |
// Think of the State Pattern as an alternative to putting lost of conditionals in your context; | |
// By encapsulating the behaviors within state objects, you can simply change the state object in context to change it's behavior | |
// -------------------------- START DEMO -------------------------- | |
// # Implemnting the game!!! | |
public class GameGumballMachine { | |
State soldOutState; | |
State noQuarterState; | |
State hasQuarterState; | |
State soldState; | |
State winnerState; // Brand new!!! | |
// Added getter/initialization for winnerState | |
} | |
public class WinnerState implements State { | |
// instance variables and constructor | |
// insertQuarter + ejectQuarter + turnCrank error | |
public void dispense() { | |
System.out.println("YOU'RE A WINNER! You get two gumballs for your quarter"); | |
gm.releaseBall(); | |
if (gm.getCount() == 0) gm.setState(gm.getSoldOutState()); | |
else { | |
gm.releaseBall(); | |
if (gm.getCount() > 0) gm.setState(gm.getNoQuarterState()); | |
else { | |
System.out.Println("Oops, out of gumballs!"); | |
gm.setState(gm.getSOldOutState()); | |
} | |
} | |
} | |
} | |
public class HasQuarterState implements State { | |
Random randomWinner = new Random(System.currentTimeMillis()); | |
GumballMachine gm; | |
// constructor | |
// insertQuarter + dispense error | |
public void ejectQuarter() { | |
System.out.println("Quarter returned"); | |
gm.setState(gm.getNoQuarterState()); | |
} | |
public void turnCrank() { | |
System.out.println("You turned..."); | |
int winner = randomWinner.nextInt(10); | |
if ((winner == 0) && (gm.getCount() > 1)) { | |
gm.setState(gm.getWinnerState()); | |
} else { | |
gm.setState(gm.getSoldState()); | |
} | |
} | |
} | |
// -------------------------- END DEMO -------------------------- |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment