22 Ağustos 2017 Salı

GoF - State Örüntüsü (Durum Makinesi)

Not : GoF Tasarım Örüntüleri yazısına bakabilirsiniz.

Örnek
IActivityHandler, state'e IActivityEvent gelince çalışacak kodu belirtir. State değişmez. Şöyle yaparız
public interface IActivityHandler<T extends IActivityEvent>{

  void perform (T event, IEventQueue queue);
}
IStateChangeHandler, state'in ITransitionEvent gelince state girişinde ve çıkışında çalışacak kodu belirtir. Şöyle yaparız
public interface IStateChangeHandler{

  void onEnter (State fromState, State totate, IEvent event, IEventQueue queue);

  void onExit (State fromState, State toState, IEvent event, IEventQueue queue);

}
ITransitionAction transition çalışınca çalışacak kodu belirtir. Şöyle yaparız
public interface ITransitionAction{

  void perform (Transition source, IEvent event);
}
Kuyruk için şöyle yaparız
public class FSMEventQueue : IEventQueue {

  private Queue<IEvent> queue = new LinkedList<>();

  private FSM fsm;


  @Override
  public void postEvent(IEvent event){
    eventQueue.offer(event);
    processEvents();
  }


  protected void processEvents(){
    while(!eventQueue.isEmpty()){
      IEvent event = eventQueue.poll();
      fsm.handleEvent(event);
    }
  }
}
FSM için şöyle yaparız
public class FSM{

  private IEventQueue queue = new FSMEventQueue();

  private Map<String, State> states = new LinkedHashMap();

  private State currentState;


  public void addState(State state){
    states.put(state.getIdentifier(),state);
  }


  //Bu kod aslında başka bir yardımcı sınıfta da olabilirdi
  public addTransition (State fromState, State toState, Object eventType,
    ITransitionAction action){
    Transition transition = new Transition (fromState,toState,eventType,action))
    fromState.addTransiton(transition);
  }


  public void handleEvent(IEvent event){

    if (event instanceof IActivityEvent){
      ActivityEvent activityEvent = (ActivityEvent)event;
      currentState.handleActivityEvent(activityEvent,queue);

    } else if (event instanceof ITransitionEvent){

      ITransitionEvent transitionEvent = ITransitionEvent)event;
      Object eventType = transitionEvent.getEventType();
      Transition transition = currentState.getTransition(eventType);
      currentState = transition.performTransition(transitionEvent,queue);

    }
  }
}
State için şöyle yaparız. eventType olarak Object kullanılıyor. Mesela eğer istersek hep String geçebiliriz.
public class State{

  private HashMap<Object,Transition> transition = new HashMap<>();

  private IActivityHandler activityHandler;

  private IStateChangeHandler stateChangeHandler;

  public void addTransition(Transition transition, Object eventType){
    transitions.put(eventType,transition);
  }


  public Transiton getTransition(Object eventType){
    return transitions.get(eventType);
  }


  public handleActivityEvent(IActivityEvent event, IEventQueue queue){
    activityHandler.handleActivity(event,queue);
  }

  public void onEnter(State fromState, IEvent event,IEventQueue queue){
    stateChangeHandler.onEnter(fromState,this,event,queue);
  }


  public void onExit(State toState, IEvent event,IEventQueue queue){
    stateChangeHandler.onEnter(this,toState,event,queue);
  }
}
Transition için şöyle yaparız
public class Transition{

  State fromState;

  State toState;

  Object eventType;

  ITransitionAction transitionAction;

  public State performTransition(ITransitionEvent event, IEventQueue queue){
    fromState.onExit(toState,event,queue);
    transitionAction.perform(event);
    toState.onEnter(fromState,event,queue);
    return toState;
  }
}
State - Davranışsal Örüntü
Tasarım örüntülerinde gösterilen state machine aslında Moore Machine, çünkü he bir state'in ismi var. State örüntüsünü kullanmadan önce aşağıdaki örnekte görüldüğü gibi bir modemi kontrol etmek için switch/case'ler kullanılabilir. Ancak bir miktar global değişken kullanmak gerekebilir.
switch(stage){
   case Power:
      powerOn();                 // Turn the modem on
      nextstage = ResetCmd;      // Go perform a reset
      attemptsLeft = 5;          // Send the reset command up to five times
      break;
   case ResetCmd;
      modem.write("ATZ\n");      // ATZ - reset
      attemptsLeft--;            // Use one of our attempts
      nextstage = ResetReply;    // Next wait for a response (should be OK)
      timeout = millis() + 5000; // Wait for up to 5 seconds each attempt
      break;
   case ResetReply;
      if(receivedResponse() == OK)     // Success
      {
         nextStage = NetworkAttachCmd; // Attach to the cellular network
      } elseif(receivedResponse() == ERROR || timeout < millis())
      {  // If we get an error or timeout, reattempt if we can, power on if we can't
         if(attemptsLeft > 0)
         {
            nextStage = ResetCmd;
         } else {
            nextStage = Power;
         }
      } 
      break;
   case NetworkAttachCmd:
   ...
}
stage = nextstage;        
Kod karmaşıklaştıkça global değişkenlerden kurtulmak ve daha anlaşılır hale getirmek için bu örüntüye ihtiyaç duyuluyor.

Current State
State örüntüsünde içinde bulunan state kutusuna "current state" ismi verilir.

State Metodları
Bir state OnEnter (), Process (), OnExit gibi metodlarsa sahip olabilir. Bu metodlar state'in hangi transition yolunu kullanarak değiştiğine bakmaksızın çalışır. Yani A'dan B'ye geçiş veya A'dan C'ye geçiş farketmez.
interface IState {
  void OnEnter
  void Process
  StateEnum OnExit
}

StateManager::Initialize (){
  stateList.Add (new InitialState (), INITIAL_STATE);
  stateList.Add (new AwaitingState (), AWAITING_STATE);
  ...
  currentState = stateList [ INITIAL_STATE ];

}

StateManager::Run () {
  currentState -> OnEnter (); // Do something
  currentState -> Process ();  //Do actual work
  SetNextState (currentState -> OnExit ()); // Decide which state is next
}

StateManager::SetNextState (StateEnum value) {
  currentState = stateList [ value ];
}
Bir State'ten Diğerine Geçmek - Transition
Bir state'ten diğerine geçmek için harici (external) veya dahili (internal) bir olayın (event) olması gerekir.

Bazı daha karmaşık State örüntüsünden bir state'ten birden fazla state'e geçiş olabiliyor. Bu durumda state kendi içinde bir Transition listesi tutar. Transition sınıfı şöyledir
class Transition {
  State FromState;
  State ToState;
}
StateManager şöyle yapar.
StateManager::Run (IEvent event) {
  Transition transition = currentState->GetTransition (event);
  currentState = transition.transit (event)
}
Transition çıktığı state'in OnExit(), girdiği state'in OnEnter() metodunu çağırır. Ayrıca Transition perform() metoduna da sahip olabilir. Böylece sadece belli bir yolu izlerken bazı işler yapılabilir. Yani sadece A'dan B'ye geçiş durumnda bir kod çalıştırabiliriz. A'dan C'ye geçişte ise çalıştırmayız.

StateEventQueue
Bazen state geçişleri olan event'lerin başka bir thread'den gelmesi ve asenkron olarak işlenmesi gerekir. Bu durumda event'ler bir kuyruğa konulur ve bu kuyruğu bir thread boşaltıp StateManager'ı çağırır.

Harici Olay
Harici olay dışarıdan gelen bir girdidir. Harici olay current state'e StateManager tarafından iletilir. Harici olayın bir event hiyerarşisinden türemesi iyi bir tasarımdır. Böylece her olay için state arayüzü değişmez. Current State olayı işledikten sonra bir sonraki state'i StateManager'a döndürür. Buraya kadar olan kısım kolay.

Dahili Olay
Dahili geçiş ise genelde bir timer aracılığıyla olur. State bir işlem başlatır ve beklemeye başlar. Bekleme süresi sonunda istenilen netice elde edilmemişse, bir başka state'e geçmek gerekir. İşte bu durumda State kendisini içeren StateManager veya StateContext isimli sınıfa çağrı yapar ve bir sonraki geçilmesi gereken state'i bildirir.

Genel Transition Kararları
Gelen girdiye göre içinde bulunulan state bir sonraki state'e karar verir. Bazen aksiyon state'lerinden önce READY state'leri koymak faydalı olabilir. Böylece gerekli mantık kontrollerin yapıldığından emin olunur.


STOPPED -> READY_TO_WASH -> WASHING

Non-deterministic State Machine
Eğer bir state'ten aynı girdi ile birden fazla state'e geçilebiliyorsa, bu örüntüye non-deterministic state machine adı verilir.

Karnaugh Map
Karnaugh Map yani K-Map state geçişlerinde belki işe yarayabilir.

Hiç yorum yok:

Yorum Gönder