C#解析的开闭原则
C# Open but closed principle for parsing
自定义解析应该使用什么来提供可扩展性而不必继承?换句话说,我正在寻找等效于解析自定义对象的 IFormatProvider(用于输出)。
与以下代码相反:
var str = String.Format(new MyFormatProvider(), "{0:custom}", obj);
其中 MyFormatProvider
实现 IFormatProvider
Open/Closed 基本上只是说新的或更改的行为应该是可添加的,而无需修改现有代码。在 OOP 中,这通常通过继承或接口(一种特殊的 "inheritance")来完成。
你需要对你的问题域更具体一些以获得更具体的答案,但作为使用接口的示例如下:
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");
这并不是 open/closed 所特有的,但是由于您提到了 IFormatProvider
,它使用另一种技术在格式字符串中传输可变数量的参数,同时仍然坚持强类型。
我不确定它是否有规范名称,但我将其称为 "stringly typed"(您可以对对象数组等执行相同的操作 - 但使用 string
是相当普遍的,并且是一个很好的双关语)。你可以在 JavaScript 的 window.open windowFeatures and MVC's HtmlHelpers htmlAttributes 中看到这种类型的 API (它使用匿名类型来达到同样的效果),我会说它实际上是一个 anti-pattern 在大多数情况下因为它打破了 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");
}
}
}
}
使用此 class 时,您必须以一种只能通过文档发现的方式专门格式化 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");
自定义解析应该使用什么来提供可扩展性而不必继承?换句话说,我正在寻找等效于解析自定义对象的 IFormatProvider(用于输出)。
与以下代码相反:
var str = String.Format(new MyFormatProvider(), "{0:custom}", obj);
其中 MyFormatProvider
实现 IFormatProvider
Open/Closed 基本上只是说新的或更改的行为应该是可添加的,而无需修改现有代码。在 OOP 中,这通常通过继承或接口(一种特殊的 "inheritance")来完成。
你需要对你的问题域更具体一些以获得更具体的答案,但作为使用接口的示例如下:
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");
这并不是 open/closed 所特有的,但是由于您提到了 IFormatProvider
,它使用另一种技术在格式字符串中传输可变数量的参数,同时仍然坚持强类型。
我不确定它是否有规范名称,但我将其称为 "stringly typed"(您可以对对象数组等执行相同的操作 - 但使用 string
是相当普遍的,并且是一个很好的双关语)。你可以在 JavaScript 的 window.open windowFeatures and MVC's HtmlHelpers htmlAttributes 中看到这种类型的 API (它使用匿名类型来达到同样的效果),我会说它实际上是一个 anti-pattern 在大多数情况下因为它打破了 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");
}
}
}
}
使用此 class 时,您必须以一种只能通过文档发现的方式专门格式化 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");