Programming Improvement 2 – Using interfaces to force proper grammar in a fluent interface

After making yesterday’s improvement to my multi-database SQL query builder (making the library code safer for programmers who use it), I realized probably I could do better.

Instead of throwing an exception if the programmer forgot to specify something, what if I could force them to make all the required calls before they could call the final method?

As an example, the way the code worked, a programmer could possibly write this:

This would throw one of the exceptions I added yesterday, since it didn’t have a WHERE condition and the programmer hadn’t called ApplyToAllRows().

Since all the methods of the DeleteQuery object returned the DeleteQuery object (this is needed to make the fluent interface), all methods were available at all times.  I wanted a way to only let the programmer get the SQLCommand object if they either added a WHERE condition or called ApplyToAllRows().

Here’s how I did it.

First, I created an interface named ICanApplyToAllRowsOrAddWhereCondition (I know, I like long names).  This interface would only let the user call the ApplyToAllRows() or Where(columnName, comparator, value) methods of the class.  The factory method that used to return a DeleteQuery object now used this interface type as the return type.

So, when the programmer gets the object from the factory, the only options are to call one of these two methods.

If the programmer calls the Where method, it returns an interface type of ICanAddWhereConditionOrGetSQLCommandObject.  This lets the programmer add more WHERE conditions or get the SQLCommand object.  It won’t allow them to call ApplyToAllRows(), once they’ve made a call to Where().

If the programmer called the ApplyToAllRows() method instead, that now returns a new interface type of ICanGetSQLCommandObject.  The only thing this interface allows is to get the SQLCommand object.  This prevents the programmer from adding any WHERE conditions.

Now, anyone using this library can only ever get the SQLCommand object after they’ve made any other required calls.

It was a bit messy to figure this out.

Deciding what options are available at what point, and making sure I returned the correct interface type, was a bit difficult to keep straight.  Fortunately, the unit tests showed where I needed to make corrections.

I was also able to delete the exceptions I created yesterday.  Since it’s no longer possible to get in one of those invalid query situations, the exceptions aren’t needed.

Here is a copy of the QueryBuilder and DeleteQuery classes (with a bunch of the code snipped out), along with the interfaces the DeleteQuery uses.

QueryBuilder.cs

 DeleteQuery.cs

 ICanApplyToAllRowsOrAddWhereCondition.cs

ICanGetSQLCommandObject.cs

ICanAddWhereConditionOrGetSQLCommandObject.cs

 

Today’s improvement: Instead of throwing error conditions if a programmer doesn’t use your library as expected, force them to use it correctly by limiting available options through interfaces.

Leave a Reply

Your email address will not be published. Required fields are marked *