Estoy escribiendo un tipo de implementación de cola que tiene un TryDequeue
método que usa un patrón similar a varios TryParse
métodos .NET , donde devuelvo un valor booleano si la acción tuvo éxito, y uso un out
parámetro para devolver el valor en cola real.
public bool TryDequeue(out Message message) => _innerQueue.TryDequeue(out message);
Ahora, me gusta evitar los out
params siempre que puedo. C # 7 nos brinda delicaciones variables para facilitar el trabajo con ellas, pero sigo considerando los parámetros más un mal necesario que una herramienta útil.
El comportamiento que quiero de este método es el siguiente:
- Si hay un artículo que quitar, devuélvalo.
- Si no hay elementos que retirar (la cola está vacía), proporcione a la persona que llama la información suficiente para actuar de manera adecuada.
- No solo devuelva un elemento nulo si no quedan elementos.
- No arroje una excepción si intenta salir de una cola vacía.
En este momento, una persona que llama de este método casi siempre usaría un patrón como el siguiente (usando la sintaxis variable C # 7):
if (myMessageQueue.TryDequeue(out Message dequeued))
MyMessagingClass.SendMessage(dequeued)
else
Console.WriteLine("No messages!"); // do other stuff
Lo cual no es lo peor, todo dicho. Pero no puedo evitar sentir que podría haber mejores formas de hacer esto (estoy totalmente dispuesto a admitir que tal vez no exista). Odio cómo la persona que llama tiene que romper su flujo con un condicional cuando todo lo que quiere es obtener un valor si existe.
¿Cuáles son algunos otros patrones que existen para lograr este mismo comportamiento de "intento"?
Para el contexto, este método puede llamarse potencialmente en proyectos VB, por lo tanto, puntos de bonificación por algo que funciona bien en ambos. Sin embargo, este hecho debería tener muy poco peso.
Option<T>
estructura y devuélvala. Esasbool Try(..., out data)
funciones son una abominación.Respuestas:
Utilice un tipo de Opción, es decir, un objeto de un tipo que tiene dos versiones, generalmente llamado "Algunos" (cuando hay un valor) o "Ninguno" (cuando no hay un valor) ... O de vez en cuando Se llaman Just and Nothing. Luego, hay funciones en estos tipos que le permiten acceder al valor si está presente, probar la presencia y, lo más importante, un método que le permite aplicar una función que devuelve una Opción adicional al valor si está presente (generalmente en C # esto debería se llamará FlatMap, aunque en otros idiomas a menudo se llama Bind en su lugar ... El nombre es crítico en C # porque tener un método s de este nombre y tipo le permite usar sus objetos Option en las declaraciones LINQ).
Las características adicionales pueden incluir métodos como IfPresent e IfNotPresent para invocar acciones en las condiciones relevantes, y OrElse (que sustituye un valor predeterminado cuando no hay ningún valor pero no es una opción), y así sucesivamente.
Su ejemplo podría verse así:
Este es el patrón de mónada Opción (o Quizás), y es extremadamente útil. Existen implementaciones existentes (por ejemplo, https://github.com/nlkl/Optional/blob/master/README.md ), pero tampoco es difícil para usted.
(Es posible que desee extender este patrón para que devuelva una descripción de la causa del error en lugar de nada cuando el método falla ... Esto es totalmente factible y a menudo se llama la mónada Either; como su nombre lo indica, puede usar el mismo FlatMap patrón para facilitar el trabajo en ese caso también)
fuente
Option
/Maybe
es que es una mónada (si se implementa como una, por supuesto), lo que significa que se puede encadenar, envolver, desenvolver y procesar de forma segura sin tener que manejar los diferentes casos directamente, y este encadenamiento y el procesamiento se puede hacer con expresiones de consulta LINQ.flatMap
/bind
se llamaSelectMany
en .NET.Try...
las convenciones de nomenclatura en .NET (y mantenedores potencialmente confusos).Las respuestas dadas son buenas y yo iría con una de ellas. Considere que esta respuesta es solo completar algunas esquinas con algunas ideas alternativas:
Hacer subclases de
Message
llamadasSomeMessage
yNoMessage
.Dequeue
ahora puede regresarNoMessage
si no hay mensaje ySomeMessage
si hay un mensaje. Si la persona que llama se preocupa por detectar en qué caso se encuentra, puede hacerlo fácilmente mediante inspección de tipo. Si no lo hacen, bueno, simplementeNoMessage
no hagan nada cada vez que se llame a alguno de sus métodos, y oigan, obtengan lo que pidieron.Lo mismo que lo anterior, pero hace
Message
que sea implícitamente convertiblebool
(o implementeoperator true / operator false
). Ahora puede decirif (message)
y hacer que sea verdadero si el mensaje es bueno y falso si es malo. (¡Esta es casi una mala idea, pero la incluyo para completarla!)C # 7 tiene tuplas. Devuelve una
(bool, Message)
tupla.Haga
Message
un tipo de estructura y regreseMessage?
.fuente
En C # 7, puede usar la coincidencia de patrones para lograr lo mismo de una manera más elegante:
Sin embargo, en este caso particular, probablemente usaría eventos en lugar de sondear la cola repetidamente.
fuente
TryDequeue
devolver alguna interfaz de marcador con unaMessage
implementación y alguna implementación de "nada"? Claro, es más elegante en el sitio de llamadas, pero está atascado implementando 3 implementaciones en el código real, lo que en sí mismo puede ser imposible siMessage
es un tipo existente.No hay nada de malo con un método Try ... (que tiene un parámetro out) para un escenario en el que la falla (sin valor) es tan común como el éxito.
Pero, si insiste en hacer las cosas de manera diferente, es posible que desee devolver un mensaje vacío y enviarlo (ya no es su problema) o posponer la declaración if hasta que realmente deba saber si tiene un mensaje con contenido o no.
Tenga en cuenta que alguien tiene que hacer la prueba en algún momento de todos modos. Yo diría que la prueba debe hacerse cuando y donde la pregunta es actual. Esto es en el momento de la extracción.
fuente