Comúnmente, los objetos de dominio tienen propiedades que pueden ser representadas por un tipo incorporado pero cuyos valores válidos son un subconjunto de los valores que pueden ser representados por ese tipo.
En estos casos, el valor puede almacenarse utilizando el tipo incorporado, pero es necesario asegurarse de que los valores siempre se validan en el punto de entrada, de lo contrario podríamos terminar trabajando con un valor no válido.
Una forma de resolver esto es almacenar el valor como una costumbre structque tiene un solo private readonlycampo de respaldo del tipo incorporado y cuyo constructor valida el valor proporcionado. Entonces siempre podemos estar seguros de usar solo valores validados al usar este structtipo.
También podemos proporcionar operadores de conversión desde y hacia el tipo incorporado subyacente para que los valores puedan ingresar y salir sin problemas como el tipo subyacente.
Tome como ejemplo una situación en la que necesitamos representar el nombre de un objeto de dominio, y los valores válidos son cualquier cadena que tenga entre 1 y 255 caracteres de longitud inclusive. Podríamos representar esto usando la siguiente estructura:
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;
}
}
El ejemplo muestra el stringlanzamiento como implicitesto nunca puede fallar, pero el stringlanzamiento explicitcomo esto arrojará valores no válidos, pero, por supuesto, ambos podrían ser implicito explicit.
Tenga en cuenta también que solo se puede inicializar esta estructura a través de un lanzamiento desde string, pero se puede probar si dicho lanzamiento fallará de antemano utilizando el IsValid staticmétodo.
Este parece ser un buen patrón para exigir la validación de los valores de dominio que pueden representarse mediante tipos simples, pero no veo que se use con frecuencia o se sugiera y me interesa saber por qué.
Entonces mi pregunta es: ¿cuáles considera que son las ventajas y desventajas de usar este patrón y por qué?
Si cree que este es un mal patrón, me gustaría entender por qué y también cuál es la mejor alternativa.
NB : Originalmente hice esta pregunta en Stack Overflow, pero se suspendió porque se basa principalmente en la opinión (irónicamente subjetiva en sí misma), espero que pueda tener más éxito aquí.
Arriba está el texto original, debajo de un par de pensamientos más, en parte en respuesta a las respuestas recibidas allí antes de que se suspendiera:
- Uno de los puntos principales de las respuestas fue la cantidad de código de placa de caldera necesaria para el patrón anterior, especialmente cuando se requieren muchos de estos tipos. Sin embargo, en defensa del patrón, esto podría automatizarse en gran medida usando plantillas y, en realidad, para mí no parece tan malo de todos modos, pero esa es solo mi opinión.
- Desde un punto de vista conceptual, no parece extraño cuando se trabaja con un lenguaje fuertemente tipado como C # para aplicar solo el principio fuertemente tipado a valores compuestos, en lugar de extenderlo a valores que pueden ser representados por una instancia de un tipo incorporado?