Refactoring to theState Design Pattern    Jim Roepcke <jr@uvic.ca>       CSC 578D Fall 2009
Motivation• Improve the design of the GameStats  application • Make it easier to understand at a glance • Make it easier t...
Backup              BackupReplica             Replica          Primary          ReplicaBackup              BackupReplica  ...
Primary-Backup• Bonjour can guarantee no more than one  Primary replica can publish its service• Multiple Backup replicas ...
failed to     initial                                                                        become                       ...
Context                             StateRequest()                        Handle()    state->Handle()                     ...
State      Context                             TransitionTo(Context c, State s)                             Enter(Context ...
primary  stopstopping                               errorprimary           primary did stop                              s...
Original Code    - (void) stop{!   if (self.state == GSGameControllerStatePrimary) {!   ! self.state = GSGameControllerSta...
First Refactoring    - (oneway void) stop{     self.state = GSGameControllerStateStopping;}
The Devil   (is in the details)     - (void) setState: (GSGameControllerState)newState{!   GSGameControllerState oldState ...
The Devil   (is in the details)        !   !    } else if (oldState != GSGameControllerStateError) {!   !       !    self....
The Devil   (is in the details)      !   !    !    [self tearDownBackup];!   !     !    [self tellDelegate:_delegate perfo...
First Refactoring           - (oneway void) stop           {                self.state = GSGameControllerStateStopping;   ...
} else if (_state == GSGameControllerStateStopping) {     if (oldState == GSGameControllerStatePrimary) {         self.sta...
} else if (_state == GSGameControllerStateStoppingPrimary) {    [self stopServicingBackups];    [_server stop];    self.st...
} else if (_state == GSGameControllerStateStopped) {    if (oldState == GSGameControllerStatePrimary) {        [self tearD...
State Refactoring    - (oneway void) stop{      [self.state stop: self];}
Stopping a primary@implementation GSGameControllerStatePrimary- (void) enter: (GSGameController *)gc{! [gc installServerTa...
Only I know how@implementation GSGameControllerStateStoppingPrimary- (void) enter: (GSGameController *)gc{! [gc stopServic...
Transition to stopped@implementation GSGameControllerStateStoppingPrimary- (void) enter: (GSGameController *)gc{! [gc stop...
Done@implementation GSGameControllerStateStopped- (void) enter: (GSGameController *)gc{    [gc tellDelegate:gc.delegate   ...
Same result on stop} else if (_state == GSGameControllerStateStopped) {    if (oldState == GSGameControllerStatePrimary) {...
Result• GSGameController only knows its own  operations • Not states or transitions • Separation of concerns• Each state i...
Motivation• Improve the design of the GameStats  application • Make it easier to understand at a glance • Make it easier t...
ConclusionThe State Design Patternmade complex code easierto understand and modify
Questions?The State Design Patternmade complex code easierto understand and modify
Upcoming SlideShare
Loading in …5
×

Refactoring to the State Design Pattern

1,194 views

Published on

Presentation for graduate design patterns term paper, December 2009.

Published in: Technology
  • Be the first to comment

Refactoring to the State Design Pattern

  1. 1. Refactoring to theState Design Pattern Jim Roepcke <jr@uvic.ca> CSC 578D Fall 2009
  2. 2. Motivation• Improve the design of the GameStats application • Make it easier to understand at a glance • Make it easier to make improvements
  3. 3. Backup BackupReplica Replica Primary ReplicaBackup BackupReplica Replica
  4. 4. Primary-Backup• Bonjour can guarantee no more than one Primary replica can publish its service• Multiple Backup replicas communicate with the Primary to keep all replicas synchronized• Writes are made to the Primary and propagated back to the Backup replicas• If the Primary fails, a Backup can take over. The remaining backups sync to the new Primary
  5. 5. failed to initial become primary start primary failed to start trying to trying to become connect to primary primary backup did startprimary did start backup failed to start failed to primary connect to backup primary stop stop stopping stopping error primary backup primary did stop backup did stop stopped
  6. 6. Context StateRequest() Handle() state->Handle() ConcreteStateA ConcreteStateB Handle() Handle()
  7. 7. State Context TransitionTo(Context c, State s) Enter(Context c)SetState(State s) Leave(Context c)Foo()Bar() Foo(StateContext c)Baz() Bar(StateContext c).... Baz(StateContext c) ... state->Foo() BaseState this->Leave(c) TransitionTo(Context c, State s) c->SetState(s) Enter(Context c) s->Enter(c) Leave(Context c) Foo(StateContext c) Bar(StateContext c) Baz(StateContext c) ... ConcreteStateA ConcreteStateB TransitionTo Enter(Context c) Foo(StateContext c) (c, ConcreteStateA) Baz(StateContext c) Bar(StateContext c)
  8. 8. primary stopstopping errorprimary primary did stop stopped
  9. 9. Original Code - (void) stop{! if (self.state == GSGameControllerStatePrimary) {! ! self.state = GSGameControllerStateStopping;! ! [self uninstallServerTargets];! ! [_server stop];! ! self.state = GSGameControllerStateStopped;! } else if (self.state == GSGameControllerStateBackup) {! ! self.state = GSGameControllerStateStopping;! ! [_memberManager stopMonitoring:_primaryService];! ! [_clientToPrimary stop];! ! self.state = GSGameControllerStateStopped;! } else if (self.state != GSGameControllerStateStopped) {! ! self.state = GSGameControllerStateError;! }}
  10. 10. First Refactoring - (oneway void) stop{ self.state = GSGameControllerStateStopping;}
  11. 11. The Devil (is in the details) - (void) setState: (GSGameControllerState)newState{! GSGameControllerState oldState = _state;! _state = newState;! if ( _state == GSGameControllerStateTryingToFindPrimary) {! ! // [self findPrimary];! ! self.state = GSGameControllerStateTryingToBecomePrimary; // TODO: remove the findprimary state! } else if (_state == GSGameControllerStateTryingToBecomePrimary) {! ! [self startPrimaryServer];! } else if (_state == GSGameControllerStateFailedToBecomePrimary) {! ! [self tearDownPrimary];! ! // FIXME: this could be an endless loop of failing to become primary, put in a limit or something! ! self.state = GSGameControllerStateTryingToBecomePrimary;! } else if (_state == GSGameControllerStateTryingToConnectToPrimary) {! ! [self connectToPrimary];! } else if (_state == GSGameControllerStateFailedToConnectToPrimary) {! ! [self tearDownBackup];! ! self.state = GSGameControllerStateTryingToBecomePrimary;! } else if (_state == GSGameControllerStatePrimary) {! ! [self installServerTargets];! ! [self tellDelegate:_delegate performSelectorWithSelf:@selector(gameControllerDidBecomePrimary:)];! } else if (_state == GSGameControllerStateBackup) {! ! [self tellPrimaryWhoIAm];! ! [self monitorPrimary];! ! [self synchronizeWithPrimaryFromVersion:_game.version];! ! [self tellDelegate:_delegate performSelectorWithSelf:@selector(gameControllerDidBecomeBackup:)];! } else if (_state == GSGameControllerStateStopping) {! ! if (oldState == GSGameControllerStatePrimary) {! ! ! self.state = GSGameControllerStateStoppingPrimary;! ! } else if (oldState == GSGameControllerStateBackup) {! ! ! self.state = GSGameControllerStateStoppingBackup;
  12. 12. The Devil (is in the details) ! ! } else if (oldState != GSGameControllerStateError) {! ! ! self.state = GSGameControllerStateStopped;! ! }! } else if (_state == GSGameControllerStateStoppingPrimary) {! ! [self stopServicingBackups];! ! [_server stop];! ! // TODO: actually monitor the stop instead of just setting state to GSGameControllerStateStopped! ! self.state = GSGameControllerStateStopped;! } else if (_state == GSGameControllerStateStoppingBackup) {! ! [self stopMonitoringPrimary];! ! [_clientToPrimary stop];! ! // TODO: actually monitor the stop instead of just setting state to GSGameControllerStateStopped! ! self.state = GSGameControllerStateStopped;! } else if (_state == GSGameControllerStateStopped) {! ! if (oldState == GSGameControllerStatePrimary) {! ! ! [self tearDownPrimary];! ! } else ! if (oldState == GSGameControllerStateStoppingPrimary) {! ! ! [self tearDownPrimary];! ! } else if (oldState == GSGameControllerStateBackup) {! ! ! [self tearDownBackup];! ! } else if (oldState == GSGameControllerStateStoppingBackup) {! ! ! [self tearDownBackup];! ! }! ! [self tellDelegate:_delegate performSelectorWithSelf:@selector(gameControllerDidStop:)];! } else if (_state == GSGameControllerStateError) {! ! if (oldState == GSGameControllerStateTryingToFindPrimary) {! ! ! [self tearDownPrimary];! ! } else if (oldState == GSGameControllerStateTryingToBecomePrimary) {! ! ! [self tearDownPrimary];! ! ! [self tellDelegate:_delegate performSelectorWithSelf:@selector(gameControllerFailedToStart:)];! ! } else if (oldState == GSGameControllerStateTryingToConnectToPrimary) {
  13. 13. The Devil (is in the details) ! ! ! [self tearDownBackup];! ! ! [self tellDelegate:_delegate performSelectorWithSelf:@selector(gameControllerFailedToStart:)];! ! } else if (oldState == GSGameControllerStatePrimary) {! ! ! [self tearDownPrimary];! ! ! // TODO: should this tellDelegate gameControllerDidStop: ?! ! } else if (oldState == GSGameControllerStateBackup) {! ! ! [self tearDownBackup];! ! ! // TODO: should this tellDelegate gameControllerDidStop: ?! ! } else if (oldState == GSGameControllerStateStopping) {! ! ! [self tellDelegate:_delegate performSelectorWithSelf:@selector(gameControllerFailedToStop:)];! ! ! // TODO: should this tellDelegate gameControllerDidStop: ?! ! } else if (oldState == GSGameControllerStateStoppingPrimary) {! ! ! [self tearDownPrimary];! ! ! // TODO: should this tellDelegate gameControllerDidStop: ?! ! } else if (oldState == GSGameControllerStateStoppingBackup) {! ! ! [self tearDownBackup];! ! ! // TODO: should this tellDelegate gameControllerDidStop: ?! ! }! ! /* else ??? */ [self tellDelegate:_delegateperformSelectorWithSelf:@selector(gameControllerErrorOccurred:)];! }}
  14. 14. First Refactoring - (oneway void) stop { self.state = GSGameControllerStateStopping; }- (void) setState: (GSGameControllerState)newState{! GSGameControllerState oldState = _state;! _state = newState;
  15. 15. } else if (_state == GSGameControllerStateStopping) { if (oldState == GSGameControllerStatePrimary) { self.state = GSGameControllerStateStoppingPrimary; } else if (oldState == GSGameControllerStateBackup) { self.state = GSGameControllerStateStoppingBackup; } else if (oldState != GSGameControllerStateError) { self.state = GSGameControllerStateStopped; }}
  16. 16. } else if (_state == GSGameControllerStateStoppingPrimary) { [self stopServicingBackups]; [_server stop]; self.state = GSGameControllerStateStopped;}
  17. 17. } else if (_state == GSGameControllerStateStopped) { if (oldState == GSGameControllerStatePrimary) { [self tearDownPrimary]; } else if (oldState == GSGameControllerStateStoppingPrimary) { [self tearDownPrimary]; } else if (oldState == GSGameControllerStateBackup) { [self tearDownBackup]; } else if (oldState == GSGameControllerStateStoppingBackup) { [self tearDownBackup]; } [self tellDelegate:_delegate performSelectorWithSelf: @selector(gameControllerDidStop:)];}
  18. 18. State Refactoring - (oneway void) stop{ [self.state stop: self];}
  19. 19. Stopping a primary@implementation GSGameControllerStatePrimary- (void) enter: (GSGameController *)gc{! [gc installServerTargets];}- (void) error: (GSGameController *)gc{! [gc tearDownPrimary];! [super error: gc];}- (void) incrementIntegerForKey: (id)aKey context: (GSGameController *)gc{! [gc primaryIncrementIntegerForKey: aKey];}- (void) stop: (GSGameController *)gc{! [self transition: gc to: [GSGameControllerStateStoppingPrimary state]];}@end
  20. 20. Only I know how@implementation GSGameControllerStateStoppingPrimary- (void) enter: (GSGameController *)gc{! [gc stopServicingBackups];}- (void) error: (GSGameController *)gc{! [gc tearDownPrimary];! [super error: gc];}- (void) primaryDidStop: (GSGameController *)gc{! [gc tearDownPrimary];! [self transition: gc to: [GSGameControllerStateStopped state]];}@end
  21. 21. Transition to stopped@implementation GSGameControllerStateStoppingPrimary- (void) enter: (GSGameController *)gc{! [gc stopServicingBackups];}- (void) error: (GSGameController *)gc{! [gc tearDownPrimary];! [super error: gc];}- (void) primaryDidStop: (GSGameController *)gc{! [gc tearDownPrimary];! [self transition: gc to: [GSGameControllerStateStopped state]];}@end
  22. 22. Done@implementation GSGameControllerStateStopped- (void) enter: (GSGameController *)gc{ [gc tellDelegate:gc.delegate performSelectorWithSelf: @selector(gameControllerDidStop:)];}@end
  23. 23. Same result on stop} else if (_state == GSGameControllerStateStopped) { if (oldState == GSGameControllerStatePrimary) { [self tearDownPrimary]; } else if (oldState == GSGameControllerStateStoppingPrimary) { [self tearDownPrimary]; } else if (oldState == GSGameControllerStateBackup) { [self tearDownBackup]; } else if (oldState == GSGameControllerStateStoppingBackup) { [self tearDownBackup]; } [self tellDelegate:_delegate performSelectorWithSelf: @selector(gameControllerDidStop:)];}
  24. 24. Result• GSGameController only knows its own operations • Not states or transitions • Separation of concerns• Each state is a black box • Doesn’t know details of other states
  25. 25. Motivation• Improve the design of the GameStats application • Make it easier to understand at a glance • Make it easier to make improvements
  26. 26. ConclusionThe State Design Patternmade complex code easierto understand and modify
  27. 27. Questions?The State Design Patternmade complex code easierto understand and modify

×