This is related to my recent question regarding naming awkward domain objects. A number of answers indicated I was using the wrong representation for the domain objects. To summarize, I chose to use an enum to represent some domain objects used as keys in lookups.
For this question, I want to review my decision to use an enum and identify alternatives. Jump to the bottom of my question for the TL;DR.
Domain Description
My application is responsible for performing some calculations involving chemical compounds.
Some basic equations are:
- calculating the number of moles of a compound
- calculating the specific gravity or density of a compound at various concentrations
These equations could be handled as methods within a Chemical
class like:
Chemical.GetMolesFromMass(double mass_grams)
or
Chemical.GetDensity(double concentration)
or they could be implemented as methods off of a generic chemical properties class like:
Chemical.GetMolesFromMass(Compound cmpd, double mass_grams)
or
Chemical.GetDensity(Compound cmpd, double concentration)
I think from an object responsibility point of view, the first option of methods off of the Chemical
class would be a better representation.
Some advanced equations involve:
- calculating quantities of a compound consumed by a business process
- calculating storage requirements of compounds
- optimizing deliveries based upon standard container sizes
In these equations, the chemical compound is an input to the method.
StorageTankCluster.Generate(Compound cmpd, double requiredVolume)
The StorageTankCluster
needs to know about the chemical because the dimensions and spacing of the tanks within the cluster will change based upon the chemical and required volume. The chemical being stored can affect tank material selection as well as impacting safety clearances between tanks.
At the moment, I’m considering those parameters (spacing, material, …) as lookup items instead of elements that the compound is responsible for knowing about.
Likewise, the delivery optimization as affected by the chemical compound but it’s arguable on whether or not those aspects are the compound’s responsiblity.
Both the StorageTankCluster
and the delivery optimization components are intended to be reused by other non-chemical aspects of the application. As this question is already getting complicated, we can just call those other elements Foo
and Bar
. Both Foo
and Bar
could utilize the delivery optimization, but only Foo
can be used with the StorageTankCluster
.
It’s not in the application now, but all of the various properties discussed so far will need to be stored within a database / persistence layer. I realize that shouldn’t affect a good design, but I thought it was worth mentioning.
I’ll admit that some of my decisions have been biased because I’m porting this application from another source that forces everything to be done in a structured manner. Writing out this question has highlighted a few areas where I should have better examined what object had responsibility for that functionality.
My design so far has been oriented around lookup tables and I’m using the enum values as the keys to those tables. It is their use as keys that causes me to treat them as named objects within the program instead of treating them as objects the system would operate upon. This has pretty much relegated the chemical compounds to 2nd-tier class status, and I’m wondering if that was the right decision. On the other hand, I’m concerned about the chemical compound class becoming bloated beyond usefulness.
TL;DR
My implementation is / must be in C#. So was using an enum the right choice, or what should I have done and why?
We want to code to be as simple as possible. We don’t want it cluttered up with domain knowledge that the programmer shouldn’t need to know. Thus, whenever possible, we want to avoid putting something as domain specific as chemical products into the code. That’s why we think that putting chemical products as an enum
into your code is probably a bad idea unless proven otherwise.
As a similar case, consider a course registration system. You wouldn’t want the code to contain any references to various courses that people should be taking. CSI101 may be “Introduction to Computer Graphics”, but that should not be in the code at all. That information should only be in a database row for the course. The course registration system code treats all classes the same, only perhaps considering properties of the course such as name, prerequisites, credit hours, etc.
So the first question is whether you actually need to have references to the various chemical compounds in your code. This is independent of whether or not you are using lookup tables. Chemical.GetDensity(Compound cmpd, double concentration)
can work just the same whether Compound
is a enum, object, string, or int. StorageTankCluster.Generate(Compound cmpd, double requiredVolume)
, is the same, the function works the same whether or not the Compounds are actually named entities in the code. None of these things remotely require you to reference compounds in your code.
Of course, somewhere you have to specify which compound is being considered. It can’t always be a parameter to your function. It is the nature of that specification that determines whether or not you need to have reference to the chemicals in your code. Your statements seem to indicate that the business process determines which compounds need to be considered. I gather that the business process is written into code, and that’s why you need to refer the chemical compounds in code. But the question is whether the business process really needs to be implemented in code.
To see what I mean, lets look back at the course registration. Suppose that you’ve got a class with a complicated rules for whether you are allowed to take it.
boolean CSI250::MayTake(Student student)
{
// if the student has special permission, he can always take the course
if( student.HasOverrideForCourse("CSI250") ) return true;
// Student must have taken 101 first
if( !student.HasTaken("CSI101") ) return false;
// Student must have at least a B in CSI 101
if( student.GetGrade("CSI101") < Grades.B) return false
// Student must have taken either CSI150 or CSI201
if( !student.HasTaken("CSI150") || !student.HasTaken("CSI201") ) return false;
return true;
}
If I understand correctly, you need an enum because you are referring to various chemical products in your process like I’ve done for courses above. (I’ve used strings, but I could have done enums just as well.) But the above code wouldn’t be a good idea, because it puts too much domain information into the process. It’ll be a pain maintaining it as the powers that be tweak and adjust things. So what we’d rather do is store the prerequisite information in the database, something like:
Course Prereq Grade Required Alternate Grade Required
CSI250 CSI101 B
CSI250 CSI150 D CSI200 B
Now the prerequisite information is moved from the code into the database. It can be manipulated by the administration, the code is simpler, etc. The question is whether you can do the same for your business processes. There are many benefits to be had by moving something like that out of the code into data. Maybe you can’t, but its what I’d look to do.
I’d probably avoid enum
even if you have to put these entities into the code. enum
s are pretty stupid. I think it’d be better to be able to say: Chemicals.SodiumPhosphate.GetMolesFromMass(1000)
than to say GetMolesFromMass(Chemicals.SodiumPhosphate, 1000)
. It also gives you future flexibility because the method can do whatever it wants. It can use a lookup table, or store the data inside, or whatever.
1