Design disaccoppiato tramite interfacce statiche

4

Per prima cosa vorrei menzionare che non sono abbastanza sicuro di quale sia il termine che descrive il problema, ma spero di poterlo illustrare con un po 'di codice.

Problema: una libreria di classi condivisa definisce un'interfaccia e ogni applicazione che fa riferimento a questa libreria fornisce la propria implementazione di tale interfaccia. Ora all'interno della libreria condivisa, voglio chiamare l'effettiva implementazione di quel metodo.

Cercando di risolvere questo problema ho immaginato un semplice scenario in cui 2 applicazioni specifiche della piattaforma Windows , Android condividono del codice tranne alcune funzioni specifiche della piattaforma che devono essere implementate separatamente per ciascun sistema. In fase di runtime, devo chiamare in modo trasparente tale implementazione sapendo che è già fornita dall'implementatore.

Vengono create tre applicazioni console condivise , Windows , Android

Progetto condiviso definisce un'interfaccia IShared

namespace Project
{
    public interface IShared
    {
        void Speak(string word);
    }
}

Implementi Android IShared

namespace Project.Android
{
    public class SpeakAndroid: IShared
    {
        public void Speak(string word)
        {
            Console.WriteLine("Android Speaks");
        }
    }
}

Implementi di Windows IShared

namespace Project.Windows
{
    public class SpeakWindows: IShared
    {
        public void Speak(string word)
        {
            Console.WriteLine("Speak Windows");
        }
    }
}

Ora che il progetto condiviso è in grado di chiamare ogni funzione specifica Speak di ogni piattaforma, ho deciso di dichiarare un

public static IShared SharedDefinition { get; set; }

a cui assegno una nuova implementazione all'interno di progetti Windows e Android come

Windows:

Project.Program.SharedDefinition = new SpeakWindows();

Anrdoid:

Project.Program.SharedDefinition = new SpeakAndroid();

In questo modo nel progetto condiviso posso chiamare Program.SharedDefinition.Speak(); e, a seconda della piattaforma corrente, otterrò diverse implementazioni di Speak .

Question (s):

  • Questo è un modo corretto di risolvere il problema precedente? servizio Locator Pattern ha qualcosa a che fare con questo?
  • Per ulteriori letture, il problema sopra riportato ha un nome o un modello che meglio si adatta a questo?
posta Ahmed Tarek 01.09.2018 - 23:27
fonte

3 risposte

4

In breve

Questo è un modo valido per risolvere il problema di astrazione: assicura che la tua libreria non dipenda da implementazioni concrete.

Il termine generale di questa tecnica è chiamato inversione di dipendenza . Ma invece di inserire la dipendenza negli oggetti o funzioni che ne hanno bisogno, usi un intermediario globale. Tale intermediario è un identificatore di servizio .

Ulteriori dettagli: i localizzatori di servizi non sono più complessi?

Gli localizzatori di servizi solitamente incapsulano l'accesso a diversi tipi di servizi e per ciascuno dei vari servizi. Lo scenario è quindi più complesso che nel tuo caso:

  • SL viene inizializzato. In linea di principio è un singleton
  • I servizi vengono quindi registrati alla SL (opzionalmente con alcuni attributi per ritrovarli)
  • Il client ottiene quindi il servizio dalla SL (opzionalmente con qualche ricerca per scegliere il più adatto)

Ma nel tuo caso, il localizzatore è semplificato all'estremo, poiché esponi solo un singolo servizio:

  • SL è solo un oggetto statico globale.
  • Il singolo servizio IShared utilizzato dalla libreria viene registrato mediante l'utilizzo del contesto tramite il setter dell'oggetto statico globale.
  • La libreria accede al servizio configurato tramite il getter dell'oggetto globale.

In questo articolo troverai un altro esempio di localizzatore di servizi tra questi due estremi. ( Ne parlo qui più a causa della netta distinzione che fa tra l'inversione di dipendenza, l'iniezione di dipendenza e l'inversione di controllo )

Ulteriori dettagli: è una buona idea?

L'inconveniente principale del localizzatore di servizi è che tutte le classi della libreria possono dipendere da esso, forse creando un accoppiamento più alto del necessario all'interno della libreria.

Potresti perdere un po 'di flessibilità perché non puoi usare diverse implementazioni dell'astrazione per oggetti diversi. Potrebbe essere che non ne hai bisogno qui, ma in altri scenari questo schema potrebbe essere controproducente.

    
risposta data 02.09.2018 - 02:48
fonte
2

re: perché è statico?

Quindi hai ben estratto il tuo 'ISpeaker' (non lo chiamiamo condiviso) ma poi lo metti in un Program.Speaker statico.

Ora se hai un'altra classe nella tua app, dì LoginController che vuole usare l'altoparlante che avrà ..

public class LoginController 
{
    public void Login() 
    {
        Program.Speaker.Speak("you have logged in!");
    }
}

Sebbene le tue lezioni concrete di altoparlanti non siano più accoppiate all'app, il tuo LoginController è inutilmente abbinato al tuo Programma.

Un'alternativa migliore sarebbe:

public class LoginController 
{
    public LoginController(ISpeaker speaker)
    {
        this.speaker = speaker;
    }
    public void Login() 
    {
        this.speaker.Speak("you have logged in!");
    }
}

Ora il codice è ancora meno abbinato, con tutti i vantaggi che ne derivano.

re: Perché un Localizzatore di servizio è cattivo?

Un localizzatore di servizi sarebbe un oggetto che ha restituito su richiesta un'istanza concreta di ISpeaker. ad esempio

public class LoginController 
{
    public LoginController(ILocator locator)
    {
        this.locator= locator;
    }
    public void Login() 
    {
        var speaker = this.locator.Get<ISpeaker>();
        speaker.Speak("you have logged in!");
    }
}

Questo aggira il campo statico strettamente accoppiato dal primo esempio, ma può causare un errore di runtime se il localizzatore non è configurato correttamente con un ISpeaker.

Questo è particolarmente grave perché senza guardare all'interno del codice LoginController, non si sa che ha bisogno di un ISpeaker. Considerando che richiedere esplicitamente un ISpeaker come parametro di costruzione costringe il codice chiamante a passare qualcosa in.

re: il problema ha un nome?

La soluzione discussa si chiama Dependency Injection. Non sono sicuro che il problema della condivisione del codice tra due applicazioni abbia un nome.

    
risposta data 02.09.2018 - 15:10
fonte
1

Per quanto posso vedere dal link questo , Localizzatore di servizio indica che esiste un altro ShardProvider di classe, che provode tutti gli altri codici la corretta implementazione di IShared. È pensato per essere istanziato come Singleton.

Essenzialmente questo ha gli stessi aspetti negativi e negativi dell'uso del pattern Sigleton, ma ha semplicemente spostato la responsabilità di istanziare IShared dall'IShard stesso a una classe separata.

Nel tuo codice, se la tua proprietà SharedDifinition è definita in alcune impostazioni della classe di utilità, allora sembra Localizzatore di servizio, con differenza:

  • sembra essere scrivibile a qualsiasi cosa, che è pericoloso. Il setter dovrebbe essere almeno internal .
  • lo schema di localizzazione del servizio preferisce invece prescrivere di avere la proprietà non statica, invece di impostare un singleton

Se la proprietà SharedDifinition si trova nella classe specifica del problema che effettivamente la usa, sembra essersi allungata abbastanza lontano da Service Locator o Singleton. Si noti che l'utente del codice specifico del problema dovrebbe sempre dedicare la propria attenzione a ricordare da dove il codice questo prende IShared, mentre sta ancora ottenendo degli svantaggi di Singleton / Service Locator, perché la proprietà è statica.

    
risposta data 02.09.2018 - 05:18
fonte

Leggi altre domande sui tag