【问题标题】:Open/Closed for flexible software打开/关闭灵活的软件
【发布时间】:2011-05-06 12:06:05
【问题描述】:

标题可能不太具有描述性,但我想不出更好的标题。对此我很抱歉。

所以,我在这里遇到的问题是我现在遇到过几次的问题。它实际上与设计模式和原则有关,只要您的语言中有 OO 工具,它就与语言无关。我将带您了解当前遇到的问题,因为如果没有实际示例,很难解释问题所在。

所以,我在这里使用一些类来描述逻辑语句。想想这样的事情:

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

现在这一切都是花花公子,但是当我想使用这个类系统时问题就来了。现在,我正在制作一个需要将条件转换为 SQL WHERE 子句的系统。

我可以通过多种方式做到这一点,但似乎都没有遵循开放/封闭原则。例如,我可以让Database 类解析条件并将其设为 SQL。问题是,通过这种方式,我无法扩展我的Condition 而无需更改我的Database,因此我们没有遵循打开/关闭。

另一种方法——在这里看起来合乎逻辑——是在我的Conditions 中添加一个toSQL() 函数。但是,在这种情况下,我无法将我的数据库换成一个(只是为了命名)使用 XML 的数据库,它不希望 SQL 格式的条件。

我过去解决此问题的一种方法是使用工厂。在这种情况下,工厂方法看起来像这样:

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);
    }
}

这不是那么好,但它会减少对 Open/Closed 的违反。

现在,您可以很好地对数据库进行子类化。您也只需要对您的工厂进行子类化,并且还必须为您的Database 创建一组新的Condition 类。你也可以在 Condition 类上发挥你的魔力。您可以创建一个新的Conditions,但您还必须为每个Database 创建配套类。最后,你必须修改你的工厂。

这是迄今为止我们见过的最小的违规行为,但我们仍然违反了 Open/Closed。但事实上,我宁愿完全不违反它。有没有办法在一直坚持打开/关闭的同时做到这一点?

【问题讨论】:

    标签: design-patterns factory-pattern open-closed-principle


    【解决方案1】:

    这不是那么好,但它会减少对打开/关闭的违反

    其实没有。该方法违反了 OSP ;)

    这个问题并不像你想象的那么难。简单地创建一个 SQL 语句工厂,在其中将每个 Condition 类映射到一个 SQL 条件类。

        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);
            }
        }
    

    用法:

            SqlFactory factory = new SqlFactory();
            factory.Assign<And>(obj => new SqlAnd(obj.Value));
    
            var and = new And();
            var sqlAnd = factory.Create(and);
    

    【讨论】:

    • 我在理解您使用的代码时遇到了一些麻烦。可能只是我不知道它是用哪种语言编写的,没有使用所有语言提供的所有通用功能,并且可能只是一种我不太流利的语言。但是,它看起来像你只是将问题转移到另一个地方。在某个地方,您仍然会调用 assign 函数,这就是您在创建新的 IConditions 时需要修改代码的地方。
    • 除非您在实例化谓词的代码中确实正确使用了它,但在这种情况下,它不是一个完全透明的系统,从Storage 切换需要付出很多努力到另一个不同类型的。
    【解决方案2】:

    我想知道装饰器模式的一些扩展在这里是否有用?

    您可能希望将您的谓词定义为一种 DSL,或者可以适当解析的规则列表。

    如果你把你的 WherePredicate 装饰成一个 SqlPredicate,那么它会读取这组规则并返回一个 SQL 语句; XmlPredicate 会做类似的事情。

    这会让你处于一个很好的打开/关闭状态,通过添加新的装饰器打开扩展并关闭修改,因为你的规则列表是不可侵犯的。

    【讨论】:

    • 我不完全确定你的提议,但如果我没记错的话,这意味着如果你决定使用不同类型的存储,你将不得不更改你所在的每个实例做了一个谓词。我想要的是能够将一个存储换成另一个完全透明的存储。当然,对此有解决方案(仅在执行前进行装饰,通过 Storage 上的工厂方法进行装饰),但事实如此,也表明您没有采取任何措施来改变 Open/Closed 违规的原始情况.
    • 在您的解决方案中,如果我没看错的话,SqlPredicateXmlPredicate 需要知道不是装饰器的每个不同的 Predicate。因此,如果您创建一个新的谓词,您将不得不更改SqlPredicate。那不是打开/关闭。
    • 当然,我可能只是误解了您提出的解决方案。在那种情况下,我很抱歉。
    【解决方案3】:

    Protected Variations (PV) 力求在一个区域中允许变化,而无需在另一个区域中进行修改。间接(委托)是@jgauffin 提出的,但正如您指出的那样,它只保护一侧。也许您还可以对不同的Storage 类型使用数据驱动设计(例如属性文件)来保护该方向的变化。阅读您的问题时,我想到了 Hibernate 和 Data Mapper

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-02-12
      • 2010-09-16
      • 1970-01-01
      • 2016-03-21
      • 2017-01-16
      • 1970-01-01
      • 2016-12-07
      • 1970-01-01
      相关资源
      最近更新 更多