Las primitivas, como string
o int
, no tienen significado en un dominio comercial. Una consecuencia directa de esto es que puede usar una URL por error cuando se espera un ID de producto, o usar la cantidad cuando espera el precio .
Esta es también la razón por la cual el desafío Object Calisthenics presenta el primitivo envoltorio como una de sus reglas:
Regla 3: Envuelva todas las primitivas y cadenas
En el lenguaje Java, int es un objeto primitivo, no real, por lo que obedece a reglas diferentes que los objetos. Se usa con una sintaxis que no está orientada a objetos. Más importante aún, un int por sí solo es solo un escalar, por lo que no tiene sentido. Cuando un método toma un int como parámetro, el nombre del método debe hacer todo el trabajo de expresar la intención. Si el mismo método toma una Hora como parámetro, es mucho más fácil ver qué está pasando.
El mismo documento explica que hay un beneficio adicional:
Los objetos pequeños como Hour o Money también nos dan un lugar obvio para poner un comportamiento que de otro modo habría estado plagado de otras clases .
De hecho, cuando se usan primitivas, generalmente es extremadamente difícil rastrear la ubicación exacta del código relacionado con esos tipos, lo que a menudo conduce a una duplicación severa del código . Si hay Price: Money
clase, es natural encontrar el rango comprobando dentro. Si, en cambio, se utiliza un int
(peor, a double
) para almacenar los precios de los productos, ¿quién debería validar el rango? ¿El producto? El descuento? ¿El carro?
Finalmente, un tercer beneficio no mencionado en el documento es la capacidad de cambiar relativamente fácilmente el tipo subyacente. Si hoy ProductId
tiene mi short
tipo subyacente y luego necesito usarlo int
en su lugar, es probable que el código que cambie no abarque toda la base de código.
El inconveniente, y el mismo argumento se aplica a todas las reglas del ejercicio de Calistenia de Objetos, es que si rápidamente se vuelve demasiado abrumador para crear una clase para todo . Si Product
contiene ProductPrice
qué hereda de PositivePrice
qué hereda de quién a Price
su vez hereda Money
, esto no es una arquitectura limpia, sino más bien un desastre completo donde, para encontrar una sola cosa, un mantenedor debe abrir algunas docenas de archivos cada vez.
Otro punto a considerar es el costo (en términos de líneas de código) de crear clases adicionales. Si los envoltorios son inmutables (como deberían ser, por lo general), significa que, si tomamos C #, debe tener, al menos en el envoltorio:
- El captador de propiedades,
- Su campo de apoyo,
- Un constructor que asigna el valor al campo de respaldo,
- Una costumbre
ToString()
,
- Comentarios de documentación XML (que forman muchas líneas),
- A
Equals
y una GetHashCode
anulación (también una gran cantidad de LOC).
y eventualmente, cuando sea relevante:
- Un atributo DebuggerDisplay ,
- Una anulación de
==
y !=
operadores,
- Eventualmente, una sobrecarga del operador de conversión implícita para convertir sin problemas hacia y desde el tipo encapsulado,
- Contratos de código (incluido el invariante, que es un método bastante largo, con sus tres atributos),
- Varios convertidores que se utilizarán durante la serialización XML, la serialización JSON o el almacenamiento / carga de un valor a / desde una base de datos.
Cien LOC para un envoltorio simple lo hacen bastante prohibitivo, por lo que también puede estar completamente seguro de la rentabilidad a largo plazo de dicho envoltorio. La noción de alcance explicada por Thomas Junk es particularmente relevante aquí. Escribir cien LOC para representar un ProductId
usado en toda su base de código parece bastante útil. Escribir una clase de este tamaño para un fragmento de código que forma tres líneas dentro de un solo método es mucho más cuestionable.
Conclusión:
Envuelva primitivas en clases que tengan un significado en un dominio comercial de la aplicación cuando (1) ayuda a reducir errores, (2) reduce el riesgo de duplicación de código o (3) ayuda a cambiar el tipo subyacente más adelante.
No envuelva automáticamente cada primitivo que encuentre en su código: hay muchos casos en los que usar string
o int
está perfectamente bien.
En la práctica, public string CreateNewThing()
devolver una instancia de ThingId
clase en lugar de string
podría ayudar, pero también puede:
Devuelve una instancia de Id<string>
clase, que es un objeto de tipo genérico que indica que el tipo subyacente es una cadena. Tiene el beneficio de la legibilidad, sin el inconveniente de tener que mantener muchos tipos.
Devuelve una instancia de Thing
clase. Si el usuario solo necesita la ID, esto se puede hacer fácilmente con:
var thing = this.CreateNewThing();
var id = thing.Id;