Refactoring to the State Design Pattern

27
Refactoring to the State Design Pattern Jim Roepcke <[email protected]> CSC 578D Fall 2009

description

Presentation for graduate design patterns term paper, December 2009.

Transcript of Refactoring to the State Design Pattern

Page 1: Refactoring to the State Design Pattern

Refactoring to theState Design Pattern

Jim Roepcke <[email protected]>CSC 578D Fall 2009

Page 2: Refactoring to the State Design Pattern

Motivation

• Improve the design of the GameStats application

• Make it easier to understand at a glance

• Make it easier to make improvements

Page 3: Refactoring to the State Design Pattern

PrimaryReplica

Backup

Replica

Backup

Replica

Backup

Replica

Backup

Replica

Page 4: Refactoring to the State Design Pattern

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

Page 5: Refactoring to the State Design Pattern

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

Page 6: Refactoring to the State Design Pattern

Request()

Context

state->Handle()

Handle()

State

Handle()

ConcreteStateA

Handle()

ConcreteStateB

Page 7: Refactoring to the State Design Pattern

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)

Page 8: Refactoring to the State Design Pattern

primary

stopping primary

stopped

error

stop

primary did stop

Page 9: Refactoring to the State Design Pattern

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;! }}

Page 10: Refactoring to the State Design Pattern

First Refactoring- (oneway void) stop

{ self.state = GSGameControllerStateStopping;}

Page 11: Refactoring to the State Design Pattern

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;

Page 12: Refactoring to the State Design Pattern

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) {

Page 13: Refactoring to the State Design Pattern

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:)];! }}

Page 14: Refactoring to the State Design Pattern

First Refactoring

- (oneway void) stop { self.state = GSGameControllerStateStopping; }

- (void) setState: (GSGameControllerState)newState{! GSGameControllerState oldState = _state;! _state = newState;

Page 15: Refactoring to the State Design Pattern

} else if (_state == GSGameControllerStateStopping) { if (oldState == GSGameControllerStatePrimary) { self.state = GSGameControllerStateStoppingPrimary; } else if (oldState == GSGameControllerStateBackup) { self.state = GSGameControllerStateStoppingBackup; } else if (oldState != GSGameControllerStateError) { self.state = GSGameControllerStateStopped; }}

Page 16: Refactoring to the State Design Pattern

} else if (_state == GSGameControllerStateStoppingPrimary) { [self stopServicingBackups]; [_server stop]; self.state = GSGameControllerStateStopped;}

Page 17: Refactoring to the State Design Pattern

} 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:)];}

Page 18: Refactoring to the State Design Pattern

State Refactoring

- (oneway void) stop{ [self.state stop: self];}

Page 19: Refactoring to the State Design Pattern

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

Page 20: Refactoring to the State Design Pattern

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

Page 21: Refactoring to the State Design Pattern

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

Page 22: Refactoring to the State Design Pattern

Done@implementation GSGameControllerStateStopped

- (void) enter: (GSGameController *)gc{ [gc tellDelegate:gc.delegate performSelectorWithSelf: @selector(gameControllerDidStop:)];}

@end

Page 23: Refactoring to the State Design Pattern

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:)];}

Page 24: Refactoring to the State Design Pattern

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

Page 25: Refactoring to the State Design Pattern

Motivation

• Improve the design of the GameStats application

• Make it easier to understand at a glance

• Make it easier to make improvements

Page 26: Refactoring to the State Design Pattern

Conclusion

The State Design Pattern made complex code easier to understand and modify

Page 27: Refactoring to the State Design Pattern

Questions?

The State Design Pattern made complex code easier to understand and modify