SpazioCodice

Codice senza IF: State Design Pattern

In questo post descriviamo un esempio di implementazione del State Design Pattern.

Contesto

Come punto di partenza c’è un’interfaccia chiamata IService, che rappresenta un servizio generico. L’interfaccia offre tre metodi: start(), stop() e isRunning().
				
					public interface IService {
   void start(); 
   void stop();  
   boolean isRunning(); 
}
				
			

Questa è un’implementazione:

				
					public class ServiceImpl implements IService {
   private volatile boolean started;
   private Thread thread;
   
   @Override
   public synchronized void start() { 
      if (started) {
         return;
      }
      
      started = true;
      
      thread = ... // Create a daemon thread instance
      thread.setDaemon(true);
      thread.start();
   }
   
   @Override
   public synchronized void stop() {      
      if (!started) {
         return;
      }
      started = false;
      thread.interrupt();
      
      try {
         thread.join();
      }
      catch (InterruptedException ignore) {  
         ...       
      }
   }
   
   @Override
   public synchronized boolean isRunning() {
      return started;
   }
}
				
			
Il codice sopra funziona… tuttavia, cosa non va bene? Mantenere lo stato del servizio in una variabile booleana e fare affidamento su di essa per sapere se il servizio è in esecuzione oppure no. La logica condizionale all’inizio dei metodi start() e stop(). Un oggetto dovrebbe essere consapevole del proprio stato attuale: se stai guidando un’auto, come fai a sapere che è in movimento? Perché si sta muovendo, perché la stai guidando, non perché hai messo un post-it sul sedile (“Attenzione! È in funzione!”).

State Pattern

Lo State pattern è un behavioral Software Design Pattern. Incapsula lo stato interno dell’oggetto, che può modificare il proprio comportamento quando avviene una transizione. Oltre a rappresentare gli stati possibili di un dato oggetto, ciascun implementatore concreto di State definisce anche le regole per la transizione da uno stato all’altro. Per questo motivo lo State pattern è molto vicino al concetto di finite-state machine. Il seguente codice dell’interfaccia IService è un’implementazione dello State Pattern:
				
					public class IfLessService implements IService {
   private Thread _watcher;
 
   private final IService running = new IService() {
      public boolean isRunning()
      {
         return true; 
      }

      public void start() {
         // Nothing to do here...it is already started.
      }
 
      public void stop()  {
         _watcher.interrupt();      
         try  {
            _watcher.join();
         }
         catch (InterruptedException ignore)
         {         
            ...
         }
        state = notRunning;
      }
   };
   
   private final IService notRunning = new IService()  {      
      
      public boolean isRunning()  {
         return false; 
      }

      public synchronized void start()  {
         _watcher = ...
         _watcher.setDaemon(true);
         _watcher.start();

         // ...make a state change
         // from NOT-RUNNING to RUNNING.
         state = running;
      }

      public void stop() {
         // Nothing to do here...it is already stopped!         
      }      
   };   
   
   // Default initial state is stopped.
   private IService state = notRunning;

   public synchronized void start() {      
      state.start(); 
   }

   public synchronized void stop() {      
      state.stop();    
   }

   public boolean isRunning() {
      return state.isRunning(); 
   }
}
				
			
L’implementazione del servizio delega l’esecuzione dei metodi dell’interfaccia IService allo stato interno (attuale). Ogni possibile State è a sua volta un’implementazione dell’interfaccia IService. Questo è uno scenario “binario”, quindi esistono solo due classi di State: running e notRunning. Nota che la responsabilità di ciascuno State è di gestire il comportamento dell’oggetto proprietario in un determinato momento, ma anche – quando necessario – di gestire una transizione di stato verso un altro State: ad esempio, quando il servizio non è in esecuzione (cioè currentState = notRunning), se chiami il metodo start() ci sarà una transizione di stato (da notRunning a running). Una volta che il servizio è avviato, richiamare il metodo start() non avrà più effetto.

Share this post

Rispondi

Scopri di più da SpazioCodice

Abbonati ora per continuare a leggere e avere accesso all'archivio completo.

Continua a leggere