3

The Scenario

I'm creating a dynamic query builder to send to another component (a report builder).

Some parts of the query have placeholders. For example:

SELECT DISTINCT ID, NAME AS VALUE FROM EVENTS
WHERE {{ESTABLISHMENTFILTER.ID}} IS NULL OR ESTABLISHMENT_ID =   {{ESTABLISHMENTFILTER.ID}}

The data to be replaced in the where clause can be an Integer, a String, a Date, and each one has a different behavior (for example: include single quote around the value in a string case).

My first approach was to create a Enum:

public enum FilterType
{
    Integer,
    String
}

and use it like this (in business layer for example)

switch (filter.Type)
{
   case FilterType.Integer:
        //Do replace logic for an integer
        break;
   case FilterType.String:
        //Do replace logic for a string
        break;
   default:
        break;
}

I'm also applying SOLID principles to my code, and I figured out that this could break OCP. So I refactored to use a base class

public abstract class FilterType
{
    public abstract string Replace(string baseString, string oldValue, string newValue);
}

and each Type has its own implementation:

public class FilterTypeInteger : FilterType
{
    public override string Replace(string baseString,string oldValue, string newValue)
    {
        //Do logic to replace for an Integer type
    }
}

The problem

The SOLID solution worked for my tests, but in production code there's an int column in Database to determine the type. So I'm basically transferring the 'switch-case' logic to the datalayer, which will have to check this column to instantiate the correct FilterType (the code below is a pseudo-code because I have not implemented it yet):

    if (dataReader["FILTERTYPE"] == 1)
        filter.Type = new FilterTypeInteger();
    else if (dataReader["FILTERTYPE"] == 2)
        filter.Type = new FilterTypeString();

The Questions

1) The method implementing the 'if-else' logic above is breaking the OCP? Because if a new Type is created, a new else clause must be implemented
2) Is there another approach to keep the SOLID OCP principle to both database and business code without using the switch ou if-else clauses?

Danilo Ruziska
  • 457
  • 2
  • 6
  • 13
  • If you are building a SQL statement why not use a library and a prepared statement to handle those things for you? – Seth Jan 16 '17 at 14:17
  • The query is created through an existing user interface, so it must support the placeholders defined in a template. Also it must support cascading queries That's why I decided to build my own component. – Danilo Ruziska Jan 16 '17 at 15:00

1 Answers1

1

Replacing conditional with polymorphysim will ensure that the decision making will only have to happen once, so it probably is a good idea. If you have additional specialized operations per type at some point then they should be easy to implement.

Now, for creating the concrete types you may encapsulate this logic in a factory. In it's most simplistic form the factory will be a static one with a large switch statement. It doesn't respect the OCP, but it's still an acceptable design most of the time.

However, if you want to be extensible by design and at runtime that will not do it and you need to introduce a way that allows to discover/register new types at runtime.

This can be done in tons of ways, but an example would be to have a method on the factory that allows you to register new types.

E.g.

filterTypeFactory.RegisterFilter(1, typeof(FilterTypeInteger));

Anyhow, before you go own and build your own SQL statement builder you should look at existing libraries. You may have an intermediary DSL (your templates) that gets parsed into an AST and then process this AST to generate a SqlCommand or something like that.

openshac
  • 4,966
  • 5
  • 46
  • 77
plalx
  • 42,889
  • 6
  • 74
  • 90
  • That's a good solution. I think the lesson learned from this is: sometimes is ok not to use SOLID but keep a good pattern. I'll search more about those libraries you mentioned. – Danilo Ruziska Jan 16 '17 at 15:15
  • @DaniloRuziska The SOLID principles are guidelines, but you have to stay pragmatic. If a static factory does the job done then it's probably better to stick with it rather than building a very sophisticated extensible mecanism that allows to auto-discover new filter types when you do not need it. – plalx Jan 16 '17 at 15:19
  • @plalx Occam's Razor is a good excuse for 'violating' principles. – CSharpie Jan 16 '17 at 15:21
  • @CSharpie Only if we assume that simpler usually leads to better in software development, but I think we can ;) – plalx Jan 16 '17 at 15:26