Perplesso per principio di dipendenza dalla dipendenza: cosa succede se è dipendente dal runtime quali oggetti vengono creati?

1

Il Il gioco antipasto-plagato che sto creando utilizza ASP.NET Core, che, in molti casi, richiede l'uso dell'iniezione delle dipendenze. Questo è nuovo e contro-intuitivo per me. Fino ad ora, sono riuscito a limitare l'uso dell'iniezione di dipendenza al punto in cui è strettamente richiesto dal framework, mentre scrivevo la meccanica del gioco nel "mio" vecchio modo, cioè con molte creazioni di oggetti esplicite ( var blah = new SomeClass(arg1, arg2) ecc.) (E senza creare interfacce solo quando una classe le implementerebbe.)

Leggendo un po 'sull'iniezione delle dipendenze, che la documentazione del framework raccomanda di utilizzare ovunque, sono rimasto perplesso nel constatare che non dovrei chiamare costruttori né creare oggetti esplicitamente, perché invece dovrei richiedere tutti gli oggetti che vorrei ClassA per mai creare nel costruttore di ClassA e lasciare che sia il framework a fornirmi per me!

Sfogliando il mio codice, in molti casi non riesco a vedere come potrei realizzare questo. Semplicemente spostando tutto new s nel costruttore non sembra funzionare.

Un esempio è quando sembra essere dipendente dal tempo di esecuzione su quali oggetti devono essere creati.

Per essere più specifici, lascia che ti fornisca un esempio per questo esempio; P Ci sono molti% di caratteri che i% s di co_de possono usare:

public class Punch : Move
{
    // ...
}

public class SwordSlash : Move
{
    // ...
}

ecc.

Il client (browser) invierà di solito al server un comando con il nome della mossa che il giocatore vorrebbe eseguire. Omettendo le molte validazioni necessarie (per semplicità) questo è il codice del metodo rilevante del server:

var moveType = System.Type.GetType("Game.Mechanics."+moveTypeName);
if (!(moveType.BaseType == typeof(Move))) return; // OK one of the many validations I did not omit
move = (Move)Activator.CreateInstance(moveType, args);

Proprio così - il server guarda se c'è una classe il cui nome è equivalente alla stringa inviata dal client. Bene, violazione dell'iniezione di dipendenza?

Quindi come dovrei farlo invece?

L'unico modo che mi viene in mente è di avere una classe il cui contructor accetterà le mosse ALL (attualmente 79 e contando) e che avrà un metodo con un interruttore gigantesco per selezionare una mossa corretta:

public class MoveSelector : IMoveSelector
{
    private readonly IPunch punch;
    private readonly ISwordSlash swordSlash;
    // 77 more privates

    public MoveSelector (
        IPunch punch,
        ISwordSlash swordSlash,
        // 77 more args
    ) {
        this.punch = punch;
        this.swordSlash = swordSlash;
        // 77 more assignments
    }

    public IMove SelectMove(string moveName)
    {
        switch(moveName)
        {
            case "Punch":
                return punch;
            case "SwordSlash":
                return swordSlash;
            // 77 more cases
        }
    }
}

In qualche modo ritengo che questa non sia la strada giusta da percorrere. Tanto più che l'aggiunta di una nuova mossa sarebbe diventata piuttosto noiosa.

Quindi, come posso risolvere il mio codice in modo da non violare la regola che dovrei evitare la parola chiave Move ?

    
posta gaazkam 13.09.2018 - 21:53
fonte

3 risposte

1

L'iniezione di dipendenza non deve essere utilizzata senza pensare e sicuramente non dappertutto.

Nel moderno sviluppo di applicazioni, l'integrazione delle dipendenze è praticamente necessaria per le classi di servizio (bean), ad esempio quelle classi per le quali è davvero sensato avere un'istanza sull'intera applicazione. Alcuni esempi potrebbero essere il contenitore di dipendenze stesso, le istanze del repository, forse il client HTTP, ...

Ma ovviamente puoi chiamare il new di volta in volta. Se qualche oggetto rappresenta un'istanza di qualcosa e non è una classe di servizio e non una classe di servizio dipendente, puoi istanziarla direttamente ed è completamente soddisfacente.

Se si dispone di una classe di cui un'istanza è dipendente dal runtime e questa classe dipende da un oggetto servizio, l'istanziazione di tali oggetti viene solitamente eseguita utilizzando una factory (dove factory è una classe di servizio), ad es. così:

class MyClassDependendOnServiceClass
{
    public MyClassDependendOnServiceClass(userInput: UserInput, service: Service)
    {
        // ...
    }
}

class MyClassDependendOnServiceClassFactory
{
    private Service service;

    public MyClassDependendOnServiceClassFactory(service: Service)
    {
        this.service = service;
    }

    public MyClassDependendOnServiceClass Create(userInput: UserInput)
    {
        return new MyClassDependendOnServiceClass(userInput, this.service);
    }
}

Inietti quindi l'istanza di fabbrica ad es. un controller e ottieni un oggetto MyClassDependendOnServiceClass che lo utilizza.

Se le tue mosse non dipendono da nulla di simile e dovrebbero essere create ogni volta, fallo semplicemente. Non c'è problema.

    
risposta data 13.09.2018 - 22:07
fonte
1

That's right - the server just looks if there is a class whose name is equivalent to the string sent by the client. Well, dependency injection violation?

No. Va bene. È fondamentalmente ciò che fa la primavera. Lo fanno solo con un file xml.

Ma per favore non confondete l'iniezione della dipendenza da quadro con un'iniezione di dipendenza pura.

Framework DI aiuta a mantenere la costruzione separata dall'uso facendoti costruire in una lingua diversa. In caso di molle, quest'altra lingua è spesso XML. Nel tuo caso sono stringhe semplici.

Pure DI ti consente di restare fedele alla tua lingua principale. Devi solo imparare come mantenere l'uso e la costruzione separati per conto tuo.

Ciò significa rompere l'abitudine di costruire, o andare e trovare qualcosa, nel momento in cui ti rendi conto di averne bisogno. Invece tu annunci di dipendere da ciò chiedendogli di essere passato a te. Questo rende espliciti i tuoi bisogni. Li rende più facili da individuare poiché nessuno deve eseguire la scansione del codice per sapere di cosa ha bisogno.

Costruisci oggetti più in alto nello stack di chiamate il più possibile. Ciò non significa che tutto sia costruito in main. Tutto ciò che vive purché principale. Questo non è tutto. Ad esempio, è giusto costruire i timestamp ora quando sono necessari perché hanno bisogno di informazioni che non esistevano fino a poco tempo fa.

Avviso Non ho detto nulla su quando viene usato il timestamp. Tutto quello che mi interessa è se ho quello che devo sapere per costruirlo. Non lasciare usare la costruzione del disco. Consenti alla disponibilità di informazioni che la costruzione dipende dalla costruzione del disco.

    
risposta data 14.09.2018 - 01:23
fonte
0

Mi concentrerò su un punto e, si spera, questo ti aiuterà a capire meglio il modello.

"the server just looks if there is a class whose name is equivalent to the string sent by the client. Well, dependency injection violation?"

Si noti che stiamo parlando di un limite di applicazione qui - un confine di rete per essere precisi. Ciò significa fondamentalmente che dovresti considerare il client e il server come due distinte (anche se correlate) paci di software, che comunicano su una rete scambiando dati.

Non si fa l'iniezione di dipendenza cross-bondary - questo sta estendendo la metafora troppo lontano (o almeno, non è così utile estenderlo così lontano in questo contesto).

Sul lato server (l'intera discussione di seguito si riferisce al lato server), la dipendenza è il codice che riceve ed elabora i dati che arrivano sulla rete (che una forma di input), nel senso che esiste un codice di livello superiore che implementa una logica e dipende dal codice di elaborazione dell'input.

inverti quella dipendenza introducendo un'astrazione che rappresenta la dipendenza. Ad esempio, è possibile introdurre un'interfaccia e quindi (1) rendere la propria dipendenza implementare l'interfaccia e (2) modificare il codice di livello superiore laddove necessario in modo che tutta la logica sia scritta su tale interfaccia. Quindi puoi anche eseguire injection injection (nel codice di livello superiore) passando (preferibilmente attraverso il costruttore) un'istanza che implementa l'interfaccia.

Un altro punto importante: i tipi di spostamento concreti sono solo una parte del modello interno sul server. Fanno parte del nucleo del codice (nell'architettura aziendale, si direbbe che fanno parte del modello di dominio). Questo è il cuore della tua applicazione (server) che tutto il resto (all'interno dell'applicazione) dipende in ultima analisi. La logica del gioco è scritta in termini di queste classi principali. Non lo inietti dall'esterno. Inietti le cose che si trovano ai limiti dell'applicazione - i livelli esterni, elementi che supportano la tua logica principale ma che non sono una parte fondamentale di esso.

Si noti che l'inversione di dipendenza e l'iniezione di dipendenza sono due cose distinte, ma possono essere e spesso sono combinati insieme.

Sebbene sia possibile applicare l'inversione della dipendenza insieme all'iniezione delle dipendenze in molte parti della propria applicazione, è oltre i limiti dei livelli logici all'interno dell'applicazione dove è veramente importante. Controlla le direzioni di dipendenza (accoppiamento) dei livelli e dei sottosistemi all'interno dell'applicazione. Puoi applicarlo all'interno dei livelli stessi, ma non è un problema se non lo fai. Devi anche avere qualche idea su come vuoi stratificare la tua applicazione. Se vogliamo prendere spunto dall'IO / separazione pura nella programmazione funzionale, dovrebbero esserci almeno due livelli: la Logica dell'applicazione e il livello "Gateways" verso vari sink e sorgenti di dati esterni. Nell'immagine qui sotto, ho raffigurato un disegno a 3 strati (senza contare il livello "Librerie, Quadri").

(ingrandisci)

    
risposta data 14.09.2018 - 04:15
fonte

Leggi altre domande sui tag