0

The title may not be too descriptive, but I couldn't think of a better one. I'm sorry for that.

So, the problem I am having here is one I have come across a couple of times now. It's really about design patterns and principles, and is language independent as long as you have OO facilities in your language. I'll take you through the current problem I am having, as it is hard to explain what the problem is without an actual example.

So, I am using some classes to describe logic statements here. Think of something like this:

condition = new And(new Equals("x", 5),
                    new EqualsOrOver("y", 20));

Now this is all dandy and all, but the problem comes when I want to use this class system. Right now, I am making a system where the condition needs to be translated to an SQL WHERE clause.

I could do this in several ways, but none seem to adhere to the Open/Closed principle. For example, I could have the Database class parse the condition and make it SQL. The problem is that in this way, I can't extend my Condition without the need to change my Database, thus we're not following Open/Closed.

Another way - which would seem logical here - is to add a toSQL() function to my Conditions. However, in that case, I am unable to swap my Database for one that (just to name something) uses XML, that doesn't want the condition in SQL format.

One way I have worked around this problem in the past has been to use a factory. The factory method in this case would look something like this:

turnIntoSQLCondtion(Condition c)
{
    if (c instanceof Equals)
    {
         return new SQLEquals(c);
    }
    else if (c instanceof EqualsOrOver)
    {
         return new SQLEqualsOrOver(c);
    }
    else if (c instanceof And)
    {
         return SQLAnd(c);
    }
}

This isn't all that nice, but it will reduce the violation of Open/Closed.

Now, you can subclass your Database just fine. You will just have to subclass your factory as well, and you will have to make a new bunch of Condition classes specific to your Database as well. You can also work your magic on the Condition classes as well. You can make a new Conditions, but you will have to make companion classes for each Database as well. Lastly, you will have to modify your factories.

It's the smallest violation we have seen so far, but we are still violating Open/Closed. But as a matter of fact, I would much rather not violate it at all. Is there a way to do this while sticking to Open/Closed all the way?

Jasper
  • 11,590
  • 6
  • 38
  • 55

3 Answers3

1

This isn't all that nice, but it will reduce the violation of Open/Closed

Actually no. That method is a violation of OSP ;)

The problem is not as hard as you might think. Simple create a SQL statement factory where you map each Condition class to a SQL condition class.

    public class SqlFactory
    {
        private Dictionary<Type, Delegate> _factoryMethods
            = new Dictionary<Type, Delegate>();

        public void Assign<T>(Func<T, ISqlCondition> factoryMethod)
            where T : ICondition
        {
            _factoryMethods.Add(typeof (T), factoryMethod);
        }

        public ISqlCondition Create<T>(T source) where T : ICondition
        {
            Delegate factory;
            if (!_factoryMethods.TryGetValue(source.GetType(), out factory))
                return null;

            return ((Func<T, ISqlCondition>) factory)(source);
        }
    }

Usage:

        SqlFactory factory = new SqlFactory();
        factory.Assign<And>(obj => new SqlAnd(obj.Value));

        var and = new And();
        var sqlAnd = factory.Create(and);
jgauffin
  • 99,844
  • 45
  • 235
  • 372
  • I'm having some trouble understanding the code you are using. It may just be that I don't know in which language it is written, uses not all that general functionality given by all languages and may just be a language I'm not all that fluent in. However, it looks to me like you are just moving the problem to another place. Somewhere, you will still be calling the assign function and that is the spot where you need to modify your code when you make new `ICondition`s. – Jasper May 06 '11 at 17:08
  • Unless you are using it indeed right with the code where you instantiate the Predicate, but in that case, it's not an exactly transparent system and it will take a lot of effort to switch from one `Storage` to another one of a different type. – Jasper May 06 '11 at 17:12
0

Protected Variations (PV) strives to allow variations in one zone without requiring modification in another. An indirection (delegation) is what @jgauffin proposed, but it only protects one side as you point out. Perhaps you can also use data-driven design (e.g., property file) for different Storage types to protect variations in that direction. I thought of Hibernate and Data Mapper when reading your question.

Fuhrmanator
  • 11,459
  • 6
  • 62
  • 111
0

I'm wondering if some extension by the Decorator patterns would be useful here ?

You'd want to define your predicates almost as a kind of DSL, or a list of rules that can be parsed appropriately.

If you decorate your WherePredicate as a SqlPredicate, then it would read this set of rules and return a SQL statement; an XmlPredicate would do similar.

That would leave you in a nice open/closed situation, Open to extend by adding a new decorator and closed for modification as you the list of rules is inviolable.

Russ Clarke
  • 17,511
  • 4
  • 41
  • 45
  • I'm not entirely sure what you are proposing, but if I'm not mistaken that would mean that if you decide to use a different type of Storage, you would have to change every instance where you made a Predicate. What I want is to be able to swap out one Storage for another completely transparent. Of course, there are solutions for this (only decorating just before execution, decorating through a factory method on the Storage), but the fact that it is, also shows that you have done nothing to change the original situation with an Open/Closed violation. – Jasper May 06 '11 at 16:54
  • In your solution, if I read it correctly, `SqlPredicate` and the `XmlPredicate` would need to know every different `Predicate` that is not a decorator. As such, if you make a new Predicate, you will have to change the `SqlPredicate`. That's not Open/Closed. – Jasper May 06 '11 at 16:57
  • Of course, I may just be misunderstanding your proposed solution. In that case, I'm sorry. – Jasper May 06 '11 at 16:57