2011-07-19 7 views
1

私はStreamReaderを使ってテキストファイルを読み込んで処理することを仕事とするFileReaderクラスを持っています。ユニットテストを容易にするために、このクラスに型パラメータを指定して、StreamReaderFakeReaderに交換して、ファイルシステムと実際にはやり取りしないでください(OutOfMemoryなどの例外がスローされるため、テストできますエラー処理はFileReader)。 、ReadLine()F#:型パラメータとしてデフォルトのコンストラクタがないクラス?

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

と単純にファイル名を取るコンストラクタ、EndOfStreamプロパティのゲッターを持つようにFakeReaderを定義します。

理想的には、私は(わかりやすくするために矮小)このようなFileReader何かを定義したいのですがメソッド、および空のDispose()メソッドです。しかし、F#には、"Calls to object constructors on type parameters cannot be given arguments."を含むこの型定義に関するいくつかの苦情があります。StreamReaderにはデフォルトのコンストラクタがないので、このアプローチは無駄なようです。

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

し、必要に応じてFakeReader新しいまたは新規StreamReaderのいずれかを返すファクトリメソッドを使用します。私は仕事にこれを得ている

これまでのところ唯一の方法は、StreamReaderからFakeReaderを継承することです。

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になりたいかまたは任意の他のより機能的なアプローチはまたあなたのために動作します:これは、新しいTextReaderクラスを実装するよりもかなり簡単ですか? – Ankur

+1

より機能的なアプローチは実際には素晴らしいでしょう。私は、長年の命令的な土地のためにOOPの松葉杖に戻ってしまう傾向があります。 – FSharpN00b

答えて

3

目的のタイプのオブジェクトを作成して返す関数を渡すことができます。

ジェネリック型引数を削除したためにキャストが必要でしたが、実際の型を推測できます。

+2

'FileReader'の実装は' TextReader'のpublicメソッドだけを使用しているので、 'string - > TextReader'(関数の型引数は必要ありません)実際の読者の –

+0

(アルファ、書式を修正しようとしています)返信いただきありがとうございます - 私が正しく理解していれば、あなたは考えています: type ReaderType = Fake | SR let readerFactory readerType(ファイル:文字列)= と一致するreaderType |偽 - >新しいFakeReader():> StreamReader | SR - >新しいStreamReaderを(ファイル) 型FileReaderの(工場出荷時:文字列 - >のStreamReader)= メンバーthis.Readファイル= 利用SR =工場出荷時のファイル ないsr.EndOfStreamは printfn "%sの" <行いながら|をsr.ReadLine() let reader = new FileReader(readerFactoryFake) – FSharpN00b

+0

閉じる;新しいファイルリーダー(fun fn - >新しいStreamReader(fn)) 'vs'新しいFileReader(fun fn - >新しい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>値を作成します。

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

testInput |> processInput 
+0

これは実際にTextReaderを再実装するよりもはるかにクリーンであり、私は型パラメータアプローチの仕事をしていたはずの明白なものを見逃していなかったことを知っておいてよかったです。いつものようにTomasありがとう。 – FSharpN00b

関連する問題