Primitive Obsession
Primitive Obsession AKA StringlyTyped means being obsessed with primitives. It is a Code Smell that degrades the quality of software.
“Primitive Obsession is using primitive data types to represent domain ideas” #
The code in StringlyTyped
demonstrates the problems associated with using primitives to represent domain concepts. It provides an alternative by using Value Objects. These examples are provided in the ValueObjectTests.cs
file.
- Equality - primitives only provide reference equality whereas Value Objects also provide value equality.
- Validation - Value Objects can perform validation of data to ensure it conforms to the requirements of the domain.
- Nullness - Value Objects can have a null value. You can use this to represent the concept of “no value”.
- Hashing - Value Objects can be hashed.
Examples
- Age is a Value Object in
ValueObjectTests.cs
which validates that the age is greater than or equal to 12. - EightiesDate is a Value Object that validates that a date falls in the 1980s.
- Dave is a Value Object that validates that a name contains the string “dave”.
Advantages of using Value Objects
- Improved code readability - Value Objects make your code more readable as they make it clear what kind of data is being used.
- Reduced code duplication - Value Objects help to reduce code duplication as they encapsulate validation and other logic.
- Easier to test - Value Objects are easier to test as they have well-defined interfaces.
Code Snippets
ValueObjectTests.cs
public class ValueObjectTests
{
[Fact]
public void validation()
{
Func act = () => Age.From(12);
act.Should().ThrowExactly();
Func act2 = () => EightiesDate.From(new DateTime(1990, 1,1));
act2.Should().ThrowExactly();
Func act3 = () => EightiesDate.From(new DateTime(1985, 6,10));
act3.Should().NotThrow();
string[] validDaves = new[] { "dave grohl", "david beckham", "david bowie" };
foreach (var name in validDaves)
{
Func act4 = () => Dave.From(name);
act4.Should().NotThrow();
}
string[] invalidDaves = new[] { "dafid jones", "fred flintstone", "davidoff cool water" };
foreach (var name in invalidDaves)
{
Func act5 = () => Dave.From(name);
act5.Should().ThrowExactly();
}
Func act6 = () => MinimalValidation.From(1);
act6.Should().ThrowExactly().WithMessage("[none provided]");
}
}
BasicExample.cs
internal class BasicExample
{
public static void Run()
{
// we can't mess up the order of parameters - doing the following results in:
// Argument 1: cannot convert from 'SupplierId' to 'CustomerId'
// new CustomerProcessor().Process(SupplierId.From(123), SupplierId.From(321), Amount.From(123));
new CustomerProcessor().Process(CustomerId.From(123), SupplierId.From(321), Amount.From(123));
}
internal class CustomerId : ValueObject
{
}
internal class SupplierId : ValueObject
{
}
internal class Amount : ValueObject
{
}
internal class CustomerProcessor
{
internal void Process(CustomerId customerId, SupplierId supplierId, Amount amount) =>
Console.WriteLine($"Processing customer {customerId}, supplier {supplierId}, with amount {amount}");
}
}
Top-Level Directory Explanations
samples/ - This directory contains example projects demonstrating the usage of StringlyTyped library.
src/ - This directory contains the source code of the StringlyTyped library.
tests/ - This directory contains unit tests for the StringlyTyped library. It includes benchmark tests and small tests.