venerdì 18 gennaio 2013

Extreme Design: una piccola provocazione (1° parte)

Gli oggetti dovrebbero interagire tramite messaggi: normalmente per avere un feedback da un oggetto, si scrive una funzione che restituisce un valore. Per ovviare a questa strada, facciamo una analogia con il meccanismo di comunicazione che avviene tra i neuroni. Quando due neuroni vogliono comunicare si collegano tramite una giunzione (sinapsi), in questo caso il neurone A invia un impulso chimico/elettrico al neurone B.

Proviamo a seguire lo stesso schema e cerchiamo di sviluppare una soluzione per gestire il conto corrente di un ipotetico cliente di una banca. L'idea è che un operatore allo sportello potrà fare le operazioni di: saldo, lista movimenti, prelievo, deposito. Per rendere la cosa più divertente dobbiamo seguire le seguenti regole:
  • tutti i metodi sono void
  • il minor numero di parametri per metodo
  • meno if possibili
  • tutto il codice deve poter essere messo sotto test
  • l'aggiunta di una nuova operazione non deve essere invasiva
  • le operazioni da eseguire devono essere isolate nel contesto di utilizzo
  • minima dipendenza tra oggetti
Dobbiamo realizzare una console application che riceve in input le operazioni da svolgere e deve restituire il feedback per l'operazione richiesta.

Il risultato finale potrebbe assomigliare a:

   public class Program
   {
      public Program(IOperations operations)
      {
         operations.Display();
      }

      private static void Main()
      {
         var account = new Account();
         var view = new View();
         var operations = new IOperation[]
            {
               new Balance(account, view),
               new Deposit(account, view),
               new Withdrawal(account,view),
               new Movements(account,view)
            };
         new Program(new Operations(view, operations));
      }
   }

vediamo come realizzare l'oggetto Operations:

   public class Operations : IOperations, IOperationsInput
   {
      private readonly IViewOperations view;
      private readonly IOperation[] operations;

      public Operations(IViewOperations view, IOperation[] operations)
      {
         this.view = view;
         this.operations = operations;
      }

      public void Display()
      {
         var index = 0;
         foreach (var operation in operations)
            operation.Display(value => 
               view.Response(string.Format("[{0}] - {1}", ++index, value)));
         view.Response("Seleziona l'operazione da eseguire:");
         view.Request(this);
      }

      void IOperationsInput.Return(string value)
      {
         if (value.ToLower() == "q")
            return;
         int index;
         if (int.TryParse(value, out index) && IsAvailableOperation(index))
         {
            operations[index - 1].Execute();
            Display();
            return;
         }
         view.Response("Operazione selezionata non valida");
         Display();
      }

      private bool IsAvailableOperation(int index)
      {
         return index > 0 && index <= operations.Length;
      }
   }

Operation interagisce con due tipologie di oggetti: IViewOperations e IOperation.

IViewOperations ha la responsabilità di interagire con la fonte dati, nel nostro caso la console utilizzata dall'operatore per gestire le operazioni sul conto corrente.

IOperation ha la responsabilità di eseguire l'operazione sul conto corrente.

Vediamo come è stata implementata la richiesta di selezionare una operazione.

   public interface IViewResponse
   {
      void Response(string output);
   }

   public interface IViewOperations: IViewResponse
   {
      void Request(IOperationsInput operations);
   }

   public class ViewIViewOperations
   {
      void IViewResponse.Response(string output)
      {
         Console.WriteLine(output);
      }

      void  IViewOperations.OperationRequest(IOperationsInput operations)
      {
         operations.Return(Console.ReadLine());
      }
   }

View ha tutti i metodi void e solo un parametro in ingresso. Notate come sono state isolate le varie operazioni tramite interfacce specifiche, questo comporta che OperationRequest è disponibile solo quando View è trattato come IViewOperations. Per evitare di restituire un valore di ritorno mentre chiamiamo OperationRequest, passiamo un oggetto che implementa IOperationsResponse:

   public interface IOperationsResponse
   {
      void Return(string value);
   }

Abbiamo isolato le operazioni disponibili nel cotesto di utilizzo, l'oggetto Operations implementa in modo esplicito IOperationsResponse, rendendo disponibile il metodo Return solo quando viene usato da IView.OperationRequest.

Analizziamo lo stack:

Operations.IOperationsInput.Return(string value)
View.IViewOperations.Request(IOperationsInput operations)
Operations.Display()

Come vedete Operations richiede alla View quale operazione eseguire la View invia all'oggetto Operations qual è stata l'operazione selezionata dall'utente.
View e Operations stanno interagendo tramite messaggi isolando le responsabilità nei metodi di competenza. Tutti i metodi possono essere messi direttamente sotto test.

Nessun commento:

Posta un commento