Individuare le dipendenze usando lo schema Factory

Completato

Il primo schema che verrà esaminato è lo schema Factory. Lo schema Factory instrada il client attraverso un intermediario, denominato factory, per creare la propria dipendenza.

Diagramma che illustra uno schema Factory, in cui un client usa una factory per creare un servizio.

Il codice specifico della piattaforma e il codice multipiattaforma condividono la factory. La factory è definita nell'assembly di codice condiviso. Ha la responsabilità di creare la dipendenza specifica della piattaforma. Solo la classe factory è in grado di creare la dipendenza.

È possibile creare la dipendenza nella factory in diversi modi. Si può decidere di usare un delegato oppure una classe parziale. Tenere presente che, indipendentemente dall'opzione scelta, la dipendenza deve essere creata nel codice specifico della piattaforma.

Che cos'è un delegato?

Un delegato è una struttura di dati che fa riferimento a un metodo. Può trattarsi di un metodo statico o di un metodo di istanza da un'istanza della classe. La cosa più semplice è pensare a un delegato come a un elemento che punta a un metodo.

Per usare un delegato, si definirà il delegato personalizzato e quindi se ne creerà un'istanza. Si passerà il metodo da eseguire al nuovo delegato come parametro del costruttore. È anche possibile assegnare un metodo senza parametri direttamente a un delegato.

In questo esempio vengono usati un delegato e un metodo senza parametri:

using System;
using System.IO;

delegate bool LoggerMethod();

public class TestDelegate
{
   public static void Main()
   {
      OutputProcess outputProcess = new OutputProcess();
      LoggerMethod loggerCall = outputProcess.SendToLogger;

      if (loggerCall())
         Console.WriteLine("Success!");
      else
         Console.WriteLine("Logger operation failed.");
   }
}

public class OutputProcess
{
   public bool SendToLogger()
   {
        try {
            ...
            return true;
        }
        catch
        {
            ...
            return false;
        }
   }
}

Il tipo delegato è denominato LoggerMethod. A un'istanza di quel tipo denominata loggerCall è assegnato il metodo di istanza outputProcess.SendToLogger. È possibile eseguire loggerCall come qualsiasi altro metodo.

Che cos'è Func<TResult>?

Func<TResult> rappresenta un metodo che non ha parametri e restituisce un valore del tipo specificato dal parametro TResult.

Si noti la somiglianza tra l'uso di Func e l'uso di un delegato. La differenza è che non è necessario definire un delegato personalizzato e crearne un'istanza in modo esplicito. Func<TResult> può essere considerato la sintassi abbreviata per un delegato che punta a un metodo senza parametri e restituisce un valore.

In questo esempio viene usato Func<TResult> con lo stesso metodo senza parametri, come nel caso precedente:

using System;
using System.IO;

public class TestDelegate
{
   public static void Main()
   {
      OutputProcess outputProcess = new OutputProcess();
      Func<bool> loggerCall = () => outputProcess.SendToLogger;

      if (loggerCall())
         Console.WriteLine("Success!");
      else
         Console.WriteLine("Logger operation failed.");
   }
}

public class OutputProcess
{
   public bool SendToLogger()
   {
        try {
            ...
            return true;
        }
        catch
        {
            ...
            return false;
        }
   }
}

L'uso di Func consente di risparmiare tempo, perché non è necessario definire il proprio delegato.

Creare la factory

Si userà il concetto di delegato per creare la factory. È importante ricordare che occorre un elemento che crei un'istanza della dipendenza. È consigliabile usare un metodo per creare un'istanza della dipendenza. Tuttavia, è necessario definire le esigenze del metodo nel codice specifico della piattaforma e non in una classe astratta. Poiché un delegato può puntare a qualsiasi metodo, è possibile creare il delegato nella classe astratta e quindi configurarlo in un'altra posizione.

Di seguito viene ripreso un esempio precedente. Si noterà che sono state apportate alcune modifiche al codice:

using System;
using System.Collections.Generic;
using Xamarin.Forms;

namespace MyApplication
{
    public partial class AboutPage : ContentPage
    {
        public AboutPage()
        {
            InitializeComponent();
        }

        void Handle_Clicked(object sender, EventArgs e)
        {
            MessageDialog messageDialog = MessageDialog.Create();
            messageDialog.ShowMessage("About", "Some information ...", "Ok");
        }
    }

    public abstract class MessageDialog
    {
        public static Func<MessageDialog> Create { get; set; }
        public abstract void ShowMessage(string title, string message, string buttonText);
    }
}

Osservare la nuova proprietà Create nella classe astratta MessageDialog.

La proprietà Create restituirà un delegato, Func<MessageDialog>. Tale delegato è responsabile della creazione di un'istanza concreta di MessageDialog quando viene richiamato. Si noti che la proprietà è definita come statica. Questo è importante, perché indica che è possibile accedere alla proprietà da qualunque punto del codice, senza un'istanza di MessageDialog.

La possibilità di fare riferimento alla proprietà statica da qualunque punto del codice consente di configurare il delegato nel codice specifico della piattaforma. Verrà ora esaminato il funzionamento della configurazione.

Ecco un esempio per iOS:

public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
    public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
    {
        ...
        MessageDialog.Create = () => new MessageDialog_iOS();
        ...
    }
}

Ecco un esempio per Android:

public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
    protected override void OnCreate(Bundle savedInstanceState)
    {
        ...
        MessageDialog.Create = () => new MessageDialog_Android();
        ...
    }
}

Nel codice di inizializzazione di entrambe le piattaforme è possibile assegnare la proprietà MessageDialog.Create. Ricordare che si tratta di una proprietà statica, tipizzata per restituire un delegato Func<MessageDialog>. Si assegnerà la proprietà a un'espressione lambda per restituire una nuova istanza dell'oggetto MessageDialog.

Ovunque sia necessario mostrare un avviso, si passerà alla factory MessageDialog, che in questo caso è la proprietà statica. Questa factory verrà usata per ottenere un'istanza di MessageDialog.

Quali sono i vantaggi dello schema Factory?

Lo schema Factory offre i vantaggi seguenti:

  • L'implementazione della dipendenza è nascosta al client. Il client non ha bisogno di sapere come viene creata la dipendenza o da dove proviene.
  • Lo schema è facile da comprendere.
  • Si può decidere quale dovrebbe essere l'implementazione al runtime e restituire una versione specifica in base all'ambiente in uso.

Quali sono gli svantaggi dello schema Factory?

Lo schema Factory presenta gli svantaggi seguenti:

  • Lo schema richiede una "factory" separata per ogni astrazione. Se si hanno più factory, la manutenzione del codice sarà più gravosa.
  • Anche se il client non conosce la modalità di creazione della dipendenza, deve comunque avere una dipendenza dalla factory.
  • Le dipendenze mancanti non sono note fino al runtime, in quanto le dipendenze vengono risolte solo al momento dell'esecuzione dell'applicazione.