Usare un localizzatore di servizi per registrare e recuperare le dipendenze

Completato

Il secondo schema che verrà esaminato è quello del localizzatore di servizi. Lo schema del localizzatore di servizi prevede un frammento di codice che gestisce un elenco di dipendenze note. In genere le dipendenze si creano e si registrano nel codice specifico della piattaforma. Quando uno dei componenti necessita di una dipendenza, consulta il localizzatore di servizi, che quindi restituisce l'istanza registrata.

Diagramma che mostra il modello del localizzatore di servizi, in cui un client usa un localizzatore per trovare i servizi A e B.

Esempio di definizione di localizzatore di servizi

L'implementazione di un localizzatore di servizi spesso comporta l'uso di un dizionario o di una tabella hash che esegue il mapping dell'astrazione all'implementazione concreta.

Ecco un esempio di definizione:

public sealed class ServiceLocator
{
    public static ServiceLocator Instance { get; set; }

    public void Add(Type contractType, object value);
    public void Add(Type contractType, Type serviceType);
    public object Resolve(Type contractType);
    public T Resolve<T>();
}

In questo esempio si usano i metodi Add per registrare le astrazioni. In entrambi i casi, si specifica il tipo dell'astrazione. Si fornisce quindi un'istanza dell'oggetto concreto oppure si specifica il tipo dell'oggetto concreto. Specificando il tipo concreto, è possibile consentire al localizzatore di servizi di creare un'istanza dell'oggetto in base alle esigenze.

I metodi complementari Resolve vengono usati per cercare una dipendenza usando il tipo dell'astrazione come chiave.

Si noti l'accoppiamento di un secondo schema. La classe ServiceLocator ha una proprietà statica denominata Instance, che restituisce un oggetto ServiceLocator di cui è stata creata un'istanza. Se si ricorda la proprietà statica che è stata usata con lo schema Factory, qui si usa di nuovo il concetto statico. Tuttavia, l'intento è leggermente diverso perché si vuole creare un singleton.

Schema Singleton

Lo schema Singleton consente di creare un oggetto con più responsabilità. In genere un oggetto è responsabile dell'esecuzione di specifiche operazioni sulle sue proprietà. Tuttavia, si avranno alcuni oggetti con maggiori responsabilità e altri oggetti che si basano su di essi. Gli oggetti con maggiori responsabilità assumeranno un ruolo specifico. Di conseguenza, normalmente si vorrà creare una sola istanza di uno di questi oggetti per ogni tipo. Lo schema Singleton consente di assicurarsi che in qualsiasi momento sia presente un solo oggetto di un tipo specifico.

Quando si progetta la classe Singleton, i client non possono creare direttamente un'istanza della classe. Per creare una classe Singleton, si crea prima di tutto un costruttore di classe privato. Quindi, si consente ai client di richiedere un'istanza dell'oggetto tramite una proprietà statica.

Applicando il concetto al localizzatore di servizi, lo schema Singleton offre un modo semplice per individuare il localizzatore di servizi stesso. Nella classe ServiceLocator di esempio, le richieste vengono eseguite tramite la proprietà Instance. Il codice che vuole usare il localizzatore di servizi può accedervi tramite la proprietà statica della classe.

Registrazione di una dipendenza con il localizzatore di servizi

Le implementazioni concrete delle astrazioni saranno rappresentate da codice specifico della piattaforma. Si aggiungeranno le implementazioni al localizzatore di servizi registrando l'astrazione con l'implementazione. Questa registrazione archivierà il tipo di astrazione e il tipo dell'implementazione concreta nel dizionario.

Di seguito viene illustrato un esempio che registra l'interfaccia IMessageDialog di cui si è parlato in precedenza e la classe MessageDialog_iOS per iOS:

public partial class AppDelegate
{
   ...
    public override void FinishedLaunching(UIApplication application)
    {
        ...
        ServiceLocator.Instance.Add<IMessageDialog,MessageDialog_iOS>();
    }
}

Uso del localizzatore di servizi

Per usare il localizzatore, si usa la proprietà Singleton del localizzatore di servizi e si richiede l'astrazione. In questo caso, l'astrazione è l'interfaccia IMessageDialog. Si supponga che ogni progetto specifico della piattaforma abbia registrato un'implementazione di tale interfaccia con lo stesso localizzatore di servizi.

Ecco come usare l'interfaccia IMessageDialog di esempio per visualizzare una finestra di dialogo:

public void ShowMessage(string title, string message, string buttonText)
{
    var messageDialog = ServiceLocator.Instance.Resolve<IMessageDialog>();
    if (!messageDialog.ShowMessage("About",
        "Using the Service Locator Pattern to show this message", "Ok"))
    {
        ...
    }
}

Vantaggi del localizzatore di servizi

Il localizzatore di servizi offre i vantaggi seguenti:

  • Lo schema del localizzatore di servizi è facile da usare e da comprendere. Non è necessario comprendere il funzionamento interno del localizzatore di servizi per usarlo. È possibile registrare e richiedere le dipendenze in base alle proprie esigenze.
  • Il codice client può richiedere i servizi Just-In-Time. Lo sviluppatore decide quando vuole che venga creata un'istanza della dipendenza e richiede l'istanza secondo le necessità.
  • È possibile usare lo schema del localizzatore di servizi con qualsiasi client.

Svantaggi del localizzatore di servizi

Il localizzatore di servizi presenta gli svantaggi seguenti:

  • Tutti i client devono avere accesso al localizzatore di servizi. Questo fatto viene considerato uno degli svantaggi principali dello schema del localizzatore di servizi. Il localizzatore di servizi è una dipendenza globale che deve essere accessibile a tutto il codice.

  • Non sempre lo schema del localizzatore di servizi è considerato una procedura di programmazione consigliata, perché rende difficili le attività seguenti:

    • Identificare le dipendenze nel codice.
    • Rilevare le dipendenze mancanti prima del runtime.

Tuttavia, lo schema ha casi d'uso specifici che lo rendono estremamente utile. Uno di questi casi d'uso è quello dei contenitori di inversione del controllo. I contenitori IoC verranno esaminati più avanti in questo modulo.