mercoledì 2 gennaio 2013

Considerazioni sui valori di ritorno (2° parte)


Come promesso, vi presento le due implementazioni suggerite nel precedente post. Iniziamo con la soluzione battezzata “interfaccia con tre callback”.

Il metodo da chiamare ha un parametro che espone il contratto per interagire con i dati che elabora:

   public void DoSomething(IDelegate<object> @delegate)
   {
      switch (GetStatus())
      {
         case 1:
            @delegate.DoCase1(new object());
            break;
         case 2:
            @delegate.DoCase2(new object());
            break;
         case 3:
            @delegate.DoCase3(new object());
            break;
      }
   }

Quindi, l'utilizzatore del metodo DoSomething deve passare un oggetto che rispetta questo contratto:

   public interface IDelegate<in T>
   {
      void DoCase1(T value);
      void DoCase2(T value);
      void DoCase3(T value);
   }

Vediamo nella pratica cosa vuol dire:

   public class Main : IDelegate<object>
   {
      public Main()
      {
         DoSomething(this);
      }

      public void DoCase1(object value)
      {
         Console.WriteLine("case1: " + value);
      }

      public void DoCase2(object value)
      {
         Console.WriteLine("case2: " + value);
      }

      public void DoCase3(object value)
      {
         Console.WriteLine("case3: " + value);
      }
   }

La classe Main che utilizza DoSomething implementa l'interfaccia IDelegate<T> e passa se stessa per poter ricevere le informazioni in base al comportamento di DoSomething.

Questa soluzione è abbastanza concisa, purtroppo implica che la callback ci porta in un altro scope con tutto quello che ne consegue.

Vediamo come l'implementazione della seconda soluzione proposta (ovvero il builder) ci permette di ovviare al problema indicato sopra e di mantenere un codice più snello:

   public class Main
   {
      static Main()
      {
         DoSomething()
            .SetCase1(value => Console.WriteLine("case1: " + value))
            .SetCase2(value => Console.WriteLine("case2: " + value))
            .SetCase3(value => Console.WriteLine("case3: " + value))
            .Execute();
      }
   }

Come vediamo dall'esempio sopra, la sintassi è più compatta e leggibile, mantenendo flessibilità e rispettando l'obbligo di guidare l'utilizzatore.

Per fare tutto questo c'è uno sforzo maggiore da seguire.

Dobbiamo definire quattro interfacce che esprimono il comportamento del builder:

   public interface IDelegate1<out T>
   {
      IDelegate2<T> SetCase1(Action<T> action);
   }

   public interface IDelegate2<out T>
   {
      IDelegate3<T> SetCase2(Action<T> action);
   }

   public interface IDelegate3<out T>
   {
      IExecute SetCase3(Action<T> action);
   }

   public interface IExecute
   {
      void Execute();
   }

Nella classe Foo implementiamo le interfacce:

   public class FooIDelegate1<object>,
                     IDelegate2<object>,
                     IDelegate3<object>,
                     IExecute
   {
      private Action<object> action1;
      private Action<object> action2;
      private Action<object> action3;

      IDelegate2<object> IDelegate1<object>.SetCase1(Action<object> action)
      {
         action1 = action;
         return this;
      }

      IDelegate3<object> IDelegate2<object>.SetCase2(Action<object> action)
      {
         action2 = action;
         return this;
      }

      IExecute IDelegate3<object>.SetCase3(Action<object> action)
      {
         action3 = action;
         return this;
      }

      void IExecute.Execute()
      {
         switch (GetStatus())
         {
            case 1:
               action1(new object());
               break;
            case 2:
               action2(new object());
               break;
            case 3:
               action3(new object());
               break;
         }
      }
   }

Il risultato finale, a mio parere, vale lo sforzo fatto. In definitiva si riduce alla definizione di qualche interfaccia per obbligare l'utilizzatore ad impostare le callback da usare.

Nessun commento:

Posta un commento