我一直在C#中编写几个实用的,可重用的函数。许多函数返回多个值,我使用out或ref参数。许多功能还有其他信息,这些信息可能对某些呼叫者(但不是所有呼叫者)有用。
例如,读取CSV文件的功能可能包含其他信息,例如否。空白行,没有。具有重复值的行和其他一些统计信息。
其他信息还可能包括警告,消息等。
并非每个调用者都会对此信息感兴趣,因此我不希望将所有这些包含在out,ref或Tuples中,这将使调用者必须声明所有此类预期变量。
我只是想知道是否有办法让呼叫者可以获得其他信息,以便呼叫者可以选择或检索它感兴趣的一些可选附加信息。
例如,Func 1可以调用Func B.调用它后,它将获得所有标准返回值。另外,它可以调用类似FuncB.GetAdditionalInfo(infoType)的东西而不再执行Func B吗?
可以使用一个类来设计它,该类用作存储所有可选值的中介,然后根据请求将它们返回给调用者;但我希望它足够通用,可用于我的所有实用程序功能。
一种可能性是Func B将所有这些存储在某种全局变量中,如果需要,调用者可以访问这些变量。但是如果一个实用程序类有几个这样的可恢复函数,我将需要有这么多的公共变量,以获取每个函数的附加信息!
我现在在.NET 4.5上。这有设计模式吗?我很乐意知道F#中是否有一个很好的解决方案。
另外,我想避免过多的重载版本来实现这一目标! 感谢。
答案 0 :(得分:3)
我不反对向您展示理想的实现,但这是一个对我有意义的实现。设计两种不同的数据结构:一种表示函数接受的选项,另一种表示函数返回的选项。例如:
public class Helper
{
// General cover-it-all implementation that accepts an option object
// and analyzes based on the flags that are set in it
public static CSVStatistics AnalyzeCSV(string file, CSVAnalysisOptions options)
{
// define what we are analysing by reading it from the
// from the options object and do your magic here
}
// Specific implementation that counts only blank lines
public static long CountBlankLines(string file)
{
var analysisResult = AnalyseCSV(file, new CSVAnalysisOptions
{
IsCountingBlanks = true
});
//I'm not doing a null check here, because I'm settings the
//flag to True and therefore I expect there to be a value
return analysisResult.BlanksCount.Value;
}
}
// Analysis options structure
public struct CSVAnalysisOptions
{
public bool IsCountingBlanks { get; set; }
public bool IsCountingDuplicates { get; set; }
public bool IsCountingOther { get; set; }
}
// Analysis results structure
public struct CSVStatistics
{
public long TotalLineCount { get; set; }
public long? BlanksCount { get; set; }
public long? DuplicatesCount { get; set; }
}
在上面的例子中,CountBlankLines
是一个特定的实现,只计算空行并且作为" sugar"这简化了调用,而AnalyzeCSV
是实际进行计数的方法。另外,请注意CSStatistics
结构如何具有可空long
个。这将允许您检查值是否为空,因此知道它实际上没有被分析而不是输出零(这是一个可能的值)。
CSVAnalysisOptions
结构也可以用位标志代替,你可以在这里阅读它们 - https://developer.mozilla.org/en-US/docs/Web/API/Window/parent。
答案 1 :(得分:3)
我觉得你要做的就是构建一个非常的大块API,可以一次完成很多事情。一般来说,我们不喜欢粗糙的API,因为它们会变得复杂,特别是如果API中的选项之间的交互存在副作用或异常怪癖。
老实说,这样做的最好方法是创建一个更加可爱的API,其中每个调用都做一件事,做得对,做得好。
执行此操作时,代码最终会更容易进行因子和单元测试。
这并不是说没有引起适度的厚度,但这应该是合乎逻辑的。
例如,如果你要破解图像文件来解码,比如PNG或JPG,你需要预先设置图像的宽度,高度,分辨率和颜色类型。一次性抓住所有这些是完全合理的。您需要立即挖掘元数据信息或颜色配置文件吗?可能不是。
因此,有一个调用可以返回并汇总所有基本图像信息,然后单独调用以获取其余信息。
"但表现!"你说,"性能怎么样?!"
简单。你测量它,看看会发生什么。几年前,我写了一个PNG解码器,不像libpng顺序读取块,我认为在前面构建一个映射文件每个块的数据库会更容易,然后引用该数据库来查找任何给定的块。令人惊讶的是,这无法显着影响性能,并使消费代码更易于阅读和维护。
让事情被多次调用,如果存在性能问题,请弄清楚如何解决它。通常,您使用对获取信息的对象专用的缓存或会话执行此操作。
您所描述的内容听起来既不容易阅读也不易维护,更不用说测试了。
答案 2 :(得分:0)
这是一个可能适合您的用例的F#模式。它与Argu库用于命令行参数的模式几乎相同:您声明一个discriminated union,其中包含所有可能的"标记"你的函数可能想要返回(我把" flags"用引号,因为它们中的一些可能不只是布尔值),然后你的函数可以返回这些值的列表。如果有几十个,那么一个集合可能是值得的(因为列表必须线性搜索),但是如果你不希望返回超过七个或八个这样的标志,那么集合的额外复杂性就是& #39;值得,你也可以使用一个清单。
一些F#代码说明了如何使用这种模式,使用虚拟函数来实现业务逻辑:
type Notifications
| InputWasEmpty
| OutputWasEmpty
| NumberOfBlankLinesInOutput of int
| NumberOfDuplicateLinesInOutput of int
| NumberOfIgnoredErrors of int
// Whatever else...
type ResultWithNotifications<'a> = 'a * Notifications list
// The syntax "TypeA * TypeB" is F# syntax for Tuple<TypeA,TypeB>
// And the 'a is F# syntax for a generic type
type outputRecord = // ... insert your own data type here
// Returns the filename of the output file, plus various notifications
// that the caller can take action on if they want to
let doSomeProcessing data : ResultWithNotifications<outputRecord list>
let mutable notifications = []
let outputFileName = getSafeOutputFilename()
if List.isEmpty data then
notifications <- InputWasEmpty :: notifications
let output = data |> List.choose (fun record ->
try
let result = record |> createOutputRecordFromInputRecord
Some result
except e
eprintfn "Got exception processing %A: %s" record (e.ToString())
None
)
if List.isEmpty output then
notifications <- OutputWasEmpty :: notifications
if List.length output < List.length data then
let skippedRecords = List.length data - List.length output
notifications <- (NumberOfIgnoredErrors skippedRecords) :: notifications
// And so on. Eventually...
output |> writeOutputToFilename outputFileName
outputFileName, notifications // Function result
希望F#代码在没有解释的情况下是可理解的,但如果上面有任何不清楚的地方,请告诉我,我会尝试解释。