I am working on a command-line application that runs simulations. It has to be heavily configurable; the user should be able to provide a very large number (100+) of parameters, some mandatory and some optional. Obviously, these parameters will be used by a variety of classes in the application.
I can see two approaches I can use; which one is a better design in the long run?
Approach 1
Have the (text) input file directly control object creation in the application. For example, user would provide the following input:
root=Simulation
time=50
local_species=Sentient
strength=3.5
intelligence=7.1
invading_species=Primitive
strength=5.1
sociability=2.6
The application would then recursively create objects of the class specified on the rhs of the equal sign, passing the class’ constructor the arguments provided in the relevant sub-branch of the input file, if any. I’m using Python, which supports keyword arguments and default argument values when no argument is provided, but I hope the question isn’t terribly language-specific.
For example line 1 would tell the app to create an object of class Simulation
, with three constructor arguments. The first constructor argument is time=50
. The second argument is an instance of class Sentient
, which is created by passing Simulation
‘s constructor arguments strength=3.5
, intelligence=7.1
. The third argument is an instance of class Primitive
, which is created with by passing the constructor arguments strength=5.1
, sociability=2.6
.
The whole object creation is handled automatically, with just a few lines of (recursive) code. The highest-level object (instance of Simulation
in this case) would be passed to the visualization layer.
Approach 2
The input file still looks the same. However, the application’s classes now have full control over how they interpret user input. The lhs and rhs values may or may not correspond to keyword parameters and classes. For example, the above input may result in the application creating an instance of class InvasionSimulation
with arguments strength_local=3.5
, intelligence_local=7.1
, strength_invading=5.1
, sociability_invading=2.6
, and then calling its method set_runtime(time=50)
, before passing the object to the visualization layer.
One obvious advantage of approach 2 is that the user’s input can remain stable even as the application is completely redesigned internally. But I think there’s nothing wrong with designating a subset of classes as “public API”, and therefore ensuring that it doesn’t change in the future, is there?
On the other hand, if the application internally follows something quite similar to approach 1 anyway, it feels that approach 2 requires adding a lot of redundant code: instead of automatically interpreting user input, we now need to do it “by hand” for each class.
Any additional considerations would be much appreciated. Are there any names for these approaches that I can search for?
2
I would use somethink like JAXB (in java and with xml configuration), or some JSON mapper (with JSON configuration) (I don’t like languages where number of whitespaces is significant, but thats personal taste). Or some other mapper for your prefered/forced format.
With this, I would use mapped objects if they match what I need inside aplication (approach 1) , and if not I would use them as parameter for factory methods (similar to approach 2), so format of their textual source does not matter, only their content while loaded.
This would allow to use multiple input formats without changing anything else than reading of those files.
I would start with approach 1 if it looks promising and refactor later.
3
In my personal experience the single best approach to pulling command or config arguments into a command line program is a dictionary. The input is naturally structured as key-value-pairs so it just make sense from an input standpoint, but the bonus is maintainability when you merely have to add new key handlers and never new parsing code as the parser simply pulls everything into a dictionary allowing whatever keys needed. Also it doesn’t force scoping onto the consumer of the arguments space, so each argument and value is available by default to any consumer who feels it relevant, you don’t have to do leg work to publicize the argument and value to consumers.
Moreover, I have had a habit of creating at start a dictionary available to main which has a list of keys that correspond to arguments, and a list of values which are functions to parsing and handling said keys in so far as those keys require configuration of the system state before processing occurs. For example, a key of v may have a func that parses the value of that arguments key into setting System.Verbosity = true;
and the argument parser just calls _startupDictionary[argument](value);
and if the function returns false that means the argument was invalid or had an invalid argument, in this way it may notify the argument parser that the system state will be inconsistent given the set of arguments available and cannot therefore process correctly.
3