Le classi iniettabili possono avere parametri di costruzione in DI?

4

Dato il seguente codice:

class ClientClass{

    public function print(){
        //some code to calculate $inputString
        $parser= new Parser($inputString);
        $result= $parser->parse();
    }
}

class Parser{
    private $inputString;

    public __construct($inputString){
        $this->inputString=$inputString;
    }
    public function parse(){
        //some code
    }
}

Ora ClientClass ha dipendenza dalla classe Parser . Tuttavia, se volessi utilizzare Dipendenza dell'iniezione per il test delle unità, ciò causerebbe un problema perché ora non posso inviare l'input stringa al costruttore di parser come prima come calcolata all'interno di ClientCalss stesso:

class ClientClass{
        private $parser;
        public __construct(Parser $parser){
            $this->parser=$parser;
        }
        public function print(){
            //some code to calculate $inputString
            $result= $this->parser->parse(); //--> will throw an exception since no string was provided

        }
}

L'unica soluzione che ho trovato è stata la modifica di tutte le mie classi che hanno utilizzato parametri nei loro costruttori per utilizzare invece Setters (esempio: setInputString() ).
Tuttavia, penso che potrebbe esserci una soluzione migliore di questa, perché a volte modificare le classi esistenti può causare molto più danni che benefici.

  • Le classi iniettabili non sono autorizzate ad avere parametri di input?
  • Se una classe deve prendere parametri di input nel suo costruttore, quale sarebbe il modo di iniettarla correttamente?

Aggiorna
Solo per chiarimenti, il problema si verifica quando nel mio codice di produzione decido di farlo:

$clientClass= new ClientClass(new Parser($inputString));//--->I have no way to predict $inputString as it is calculated inside 'ClientClass' itself.

UPDATE 2
Sempre per chiarimenti, sto cercando di trovare una soluzione generale al problema non per questo codice di esempio solo perché alcune delle mie classi hanno 2, 3 o 4 parametri nei loro costruttori, non solo uno.

    
posta Songo 24.10.2012 - 20:30
fonte

4 risposte

11

Nel vecchio codice, ClientClass costruisce Parser . Quindi il minimo cambiamento sarebbe quello di inserire una fabbrica di parser , non un'istanza. La factory può avere qualsiasi argomento tu abbia bisogno e ovviamente può passarli al costruttore della classe creata.

Per fare un esempio, il codice in questione dovrebbe essere modificato sulla falsariga di:

class ClientClass{
    private $parserFactory;
    public __construct($parserFactory){
        $this->parserFactory=$parserFactory;
    }
    public function print(){
        $parser = $this->parserFactory($inputString)
        $result= $parser->parse();
    }
}

e chiamato come:

new ClientClass(function($inputString) { return new Parser($inputString) });

(ovviamente è possibile utilizzare anche una funzione con nome). Questo è perfettamente generico; si avvolge la costruzione dell'oggetto di cui si ha bisogno in una funzione che passerà attraverso tutti gli argomenti necessari, lo fornirà alla classe dipendente e all'interno di quella classe chiamerai la funzione invece di costruire direttamente la dipendenza. Ovviamente la funzione che passi può creare invece un oggetto mock o modificare gli argomenti o hardcode alcuni argomenti extra o qualsiasi altra cosa ti serva.

Se la tua lingua non ha oggetti funzione e espressioni lambda, puoi invece creare una classe factory. Dove sono disponibili oggetti funzione, sono più facili da scrivere.

PS: non sono abile in PHP, quindi non sono assolutamente sicuro di non avere errori di sintassi nell'esempio.

    
risposta data 25.10.2012 - 11:47
fonte
4
 > Are injectable classes allowed to have constructor parameters in DI?

In java / spring è possibile avere classi che richiedono i parametri nel costruttore come indicato da @Jeff Vanzella.

Tuttavia, nel tuo esempio è più difficile usare di perché la creazione del parser non è corretta, dipende da un contesto non statico ($ inputString).

Una possibile anima generale di questo è: il costruttore della classe parser ottiene un parametro contextlocator che sa come ottenere il contesto.

class Parser{
    private $contextLocator;

    public __construct($contextLocator){
        $this->contextLocator=$contextLocator;
    }
    public function parse(){
        $inputString = $contextLocator->getContext()
        //some code
    }
}   

Ma questo approccio aggiunge una complessità inutile alla tua anima. L'uso di un metodo init che definisce il contesto (o l'equivalente funzionale dell'uso di una proprietà per impostare il contesto come suggerito) è molto più semplice.

nel tuo esempio speciale questo non è necessario. Sposta il parametro dal costruttore al metodo di analisi.

class Parser{
    private $inputString;

    public __construct(){
    }
    public function parse($inputString){
        $this->inputString=$inputString;
        //some code
    }
}
    
risposta data 24.10.2012 - 21:05
fonte
-1

Risposta semplice: Sì, possono avere parametri di input. È così che di solito uso DI e IoC.

L'iniezione viene eseguita appena possibile nel programma. Per quanto riguarda i test, nel tuo caso, è sufficiente iniettare una stringa nota. Per le altre classi, dovresti effettivamente rendere il parametro un'interfaccia e iniettare una classe di simulazione che fa qualcosa di noto, o un oggetto di simulazione creato da un framework di simulazione, che contiene numerosi framework open source e pagati.

Sono disponibili numerose informazioni qui . La maggior parte del codice è in C #, ma le idee e le teorie dietro sono ben scritte

    
risposta data 24.10.2012 - 20:39
fonte
-1

Mi rendo conto che questo potrebbe essere un esempio semplificato di ciò che gestisci, ma sembra che il tuo metodo print (ora supponendo che questo sia un codice con cui lavori e possa cambiare) fa un po ' troppo. Per essere precisi, calcola il valore che deve stampare. Spostare quel comportamento per separare il costrutto (un'altra dipendenza) facilmente attenuerà il problema:

class ClientClass{
    private $parser;
    private $inputProvider;

    public __construct(InputProvider $inputProvider, Parser $parser){
        $this->inputProvider = $inputProvider;
        $this->parser = $parser;
    }

    public function print(){
        $input = $this->inputProvider->calculate();
        $result = $this->parser->parse($input);
    }
}

In questo modo, non solo non devi modificare alcun codice esistente ( Parser ), ma guadagni un po 'di più in termini di perdita di accoppiamento / separazione delle preoccupazioni. La stampa viene stampata e la logica di calcolo dell'ingresso (che può anche essere sottoposta a test dell'unità separatamente) è delegata all'esterno.

    
risposta data 25.10.2012 - 11:41
fonte