首页 > 解决方案 > 如何实现我自己的文件类型?

问题描述

我有一个打开文件并使用该函数写入文件的os.OpenFile函数。

我基本上想做的是模拟它返回的文件以编写测试。因为我想更好地理解 Go,所以我想在不使用第三方包的情况下做到这一点。

这是我尝试过的:

我的包裹

package logger

import (
    "fmt"
    "time"
    "sync"
    "os"
    "strings"
    "path/filepath"
    "io"
)

const timestampFormat = "2006-01-02 15:04:05.999999999"

type FileOpener interface {
    OpenFile(name string, flag int, perm os.FileMode) (*os.File, error)
}

type RotateWriter struct {
    fileOpener FileOpener
    lock       sync.Mutex
    filename   string
    fp         *os.File
}

type defaultFileOpener struct{}

func (fo defaultFileOpener) OpenFile(name string, flag int, perm os.FileMode) (*os.File, error) {
    return os.OpenFile(name, flag, perm)
}

func CreateRotateWriter(filename string, fileOpener FileOpener) (RotateWriter) {
    if (fileOpener == nil) {
        return RotateWriter{filename: filename, fileOpener: defaultFileOpener{}}
    }
    return RotateWriter{filename: filename, fileOpener: fileOpener}
}

func (writer RotateWriter) Write(bytes []byte) (int, error) {
    writer.lock.Lock()
    defer writer.lock.Unlock()

    extension := filepath.Ext(writer.filename)
    filename := strings.TrimSuffix(writer.filename, extension)

    // There is a specific constants that are used for formatting dates.
    // For example 2006 is the YYYYY, 01 is MM and 02 is DD
    // Check https://golang.org/src/time/format.go line 88 for reference
    fullFilename := filename + time.Now().UTC().Format("-2006-01-02") + extension

    // Open the file for the first time if we don't already did so
    if writer.fp == nil {
        fp, err := os.OpenFile(fullFilename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666)
        if err != nil {
            return 0, err
        }
        writer.fp = fp;
    }

    // We are now pointing to a different file. Close the previous one and open a new one
    if fullFilename != writer.fp.Name() {
        writer.fp.Close()
        fp, err := os.OpenFile(fullFilename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666)
        if err != nil {
            return 0, err
        }
        writer.fp = fp;
    }

    return writer.fp.Write([]byte("[" + time.Now().UTC().Format(timestampFormat) + "] " + string(bytes)))
}

我希望在我的测试包中做的是这样的

type file interface {
    io.Closer
    io.Reader
    io.ReaderAt
    io.Seeker
    Stat() (os.FileInfo, error)
}

type fileType struct{
    fd      int
    name    string
    contents string // Where I'll keep the in-memory contents maybe
}
type defaultFileOpener struct{

}
func (fo defaultFileOpener) OpenFile(name string, flag int, perm os.FileMode) (*file, error){
    return &fileType{1, name, ""}, nil //Cannot use &fileType{1, name, ""}(type *fileType) as type *file
}

func (f fileType) Write(bytes []byte) (int, error){
    f.contents += string(bytes)
    return len(bytes), nil
}

我很可能误解了一些东西,甚至可以在 go 中创建我自己的文件类型吗?

标签: go

解决方案


从片段中不清楚是否*fileType实现了file接口的所有方法,但如果没有,您应该首先确保它实现了。因为如果不是这样,您将无法按照您的意愿在测试中使用它。


您的文件打开器的OpenFile方法应该将接口作为其返回类型,而不是指向接口的指针。那是:

func (fo defaultFileOpener) OpenFile(name string, flag int, perm os.FileMode) (file, error) {

这是因为*file与 的类型不同file,并且实现file接口的类型的值(例如 yours *fileType)不能在预期指向该接口的指针的地方使用。


并且返回指向接口的指针几乎从来都不是您真正想要的,如果您想使用指针间接将接口值切换为另一个接口值,那么这样做可能是有意义的,但这里似乎并非如此。如果您浏览标准库,您将很难找到返回指向接口类型的指针的函数/方法......

但是假设这就是你想要做的,那么你必须返回一个指向接口类型值的指针,而不是一个指向实现接口的类型的指针。

例如

var f file = &fileType{} // f is of type file
return &f, nil           // &f is of type *file

请记住,它f的类型必须是file返回工作,如果它只是*fileType它不会编译。

var f = &fileType{} // f is of type *fileType
return &f, nil      // &f is of type **fileType

推荐阅读