Pattern per passare il contesto attraverso una catena di metodi


19

Questa è una decisione progettuale che sembra emergere molto: come passare il contesto attraverso un metodo che non ne ha bisogno a 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 contestuale
  • passare la dipendenza
  • curry baz e passalo al bar con la dipendenza impostata come primo arg
  • iniezione di dipendenza

Esempi di dove viene

Elaborazione richiesta HTTP

Vengono spesso utilizzati oggetti di contesto sotto forma di attributi di richiesta: consultare expressjs, servlet Java o .net's owin.

Registrazione

Per la registrazione di Java, le persone usano spesso globi / singleton. Vedi i tipici schemi di log4j / commons logging / java logging.

Le transazioni

I locali dei thread vengono spesso utilizzati per mantenere una transazione o una sessione associata a una catena di chiamate al metodo per evitare di doverle passare come parametri a tutti i metodi che non ne hanno bisogno.


Si prega di utilizzare un esempio più significativo.
Tulains Córdova,

Ho aggiunto alcuni esempi di come viene fuori.
Jamie McCrindle,

3
Intendevo un codice di esempio più significativo.
Tulains Córdova,

Risposte:


11

L'unica risposta corretta è che dipende dai modi di dire del tuo paradigma di programmazione. Se stai usando OO, è quasi certamente errato passare una dipendenza da metodo a metodo a metodo. È un odore di codice in OO. In realtà, questo è uno dei problemi che OO risolve: un oggetto corregge un contesto. Quindi, in OO, un approccio corretto (ci sono sempre altri modi) è quello di fornire la dipendenza tramite il contraente o la proprietà. Un commentatore menziona "Iniezione di dipendenza" e questo è perfettamente legittimo, ma non è strettamente necessario. Fornisci la dipendenza in modo che sia disponibile come membro a fooe baz.

Lei parla di curry, quindi suppongo che la programmazione funzionale non sia fuori discussione. In tal caso, un equivalente filosofico del contesto dell'oggetto è la chiusura. Qualsiasi approccio che, ancora una volta, corregge la dipendenza in modo che sia disponibile per le persone a carico funziona bene. Currying è uno di questi approcci (e ti fa sembrare intelligente). Ricorda solo che ci sono altri modi per chiudere una dipendenza. Alcuni sono eleganti e altri terribili.

Non dimenticare la 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 altro codice. La gente di AOP la chiama " tessitura ". Gli aspetti comuni sono inseriti nel codice nei punti appropriati. Questo rende il tuo codice più facile da pensare ed è dannatamente bello, ma aggiunge anche un nuovo onere di prova. Avrai bisogno di un modo per determinare se i tuoi artefatti finali sono validi. AOP ha risposte anche per questo, quindi non sentirti intimidito.


Affermare che passare parametri all'interno dei metodi OO è un odore di codice è un'affermazione molto controversa. Direi l'esatto contrario: incoraggiare la miscelazione di stato e funzionalità all'interno di una classe è uno dei più grandi errori commessi dal paradigma OO ed evitarlo iniettando dipendenze direttamente nei metodi, piuttosto che attraverso un costruttore è un segno di un pezzo ben progettato di codice, OO o no.
David Arno,

3
@DavidArno Suggerirei di usare un paradigma diverso rispetto alla conclusione della signfulness dell'oggetto è "uno dei più grandi errori commessi dal paradigma OO" e quindi aggirare il paradigma. Non ho nulla contro quasi nessun approccio, ma generalmente non mi piace il codice in cui l'autore sta combattendo il loro strumento. Lo stato privato è una caratteristica distintiva di OO. Se si evita quella funzione, si perde parte del potere di OO.
Scant Roger,

1
@DavidArno Una classe che è tutto stato e nessuna funzionalità non ha alcun meccanismo per imporre relazioni invarianti nello stato. Una tale classe non è affatto OO.
Kevin Krumwiede,

@KevinKrumwiede, in una certa misura, hai applicato la riduzione ad absudium al mio commento, ma il tuo punto è ancora ben espresso. L'invarianza sullo stato è una parte importante del "passaggio da OO". Pertanto, evitare la miscelazione di funzionalità e stato deve consentire una funzionalità sufficiente in un oggetto stato, se necessario, per raggiungere l'invarianza (campi incapsulati impostati dal costruttore e accessibili tramite getter).
David Arno,

@ScantRoger, sono d'accordo che può essere adottato un altro paradigma, vale a dire il paradigma funzionale. È interessante notare che la maggior parte dei linguaggi "OO" moderni ha un elenco crescente di caratteristiche funzionali e quindi è possibile attenersi a tali linguaggi e adottare il paradigma della funzione, senza "combattere lo strumento".
David Arno,

10

Se bardipende da baz, che a sua volta richiede dependency, allora barrichiede dependencyanche troppo per un corretto utilizzo baz. Pertanto, gli approcci corretti sarebbero o passare la dipendenza come parametro a bar, o curry baze passarla a bar.

Il primo approccio è più semplice da implementare e leggere, ma crea un accoppiamento tra bare baz. Il secondo approccio rimuove tale accoppiamento, ma potrebbe risultare in un codice meno chiaro. L'approccio migliore dipenderà quindi probabilmente dalla complessità e dal comportamento di entrambe le funzioni. Ad esempio, se bazo dependencyhanno effetti collaterali, la facilità di test sarà probabilmente un driver di grandi dimensioni in cui viene scelta la soluzione.

Suggerirei che tutte le altre opzioni che proponete siano di natura "confusa" e che possano causare problemi sia con i test che con problemi difficili da rintracciare.


1
Sono quasi completamente d'accordo. L'iniezione di dipendenza può essere un altro approccio non "confuso".
Jonathan van de Veen,

1
@JonathanvandeVeen, sicuramente l'atto stesso di passare dependencyattraverso i parametri è l'iniezione di dipendenza?
David Arno,

2
@DavidArno I framework di iniezione delle dipendenze non eliminano questo tipo di dipendenze, ma semplicemente le spostano. La magia è che li spostano al di fuori delle tue lezioni, in un posto in cui il test è Problema di qualcun altro.
Kevin Krumwiede,

@JonathanvandeVeen Sono d'accordo, l'iniezione di dipendenza è una soluzione valida. In effetti è quello che sceglierei più spesso.
Jamie McCrindle,

1

Filosoficamente parlando

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 di OO è, si potrebbe dire, tutto basato sul contesto. È un vasto foglio di carta bianco pieno di possibilità.

Trattare con il codice esistente è un contesto diverso, bene.



Sto lavorando allo stesso problema proprio adesso. Bene, sto riparando le centinaia di righe di codice copy-n-paste che sono state fatte proprio per poter iniettare un valore.

Modularizza il codice

Ho gettato via 600 righe di codice duplicato, quindi ho eseguito il refactoring, quindi invece di "A chiama B chiama C chiama D ..." Ho "Chiama A, ritorna, Chiama B, ritorna, Chiama C ...". Ora dobbiamo solo iniettare il valore in uno di quei 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 cambia solo 1 riga per passare la variabile nella struttura modulare refactored; e una piccola modifica nel metodo E per usarlo.


chiusure

Un thread di programmatori - "Perché un programma dovrebbe usare una chiusura?"

In sostanza, si stanno iniettando valori in un metodo che restituisce un metodo personalizzato con i valori. Tale metodo personalizzato viene successivamente eseguito.

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


Questo approccio sembra stranamente familiare ...

Roger sul problema dell'accoppiamento temporale (il tuo link), @Snowman. È importante che l'ordine di esecuzione richiesto sia incapsulato.
radarbob
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.