Gli oggetti di dominio comune hanno proprietà che possono essere rappresentate da un tipo built-in ma i cui valori validi sono un sottoinsieme dei valori che possono essere rappresentati da quel tipo.
In questi casi, il valore può essere memorizzato utilizzando il tipo built-in, ma è necessario garantire che i valori siano sempre convalidati nel punto di immissione, altrimenti potremmo finire con un valore non valido.
Un modo per risolvere questo è memorizzare il valore come struct
personalizzato che ha un singolo campo di supporto private readonly
del tipo built-in e il cui costruttore convalida il valore fornito. Possiamo quindi essere sempre sicuri di utilizzare solo valori convalidati utilizzando questo tipo di struct
.
Possiamo anche fornire operatori di cast da e verso il tipo built-in sottostante in modo che i valori possano entrare e uscire senza interruzioni dal tipo sottostante.
Prendiamo ad esempio una situazione in cui dobbiamo rappresentare il nome di un oggetto dominio, e i valori validi sono qualsiasi stringa che sia compresa tra 1 e 255 caratteri di lunghezza inclusi. Possiamo rappresentarlo usando la seguente struttura:
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'esempio mostra il to- string
cast come implicit
in quanto questo non può mai fallire ma il cast from- string
come explicit
come questo genererà valori non validi, ma ovviamente questi potrebbero essere entrambi implicit
o explicit
.
Nota anche che si può inizializzare questa struttura solo con un cast da string
, ma si può verificare se un cast di questo tipo fallirà in anticipo usando il metodo IsValid
static
.
Questo sembra essere un buon modello per applicare la convalida dei valori di dominio che possono essere rappresentati da tipi semplici, ma non li vedo spesso usati o suggeriti e sono interessato al perché.
Quindi la mia domanda è: quali sono i vantaggi e gli svantaggi dell'uso di questo modello e perché?
Se ritieni che questo sia un modello errato, mi piacerebbe capire perché e anche ciò che ritieni sia l'alternativa migliore.
NB ho originariamente chiesto questa domanda su Stack Overflow ma è stata messa in attesa principalmente come basata sull'opinione pubblica (di per sé soggettiva), speriamo che possa avere più successo qui.
Qui sopra c'è il testo originale, sotto un paio di altri pensieri, in parte in risposta alle risposte ricevute prima che andasse in attesa:
- Uno dei punti principali delle risposte era la quantità di codice della piastra della caldaia necessaria per il modello sopra, specialmente quando sono richiesti molti di questi tipi. Tuttavia, in difesa del pattern, questo potrebbe essere ampiamente automatizzato usando i template e in realtà per me non sembra poi così male, ma questa è solo la mia opinione.
- Da un punto di vista concettuale, non sembra strano quando si lavora con un linguaggio strongmente tipizzato come C # per applicare solo il principio strongmente tipizzato ai valori compositi, piuttosto che estenderlo a valori che possono essere rappresentati da un istanza di un tipo predefinito?