Os objetos de domínio comumente têm propriedades que podem ser representadas por um tipo interno, mas cujos valores válidos são um subconjunto dos valores que podem ser representados por esse tipo.
Nesses casos, o valor pode ser armazenado usando o tipo interno, mas é necessário garantir que os valores sejam sempre validados no ponto de entrada, caso contrário, podemos acabar trabalhando com um valor inválido.
Uma maneira de resolver isso é armazenar o valor como um costume, structque possui um único private readonlycampo de apoio do tipo interno e cujo construtor valida o valor fornecido. Em seguida, sempre podemos ter certeza de usar apenas valores validados usando esse structtipo.
Também podemos fornecer operadores de conversão de e para o tipo interno subjacente, para que os valores possam entrar e sair sem problemas como o tipo subjacente.
Tome como exemplo uma situação em que precisamos representar o nome de um objeto de domínio, e valores válidos são qualquer sequência que tenha entre 1 e 255 caracteres, inclusive. Podemos representar isso usando a seguinte estrutura:
public struct ValidatedName : IEquatable<ValidatedName>
{
private readonly string _value;
private ValidatedName(string name)
{
_value = name;
}
public static bool IsValid(string name)
{
return !String.IsNullOrEmpty(name) && name.Length <= 255;
}
public bool Equals(ValidatedName other)
{
return _value == other._value;
}
public override bool Equals(object obj)
{
if (obj is ValidatedName)
{
return Equals((ValidatedName)obj);
}
return false;
}
public static implicit operator string(ValidatedName x)
{
return x.ToString();
}
public static explicit operator ValidatedName(string x)
{
if (IsValid(x))
{
return new ValidatedName(x);
}
throw new InvalidCastException();
}
public static bool operator ==(ValidatedName x, ValidatedName y)
{
return x.Equals(y);
}
public static bool operator !=(ValidatedName x, ValidatedName y)
{
return !x.Equals(y);
}
public override int GetHashCode()
{
return _value.GetHashCode();
}
public override string ToString()
{
return _value;
}
}
O exemplo mostra a exibição string, implicitpois isso nunca pode falhar, mas a exibição string, explicitpois isso gera valores inválidos, mas é claro que ambos podem ser um implicitou outro explicit.
Observe também que só é possível inicializar essa estrutura por meio de uma conversão de string, mas é possível testar se essa conversão falhará antecipadamente usando o IsValid staticmétodo
Esse parece ser um bom padrão para impor a validação de valores de domínio que podem ser representados por tipos simples, mas não o vejo usado com frequência ou sugerido e estou interessado no motivo.
Então, minha pergunta é: quais são as vantagens e desvantagens do uso desse padrão e por quê?
Se você acha que esse é um padrão ruim, eu gostaria de entender por que e também o que você acha que é a melhor alternativa.
Nota : originalmente eu fiz essa pergunta no Stack Overflow, mas ela foi colocada em espera principalmente como baseada em opiniões (ironicamente subjetiva em si mesma) - espero que possa ter mais sucesso aqui.
Acima está o texto original, abaixo mais algumas reflexões, em parte em resposta às respostas recebidas antes da suspensão:
- Um dos principais pontos apontados pelas respostas foi a quantidade de código da placa da caldeira necessária para o padrão acima, especialmente quando muitos desses tipos são necessários. No entanto, em defesa do padrão, isso pode ser amplamente automatizado usando modelos e, na verdade, para mim não parece tão ruim assim, mas essa é apenas a minha opinião.
- Do ponto de vista conceitual, não parece estranho quando se trabalha com uma linguagem fortemente tipada como C # aplicar apenas o princípio fortemente tipado a valores compostos, em vez de estendê-lo a valores que podem ser representados por uma instância de um tipo embutido?