Modelli per il passaggio del contesto attraverso una catena di metodi

18

Questa è una decisione progettuale che sembra emergere parecchio: come passare il contesto attraverso un metodo che non ne ha bisogno per un metodo che lo fa. C'è una risposta giusta o dipende dal contesto.

Codice di esempio che richiede una soluzione

// needs the dependency
function baz(session) {
  session('baz');
}

// doesn't care about the dependency
function bar() {
  baz();
}

// needs the dependency
function foo(session) {
   session('foo')
   bar();
}

// creates the dependency
function start() {
  let session = new Session();
  foo(session);
}

Possibili soluzioni

  • ThreadLocal
  • globale
  • oggetto contesto
  • passa la dipendenza attraverso
  • curry baz e passalo in bar con il set di dipendenze come primo argomento
  • iniezione di dipendenza

Esempi di dove si trova

Elaborazione richiesta HTTP

Gli oggetti di contesto sotto forma di attributi di richiesta sono spesso usati: vedere expressjs, Java Servlet o .net owin.

Accesso

Per le persone che registrano Java spesso usano globals / singleton. Vedi i tipici log di registrazione / java log4j / commons.

Rapporti

I locals di thread sono spesso usati per mantenere una transazione o una sessione associata a una catena di chiamate di metodo per evitare di doverli passare come parametri a tutti i metodi che non ne hanno bisogno.

    
posta Jamie McCrindle 27.11.2015 - 13:57
fonte

3 risposte

11

L'unica risposta corretta è che dipende dagli idiomi del tuo paradigma di programmazione. Se stai usando OO, è quasi certamente scorretto passare una dipendenza dal metodo al metodo al metodo. È un odore di codice in OO. In effetti, questo è uno dei problemi che OO risolve - un oggetto risolve un contesto. Quindi, in OO, un approccio corretto (ci sono sempre altri modi) è quello di fornire la dipendenza tramite contructor o proprietà. Un commentatore menziona "Dependency Injection" e questo è perfettamente legittimo, ma non è strettamente necessario. Fornisci la dipendenza in modo che sia disponibile come membro su foo e baz .

Mi riferisci al curriculum, quindi presumo che la programmazione funzionale non sia fuori questione. In tal caso, un equivalente filosofico del contesto oggettuale è la chiusura. Qualsiasi approccio che, ancora una volta, corregge la dipendenza in modo che sia disponibile per i dipendenti, funziona perfettamente. Il curry è uno di questi approcci (e ti fa sembrare intelligente). Ricorda solo che ci sono altri modi per chiudere una dipendenza. Alcuni di loro sono eleganti e alcuni di loro orribili.

Non dimenticare Programmazione orientata agli aspetti . Sembra essere caduto in disgrazia negli ultimi anni, ma il suo obiettivo principale è risolvere esattamente il problema che descrivi. In effetti, il classico esempio di Aspect è la registrazione. In AOP, la dipendenza viene aggiunta automaticamente dopo la scrittura di un altro codice. Le persone AOP chiamano questo " tessere ". Aspetti comuni sono intrecciati nel codice nei luoghi appropriati. Questo rende il tuo codice più facile da pensare ed è dannatamente interessante, ma aggiunge anche un nuovo carico di test. Avrai bisogno di un modo per determinare che i tuoi artefatti finali siano validi. AOP ha anche risposte per questo, quindi non sentirti intimidito.

    
risposta data 27.11.2015 - 16:51
fonte
10

Se bar dipende da baz , che a sua volta richiede dependency , quindi bar richiede dependency anche per utilizzare correttamente baz . Pertanto, l'approccio corretto sarebbe quello di passare la dipendenza attraverso come parametro a bar , o curry baz e passare a bar .

Il primo approccio è più semplice da implementare e leggere, ma crea un accoppiamento tra bar e baz . Il secondo approccio rimuove tale accoppiamento, ma potrebbe comportare un codice meno chiaro. L'approccio migliore sarà quindi probabilmente dipendente dalla complessità e dal comportamento di entrambe le funzioni. Ad esempio, se baz o dependency hanno effetti collaterali, la facilità di test sarà probabilmente un driver grande in cui viene scelta la soluzione.

Suggerirei che tutte le altre opzioni che proponi siano di natura "hacky" e che potrebbero portare a problemi sia con i test che con bug difficili da rintracciare.

    
risposta data 27.11.2015 - 14:10
fonte
1

Parlare con filosofia

Sono d'accordo con la preoccupazione di David Arno .

Sto leggendo l'OP come alla ricerca di soluzioni di implementazione. Tuttavia, la risposta è cambiare il design . "Patterns"? Il design OO è, si potrebbe dire, tutto sul contesto. È un vasto foglio di carta bianco, pieno di possibilità.

Gestire il codice esistente è un contesto diverso, bene.

Sto lavorando a 'esattamente lo stesso problema in questo momento. Bene, sto fissando le centinaia di righe di codice copy-n-paste che sono state fatte proprio così un valore può essere iniettato.

Modularizza il codice

Ho buttato via 600 righe di codice duplicato e poi refactored così invece di "A chiama B chiama C chiama D ..." Ho "Chiama A, ritorna, Chiama B, ritorna, Chiama C ...". Ora abbiamo solo bisogno di iniettare il valore in uno di questi metodi, diciamo il metodo E.

Aggiungi un parametro predefinito al costruttore. I chiamanti esistenti non cambiano - "opzionale" è la parola chiave qui. Se non viene passato un argomento, viene utilizzato il valore predefinito. Quindi solo 1 riga cambia per passare la variabile nella struttura modulare refactored; e un piccolo cambiamento nel metodo E per usarlo.

Chiusure

Un thread dei programmatori - "Perché un programma dovrebbe utilizzare una chiusura? "

In sostanza, stai iniettando valori in un metodo che restituisce un metodo personalizzato con i valori. Questo metodo personalizzato viene successivamente eseguito.

Questa tecnica ti permetterebbe di modificare un metodo esistente senza cambiarne la firma.

    
risposta data 29.11.2015 - 00:12
fonte

Leggi altre domande sui tag