Patrones para pasar contexto a través de una cadena de métodos

19

Esta es una decisión de diseño que parece surgir bastante: cómo pasar el contexto a través de un método que no lo necesita a un método que sí lo necesita. ¿Hay una respuesta correcta o depende del contexto?

Código de muestra que requiere una solución

// 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);
}

Soluciones posibles

  • hilo local
  • global
  • objeto de contexto
  • pasar la dependencia a través de
  • curry baz y pasarlo a la barra con la dependencia establecida como el primer argumento
  • inyección de dependencia

Ejemplos de dónde viene

Procesamiento de solicitudes HTTP

Con frecuencia se usan objetos de contexto en forma de atributos de solicitud: consulte expressjs, Java Servlets o .net's owin.

Inicio sesión

Para el registro de Java, la gente a menudo usa globals / singletons. Vea los patrones típicos de log4j / commons logging / java logging.

Actas

Los locales de subprocesos a menudo se usan para mantener una transacción o sesión asociada con una cadena de llamadas a métodos para evitar la necesidad de pasarlos como parámetros a todos los métodos que no los necesitan.

Jamie McCrindle
fuente
Por favor, use un ejemplo más significativo.
Tulains Córdova
He agregado algunos ejemplos de dónde surge.
Jamie McCrindle
3
Me refería a un código de ejemplo más significativo.
Tulains Córdova

Respuestas:

11

La única respuesta justa es que depende de los modismos de su paradigma de programación. Si está utilizando OO, es casi seguro que sea incorrecto pasar una dependencia de un método a otro. Es un olor a código en OO. De hecho, ese es uno de los problemas que OO resuelve: un objeto arregla un contexto. Entonces, en OO, un enfoque correcto (siempre hay otras formas) es entregar la dependencia a través de un constructor o propiedad. Un comentarista menciona "Inyección de dependencia" y eso es perfectamente legítimo, pero no es estrictamente necesario. Solo proporcione la dependencia para que esté disponible como miembro para fooy baz.

Mencionas curry, así que asumiré que la programación funcional no está descartada. En ese caso, un equivalente filosófico del contexto del objeto es el cierre. Cualquier enfoque que, una vez más, arregle la dependencia para que esté disponible para los dependientes funciona bien. El curry es uno de esos enfoques (y te hace sonar inteligente). Solo recuerde que hay otras formas de cerrar una dependencia. Algunos de ellos son elegantes y otros horribles.

No te olvides de la programación orientada a aspectos . Parece haber caído en desgracia en los últimos años, pero su objetivo principal es resolver exactamente el problema que usted describe. De hecho, el ejemplo clásico de Aspect es el registro. En AOP, la dependencia se agrega automáticamente después de escribir otro código. La gente de AOP llama a esto " tejido ". Los aspectos comunes están entretejidos en el código en los lugares apropiados. Esto hace que su código sea más fácil de pensar y es bastante genial, pero también agrega una nueva carga de prueba. Necesitará una forma de determinar que sus artefactos finales son sólidos. AOP también tiene respuestas para eso, así que no te sientas intimidado.

Roger escaso
fuente
Afirmar que pasar parámetros dentro de los métodos OO es un olor a código es una declaración muy controvertida. Argumentaría todo lo contrario: fomentar el estado de mezcla y la funcionalidad dentro de una clase es uno de los errores más grandes cometidos por el paradigma OO y evitarlo al inyectar dependencias directamente en los métodos, en lugar de hacerlo a través de un constructor, es un signo de una pieza bien diseñada. de código, ya sea OO o no.
David Arno
3
@DavidArno Sugeriría que usar un paradigma diferente frente a la capacidad de estado del objeto final es "uno de los mayores errores cometidos por el paradigma OO" y luego eludir el paradigma. No tengo nada en contra de casi ningún enfoque, pero generalmente no me gusta el código donde el autor está luchando contra su herramienta. El estado privado es una característica distintiva de OO. Si evita esa característica, pierde parte del poder de OO.
Escaso Roger
1
@DavidArno Una clase que es todo estado y sin funcionalidad no tiene ningún mecanismo para imponer relaciones invariantes en el estado. Tal clase no es en absoluto OO.
Kevin Krumwiede
@KevinKrumwiede, hasta cierto punto, ha aplicado reducto ad absudium a mi comentario, pero su punto aún está bien hecho. La invarianza en el estado es una parte importante de "pasar de OO". Por lo tanto, evitar mezclar la funcionalidad y el estado tiene que permitir suficiente funcionalidad en un objeto de estado según sea necesario para lograr la invariancia (campos encapsulados establecidos por el constructor y accedidos a través de getters).
David Arno
@ScantRoger, estoy de acuerdo con que se puede adoptar otro paradigma, el paradigma funcional. Curiosamente, los lenguajes "OO" más modernos tienen una lista creciente de características funcionales y, por lo tanto, es posible seguir con esos lenguajes y adoptar el paradigma de la función, sin "luchar contra la herramienta".
David Arno
10

Si bardepende de baz, lo que a su vez requiere dependency, entonces también lo barrequiere dependencypara usarlo correctamente baz. Por lo tanto, los enfoques correctos serían pasar la dependencia a través de un parámetro baro curry bazy pasar eso a bar.

El primer enfoque es más sencillo de implementar y leer, pero crea un acoplamiento entre bary baz. El segundo enfoque elimina ese acoplamiento, pero podría dar como resultado un código menos claro. Por lo tanto, qué enfoque es mejor dependerá de la complejidad y el comportamiento de ambas funciones. Por ejemplo, si tiene bazo dependencytiene efectos secundarios, la facilidad de prueba probablemente será un gran impulsor para elegir la solución.

Sugeriría que todas las demás opciones que proponga sean de naturaleza "hacky" y puedan generar problemas tanto con las pruebas como con errores difíciles de rastrear.

David Arno
fuente
1
Casi estoy completamente de acuerdo. La inyección de dependencia puede ser otro enfoque no "hacky".
Jonathan van de Veen
1
@JonathanvandeVeen, ¿seguramente el acto mismo de pasar dependencypor parámetros es la inyección de dependencia?
David Arno
2
Los marcos de inyección de dependencia de @DavidArno no eliminan este tipo de dependencias, solo las mueven. La magia es que los mueven fuera de sus clases, a un lugar donde las pruebas son el problema de alguien más.
Kevin Krumwiede
@JonathanvandeVeen Estoy de acuerdo, la inyección de dependencia es una solución válida. De hecho, es el que elegía con más frecuencia.
Jamie McCrindle
1

Hablando filosóficamente

Estoy de acuerdo con la preocupación de David Arno .

Estoy leyendo el OP en busca de soluciones de implementación. Sin embargo, la respuesta es cambiar el diseño . "Patrones"? El diseño OO es, se podría decir, todo sobre el contexto. Es una vasta hoja de papel en blanco llena de posibilidades.

Tratar con el código existente es un contexto diferente, bueno.



Estoy trabajando en 'zactly el mismo problema ahora mismo. Bueno, estoy arreglando los cientos de líneas de código copiar y pegar que se hicieron para que se pueda inyectar un valor.

Modularizar el código

Tiré 600 líneas de código duplicado y luego las refactoricé para que en lugar de "A llame a B llame a C llame a D ..." Tengo "Llamar A, regresar, Llamar B, regresar, Llamar C ...". Ahora solo necesitamos inyectar el valor en uno de esos métodos, digamos el método E.

Agregue un parámetro predeterminado al constructor. Las llamadas existentes no cambian: "opcional" es la palabra clave aquí. Si no se pasa un argumento, se utiliza el valor predeterminado. Luego, solo 1 cambio de línea para pasar la variable a la estructura modular refactorizada; y un pequeño cambio en el método E para usarlo.


Cierres

Un hilo de programadores: "¿Por qué un programa usaría un cierre?"

Básicamente, está inyectando valores en un método que devuelve un método personalizado con los valores. Ese método personalizado se ejecuta posteriormente.

Esta técnica le permitiría modificar un método existente sin cambiar su firma.

radarbob
fuente
Este enfoque parece extrañamente familiar ...
Roger sobre el tema del acoplamiento temporal (su enlace), @Snowman. Es importante que la orden de ejecución requerida esté encapsulada.
radarbob