Les objets de domaine communs ont des propriétés qui peuvent être représentées par un type intégré mais dont les valeurs valides sont un sous-ensemble des valeurs qui peuvent être représentées par ce type.
Dans ces cas, la valeur peut être stockée en utilisant le type intégré mais il est nécessaire de s'assurer que les valeurs sont toujours validées au point d'entrée, sinon nous pourrions finir par travailler avec une valeur non valide.
Une façon de résoudre ce problème consiste à stocker la valeur en tant que personnalisé struct
qui possède un seul private readonly
champ de sauvegarde du type intégré et dont le constructeur valide la valeur fournie. On peut alors toujours être sûr de n'utiliser que des valeurs validées en utilisant ce struct
type.
Nous pouvons également fournir des opérateurs de transtypage depuis et vers le type intégré sous-jacent afin que les valeurs puissent entrer et sortir en toute transparence en tant que type sous-jacent.
Prenons comme exemple une situation dans laquelle nous devons représenter le nom d'un objet de domaine, et les valeurs valides sont n'importe quelle chaîne de 1 à 255 caractères inclus. Nous pourrions représenter cela en utilisant la structure suivante:
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;
}
}
L'exemple montre que le string
cast comme implicit
cela ne peut jamais échouer mais le string
cast comme explicit
car cela lancera des valeurs invalides, mais bien sûr, ces deux pourraient être soit implicit
ou explicit
.
Notez également que l'on ne peut initialiser cette structure qu'au moyen d'une conversion à partir de string
, mais on peut tester si une telle conversion échouera à l'avance en utilisant la IsValid
static
méthode.
Cela semblerait être un bon modèle pour appliquer la validation des valeurs de domaine qui peuvent être représentées par des types simples, mais je ne le vois pas utilisé souvent ou suggéré et je suis curieux de savoir pourquoi.
Ma question est donc la suivante: quels sont selon vous les avantages et les inconvénients de l'utilisation de ce modèle, et pourquoi?
Si vous pensez que c'est un mauvais schéma, j'aimerais comprendre pourquoi et ce que vous ressentez est la meilleure alternative.
NB: j'ai initialement posé cette question sur Stack Overflow, mais elle a été suspendue car elle était principalement basée sur l'opinion (ironiquement subjective en soi) - j'espère qu'elle pourra avoir plus de succès ici.
Ci-dessus se trouve le texte original, ci-dessous quelques réflexions supplémentaires, en partie en réponse aux réponses reçues avant sa mise en attente:
- L'un des principaux points soulevés par les réponses concernait la quantité de code de plaque de chaudière nécessaire pour le modèle ci-dessus, en particulier lorsque de nombreux types de ce type sont nécessaires. Cependant, pour défendre le modèle, cela pourrait être largement automatisé à l'aide de modèles et en fait, cela ne me semble pas trop mal de toute façon, mais c'est juste mon opinion.
- D'un point de vue conceptuel, ne semble-t-il pas étrange, lorsque l'on travaille avec un langage fortement typé tel que C #, d'appliquer uniquement le principe fortement typé aux valeurs composites, plutôt que de l'étendre à des valeurs qui peuvent être représentées par une instance d'un type intégré?