Una propiedad que puede representar tanto una sola fecha como un rango de fechas: ¿Cómo modelar eso adecuadamente?

8

Trabajo en un sistema que puede representar una "estimación de envío" de dos maneras:

  1. Una fecha específica: el artículo está garantizado para enviarse en esa fecha
  2. Intervalo de un día: el artículo se enviará en días "X a Y" a partir de hoy

La información sobre el modelo es semánticamente la misma, es "la estimación de envío". Cuando obtengo la información sobre la estimación de envío del sistema, puedo decir si la estimación es de la primera forma o la segunda forma.

El modelo actual para esto es similar al siguiente:

class EstimattedShippingDateDetails
{
    DateTime? EstimattedShippingDate {get; set;}
    Range? EstimattedShippingDayRange {get; set;}
}

Range es una clase simple para envolver un "principio -> fin" de enteros, algo así:

struct Range
{
    int Start {get; set}
    int End {get; set}

    public override ToString()
    {
        return String.Format("{0} - {1}", Start, End);
    }
}

No me gusta este enfoque porque solo se completará una de las propiedades en el modelo de estimación, y necesito probar nulo en uno de ellos y asumir que el otro tiene los datos.

Cada una de las propiedades se muestra de manera diferente para el usuario, pero en el mismo lugar en la interfaz de usuario, utilizando una MVC DisplayTemplate personalizada, donde reside la lógica de conmutación actual:

@Model EstimattedShippingDateDetails

@if (Model.EstimattedShippingDate.HasValue)
{
    Html.DisplayFor(m => Model.EstimattedShippingDate)
}
else
{
    Html.DisplayFor(m => Model.EstimattedShippingDayRange)
}

¿Cómo podría modelar esto para hacerlo más representativo del requisito real y al mismo tiempo mantener la lógica de visualización simple en una aplicación MVC?

Pensé en usar una interfaz y dos implementaciones, una para cada "tipo" de estimación, pero parece que no puedo entender una interfaz común para ambas. Si creo una interfaz sin ningún miembro, entonces no puedo acceder a los datos de manera unificada y es un mal diseño en mi humilde opinión. También quería mantener el modelo de vista lo más simple posible. Sin embargo, obtendría un código "correcto por construcción" con este enfoque, ya que ya no sería necesario anular: cada implementación tendría una propiedad no anulable, ya sea a DateTimeo a Range.

También consideré usar solo uno Rangey cuando ocurre la situación n. ° 1, solo use el mismo DateTimepara ambos Starty End, pero esto agregaría complicaciones sobre cómo emitir los valores a la interfaz de usuario, ya que luego tendría que detectar si es estático o intervalo por comparar los valores y formatear correctamente el rango para que se muestre como una sola fecha o un intervalo formateado.

Parece que lo que necesito es un concepto similar a las uniones de Typecript: básicamente una propiedad que puede ser de dos tipos. No existe tal cosa de forma nativa en C #, por supuesto (solo dynamicestaría cerca de eso).

julealgon
fuente
Esto es más de un conceptual "¿Cómo puedo ...?" pregunta que una solicitud de crítica abierta. Migrar esta pregunta a los programadores.
200_success
@ 200_success Lo siento. Pensé que esto sería apropiado para la revisión de código, pero tienes razón, aquí encaja mejor. Gracias por la ayuda;)
julealgon
¿El formato está establecido en piedra o, por ejemplo, podría formatear el intervalo como "se enviará entre $ date1 y $ date2"?
svick
@svick Desafortunadamente, está escrito en piedra por ahora.
julealgon

Respuestas:

17

Use un rango de fechas (es decir, dos fechas) para todas las estimaciones de envío.

Para una sola fecha, haga que X e Y sean iguales.

Robert Harvey
fuente
Mencioné esto en mi propia pregunta, ¿puede explicar los inconvenientes que planteé para utilizar este enfoque? Tenga en cuenta que el escenario de intervalo es un intervalo de día fijo y no un intervalo de fecha completo también.
julealgon
2
Rellenar ambos campos siempre debe eliminar sus objeciones a los valores nulos. ¿Qué quiere decir con "intervalo de día fijo y no un intervalo de día completo?"
Robert Harvey
Los datos que obtengo del servidor son un DateTimeobjeto, que representa la fecha de envío esperada, o dos valores enteros con los días mínimo y máximo a partir de hoy que se enviará el artículo. Si estandarizo las fechas, tendré que convertir esos enteros en DateTimes correctamente construidos, lo que aumentaría bastante la complejidad debido a todo lo que debería tenerse en cuenta, como las fechas cliente vs servidor, UTC, horario de verano diferencias, etc. Me gustaría evitar crear este tipo de complejidad en este momento.
julealgon
Sin embargo, un buen punto sobre la posibilidad de nunca tener valores nulos, sería bastante bueno.
julealgon
¿Qué tiene de malo la forma en que lo estás haciendo ahora, usando tu conmutador en la plantilla MVC?
Robert Harvey
2

Puede modelar esto usando encapsulación.

Deje que la clase tenga 2 constructores, uno para la fecha única y otro para el rango de fechas.

En el método ToString (), determine el 'estado' de la clase y genere la cadena formateada adecuada.

Agregue otros métodos según corresponda para sus otras necesidades.

hocho
fuente
Eso funcionaría bien en un sistema más simple, pero tenga en cuenta que necesito mostrar esto correctamente usando vistas MVC, y que el Rangetipo podría usarse en otros lugares del sistema y, en ese caso, mostrarse de la misma manera. Es por eso que necesito centralizar la Rangepantalla en una DisplayTemplate que se puede reutilizar. Si uso el ToStringmétodo para encapsular eso, estoy perdiendo mucha flexibilidad que proporcionan las vistas.
julealgon
Además, ¿cómo propones que conozca el "estado" del objeto? ¿Una especie de variable local enum? Un booleano? Supongo que se establecería en diferentes valores dependiendo de qué constructor se llamó ¿verdad? Para que esto funcione, probablemente también necesite hacer que la clase sea inmutable, o puedo tener problemas si alguien establece el otro valor, etc. ¿Qué hago si uno usa el constructor de fecha única e intenta acceder al Rango? propiedad también? ¿Lanzaría una excepción en ese caso o simplemente devolvería nulo? Usted ve, la usabilidad de esto todavía no es ideal.
julealgon
Sí, esencialmente la clase sería inmutable con las 2 fechas siendo de solo lectura y asignadas en los constructores. El estado podría ser determinado por un bool y / o tener la misma fecha para representar la fecha única. Sus otras funciones miembro podrían usar el estado adecuadamente y usted podría exponerlo adicionalmente, si es necesario. Lo sentimos, no puedo ser más específico porque no conozco todas las funciones que necesita para admitir.
hocho
Lo que @hocho dice es que todas las fechas de entrega se pueden modelar como intervalos, es solo que algunos intervalos tienen la misma fecha de inicio y finalización. Esto parece bastante sensato. El código del cliente que usa Rangos debe estar preparado para tratar con una fecha de inicio y finalización de la misma fecha, y en ese caso tal vez imprimir información diferente (es decir, fecha única versus rango de fechas) en el correo electrónico.
Erik Eidt
voto negativostate para ..ToString () [para] determinar el . ToString()debería simplemente "informar" el estado. El estado se determina en constructores, establecedores de propiedades, etc. Y, simplemente, no veo nada en el OP que sugiera que hay o debería haber un "estado resumen"
radarbob
1

Este es un problema bastante común, Nullables, por ejemplo, resuelve el problema del lugar común de endDates para cosas que no han terminado.

Pero creo que has elegido un mal ejemplo.

Con su caso exacto de dos fechas, un intervalo de fechas que comienza en la mañana y termina en la noche parece ser la solución perfecta. ¿O quizás una fecha de inicio y un número entero de días adicionales que podría ser?

Sin embargo, consideremos un caso más difícil. Tengo dos tipos de entrega, publicación y recogida en la tienda. Estos son obviamente mucho más diferentes, la tienda necesita el nombre y la dirección, la publicación tiene un costo, tal vez una serie de opciones de entrega, códigos de seguimiento, etc.

El enfoque estándar es buscar las cosas comunes que hacen estas dos 'opciones de entrega' y ponerlas en una clase base. La subclase los dos casos específicos con los detalles adicionales que tienen / necesitan.

En casi todos los casos, tendrá al menos un Id, un Tipo y una Descripción común a ambos tipos. Entonces:

public class DeliveryOption 
{
     Public string Id;
     Public typeEnum Type;
     public string Description;
}


Public class Collection : DeliveryOption
{
     Public string ShopName;
}

Public class Post : DeliveryOption
{
     Public DateTime EstDelivery;
}
Ewan
fuente
... un intervalo de fechas que comienza en la mañana y termina en la noche parece ser la solución perfecta. Solo usa la DataTime.Datepropiedad y no te preocupes por el tiempo.
radarbob
1

"inicio" y "fin" no son DateTime, son compensaciones

¿Cómo podría modelar esto para hacerlo más representativo del requisito real y al mismo tiempo mantener la lógica de visualización simple en una aplicación MVC?

"inicio" y "fin" son compensaciones del EstimatedShipDate. No son DateTimeellos mismos . Esto describe mejor lo que está sucediendo y reducirá drásticamente la complejidad.

No hay necesidad de un interface. No hay necesidad de una Rangeclase. No haga complejidad hasta que esté seguro de que la necesita. Sospecho firmemente que una sola clase con un constructor de 3 parámetros opcionales mantendrá las cosas mucho más simples.


Los datos que obtengo del servidor son un objeto DateTime, que representa la fecha de envío esperada, o dos valores enteros con los días mínimo y máximo a partir de hoy que se enviará el artículo.

Use un solo constructor pasando los 3 valores a través de parámetros opcionales. Esto proporciona todo el contexto necesario. La lógica del constructor único puede evaluar todas las variaciones para establecer el estado inicial correctamente. También use parámetros con nombre en la llamada al constructor si lo desea para que quede claro como el cristal.

public class ShipDate {
    public ShipDate (Datetime? shipDate = null, int earliestOffset = 0, latestOffset = 0) {
        EstShipDate = (DateTime) shipDate ?? DateTime.Now.Date;
        start = earliestOffset < 0 ? 0 : earliestOffset;
        end   = latestOffset < 0 ? 0 : latestOffset;
        // That's all, folks!
    }
}

Si estandarizo las fechas, tendré que convertir esos enteros en DateTimes correctamente construidos,

No. No hagas esto y las cosas son más simples; en su lugar use DateTime.AddDays () `.

public DateTime EstShipDate      {get; protected set;}
public DateTime EarliestShipDate { get { return EstShipDate.AddDays(start).Date; } }
public DateTime LatestShipDate   { get { return EstShipDate.AddDays(end).Date; } }

Esto es potencialmente más flexible si se permite cambiar las fechas y los valores de desplazamiento.


lo que aumentaría bastante la complejidad debido a todo lo que debería tenerse en cuenta, como las fechas de cliente frente a servidor, UTC, diferencias de horario de verano, etc. Me gustaría evitar crear este tipo de complejidad en este momento.

Lea sobre el manejo de zonas horarias aquí.

Por ahora solo incluya un DateTime.DateTimeKindparámetro de constructor (enum) y trátelo más tarde.


De una de las respuestas:

Con su caso exacto de dos fechas, un intervalo de fechas que comienza en la mañana y termina en la noche parece ser la solución perfecta. ¿O quizás una fecha de inicio y un número entero de días adicionales que podría ser?

Usa la DateTime.Datepropiedad e ignora totalmente el tiempo.

radarbob
fuente
0

¿Qué tal algo como

class EstimattedShippingDateDetails
{
    DateTime EarliestShippingDate {get; set;}
    DateTime LatestShippingDate {get; set;}
    TimeSpan Range 
    {
        get 
        { 
            return LatestShippingDate - EarliestShippingDate; 
        } 
    }
}

Una fecha específica: el artículo está garantizado para enviarse en esa fecha

Ambos EarliestShippingDatey LatestShippingDateestán configurados para la fecha de envío garantizada.

Intervalo de un día: el artículo se enviará en días "X a Y" a partir de hoy

EarliestShippingDatese establece en la actualidad y LatestShippingDatese establece entoday + (Y - X)

Manchado
fuente
0

Debe decidir cuál es la diferencia (si la hay) entre una sola fecha de envío y un rango que consta de un día. Si una "fecha de envío única" es solo un rango de días que consta de un día, entonces modele todo como fecha de inicio y fecha de finalización. Si una "fecha de envío única" y un rango de días con la misma fecha de inicio y finalización deben tratarse de manera diferente, almacene uno de los dos casos, ya sea una fecha única para una fecha de envío única y dos fechas para un rango de días .

gnasher729
fuente
0

Básicamente tienes 2 opciones:

  • 2 fechas. Si el objeto representa una sola fecha, configúrelas en el mismo valor o configure la segunda en un valor nulo.

  • 1 fecha y un intervalo de tiempo. La fecha representa el inicio, y el intervalo de tiempo muestra cuánto en el futuro puede ser el rango. Establezca el rango en 0 para una sola fecha.

Para el envío, tendría una fecha y hora en lugar de una fecha, ya que sin duda querrá modelar la entrega por la mañana / tarde. Cuál de las 2 opciones es mejor depende de cómo desee calcular la pantalla. Si está mostrando "entre x e y", entonces la primera opción podría ser más fácil de usar, si está mostrando "hasta x días a partir de y", entonces la última es más fácil de usar.

Si no le gustan los valores nulos, entonces la última opción es mejor, ya que el cálculo de la fecha original más el intervalo de tiempo se puede hacer independientemente de si el intervalo de tiempo tiene un valor o se establece en 0. Siempre obtendrá un resultado correcto sin verificar si es nulo.

gbjbaanb
fuente