No trabajo con demasiada frecuencia con los iteradores Java / C # directamente, pero cuando lo hago, siempre me pregunto cuál fue la razón para diseñar los iteradores de la siguiente manera.
Para comenzar, debe mover el iterador, para verificar si hay algunos datos, debe verificar si hay un elemento siguiente.
El concepto más atractivo para mí es este iterador: "comienza" desde el principio, y puede verificar este estado, digamos isValid
. Entonces el ciclo de toda la colección se vería así:
while (iter.isValid())
{
println(iter.current());
iter.next();
}
Como usted aquí para verificar si hay un elemento siguiente, vaya allí y luego verifique si el estado es válido.
YMMV pero de todos modos, ¿hay alguna ventaja del siguiente iterador (como en Java / C #) sobre este iterador?
Nota: esta es una pregunta conceptual sobre el diseño del lenguaje.
Respuestas:
Creo que una ventaja del
MoveNext()
modelo de C # /. NET sobre el modelo de JavahasNext()
es que el primero implica que se puede hacer algún trabajo. ElhasNext()
método implica una simple verificación de estado. Pero, ¿qué sucede si está iterando una secuencia diferida y tiene que ir a una base de datos para determinar si hay algún resultado? En ese caso, la primera llamada ahasNext()
puede ser una operación de bloqueo larga. El nombramiento delhasNext()
método puede ser muy engañoso en cuanto a lo que realmente está sucediendo.Para un ejemplo del mundo real, este problema de diseño me mordió al implementar las API de Extensiones Reactivas (Rx) de Microsoft en Java. Específicamente, para que el iterador creado por
IObservable.asIterable()
funcione correctamente, elhasNext()
método tendría que bloquear y esperar a que llegue la siguiente notificación de elemento / error / finalización. Eso no es intuitivo: el nombre implica una simple verificación de estado. Compare eso con el diseño de C #, dondeMoveNext()
tendría que bloquear, lo que no es un resultado totalmente inesperado cuando se trata de una secuencia evaluada de manera perezosa.Mi punto es este: el modelo de "siguiente iterador" es preferible al modelo de "este iterador" porque el modelo de "este iterador" es a menudo una mentira: es posible que deba buscar previamente el siguiente elemento para verificar El estado del iterador. Un diseño que comunique esa posibilidad claramente es preferible, en mi opinión. Sí, las convenciones de nomenclatura de Java siguen el modelo "siguiente", pero las implicaciones de comportamiento son similares a las del modelo "este iterador".
Aclaración: Quizás contrastar Java y C # fue una mala elección, porque el modelo de Java es engañoso. Considero que la distinción más importante en los modelos presentados por el OP es que el modelo de "este iterador" desacopla la lógica de "es válido" de la lógica de "recuperar el elemento actual / próximo", mientras que es una implementación ideal de "siguiente iterador" combina estas operaciones. Creo que es apropiado combinarlos porque, en algunos casos, determinar si el estado del iterador es válido requiere la captación previa del siguiente elemento , por lo que esa posibilidad debe hacerse lo más explícita posible.
No veo una diferencia de diseño significativa entre:
Pero veo una diferencia significativa aquí:
Tanto en el ejemplo
isValid()
como en elhasNext()
ejemplo, la operación que implica una verificación de estado rápida y sin bloqueo puede, de hecho, ser una operación de bloqueo lenta . En elmoveNext()
ejemplo, la mayor parte del trabajo se realiza en el método que esperaría, independientemente de si se trata de una secuencia evaluada con impaciencia o con pereza.fuente
hasNext()
es similar aisValid()
, ynext()
es similar acurrent()
.Si hay que hacer algún cálculo para calcular cada valor, tener que obtener el siguiente valor antes de pedir el primer valor le permite diferir el procesamiento de ese primer valor más allá de la construcción del iterador.
Por ejemplo, el iterador podría representar una consulta que aún no se ha ejecutado. Cuando se solicita el primer elemento, se consulta la base de datos para obtener los resultados. Usando su diseño propuesto, ese cálculo tendría que hacerse luego de la construcción del iterador (lo que hace que sea más difícil aplazar la ejecución) o al llamar
IsValid
, en cuyo caso es realmente un nombre inapropiado, ya que llamarIsValid
realmente obtendría el siguiente valor.fuente
hasNext
, solo su estado sobre el estado actual, no el futuro. O tomando otra vista: compare esto con los iteradores de C ++, verifique si el estado es válido(current!=end)
sin tocar los datos reales.hasNext
al principio, aquí se ejecuta la consulta, se recuperan los datos. En el segundo modelo, llamoisValid
, aquí se ejecuta la consulta. Entonces, el punto de ejecución de la consulta es el mismo.IsValid
lo que realmente estás haciendo es obtener el siguiente valor. Esto significa que está haciendo exactamente lo mismo que el iterador existente (no tiene el actual válido desde el principio, sin necesidad de llamar a un método para obtenerlo) solo tiene un nombre erróneo.