What should really drive your design ?
It is indeed tempting to use a very general library to handle units to the perfection.
It is appealing to know that it can convert between any units using the SI system. It is reinsuring to know that
javax.measure provides a function to return the right square-meter unit when raising meters unit to the power of 2. But will it help you to achieve your objectives and fulfil the requirements?
But what's a slice? a thick slice? a thin slice? a medium slice? Let's look at a slice of ham. The SI doesn't define the conversion of a slice to a gram! In fact, I doubt that it s a question of units: a slice of ham will certainly not weithg the same than a slice of cheese or a slice of tomato.
Your design should not be driven by perfection, but by requirements.
Is there a simple, pragmatic approach ?
The main requirement for a recipe is to know what quantity of each ingredient is needed, so:
double quantity; // 3
unit_id unit; // thin slices
material_id ingredient; // of ham
There are two kind of units: the known SI units, and the non-SI cooking units. The SI units in the kitchen are units of weight or unit volume. The non-SI units are pieces of something.
So you can have two classes for this:
* SI units, which depending on the dimension (volume or weight) can convert automatically to the relevant base unit (e.g. a kg is 1000 g)
* Non SI units, which depending on the ingredient can convert to other non SI or to SI units (e.g. 1 thick slice of ham is 2 thin slices of ham, which are each an average 40g of ham).
* SI units from different dimensions, mainly volume to weight and vis-versa (e.g. 1 ml of olive oil, is how many grams?). Here it is also a conversion that is ingredient dependent, but unlike exotic units, it can be simply calculated based on the density of the ingredient
So in the end you just need a conversion between different SI units (ingredient independent) and a conversion from exotic units to SI units (material dependent, and based on either averages or ranges).
The advantage of such a simple model, is that it's very easy to implement. Major ERP systems use such a simple model to routinely encode industrial BOMs and routings from everything from aircrafts to luxus sandwiches. It's a proven approach. It can work for you.
How to do it ?
You can encode the metric prefix in the relevant class.
The unit could be encoded in their own class. For every unit, you'd have an inidciation whether it's SI (and which dimension) or if it's exotic.
Finaly you'd have a class for the conversion factors. It would be ingredient dependent and define the conversion factors from one source unit to a target unit.
Otherwise stated, you'd have very little hard-coded, and most of the information that is kept in ingredient-dependent entities.
For teh sake of simplicity, I have mainly spoken about units for ingredients, and have ignored the activities. Activities are time dependent.
It's not in any SI definition, but when it comes to cooking there are two kind of times units:
- The absolute time units: Boil the egg for 4 minutes. This is straight forward.
- The relative units: Put the chicken in the oven until it's roasted. THis is more complex, since very often it's not absolute but it will depend on the ingredients (chicken doesn't get roasted as fast as salmon) and the weight ( either you hava a calculation formula based on teh energy absorbtion of the ingredient, or youdo as myself, you ask your butcher and determine a range table (half an hour for small chicken under 500g, one hour between 500 and 1kg. 2 hours above 1 kg). So here, it's a notion of scale.
I say this, because industrial practice is to store BOMs and routings separately, except for chemical industry (and cooking should belong to this), which merge the two notions into a single recipe that contains both ingredients and activities to process these ingredients.