C#开放但封闭的解析原则

时间:2015-07-07 15:21:12

标签: c# design-patterns

我应该使用什么来自定义解析以提供可扩展性而不必继承?换句话说,我正在寻找用于解析自定义对象的IFormatProvider(用于输出)。

与以下代码相反:

var str = String.Format(new MyFormatProvider(), "{0:custom}", obj);

其中MyFormatProvider实现IFormatProvider

1 个答案:

答案 0 :(得分:1)

Open/Closed基本上只是说新的或更改的行为应该是可添加的,而无需修改现有的代码。在OOP中,通常使用继承或接口(一种特殊的"继承")来完成。

您需要对问题域更具体一点,以获得更具体的答案,但作为使用接口的示例如下:

 interface IObjectParser {
     object Parse(object obj, propertyName string);
 }

 class ReflectionParser : IObjectParser {
     public object Parse(object obj, propertyName string) {
         return obj.GetType().GetProperty(propertyName).GetMethod().Invoke(obj);
     }
 }

 object parsedValue = new ReflectionParser().Parse(new MyClass(), "MyProperty");

然后,如果我们想添加一种新类型的解析器:

 class DatabaseParser : IObjectParser {
     public object Parse(object obj, propertyName string) {
         return ExecuteQuery(
            --Note the potential for SQL injection
            string.Format("SELECT {1} FROM {0} WHERE Id = @id", obj.GetType().Name, propertyName),
            ((dynamic)o).Id
         );           
     }
 }

 object parsedValue = new DatabaseParser(new MyClass(), "columnName");

这并非特别适合打开/关闭,但自从你提起IFormatProvider以来,它使用另一种技术在格式字符串中挖掘可变数量的参数,同时仍然坚持强类型。

我不确定它是否有规范名称,但我将其称为"字符串输入" (你可以对一组对象做同样的事情 - 但是使用string是相当普遍的并且是一个很好的双关语。您可以在JavaScript window.open windowFeatures和MVC' HtmlHelpers htmlAttributes(使用匿名类型达到相同效果)之类的内容中看到此类型的API,我会说& #39;在大多数情况下,它实际上是一个反模式,因为它打破了Liskov替换原则,但确实有它的利基用途。

IFormatProvider(从技术上讲,ICustomFormatter执行此部分,IFormatProvider是工厂)必须支持未知数量的可能格式。为此,它会在"之后引导任何自定义格式化指令:" - 它预计适当的ICustomFormatter将知道如何处理这些值。

IObjectParser案例中的例子如下:

 class ByteArrayParser : IObjectParser {
     public object Parse(object obj, propertyName string) {
         var bytes = obj as byte[];
         // we've tunneled multiple parameters in the propertyName string
         var nameParameters = propertyName.Split(":");
         // note we've lost our strong typing, and coupled ourselves to the propertyName format
         int index = int.Parse(nameParameters[0]);
         string readAsType = nameParameters[1];

         using (var ms = new MemoryStream(bytes))
         using (var br = new BinaryReader(ms))
         {
             ms.Position = index;
             switch (readAsType) {
                 case "float":
                     return br.ReadSingle();
                 case "int":
                     return br.ReadInt32();
                 case "string"
                     // we can even have yet another parameter only in special cases
                     if (nameParameters.Length > 2) {
                        // it's an ASCII string
                        int stringLength = int.Parse(nameParameters[2]);
                        return Encoding.ASCII.GetString(br.ReadBytes(stringLength));
                     } else {
                        // it's BinaryReader's native length-prefixed string
                        return br.ReadString();
                     }
               default:
                     // we don't know that type
                    throw new ArgumentOutOfRangeException("type");
             }
         }
     }
 }

使用此课程时,您必须以只能通过文档发现的方式(propertyName)专门格式化String.Format

 // note again, we have to know ByteArrayParser's specific format and lose strong typing
 object parsedIntValue = new ByteArrayParser().Parse(myBytes, "4:int");
 object parsedSingleValue = new ByteArrayParser().Parse(myBytes, "4:float"); 
 object parsedStringValue = new ByteArrayParser().Parse(myBytes, "4:string"); 
 object parsedAsciiStringValue = new ByteArrayParser().Parse(myBytes, "4:string:15");