SlideShare a Scribd company logo
Refactoring to the
State 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 to make improvements
Backup              Backup
Replica             Replica


          Primary
          Replica

Backup              Backup
Replica             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
failed to
     initial                                                                        become
                                                                                    primary


     start                        primary failed to start


    trying to                                                                       trying to
    become                                                                         connect to
    primary                                                                         primary


                                                                                 backup did start
primary 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
Context                             State

Request()                        Handle()




    state->Handle()
                      ConcreteStateA        ConcreteStateB

                      Handle()              Handle()
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)
primary



  stop



stopping
                               error
primary



           primary did stop



                              stopped
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

More Related Content

PDF
Storytelling For The Web: Integrate Storytelling in your Design Process
Chiara Aliotta
 
PDF
Artificial Intelligence, Data and Competition – SCHREPEL – June 2024 OECD dis...
OECD Directorate for Financial and Enterprise Affairs
 
PDF
2024 Trend Updates: What Really Works In SEO & Content Marketing
Search Engine Journal
 
PDF
Get into the FLOW with Extbase
Jochen Rau
 
PDF
Functional Programming Patterns (NDC London 2014)
Scott Wlaschin
 
PDF
Introduction to MapReduce using Disco
Jim Roepcke
 
PDF
Comparing implementations of the actor model
Jim Roepcke
 
PDF
Introduction to Python for Plone developers
Jim Roepcke
 
Storytelling For The Web: Integrate Storytelling in your Design Process
Chiara Aliotta
 
Artificial Intelligence, Data and Competition – SCHREPEL – June 2024 OECD dis...
OECD Directorate for Financial and Enterprise Affairs
 
2024 Trend Updates: What Really Works In SEO & Content Marketing
Search Engine Journal
 
Get into the FLOW with Extbase
Jochen Rau
 
Functional Programming Patterns (NDC London 2014)
Scott Wlaschin
 
Introduction to MapReduce using Disco
Jim Roepcke
 
Comparing implementations of the actor model
Jim Roepcke
 
Introduction to Python for Plone developers
Jim Roepcke
 

Recently uploaded (20)

PDF
Research-Fundamentals-and-Topic-Development.pdf
ayesha butalia
 
PDF
AI Unleashed - Shaping the Future -Starting Today - AIOUG Yatra 2025 - For Co...
Sandesh Rao
 
PDF
Data_Analytics_vs_Data_Science_vs_BI_by_CA_Suvidha_Chaplot.pdf
CA Suvidha Chaplot
 
PDF
Unlocking the Future- AI Agents Meet Oracle Database 23ai - AIOUG Yatra 2025.pdf
Sandesh Rao
 
PDF
How Open Source Changed My Career by abdelrahman ismail
a0m0rajab1
 
PDF
Economic Impact of Data Centres to the Malaysian Economy
flintglobalapac
 
PDF
Peak of Data & AI Encore - Real-Time Insights & Scalable Editing with ArcGIS
Safe Software
 
PDF
Responsible AI and AI Ethics - By Sylvester Ebhonu
Sylvester Ebhonu
 
PPTX
The Future of AI & Machine Learning.pptx
pritsen4700
 
PDF
GDG Cloud Munich - Intro - Luiz Carneiro - #BuildWithAI - July - Abdel.pdf
Luiz Carneiro
 
PDF
Automating ArcGIS Content Discovery with FME: A Real World Use Case
Safe Software
 
PPTX
OA presentation.pptx OA presentation.pptx
pateldhruv002338
 
PPTX
AI in Daily Life: How Artificial Intelligence Helps Us Every Day
vanshrpatil7
 
PDF
AI-Cloud-Business-Management-Platforms-The-Key-to-Efficiency-Growth.pdf
Artjoker Software Development Company
 
PDF
CIFDAQ's Market Wrap : Bears Back in Control?
CIFDAQ
 
PPTX
What-is-the-World-Wide-Web -- Introduction
tonifi9488
 
PDF
Brief History of Internet - Early Days of Internet
sutharharshit158
 
PPTX
IT Runs Better with ThousandEyes AI-driven Assurance
ThousandEyes
 
PDF
The Future of Artificial Intelligence (AI)
Mukul
 
PDF
Google I/O Extended 2025 Baku - all ppts
HusseinMalikMammadli
 
Research-Fundamentals-and-Topic-Development.pdf
ayesha butalia
 
AI Unleashed - Shaping the Future -Starting Today - AIOUG Yatra 2025 - For Co...
Sandesh Rao
 
Data_Analytics_vs_Data_Science_vs_BI_by_CA_Suvidha_Chaplot.pdf
CA Suvidha Chaplot
 
Unlocking the Future- AI Agents Meet Oracle Database 23ai - AIOUG Yatra 2025.pdf
Sandesh Rao
 
How Open Source Changed My Career by abdelrahman ismail
a0m0rajab1
 
Economic Impact of Data Centres to the Malaysian Economy
flintglobalapac
 
Peak of Data & AI Encore - Real-Time Insights & Scalable Editing with ArcGIS
Safe Software
 
Responsible AI and AI Ethics - By Sylvester Ebhonu
Sylvester Ebhonu
 
The Future of AI & Machine Learning.pptx
pritsen4700
 
GDG Cloud Munich - Intro - Luiz Carneiro - #BuildWithAI - July - Abdel.pdf
Luiz Carneiro
 
Automating ArcGIS Content Discovery with FME: A Real World Use Case
Safe Software
 
OA presentation.pptx OA presentation.pptx
pateldhruv002338
 
AI in Daily Life: How Artificial Intelligence Helps Us Every Day
vanshrpatil7
 
AI-Cloud-Business-Management-Platforms-The-Key-to-Efficiency-Growth.pdf
Artjoker Software Development Company
 
CIFDAQ's Market Wrap : Bears Back in Control?
CIFDAQ
 
What-is-the-World-Wide-Web -- Introduction
tonifi9488
 
Brief History of Internet - Early Days of Internet
sutharharshit158
 
IT Runs Better with ThousandEyes AI-driven Assurance
ThousandEyes
 
The Future of Artificial Intelligence (AI)
Mukul
 
Google I/O Extended 2025 Baku - all ppts
HusseinMalikMammadli
 
Ad
Ad

Refactoring to the State Design Pattern

  • 1. Refactoring to the State Design Pattern Jim Roepcke <[email protected]> CSC 578D Fall 2009
  • 2. Motivation • Improve the design of the GameStats application • Make it easier to understand at a glance • Make it easier to make improvements
  • 3. Backup Backup Replica Replica Primary Replica Backup Backup Replica Replica
  • 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. failed to initial become primary start primary failed to start trying to trying to become connect to primary primary backup did start primary 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. Context State Request() Handle() state->Handle() ConcreteStateA ConcreteStateB Handle() Handle()
  • 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. primary stop stopping error primary primary did stop stopped
  • 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. First Refactoring - (oneway void) stop { self.state = GSGameControllerStateStopping; }
  • 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. 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. 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:)]; ! } }
  • 14. First Refactoring - (oneway void) stop { self.state = GSGameControllerStateStopping; } - (void) setState: (GSGameControllerState)newState { ! GSGameControllerState oldState = _state; ! _state = newState;
  • 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. } else if (_state == GSGameControllerStateStoppingPrimary) { [self stopServicingBackups]; [_server stop]; self.state = GSGameControllerStateStopped; }
  • 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. State Refactoring - (oneway void) stop { [self.state stop: self]; }
  • 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. 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. 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. Done @implementation GSGameControllerStateStopped - (void) enter: (GSGameController *)gc { [gc tellDelegate:gc.delegate performSelectorWithSelf: @selector(gameControllerDidStop:)]; } @end
  • 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. 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. Motivation • Improve the design of the GameStats application • Make it easier to understand at a glance • Make it easier to make improvements
  • 26. Conclusion The State Design Pattern made complex code easier to understand and modify
  • 27. Questions? The State Design Pattern made complex code easier to understand and modify