2011-07-19 79 views
1

我有一个FileReader类,其作业是使用StreamReader来读取和处理文本文件。为了便于进行单元测试,我想给这个类提供一个类型参数,这样我就可以将StreamReader换成FakeReader,它实际上并没有与文件系统交互(也许会抛出异常,例如OutOfMemory,所以我可以测试错误处理在FileReader)。F#:没有默认构造函数作为类型参数的类?

理想情况下,我想定义FileReader这样的事情(无足轻重的清晰度):

type FileReader<'Reader> = 
    member this.Read file = 
     use sr = new 'Reader(file) 
     while not sr.EndOfStream do 
      printfn "%s" <| sr.ReadLine() 

,并简单地定义FakeReader有一个构造函数的文件名,EndOfStream属性getter的ReadLine()方法和(空)Dispose()方法。但是,F#有几个有关此类型定义的投诉,其中包括"Calls to object constructors on type parameters cannot be given arguments."由于StreamReader没有默认构造函数,因此此方法看起来像是一个不行。

到目前为止,我已经得到了这个工作的唯一途径是继承FakeReaderStreamReader

type FakeReader() = 
    inherit StreamReader("") with 
    override this.ReadLine() = "go away" 
    member this.EndOfStream = false 
    member this.Dispose() =() 

,并使用返回无论是新FakeReader或新StreamReader适当的工厂方法。

type ReaderType = Fake | SR 
let readerFactory (file : string, readerType) = 
    match readerType with 
    | Fake -> new FakeReader() :> StreamReader 
    | SR -> new StreamReader(file) 

type FileReader(readertype) = 
    member this.Read file = 
     use sr = readerFactory(file, readertype) 
     while not sr.EndOfStream do 
      printfn "%s" <| sr.ReadLine() 

这似乎没那么优雅。有没有办法做到这一点与类型参数?谢谢大家。

+0

你真的希望它基于是OOP或其他任何功能的做法也会为你工作? – Ankur

+1

更实用的方法实际上会很棒。由于在势在必行的土地上多年,我倾向于回到OOP拐杖。 – FSharpN00b

回答

3

您可以传递一个函数来构造并返回所需类型的对象。

type FileReader(f : string -> TextReader) = 
    member this.Read file = 
     use sr = f file 
     while sr.Peek() <> -1 do 
      printfn "%s" <| sr.ReadLine() 

type FakeReader() = 
    inherit StringReader("") 
    override this.ReadLine() = "go away" 
    override this.Peek() = 0 

let reader1 = new FileReader(fun fn -> new StreamReader(fn) :> _) 

let reader2 = new FileReader(fun fn -> new FakeReader() :> _) 

演员是必要的,因为我放弃了统一的类型参数,但实际类型可以推断。

+2

我认为这个函数可以只是'string - > TextReader'(并且你不需要类型参数),因为'FileReader'中的实现只使用'TextReader'的公共方法,而不使用类型的实际读者。如果我理解正确,你在想: type ReaderType = Fake | –

+0

(唉,支持,试图修复格式)。 SR let readerFactory readerType(file:string)= match readerType与 |假 - >新FakeReader():> StreamReader | SR - >新的StreamReader(文件) 类型的FileReader(工厂:串 - >的StreamReader)= 构件this.Read文件= 使用SR =工厂文件 同时不sr.EndOfStream做 printfn “%S”<| sr.ReadLine() let reader = new FileReader(readerFactory Fake) – FSharpN00b

+0

Close;您可以跳过额外的类型:'新的FileReader(fun fn - >新的StreamReader(fn))'vs'新的FileReader(fun fn - > new FakeReader())' –

4

使用创建读者对象的函数(如MizardX所建议的)是直接回答您的问题。但是,我可能会考虑使用与TextReader不同的抽象)。正如Ankur在评论中提到的那样,您可以使用更多功能的方法。

如果您刚刚使用TextReader从输入中读取文本行,则可以使用seq<string>类型。 FileReader类型实际上可能只是一个函数,需要seq<string>(尽管这可能是过于简单化......取决于)。

这使得更多的“功能” - 在函数式编程,你使用的功能经常变换的数据结构,这正是这个例子,:

open System.IO 

/// Creates a reader that reads data from a file 
let readFile (file:string) = seq { 
    use rdr = new StreamReader(file) 
    let line = ref "" 
    while (line := rdr.ReadLine(); !line <> null) do 
    yield !line } 

/// Your function that processes the input (provided as a sequence) 
let processInput input = 
    for s in input do 
    printfn "%s" s 

readFile "input.txt" |> processInput 

要测试processInput功能,然后你可以创建一个新的seq<string>值。这是一个比实现一个新的TextReader类显著简单:

let testInput = seq { 
    yield "First line" 
    yield "Second line" 
    raise <| new System.OutOfMemoryException() } 

testInput |> processInput 
+0

这确实比重新实现TextReader更清晰,并且很高兴知道我没有错过任何明显的东西,这会让我的类型参数方法起作用。谢谢你一如既往的托马斯。 – FSharpN00b

相关问题