F #: Класс без конструктора по умолчанию в качестве параметра типа? - PullRequest
1 голос
/ 19 июля 2011

У меня есть класс FileReader, работа которого заключается в чтении и обработке текстовых файлов с использованием StreamReader. Чтобы упростить модульное тестирование, я хотел бы предоставить параметр типа этому классу, чтобы я мог поменять StreamReader на FakeReader, который фактически не взаимодействует с файловой системой (и, возможно, выдает исключения, такие как OutOfMemory, поэтому я могу проверить обработку ошибок в FileReader).

В идеале я бы хотел определить FileReader примерно так (тривиализировано для ясности):

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

и просто определите FakeReader, чтобы иметь конструктор, который принимает имя файла, метод получения свойства EndOfStream, метод ReadLine() и (пустой) метод Dispose(). Однако F # имеет несколько претензий к этому определению типа, в том числе "Calls to object constructors on type parameters cannot be given arguments." Поскольку StreamReader не имеет конструктора по умолчанию, этот подход выглядит как запрет.

Пока что единственный способ, с помощью которого я получил это, - это наследовать FakeReader от StreamReader:

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()

Это выглядит намного менее элегантно. Есть ли способ сделать это с параметром типа? Спасибо всем.

Ответы [ 2 ]

4 голосов
/ 19 июля 2011

Использование функции, которая создает объект чтения (как предложено MizardX), является прямым ответом на ваш вопрос.Тем не менее, я мог бы рассмотреть возможность использования другой абстракции, чем TextReader).Как упомянул Анкур в комментарии, вы можете использовать более функциональный подход.

Если вы просто читаете строки текста из ввода, используя 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
3 голосов
/ 19 июля 2011

Вы можете передать функцию, которая создает и возвращает объект нужного вам типа.

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() :> _)

Приведение было необходимо, потому что я отбросил универсальный аргумент типа, но фактический тип может быть выведен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...