Eu trabalho em um sistema que pode representar uma "estimativa de remessa" de duas maneiras:
- Uma data específica: é garantido que o item seja enviado nessa data
- Um intervalo de dias: o item será enviado "X a Y" dias a partir de hoje
As informações no modelo são semanticamente iguais, é "a estimativa de remessa". Quando obtenho as informações sobre a estimativa de remessa no sistema, posso saber se a estimativa é do primeiro ou do segundo formulário.
O modelo atual para isso é semelhante ao seguinte:
class EstimattedShippingDateDetails
{
DateTime? EstimattedShippingDate {get; set;}
Range? EstimattedShippingDayRange {get; set;}
}
Range
é uma classe simples para agrupar um "começo -> fim" de números inteiros, mais ou menos assim:
struct Range
{
int Start {get; set}
int End {get; set}
public override ToString()
{
return String.Format("{0} - {1}", Start, End);
}
}
Não gosto dessa abordagem, porque apenas uma das propriedades no modelo de estimativa será preenchida e preciso testar o valor nulo em uma delas e assumir que a outra possui os dados.
Cada uma das propriedades é mostrada de maneira diferente para o usuário, mas no mesmo local da interface do usuário, usando um MVC DisplayTemplate personalizado, onde a lógica de comutação atual reside:
@Model EstimattedShippingDateDetails
@if (Model.EstimattedShippingDate.HasValue)
{
Html.DisplayFor(m => Model.EstimattedShippingDate)
}
else
{
Html.DisplayFor(m => Model.EstimattedShippingDayRange)
}
Como modelar isso para torná-lo mais representativo do requisito real, mantendo a lógica de exibição simples em um aplicativo MVC?
Pensei em usar uma interface e duas implementações, uma para cada "tipo" de estimativa, mas não consigo entender a interface comum para ambas. Se eu criar uma interface sem membros, não consigo acessar os dados de maneira unificada e é um IMHO de design ruim. Eu também queria manter o viewmodel o mais simples possível. Eu obteria um código "correto por construção" com essa abordagem, já que não seria mais necessário anular: cada implementação teria uma propriedade não anulável, a DateTime
ou a Range
.
Também considerei usar apenas um Range
e, quando a situação nº 1 acontecer, use o mesmo DateTime
para ambos Start
e End
, mas isso adicionaria complicações sobre como emitir os valores para a interface do usuário, pois eu precisaria detectar se é um estático ou intervalo por comparando os valores e formate corretamente o intervalo para ser exibido como uma única data ou um intervalo formatado.
Parece que o que eu preciso é de um conceito semelhante aos sindicatos do Typescript: basicamente uma propriedade que pode ser de dois tipos. Naturalmente, não existe tal coisa em C # ( dynamic
seria apenas próximo disso).
fonte
Respostas:
Use um período (ou seja, duas datas) para todas as estimativas de envio.
Para uma única data, faça X e Y iguais.
fonte
DateTime
objeto, representando a data esperada de envio ou dois valores inteiros com os dias mínimo e máximo de hoje em que o item será enviado. Se eu padronizar as datas, terei que converter esses números inteiros em DateTimes adequadamente construídos, o que aumentaria bastante a complexidade devido a tudo o que seria necessário levar em conta, como datas cliente versus servidor, UTC, horário de verão diferenças etc. Gostaria de evitar criar esse tipo de complexidade neste momento.Você pode modelar isso usando o encapsulamento.
Deixe a classe ter dois construtores, um para a data única e outro para o período.
No método ToString (), determine o 'estado' da classe e gere a sequência formatada apropriada.
Adicione outros métodos conforme apropriado para suas outras necessidades.
fonte
Range
tipo pode ser usado em outros locais do sistema e, nesse caso, ser exibido da mesma maneira. É por isso que preciso centralizar aRange
exibição em um DisplayTemplate que possa ser reutilizado. Se eu usar oToString
método para encapsular isso, estou perdendo muita flexibilidade que as visualizações fornecem.state
para ..ToString () [to] determinar o .ToString()
deve apenas "relatar" o estado. O estado é determinado em construtores, configuradores de propriedades e assim por diante. E, eu não estou vendo nada no OP que sugere que é ou deveria ser um "estado resumo"Esse é um problema bastante comum; os Nullables, por exemplo, resolvem o problema comum de endDates para coisas que não terminaram.
Mas acho que você escolheu um mau exemplo.
Com o seu caso exato de duas datas, um período que começa pela manhã e termina à noite parece ser a solução perfeita. Ou talvez uma data de início e um número inteiro de dias extras, pode ser?
No entanto, vamos considerar um caso mais difícil. Tenho dois tipos de entrega, postagem e retirada na loja. Estes são obvs muito mais diferentes, a loja precisa do nome e endereço, o correio tem um custo, pode ter várias opções de entrega, códigos de rastreamento etc.
A abordagem padrão é procurar as coisas comuns que fazem essas duas 'opções de entrega' e colocá-las em uma classe base. A subclasse dos dois casos específicos com os detalhes extras que eles têm / precisam.
Em quase todos os casos, você terá pelo menos um ID, um Tipo e uma Descrição comuns aos dois tipos. Assim:
fonte
DataTime.Date
propriedade e não se preocupe com o tempo."start" e "end" não são DateTime, são compensações
"start" e "end" são deslocamentos para o
EstimatedShipDate
. Eles não sãoDateTime
eles mesmos . Isso descreve melhor o que está acontecendo e reduzirá drasticamente a complexidade.Não há necessidade de um
interface
. Não há necessidade de umaRange
aula. Não faça complexidade até ter certeza de que precisa. Eu suspeito fortemente que uma única classe com um construtor de 3 parâmetros opcionais manterá as coisas muito mais simples.Use um único construtor passando todos os 3 valores por meio de parâmetros opcionais. Isso fornece todo o contexto necessário. A lógica do construtor único pode avaliar todas as variações para definir o estado inicial corretamente. Também use parâmetros nomeados na chamada do construtor, se desejar, para torná-lo claro.
Não. Não faça isso e as coisas são mais simples; em vez disso, use DateTime.AddDays () `.
Isso é potencialmente mais flexível se datas e valores de deslocamento puderem mudar.
Leia sobre como lidar com fusos horários aqui.
Por enquanto, inclua um
DateTime.DateTimeKind
parâmetro do construtor (enum) e lide com ele mais tarde.De uma das respostas:
Use a
DateTime.Date
propriedade e ignore totalmente o tempo.fonte
Que tal algo como
Ambos
EarliestShippingDate
eLatestShippingDate
estão definidos para a data de envio garantida.EarliestShippingDate
está definido para hoje eLatestShippingDate
está definido paratoday + (Y - X)
fonte
Você precisa decidir qual é a diferença (se houver) entre uma única data de envio e um intervalo que consiste em um dia. Se uma "única data de envio" for apenas um intervalo de dias que consiste em um dia, modele tudo como data de início e data de término. Se uma "data única de remessa" e um intervalo de dias com as mesmas datas de início e término forem tratados de maneira diferente, armazene um dos dois casos, uma única data para uma única data de remessa e duas datas para um período de dias .
fonte
Você tem basicamente 2 opções:
2 datas. Se o objeto representar uma única data, defina-os para o mesmo valor ou defina o segundo como um valor nulo.
1 data e um período de tempo. A data representa o início e o período mostra quanto no futuro o intervalo pode ser. Defina o intervalo como 0 para uma única data.
Para o transporte, eu teria uma data e hora em vez de uma data, pois você certamente desejará modelar a entrega da manhã / noite. Qual das 2 opções é a melhor depende de como você deseja calcular a exibição. Se você estiver exibindo "entre x e y", a primeira opção poderá ser mais fácil de usar; se você estiver exibindo "até x dias a partir de y", a segunda será mais fácil de usar.
Se você não gostar de valores nulos, a última opção é melhor, pois o cálculo da data original e do período pode ser feito independentemente de o período ter um valor ou estar definido como 0. Você sempre obterá um resultado correto sem verificar nulo.
fonte