2011-07-19 112 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

相關問題