One thing I continuously get taught by books that suppose to teach good programming style is that you should avoid if-cascades (for example: http://antiifcampaign.com) at almost all cost. But at answering the question on how to achieve this, everyone tells different things. And from my experience I must say: right so. There truly exist different paths which are equally valid. And you have to decide considering the situational factors. On local cases one could use the switch statement. Or return early when certain halt conditions are met. The strategy pattern solves the problem with more ceremony. There also is double dispatch. Others state, that you missed the mark of the problem when you encounter such cases and can circumvent the problem with a better domain model.
But sometimes there are cases when you must decide between different solutions simply by looking at the values of a handful of properties. In the prototyping phase or early in development I often have only two cases. If-else comes here fast to the mind. But later when you add more, I find myself in situations where I would have to add a third or fourth if-else branch. Whenever I encounter such situations I force myself to halt and put some more thought on a better solution. Often finding myself redesigning big parts of the program. I must admit, my software designs are to a big extend evolutionary, so I wouldn’t wonder if any line of code has changed at least once between its birth and the finished program. I must also admit that early in my software-developer-life I had some difficulties finishing at all. You might argue that no software is really ever finished. When I say finished I mean a first version that gets the job done to a degree that the customers and projected users are satisfied.
Back to the topic: in my current project I again stumbled over the problem of the dreading if-cascades. I’m actually making a validator, that checks different aspects of a JSON object that gets send as a string via a simple HTTP request. It’s not mainly the validity of the format. It is more the data, which partially has to get checked against a database. By the way an MSSQL database, the whole reason for using Java again, because of the excellent freetds driver, and the lack thereof in other languages. At its heart I’m using the composite pattern (not in its pure form, I have no explicit leave-type) to put validation steps for different aspects under validator-hats which again can be combined under validator-hats. I created some building blocks from whom actual validation steps inherit from.
One of those building blocks is the class SimpleStringValue. This class checks if a certain key is present in the JSON object, if its value is a string and if this string has a certain minimum and maximum length. The latter three aspects are by default optional and can be defined as mandatory … HOLD IT! … as I’m writing this, I realize that I really can avoid the whole if-cascade-situation inside this class. By making itself a validator-composite and checking the length constraints, the existence and the type in composed validation steps. Plugging them in the composite if mandatory. You know: separation of concern and one responsibility by class … [slap on the forehead]. Another good reason for writing: you discover logical holes in the things you did so far.
[mental note: redesign the SimpleStringValue class]
By now I missed the topic of this post by miles. OK, then without a concrete example, simply assume you a have a situation where you have to decide between different options by acknowledging the values of different parameters. Take a look at the following gist. It shows the application of some little helper classes I made to deal with cases were you need to avoid if-cascades and couldn’t come up with a better design:[gist https://gist.github.com/4257918]
In the first part I define possible options (objects of type Op). They are held in a container which applies the first valid option and stops then (class ExOps). I make use of some static helper methods, so that it looks a little nicer. Every option is defined by a lambda expression, wrapping the target behavior, and zero or more expectations (objects of type Exp). When all expectations of an option are met the option is taken, and the provided function (lambda expression) gets called, provided with the input (a list of objects of type Del, which stands for delivered) and is expected to return an object of type Res (a simple wrapper around any value).
Next I define a lambda expression that acts as a test executor. It takes the to-be-tested-string and a boolean value (that marks the string as optional) as input parameter and creates Del objects from this input. Notice that I directly use if-else expressions and rely on their return value. Which in the second case can be null. A really nice language feature of Xtend. Those Del objects are used as parameters for the apply method of the ExOps object from above. The wrapped value of the returned Res object is used as the return value of the local lambda expression.
The last step are some tests with different input parameters and should be quite self-explanatory. The following gist shows the classes which enable this behavior. No need to spread the classes over different files like in Java, in Xtend everything can be put in one file:[gist https://gist.github.com/4257278]
Here are some more tests of the basic behavior:[gist https://gist.github.com/4257401]
And finally an extension method I’m using:[gist https://gist.github.com/4257954]