Jun 26, 2015
Refactoring to theState Design Pattern
Jim Roepcke <[email protected]>CSC 578D Fall 2009
Motivation
• Improve the design of the GameStats application
• Make it easier to understand at a glance
• Make it easier to make improvements
PrimaryReplica
Backup
Replica
Backup
Replica
Backup
Replica
Backup
Replica
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
initial
trying to become primary
start
primary
failed to become primary
primary did start
primary failed to start
trying to connect to
primary
failed to connect to
primarybackup
stopping primary
stopping backup
stopped
error
stop stop
primary did stop backup did stop
backup did startbackup failed to start
Request()
Context
state->Handle()
Handle()
State
Handle()
ConcreteStateA
Handle()
ConcreteStateB
SetState(State s)Foo()Bar()Baz()....
Context
state->Foo()
TransitionTo(Context c, State s)Enter(Context c)Leave(Context c)
Foo(StateContext c)Bar(StateContext c)Baz(StateContext c)...
State
TransitionTo(Context c, State s)Enter(Context c)Leave(Context c)
Foo(StateContext c)Bar(StateContext c)Baz(StateContext c)...
BaseState
Enter(Context c)Baz(StateContext c)
ConcreteStateA
Foo(StateContext c)Bar(StateContext c)
ConcreteStateB
this->Leave(c)c->SetState(s)s->Enter(c)
TransitionTo(c, ConcreteStateA)
primary
stopping primary
stopped
error
stop
primary did stop
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;! }}
First Refactoring- (oneway void) stop
{ self.state = GSGameControllerStateStopping;}
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;
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) {
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:_delegate performSelectorWithSelf:@selector(gameControllerErrorOccurred:)];! }}
First Refactoring
- (oneway void) stop { self.state = GSGameControllerStateStopping; }
- (void) setState: (GSGameControllerState)newState{! GSGameControllerState oldState = _state;! _state = newState;
} else if (_state == GSGameControllerStateStopping) { if (oldState == GSGameControllerStatePrimary) { self.state = GSGameControllerStateStoppingPrimary; } else if (oldState == GSGameControllerStateBackup) { self.state = GSGameControllerStateStoppingBackup; } else if (oldState != GSGameControllerStateError) { self.state = GSGameControllerStateStopped; }}
} else if (_state == GSGameControllerStateStoppingPrimary) { [self stopServicingBackups]; [_server stop]; 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:)];}
State Refactoring
- (oneway void) stop{ [self.state stop: self];}
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
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
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
Done@implementation GSGameControllerStateStopped
- (void) enter: (GSGameController *)gc{ [gc tellDelegate:gc.delegate performSelectorWithSelf: @selector(gameControllerDidStop:)];}
@end
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:)];}
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
Motivation
• Improve the design of the GameStats application
• Make it easier to understand at a glance
• Make it easier to make improvements
Conclusion
The State Design Pattern made complex code easier to understand and modify
Questions?
The State Design Pattern made complex code easier to understand and modify