Iniezione delle dipendenze nel pattern Chain of Command

3

Ho implementato un pattern Chain of Command, con moduli che implementano un'interfaccia:

interface IRequestHandler
{
    public function handle(&$offset, &$tripData, $request = null);
    public function setSuccessor(OffsetRequestHandler $handler);
}

Ho impostato una catena di moduli che modificano gli oggetti $offset e $tripData se necessario (suppongo che non sia esattamente un modello di catena di comando, ma ottenga il lavoro fatto :)

Vorrei iniziare a usare l'iniezione di dipendenza ogni volta che è possibile facilitare il processo di scrittura dei test di unità. La mia domanda è: come gestirla, se ogni modulo potrebbe necessitare di diversi set di servizi? Posso andare con Auryn e lasciare che istanzia automaticamente tutte le dipendenze per ogni modulo (almeno spero di poterlo fare), ma in teoria non dovrebbe essere necessario ...

Aggiornamento:

Per costruire la catena, prendo i nomi delle classi dal file di configurazione, li istanziamo e li sistemo in una coda. Il controller non è a conoscenza delle dipendenze di cui ogni modulo ha bisogno. Al momento, non ce ne sono, perché ogni modulo crea da solo gli oggetti necessari (e questo è ciò che voglio cambiare). Non voglio iniettare ogni modulo con il contenitore, perché questo si trasformerà in un modello di localizzazione del servizio.

Aggiornamento 2:

Necessità:

Ricevo un set di dati da risorse esterne e devo elaborarlo per ottenere alcune metriche. Tuttavia, i dati potrebbero essere danneggiati, inviati in batch o simili e il mio script deve risolverlo (il più possibile). I tipi di problemi possono dipendere dalla risorsa esterna da cui provengono i dati. E la situazione è dinamica, nel senso che nuovi tipi di problemi e nuove risorse esterne potrebbero apparire in futuro.

La mia idea:

Crea moduli. Ogni modulo affronterà problemi specifici. Disporre i moduli in una coda (poiché l'ordine dei moduli può essere importante) ed eseguirli iniettando i dati in entrata come parametro.

La soluzione è strongmente ispirata da questo articolo su SitePoint .

Ma invece di creare le istanze una ad una come nell'esempio:

$firstHandler = new FirstHandler();
$secondHandler = new SecondHandler();
$thirdHandler = new ThirdHandler();

//the code below sets all successors through the first handler
$firstHandler->setSuccessor($secondHandler);
$secondHandler->setSuccessor($thirdHandler);

$result = $firstHandler->handle($request);

Estraggo i nomi delle classi dal file di configurazione e li creo con un ciclo foreach :

$modulesQueue = $config->get("extensions");

foreach ( $modulesQueue as $moduleName ) {
    $moduleName = "extensions\".$moduleName;
    $startModule->setSuccessor(new $moduleName());
}
// Starting the chain:
$ret = $startModule->handle($offset, $tripDataObject, $requestData);

Il problema:

Se creo un'istanza delle classi come questa, non vedo cosa debba aver iniettato ogni modulo (in questo momento nulla, poiché non viene applicato DI, ogni modulo istanzia i servizi di cui ha bisogno). Quindi vedo due opzioni qui: o passo alla creazione manuale dei moduli (che non è affatto flessibile), o comincio a usare Reflection per scoprire di cosa ha bisogno ogni modulo e fornirlo.

    
posta george007 09.05.2018 - 14:52
fonte

2 risposte

1

Ok, quindi il problema principale è creare genericamente gli oggetti del gestore comandi di creazione, ma l'iniezione del costruttore applica i singoli parametri per ciascun gestore comandi.

Questo può essere facilmente risolto separando il processo di costruzione della catena dalla chiamata effettiva del costruttore utilizzando una corrispondente classe factory per ciascuna delle classi del gestore. Ogni costruttore di fabbrica rimane senza parametri, ma all'interno di ogni fabbrica gli oggetti del gestore saranno costruiti con i servizi richiesti iniettati.

Usando uno schema di denominazione rigoroso come FirstHandlerFactory per FirstHandler , SecondHandlerFactory per SecondHandler ecc., il codice risultante sarà simile a

foreach ( $modulesQueue as $moduleName ) {
    $factoryName = "extensions\".$moduleName."Factory";
    $startModule->setSuccessor((new $factoryName())->buildModule());
}

(Nota che non ho sistemato il fatto che questo codice non configuri una catena di comando, per mantenere le cose semplici, ma sono sicuro che l'idea è stata presa.)

La funzione buildModule potrebbe quindi assomigliare a questa

 class FirstHandlerFactory
 { 
     function buildModule()
     {
        return new FirstHandler(/* provide the individual services here */);
     }
 }

Nei test delle tue unità, sarai in grado di creare i tuoi oggetti gestore solo con i servizi di simulazione iniettati, come richiesto per il test specifico, senza utilizzare le fabbriche.

Questa soluzione non richiede né un framework DI, né un meccanismo di riflessione.

    
risposta data 11.05.2018 - 13:50
fonte
3

Sai cosa è Pure Dependency Injection ? L'iniezione di dipendenza non richiede l'uso di contenitori per iniezione di dipendenza. Parli di Auryn, che è un contenitore.

Nulla è intrinsecamente sbagliato nell'usare un contenitore, ma come tutti i framework i loro autori sono desiderosi di farti dipendere da loro. Consentire che ciò accada al tuo codice base ha la conseguenza di cambiare efficacemente la lingua in cui è scritto il codice base. Non puoi pubblicizzare il lavoro come un lavoro PHP. È un lavoro PHP / Auryn. Ora devi trovare programmatori PHP / Auryn.

Piuttosto, lascia che il contenitore ridefinisca la tua lingua per poterla contenere. Non consentire nulla nella tua base di codice ma la tua composizione root (si spera che questo sia solo principale) sappi che Auryn esiste e la maggior parte della tua base di codice può essere mantenuta dai normali programmatori PHP.

To build the chain I take names of classes from the config file, I instantiate them, and I arrange them in a queue. The controller is not aware of the dependencies that each module needs. Right now, there are none, because each module creates needed objects by itself (and this is what I want to change). I don't want to inject each module with the container, because this will turn into a Service locator pattern.

Questo suona più come un problema di test / refactoring. Il test unitario è più costoso e meno efficace se introdotto in ritardo. Quindi non aspettarti che sia facile. Sembra che tu abbia qualcosa che funzioni. Hai test di integrazione? È molto più facile refactoring per codice testabile unità se avete qualche tipo di test in atto. È lento perché i test di integrazione sono lenti.

Se sei nuovo in Auryn è probabilmente una buona idea scrivere un codice da zero che funzioni con esso. Rendilo nella forma ideale che vorresti che fosse la tua base di codice. Guarda come funziona.

Ora hai un modello di quello che stai facendo mentre refactoring la tua base di codice esistente e hai qualche esperienza con Auryn. Quindi basta rompere piccole parti della tua base di codice. Scrivi test unitari per loro e porta la loro costruzione sotto la responsabilità di Auryn. Se Auryn rende difficile all'inizio, prova una soluzione DI pura. In ogni caso, impedisci alle classi di costruire le loro dipendenze o lo fai senza motivo. Costruiscile invece all'esterno e passale. A mano a mano trasformerai la tua base di codice in qualcosa di testabile.

Tieni presente che il punto dei test unitari non è quello di "testare tutte le classi!". È per aiutarmi a leggere il codice. Probabilmente hai un codice di colla noioso che non contiene alcun codice di comportamento reale. Non uccidersi cercando di provarlo. Posso leggere quel codice e prevederne il comportamento senza aiuto. Ma se viene mescolato un codice di comportamento interessante, spostalo in qualcosa di verificabile. Questo è chiamato modello di oggetto umile . È lo stesso di quando le persone insistono per spostare la logica fuori dai punti di vista e nei presentatori.

Se "interessante" sembra soggettivo, ti sto chiedendo di smettere di pensare alla struttura del tuo codice e pensare a come sembra un novellino. Dare loro un test unitario che mostri come viene usata una cosa, quale deve essere la portata del codice che deve essere letto per capirlo, chiarire cosa ti aspetti che faccia e avrai dei neofiti che possono facilmente trovare quella linea di codice che hanno bisogno di modificare. Usa i tuoi test per aiutarmi a leggere il codice. Per favore.

    
risposta data 09.05.2018 - 16:26
fonte

Leggi altre domande sui tag