¿Qué es la obsesión primitiva? Para empezar, las primitivas son los tipos de datos básicos disponibles en la mayoría de los idiomas. Estos incluyen tipos de datos como cadenas, números (int, flotantes) y booleanos. La obsesión primitiva es un olor a código en el que los tipos de datos primitivos se usan en exceso para representar sus modelos de datos. El problema con las primitivas es que son muy generales. Por ejemplo, una cadena podría representar un nombre, una dirección o incluso una identificación. ¿Por qué es esto un problema? No pueden contener ninguna lógica o comportamiento específico del modelo, lo que significa que cualquier lógica debe almacenarse en la clase contenedora. Esto significa que termina con clases que contienen mucha lógica no relacionada, lo que viola el principio de responsabilidad única ( ). lea más aquí Pierden su tipo de seguridad. Una cadena es una cadena. El compilador no sabe si le está pasando una cadena que representa un nombre o una dirección. Esto significa que es fácil asignar accidentalmente un valor al campo incorrecto, el código se ejecutará y compilará bien, y es posible que no se dé cuenta hasta que los datos estén completamente desordenados. Curar la obsesión primitiva A continuación se muestra un ejemplo de una clase de persona que sufre de obsesión primitiva: { { } Id { ; ; } FirstName { ; ; } LastName { ; ; } Address { ; ; } PostCode { ; ; } City { ; ; } Country { ; ; } { } } public class Person ( ) public Person id, firstName, lastName, address, postcode, city, country string string string string string string string // initialisation logic public string get set public string get set public string get set public string get set public string get set public string get set public string get set ( ) public void ChangeAddress address, postcode, city, country string string string string // change address logic ¿Que esta mal aquí? Bueno, tenemos una clase que consta completamente de propiedades de cadena. El constructor consta de una larga lista de parámetros de cadena: ¡puedo garantizar que en algún momento se asignará el valor incorrecto a la ranura de parámetro incorrecta! También tenemos un método para cambiar la dirección, pero realmente esta lógica no debería ser responsabilidad de la clase Persona. Finalmente, la ID también es una cadena, por lo que podría usarse accidentalmente como ID para otros tipos. ¿Qué podemos hacer? Bueno, lo primero que podemos ver es, ¿hay propiedades que tengan sentido agruparlas? Una buena prueba para esto es preguntar, ¿cuál de estas propiedades es probable que se actualicen juntas? En nuestro ejemplo, está claro que los campos Dirección, Código postal, Ciudad y País deben agruparse (si se muda de casa, es probable que todas o la mayoría de estas propiedades se actualicen juntas). Refactoricemos estas propiedades en su propia clase entonces. { { } Address { ; ; } PostCode { ; ; } City { ; ; } Country { ; ; } } { { } Id { ; ; } FirstName { ; ; } LastName { ; ; } Address Address { ; ; } } public class Address ( ) public Address address, postCode, city, country string string string string // initialisation logic public string get set public string get set public string get set public string get set public class Person ( ) public Person id, firstName, lastName, Address address string string string // initialisation logic public string get set public string get set public string get set public get set Nuestra clase Person ya se ve mucho mejor. Toda la lógica relacionada con la dirección ahora está encapsulada en la clase de dirección y hemos logrado eliminar muchos parámetros de cadena del constructor. Es posible que haya notado que, en general, todavía tenemos la misma cantidad de propiedades de cadena. Esto está bien ya que, en última instancia, es probable que necesite almacenar sus datos en una primitiva. La parte importante de evitar la obsesión primitiva es encapsular esas primitivas en objetos bien definidos que realmente representen su significado. Lo siguiente que tenemos que tratar es la identificación. Para devolver la seguridad de tipo a la identificación, podemos crear lo que se conoce como una identificación fuertemente tipada. En resumen, este es solo el valor primitivo envuelto en un objeto contenedor específico para esa entidad. Por ejemplo, una implementación de un objeto PersonId podría verse como (adaptado de ): aquí PersonId : IComparable<PersonId>, IEquatable<PersonId> { Value { ; } { Value = ; } => PersonId(Guid.NewGuid().ToString()); => .Value.Equals(other.Value); => Value.CompareTo(other.Value); { (ReferenceEquals( , obj)) ; obj PersonId other && Equals(other); } => Value.GetHashCode(); => Value.ToString(); ==(PersonId a, PersonId b) => a.CompareTo(b) == ; !=(PersonId a, PersonId b) => !(a == b); } { { } PersonId Id { ; ; } FirstName { ; ; } LastName { ; ; } Address Address { ; ; } } public readonly struct public string get ( ) public PersonId string value value PersonId ( ) public static New new ( ) public bool Equals PersonId other this ( ) public int CompareTo PersonId other ( ) public override bool Equals obj object if null return false return is ( ) public override int GetHashCode ( ) public override string ToString public static bool operator 0 public static bool operator public class Person ( ) public Person PersonId id, firstName, lastName, Address address string string // initialisation logic public get set public string get set public string get set public get set Esto es bueno ya que ahora la clase Person usa su propio PersonId para la ID. No hay forma de que pueda usarse accidentalmente como ID para otro tipo, ya que tendrá su propia ID fuertemente tipada. Sin embargo, es posible que haya notado que esta definición de PersonId es algo grande y sería engorroso tener que declarar esto para cada ID fuertemente tipado que desee crear. Para ser honesto, este obstáculo es probablemente la razón por la que los ID fuertemente tipados aún no son tan comunes en el desarrollo de C#. Afortunadamente, en C# 9, tenemos el nuevo tipo de registro que facilita mucho la definición de ID fuertemente tipados, por lo que esperamos que su uso sea mucho más común (puede leer más sobre los registros ). aquí ; personId = PersonId( ); // PersonId as a record record ( ) public PersonId Value string // how to initialise var new "my-id" Sí, eso es literalmente. Esto declara un registro PersonId con una propiedad de cadena llamada Valor, que se puede pasar a través del constructor. Los registros basan automáticamente la igualdad en los valores de sus propiedades, por lo que no es necesario agregar nada más en la declaración. Obsesión primitiva en F# Aunque este artículo se ha centrado principalmente en C#, y todos los principios aplicados anteriormente se pueden usar en F#, hay una característica única en F# que creo que vale la pena mencionar. Las uniones discriminadas son un tipo de datos en F# (y en muchos otros lenguajes, pero no en C#) que permiten devolver diferentes tipos de datos según la situación (para obtener más información sobre las uniones discriminadas, consulte ). Un caso de uso común es el manejo de errores: aquí = | Data | Error string < > type Result 'a of 'a of Este ejemplo anterior permitiría que una función devuelva algún tipo de datos genérico, pero si hay un error, devolverá una cadena. Sin embargo, una unión discriminada de un solo caso puede actuar como un envoltorio para un tipo primitivo y brinda seguridad de tipo a sus funciones. Por ejemplo, podemos tener este registro de Persona: = { FirstName: string; LastName: string; EmailAddress: string; } type Person El problema aquí es que una dirección de correo electrónico no es realmente solo una cadena: tienen un formato específico y, por lo tanto, es posible que deseemos diseñar una lógica de validación específica para ella. Debido a esto, es mejor encapsularlo en su propio tipo. Con uniones discriminadas de un solo caso, esto es realmente fácil: = EmailAddress string = { FirstName: string; LastName: string; EmailAddres: EmailAddress; } type EmailAddress of type Person Ahora el registro de Persona solo aceptará direcciones de correo electrónico del tipo Dirección de correo electrónico. Conclusión En este artículo, he introducido el concepto de obsesión primitiva, qué problemas puede causar y cómo solucionarlos. Con suerte, puede quitar parte de esta información y usarla en su propio código base para crear un código más seguro y fácil de mantener. Publico principalmente sobre desarrollo web de pila completa .NET y Vue (¡y pronto tal vez más contenido de F#!). Para asegurarse de no perderse ninguna publicación, siga este blog y . Si te ha resultado útil esta publicación, dale me gusta y compártela. También puedes encontrarme en . suscríbase a mi boletín Twitter También publicado en https://dev.to/dr_sam_walpole/a-cure-for-primitive-obsession-14l6