What you seem to be looking for is known as tagged types. They are a way of saying "this is an integer which represent age" while "this is also an integer but it represents weight" and "you can't assign one to the other". Note, that this goes further than physical units such as meters or kilograms: I may have in my program "people's heights" and "distances between points on a map", both measured in meters, but not compatible with each other since assigning one to the other doesn't make sense from the business logic's perspective.
Some languages, like Scala support tagged types quite easily (see the link above). In others, you can create your own wrapper classes, but this is less convenient.
Validation, e.g. checking that a person's height is "reasonable" is another issue. You can put such code in your Adult
class (constructor or setters), or inside your tagged type/wrapper classes. In a way, built-in classes such as URL
or UUID
fulfil such a role (among others, e.g. providing utility methods).
Whether using tagged types or wrapper classes will actually help make your code better, will depend on several factors. If your objects are simple and have few fields, the risk of assigning them wrong is low and the additional code needed to use tagged types may not be worth the effort. In complex systems with complex structures and lots of fields (especially if many of them share the same primitive type), it may actually be helpful.
In the code I write, I often create wrapper classes if I pass around maps. Types such as Map<String, String>
are very opaque by themselves, so wrapping them in classes with meaningful names such as NameToAddress
helps a lot. Of course, with tagged types, you could write Map<Name, Address>
and not need the wrapper for the whole map.
However, for simple types such as Strings or Integers, I have found wrapper classes (in Java) to be too much of a nuisance. Regular business logic wasn't so bad, but there arised a number of problems with serializing these types to JSON, mapping them to DB objects, etc. You can write mappers and hooks for all the big frameworks (e.g. Jackson and Spring Data), but the extra work and maintenance related to this code will offset whatever you gain from using these wrappers. Of course, YMMV and in another system, the balance may be different.
a.SetAge( new Age(150) )
still compile?new Age(...)
object you can't incorrectly assign it to a variable of typeWeight
in any other places. It reduces the number of places where mistakes can happen.