For the last year I have been working on and off on a framework that should interface an existing product. The interface should be abstract so that it was possible to change the product without the interface of the framework changing. It should of course also be very easy to use.
One of the things I did to make this happen was to make a class structure so that criterias for searching data could be nested (composite pattern) in a tree structure. In this way users can make complex searches and easily combine them into many levels in the tree.
Consider the following example:
FirstName = "Rune" and (LastName != "Petersen" or LastName != "Knudsen") and Age < 32
I want someone called Rune, but not Petersen or Knudsen. The person should be below 32.
This shows the object structure you need to create to be able to perform the search. Although everything is nicely separated from the actual implementation the drawback of this structure is that it takes many lines of code to construct the criteria, and it being multiple levels of objects makes it hard to instantly comprehend when you look at it.
Another not so appealing thing is that small logical changes might course the structure to be very different. For instance if you don't want to and the Age criteria, but or it. Now suddenly there must be 3 levels in the object structure.
FirstName = "Rune" and (LastName != "Petersen" or LastName != "Knudsen") or Age < 32
Results in the following object structure.
This has troubled me, since I generally like the object structure for its extendibility. But then I rediscovered operator overloading.
First I changed the criteria interface to be an abstract class. Although operator overload are static, and explicitly define what classes they act on, in C# they have to be implemented as part of the most general type used. In this case criteria. So therefore no more interface.
I implemented the & and | operator on criteria. This made wonders to usability. When I have defined my simple criterias, I can now define my whole object structure on one single line.
FirstNameCriteria & (LastNameCriteria1 | LastNameCriteria2) & AgeCriteria
Not only is it easier to overlook the whole structure, it is also much easier to change it. All it takes to generate the second structure is changing a character.
FirstNameCriteria & (LastNameCriteria1 | LastNameCriteria2) | AgeCriteria
To make it even easier to read I also overloaded the compare operators on the Fields. This only deals with the simple criterias, but the rest will have to be dealt with later. having overloaded operators on the Field, the criterias can be created like this:
FirstName == "Rune" & ( LastName != "Petersen" | LastName != "Knudsen" ) & Age < 32
What remains for me is to find a way to document this functionality. As intellisense and documentation written for each overload doesn't show, my concern is that nobody will ever use this (and thereby not save a lot of time).