mercoledì 14 ottobre 2015

IOP vs OOP (if oriented programming vs object oriented programming)



#IAD14

Quante volte avete scritto codice trabbocante di IF? Quante volte per un design errato avete riempito il vostro codice di branch per gestire casi particolari? In questa sessione dimostreremo come è possibile ridurre o concentrare il numero di IF sfruttando le buone pratiche dell’ OOP e dei Design Pattern. 

Grazie per aver partecipato alla sessione.


venerdì 29 novembre 2013

dal TDD al BDD



Durante l'Agile Day 2013 ho tenuto una sessione che 

verte sull'adozione del BDD nello sviluppo/progettazione del software.


Il materiale della sessione "dal TDD al BDD":





Grazie per la partecipazione e il feedback.

lunedì 11 febbraio 2013

Extreme Design: una piccola provocazione (6° parte)

   "Aggiungere una nuova funzionalità non deve essere invasivo"

Come possiamo evitare di violare il seguente principio?

Dobbiamo rispettare un'altro principio:

   "Le operazioni da eseguire devono essere isolate nel contesto di utilizzo"

Avevamo definito il contratto di Account come segue:

   public interface IAccount
   {
      void BalanceRequest(IBalanceResponse balance);
      void Deposit(Amount amount);
      void Withdrawal(Amount amount, IWithdrawalResponse withdrawal);
      void MovementsRequest(IMovementsResponse movements);
   }

Questo implica che Account deve gestire direttamente l'elenco dei movimenti, ma proviamo a separare le responsabilità isolando la funzionalità MovemensRequest in un'altra intefaccia:

   public interface IMovementsAccount
   {
      void MovementsRequest(IMovementsResponse movements);
   }

Ora Account è definito come segue:

   public interface IAccount
   {
      void BalanceRequest(IBalanceResponse balance);
      void Deposit(Amount amount);
      void Withdrawal(Amount amount, IWithdrawalResponse withdrawal);
   }

   public class Account : IAccount
   {
      private Amount current;
      
      public Account()
      {
         current = new Amount(0);
      }

      public void BalanceRequest(IBalanceResponse balance)
      {
         balance.Return(current);
      }

      public void Deposit(Amount amount)
      {
         current = new Amount(current.Value + amount.Value);
      }

      public void Withdrawal(Amount amount, IWithdrawalResponse withdrawal)
      {
         if (current.Value >= amount.Value)
         {
            current = new Amount(current.Value - amount.Value);
            withdrawal.Completed();
            return;
         }
         withdrawal.CreditInsufficient();
      }
   }

Siamo tornati alla versione precedente alla richiesta di gestire i movimenti.
Ora vediamo come progettare un oggetto per gestire i movimenti eseguiti sul conto corrente:

   public class MovementsAccount: Account, IMovementsAccount, IWithdrawalResponse
   {
      private readonly List<IMovementVisit> movements = new List<IMovementVisit>();
      private Amount amountWithdrawal;

      public override void Deposit(Amount amount)
      {
         base.Deposit(amount);
         movements.Add(new DepositMovement(amount));
      }

      public override void Withdrawal(Amount amount, IWithdrawalResponse withdrawal)
      {
         amountWithdrawal = amount;
         base.Withdrawal(amount, this);
      }

      void IMovementsAccount.MovementsRequest(IMovementsResponse movement)
      {
         movement.Return(movements);
      }

      void IWithdrawalResponse.Completed()
      {
         movements.Add(new WithdrawalMovement(amountWithdrawal));
      }

      void IWithdrawalResponse.CreditInsufficient()
      {
      }
}

In questa soluzione, ereditiamo dalla classe Account, facciamo l'override dei metodi Deposit e Withdrawal che sono coinvolti nella fase di registrazione dei movimenti.
Ovviamente abbiamo dovuto modificare la visibilità di Deposit e Withdrawal indicano che sono metodi virtuali. Quindi nella nostra progettazione di Account dovevamo avere una sfera di cristallo per prevedere che in futuro avremmo dovuto gestire i movimenti. Forse a questo punto è bene definire anche BalanceRequest come virtuale? Allora tutti i metodi dovrebbero essere virtuali?

Vediamo come abbiamo gestito la registrazione dei movimenti e poi analizziamo un'altra strada per evitare di creare i metodi virtuali.

Deposit risulta essere molto intuitivo, si deve solo occupare di registrare il movimento di deposito. Invece withdrawal ha una complicazione, registriamo il movimento solo se l'importo richiesto è inferiore al credito residuo. A questo punto creiamo un nuovo oggetto che decora le notifiche di IWithdrawalResponse e notifica all'oggetto richiedente il risultato del prelievo.

   public interface IWithdrawalResponseDispatcher : IWithdrawalResponse
   {
      void SetClients(params IWithdrawalResponse[] inners);
   }

   public class WithdrawalResponseDispatcherIWithdrawalResponseDispatcher
   {
      private IWithdrawalResponse[] inners = new IWithdrawalResponse[0];

      public void SetClients(params IWithdrawalResponse[] inners)
      {
         this.inners = inners;
      }

      void IWithdrawalResponse.Completed()
      {
         foreach (var inner in inners)
            inner.Completed();
      }

      void IWithdrawalResponse.CreditInsufficient()
      {
         foreach (var inner in inners)
            inner.CreditInsufficient();
      }
   }

Notate che l'oggetto non è altro che un Observer per le notifiche.


Aggiorniamo MovementsAccount:

   public class MovementsAccount : Account, IMovementsAccount, IWithdrawalResponse
   {
      private readonly IWithdrawalResponseDispatcher withdrawalResponseDispatcher;
      private readonly List<IMovementVisit> movements = new List<IMovementVisit>();
      private Amount amountWithdrawal;

      public MovementsAccount(IWithdrawalResponseDispatcher withdrawalResponseDispatcher)
      {
         this.withdrawalResponseDispatcher = withdrawalResponseDispatcher;
      }

      public override void Deposit(Amount amount)
      {
         base.Deposit(amount);
         movements.Add(new DepositMovement(amount));
      }

      public override void Withdrawal(Amount amount, IWithdrawalResponse withdrawal)
      {
         amountWithdrawal = amount;
         withdrawalResponseDispatcher.SetClients(this, withdrawal);
         base.Withdrawal(amount, withdrawalResponseDispatcher);
      }

      void IMovementsAccount.MovementsRequest(IMovementsResponse movement)
      {
         movement.Return(movements);
      }

      void IWithdrawalResponse.Completed()
      {
         movements.Add(new WithdrawalMovement(amountWithdrawal));
      }

      void IWithdrawalResponse.CreditInsufficient()
      {
      }
   }


A questo punto per ovviare i metodi virtuali potremmo seguire la stessa procedura e creare una classe che decora Account e che gestisca i movimenti:

   public class MovementsAccount : IAccount, IMovementsAccount
   {
      private readonly IAccount inner;
      private readonly  IWithdrawalResponseDispatcher withdrawalResponseDispatcher;
      private readonly List<IMovementVisit> movements;
      private Amount amountWithdrawal;

      public MovementsAccount(IAccount inner, IWithdrawalResponseDispatcher withdrawalResponseDispatcher)
      {
         this.inner = inner;
         this.withdrawalResponseDispatcher withdrawalResponseDispatcher;
         movements = new List<IMovementVisit>();
      }

      public void BalanceRequest(IBalanceResponse balance)
      {
         inner.BalanceRequest(balance);
      }

      public void Deposit(Amount amount)
      {
         inner.Deposit(amount);
         movements.Add(new DepositMovement(amount));
      }

      public void Withdrawal(Amount amount, IWithdrawalResponse withdrawal)
      {
         amountWithdrawal = amount;
         withdrawalResponseDispatcher.SetClients(this, withdrawal);
         inner.Withdrawal(amount, withdrawalResponseDispatcher);
      }

      void IMovementsAccount.MovementsRequest(IMovementsResponse movement)
      {
         movement.Return(movements);
      }
   }

In questo modo MovementsAccount contiene un' istanza di Account con la quale interagisce senza la necessità di modificare i metodi di Account creandoli virtuali.

Ora l'oggetto Movements che gestiva la richiesta dei movimenti deve interagire con IMovementsAccount, questo è corretto visto che volevamo isolare le operazioni in base al contesto e alla responsabilità.

   public class Movements : IOperation, IMovementsResponse, IMovementVisitor
   {
      private readonly IMovementsAccount account;
      private readonly IViewResponse view;

      public Movements(IMovementsAccount account, IViewResponse view)
      {
         this.account = account;
         this.view = view;
      }
   
      //...
   }

Per finire l'unica modifica “invasiva” che dobbiamo fare è cambiare l'inizializzazione delle istanze di Account.

Prima di isolare le responsabilità:

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

Dopo la modifica con la decorazione:

   private static void Main()
   {
      var account = new MovementsAccount(new Account(), new  WithdrawalResponseDispatcher());
      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));
   }

Oppure usando l'ereditarietà:

   private static void Main()
   {
      var account = new MovementsAccount(new  WithdrawalResponseDispatcher());
      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));
   }

Non è molto importante disquisire se in questo caso è meglio usare l'ereditarietà o la composizione per progettare l'oggetto MovementsAccount.
Il punto cardine rimane: possiamo fare sviluppi incrementali non invasivi nel nostro codice se e solo se isoliamo le responsabilità e le interazioni tra gli oggetti.

Questo è possibile se, mentre sviluppiamo, ricordiamo i seguenti principles: