¿Son las funciones de primera clase un sustituto del patrón de estrategia?


15

El patrón de diseño de la estrategia a menudo se considera como un sustituto de las funciones de primera clase en los idiomas que carecen de ellas.

Entonces, por ejemplo, digamos que desea pasar la funcionalidad a un objeto. En Java, tendría que pasar al objeto otro objeto que encapsule el comportamiento deseado. En un lenguaje como Ruby, simplemente pasaría la funcionalidad en forma de una función anónima.

Sin embargo, estaba pensando en ello y decidí que quizás la Estrategia ofrece más que una simple función anónima.

Esto se debe a que un objeto puede mantener un estado que existe independientemente del período en que se ejecuta su método. Sin embargo, una función anónima por sí sola solo puede mantener un estado que deja de existir en el momento en que la función finaliza la ejecución.

En un lenguaje orientado a objetos que admite funciones de primera clase, ¿el patrón de estrategia tiene alguna ventaja sobre el uso de funciones?


10
"Sin embargo, una función anónima por sí sola solo puede mantener un estado que deja de existir en el momento en que la función finaliza la ejecución".
Giorgio

"Sin embargo, una función anónima por sí sola solo puede mantener un estado que deja de existir en el momento en que la función finaliza la ejecución". : sin olvidar las variables globales y las variables estáticas como mínimo.
gbjbaanb

Respuestas:


13

Cuando el lenguaje admite referencias a la función ( Java lo hace desde la versión 8 ), a menudo son una buena alternativa para las estrategias, porque generalmente expresan lo mismo con menos sintaxis. Sin embargo, hay algunos casos en los que un objeto real puede ser útil.

Una interfaz de estrategia puede tener múltiples métodos. Tomemos una interfaz RouteFindingStragegycomo ejemplo, que encapsula diferentes algoritmos de búsqueda de rutas. Podría declarar métodos como

  • Route findShortestRoute(Node start, Node destination)
  • boolean doesRouteExist(Node start, Node destination)
  • Route[] findAllPossibleRoutes(Node start, Node destination)
  • Route findShortestRouteToClosestDestination(Node start, Node[] destinations)
  • Route findTravelingSalesmanRoute(Node[] stations)

que luego sería implementado por la estrategia. Algunos algoritmos de búsqueda de rutas pueden permitir optimizaciones internas para algunos de estos casos de uso y otros no, por lo que el implementador puede decidir cómo implementar cada uno de estos métodos.

Otro caso es cuando la estrategia tiene un estado interno. Claro, en algunos idiomas los cierres pueden tener un estado interno, pero cuando este estado interno se vuelve muy complejo, a menudo se vuelve más elegante promover el cierre a una clase completa.


2
"Cuando este estado interno se vuelve muy complejo, a menudo se vuelve útil promover el cierre a una clase completa": ¿Por qué? Si el estado interno se vuelve complejo, también puede colocarlo en un objeto / registro que esté almacenado dentro del cierre.
Giorgio

1
@Giorgio Pero entonces tendrías que mantener dos entidades de sintaxis: el cierre y la clase que administra su estado interno. Entonces el código podría simplificarse moviendo el cierre a esa clase. Esto podría ser mejor o no, lo que depende del caso de uso exacto, el lenguaje de programación y las preferencias personales.
Philipp

Me parece una visión razonable.
Giorgio

5

No es cierto que una función anónima solo pueda mantener un estado que deja de existir cuando la función finaliza la ejecución.

Tome el siguiente ejemplo en Common Lisp:

(defun number-strings (ss)
  (let ((counter 0))
    (mapcar #'(lambda (s) (format nil "~a: ~a" (incf counter) s)) ss)))

Esta función toma una lista de cadenas y antepone un contador a cada elemento de la lista. Entonces, por ejemplo, invocando

(number-strings '("a" "b" "c"))

da

("1: a" "2: b" "3: c")

La función number-stringsutiliza internamente una función anónima con una variable counterque contiene el estado (el valor actual del contador) que se reutiliza cada vez que se invoca la función.

En general, puede pensar en un cierre como un objeto con un solo método. Alternativamente, un objeto es una colección de cierres que comparten las mismas variables cerradas. Por lo tanto, no estoy seguro de si hay casos en los que necesite usar un objeto en lugar de un cierre: diría que ambas son formas de ver el mismo patrón desde diferentes perspectivas.

En particular, el patrón de estrategia requiere un objeto con un solo método, por lo que un cierre debería hacer el trabajo. Pero, como Philipp ha observado en su respuesta, dependiendo de las circunstancias (estado complejo) y los lenguajes de programación, puede obtener una solución más elegante mediante el uso de objetos.


Entonces, en un lenguaje que admita funciones de primera clase como cierres, ¿utilizaría alguna vez la estrategia 'clásica'?
Aviv Cohn

1
Tiendo a estar de acuerdo con Philipp: depende del idioma y las preferencias personales. Siempre elegiría el enfoque que haga que la notación sea lo más simple posible. Por ejemplo, en Lisp podría definir mi estado como una lista de variables a través de letay luego definir mi cierre dentro de él. Básicamente, habría definido un objeto con un método sobre la marcha. En otro lenguaje (por ejemplo, Java) podría ser más conveniente (sintácticamente más simple) definir un objeto apropiado para mantener el estado. Entonces, decidiría de un caso a otro.
Giorgio

1

El hecho de que dos diseños puedan resolver el mismo problema no significa que sean sustituciones directas entre sí.

Si necesita realizar un seguimiento del estado en un programa funcional, no mute una variable cerrada, incluso si el idioma lo permite. Arregla para llamar a una función que toma un estado como argumento y devuelve el nuevo estado como su valor de retorno.

Su arquitectura se verá muy diferente, pero logrará el mismo objetivo. No intente forzar los patrones de un paradigma directamente sobre el otro.


"Si necesita realizar un seguimiento del estado en un programa funcional, no mutará una variable cerrada, incluso si el idioma lo permite": soy un fanático del estilo puramente funcional y estoy de acuerdo con su consejo. Por otro lado, los cierres no son solo una construcción funcional, y mucho menos puramente funcional. La idea de cerrar las variables del contexto léxico es ortogonal a la transparencia / inmutabilidad referencial.
Giorgio

Lo siento, pero no puedo seguir tu argumentación. ¿Qué tiene que ver el manejo del estado en la programación puramente funcional con la pregunta en cuestión?
Philipp

1
El punto es que si usa una parte de un paradigma funcional, como funciones de primera clase, no se sorprenda si necesita incorporar otras partes del paradigma para que funcione sin problemas.
Karl Bielefeldt

1

La estrategia es un concepto , una receta útil para resolver un problema particular y recurrente. No es una construcción de lenguaje, ni se trata de ninguna forma de implementación . Se puede usar un cierre para implementar la Estrategia un día y Observador al día siguiente.

El término Estrategia es principalmente útil en conversaciones con otros programadores para expresar de manera concisa su intención. No hay nada mágico al respecto.


2
La pregunta menciona específicamente el patrón de diseño de la estrategia que tiene una estructura de clase específica. El otro significado de "estrategia" como un plan de acción destinado a lograr un objetivo específico es inexacto en este contexto.

Me inclino a estar de acuerdo con @Snowman. ¿Estás seguro de que estás hablando del patrón de estrategia ?
Rowan Freeman

1
@Snowman, incluso la página a la que se vinculó no indica exactamente cómo se debe implementar este patrón, sino que dan ejemplos en lenguajes específicos, pero no hay nada en el diagrama UML que diga que necesito usar herencia C ++, interfaces Java o bloques Ruby . Por lo tanto, no estoy de acuerdo con su análisis.
idoby
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.