¿Cuáles son las pautas generales o reglas generales para cuándo usar un objeto específico de dominio frente a una cadena o número simple?
Ejemplos:
- ¿Clase de edad vs entero?
- FirstName class vs String?
- UniqueID vs String
- PhoneNumber class vs String vs Long?
- DomainName clase vs cadena?
Creo que la mayoría de los practicantes de OOP definitivamente dirían clases específicas para PhoneNumber y DomainName. Cuantas más reglas se refieran a lo que los hace válidos y cómo compararlos, las clases simples serán más fáciles y seguras de manejar. Pero para los tres primeros hay más debate.
Nunca me he encontrado con una clase de "Edad", pero uno podría argumentar que tiene sentido dado que debe ser no negativo (está bien, sé que puede argumentar por edades negativas, pero es un buen ejemplo de que es casi equivalente a un entero primitivo).
La cadena es común para representar "Nombre" pero no es perfecta porque una cadena vacía es una cadena válida pero no un nombre válido. La comparación generalmente se haría ignorando el caso. Claro que hay métodos para verificar si está vacío, hacer una comparación que no distinga entre mayúsculas y minúsculas, etc., pero requiere que el consumidor haga esto.
¿La respuesta depende del medio ambiente? Me preocupa principalmente el software empresarial / de alto valor que vivirá y se mantendrá durante posiblemente más de una década.
Tal vez estoy pensando demasiado en esto, pero realmente me gustaría saber si alguien tiene reglas sobre cuándo elegir clase versus primitivo.
people may be one or two years older in Asian reckoning than in the western age system
Age
clase en lugar de un número entero cuando necesita el comportamiento adicional que tal clase proporcionaría.Respuestas:
La pauta general es que desea modelar su dominio en un idioma específico de dominio.
Considere: ¿por qué usamos entero? Podemos representar todos los enteros con cadenas con la misma facilidad. O con bytes.
Si estuviéramos programando en un lenguaje agnóstico de dominio que incluyera tipos primitivos para entero y edad, ¿cuál elegirías?
Lo que realmente se reduce a la naturaleza "primitiva" de ciertos tipos es un accidente de la elección del lenguaje para nuestra implementación.
Los números, en particular, generalmente requieren un contexto adicional. ¡La edad no es solo un número, sino que también tiene dimensiones (tiempo), unidades (años?), Reglas de redondeo. Agregar edades juntas tiene sentido de una manera que agregar una edad a un dinero no.
Hacer distintos los tipos nos permite modelar las diferencias entre una dirección de correo electrónico no verificada y una dirección de correo electrónico verificada .
El accidente de cómo se representan estos valores en la memoria es una de las partes menos interesantes. Al modelo de dominio no le importa un
CustomerId
es un int, o una cadena, o un UUID / GUID, o un nodo JSON. Solo quiere las posibilidades.¿Realmente nos importa si los enteros son big endian o little endian? ¿Nos importa si lo
List
que hemos pasado es una abstracción sobre una matriz o un gráfico? Cuando descubrimos que la aritmética de doble precisión es ineficiente, y que necesitamos cambiar a una representación de coma flotante, ¿ debería preocuparse el modelo de dominio ?Parnas, en 1972 , escribió
En cierto sentido, los tipos de valores específicos del dominio que presentamos son módulos que aíslan nuestra decisión de qué representación subyacente de los datos se debe utilizar.
Entonces, la ventaja es la modularidad: obtenemos un diseño donde es más fácil administrar el alcance de un cambio. La desventaja es el costo: es más trabajo crear los tipos a medida que necesita, elegir los tipos correctos requiere adquirir una comprensión más profunda del dominio. La cantidad de trabajo requerida para crear el módulo de valor dependerá de su dialecto local de Blub .
Otros términos en la ecuación pueden incluir la vida útil esperada de la solución (modelado cuidadoso para el software de script que se ejecutará una vez que tenga un retorno de la inversión pésimo), qué tan cerca está el dominio de la competencia central del negocio.
Un caso especial que podríamos considerar es el de la comunicación a través de un límite . No queremos estar en una situación en la que los cambios en una unidad desplegable requieran cambios coordinados con otras unidades desplegables. Por lo tanto, los mensajes tienden a centrarse más en las representaciones, sin tener en cuenta los invariantes o los comportamientos específicos del dominio. No vamos a tratar de comunicar "este valor debe ser estrictamente positivo" en el formato del mensaje, sino comunicar su representación en el cable y aplicar la validación a esa representación en el límite del dominio.
fuente
Estás pensando en la abstracción, y eso es genial. Sin embargo, las cosas que su listado me parecen en su mayoría atributos individuales de algo más grande (no todo lo mismo, por supuesto).
Lo que creo que es más importante es proporcionar una abstracción que agrupe los atributos y les brinde un hogar adecuado, como una clase Persona, para que el programador del cliente no tenga que lidiar con múltiples atributos, sino con una abstracción de nivel superior.
Una vez que tenga esta abstracción, no necesariamente necesita abstracciones adicionales para los atributos que son solo valores. La clase Person puede contener una fecha de nacimiento como una fecha (primitiva), junto con los números de teléfono como lista de cadenas (primitivas) y un nombre como una cadena (primitiva).
Si tiene un número de teléfono con un código de formato, ahora son dos atributos y merecería una clase para el número de teléfono (ya que probablemente también tendría algún comportamiento, como obtener números solo frente a obtener un número de teléfono formateado).
Si tiene un Nombre como atributos múltiples (por ejemplo, prefijos / títulos, primero, último, sufijo) que merecería una clase (ya que ahora tiene algunos comportamientos como obtener el nombre completo como una cadena frente a obtener piezas más pequeñas, título, etc. .).
fuente
Debe modelar según lo necesite, si simplemente desea algunos datos, puede usar tipos primitivos, pero si desea realizar más operaciones sobre estos datos, debe modelarse en clases específicas, por ejemplo (estoy de acuerdo con @kiwiron en su comentario) un La clase de edad no tiene sentido, pero sería mejor reemplazarla con una clase de Fecha de nacimiento y colocar estos métodos allí.
El beneficio de modelar estos conceptos dentro de las clases es que aplica la riqueza de su modelo, por ejemplo, en su lugar tiene un método que acepta cualquier Fecha, un usuario puede completarlo con cualquier fecha, fecha de inicio de la Segunda Guerra Mundial o fecha de finalización de la Guerra Fría, de estas fechas sigue siendo una fecha de nacimiento válida. puede reemplazarlo con Fecha de nacimiento, por lo que, en este caso, aplica su método para aceptar una Fecha de nacimiento para no aceptar ninguna Fecha.
Para Nombre no veo muchos beneficios, UniqueID también, PhoneNumber un poco, puede pedirle que le dé el país y la región, por ejemplo, porque en general PhoneNumber se refiere a países y regiones, algunos operadores usan Sufijos de números predefinidos para clasificar Mobile , Office ... números, para que pueda agregar estos métodos a esa clase, lo mismo para DomainName.
Para obtener más detalles, le recomiendo que eche un vistazo a Value Objects en DDD (Diseño controlado por dominio).
fuente
De lo que depende es si tiene un comportamiento (que necesita mantener y / o cambiar) asociado con esos datos o no.
En resumen, si es solo un dato que se arroja sin mucho comportamiento asociado, use un tipo primitivo o, para datos algo más complejos, una estructura de datos (en gran parte sin comportamiento) (por ejemplo, una clase con solo captadores y setters).
Si, por otro lado, encuentra que, para tomar decisiones, está verificando o manipulando estos datos en varios lugares de su código, lugares que a menudo no están cerca unos de otros, luego conviértalos en un objeto apropiado definiendo una clase y poner el comportamiento relevante dentro de él como métodos. Si por alguna razón considera que no tiene sentido definir dicha clase, una alternativa es consolidar este comportamiento en algún tipo de clase de "servicio" que opere con estos datos.
fuente
Sus ejemplos se parecen mucho más a las propiedades o atributos de otra cosa. Lo que eso significa es que sería perfectamente razonable comenzar solo con lo primitivo. En algún momento necesitará usar un primitivo, por lo que generalmente guardo complejidad para cuando realmente lo necesito.
Por ejemplo, digamos que estoy haciendo un juego y no tengo que preocuparme por el envejecimiento de los personajes. Usar un número entero para eso tiene mucho sentido. La edad podría usarse en verificaciones simples para ver si el personaje tiene permitido hacer algo o no.
Como otros señalaron, la edad puede ser un tema complicado dependiendo de su ubicación. Si necesita conciliar edades en diferentes lugares, etc., tiene mucho sentido presentar una
Age
clase que tengaDateOfBirth
yLocale
como propiedades. Esa clase de edad también puede calcular la edad actual en cualquier marca de tiempo dada también.Por lo tanto, hay razones para promover un primitivo en una clase, pero son muy específicas de la aplicación. Aquí hay unos ejemplos:
Básicamente, si tiene un montón de reglas sobre cómo se debe tratar un valor que desea que se use de manera consistente en todo su proyecto, cree una clase. Si puede vivir con valores simples porque no es realmente crítico para la funcionalidad, use una primitiva.
fuente
Al final del día, un objeto es solo un testigo de que algún proceso realmente se ejecutó † .
Una primitiva
int
significa que algún proceso puso a cero 4 bytes de memoria, y luego volteó los bits necesarios para representar algún número entero en el rango de -2,147,483,648 a 2,147,483,647; lo cual no es una declaración fuerte sobre el concepto de edad. Del mismo modo, un (no nulo)System.String
significa que se asignaron algunos bytes y se voltearon los bits para representar una secuencia de caracteres Unicode con respecto a alguna codificación.Por lo tanto, al decidir cómo representar su objeto de dominio, debe pensar en el proceso para el que sirve como testigo. Si
FirstName
realmente debería ser una cadena no vacía, debería ser un testigo de que el sistema ejecutó algún proceso que garantiza que se haya creado al menos un carácter que no sea un espacio en blanco en una secuencia de caracteres que se le entregaron.Del mismo modo, un
Age
objeto probablemente debería ser testigo de que algún proceso calculó la diferencia entre dos fechas. Comoints
generalmente no son el resultado de calcular la diferencia entre dos fechas, son ipso facto insuficientes para representar edades.Por lo tanto, es bastante obvio que casi siempre desea crear una clase (que es solo un programa) para cada objeto de dominio. ¿Entonces, cuál es el problema? El problema es que C # (que me encanta) y Java exigen demasiada ceremonia para crear clases. Pero podemos imaginar sintaxis alternativas que harían que la definición de objetos de dominio sea mucho más sencilla:
F #, por ejemplo, en realidad tiene una buena sintaxis para esto en forma de patrones activos :
El punto es que solo porque su lenguaje de programación hace que sea difícil definir objetos de dominio no significa que realmente sea una mala idea hacerlo.
† También llamamos a estas cosas condiciones de publicación , pero prefiero el término "testigo", ya que sirve como prueba de que algún proceso puede ejecutarse.
fuente
Piensa en lo que obtienes al usar una clase frente a una primitiva. Si usar una clase te puede ayudar
PhoneNumber
clase, debería ser imposible para mí asignarlo a una cadena o una cadena aleatoria (es decir, "pepino") donde espero un número de teléfono)y esos beneficios hacen su vida más fácil, mejoran la calidad del código, la legibilidad y superan el dolor de usar tales cosas, hágalo. De lo contrario, quédate con los primitivos. Si está cerca, haz un juicio.
También tenga en cuenta que a veces una buena nomenclatura puede manejarlo (es decir, una cadena llamada
firstName
vs. hacer una clase completa que simplemente envuelve una propiedad de cadena). Las clases no son la única forma de resolver las cosas.fuente