Golang向下转换结构体列表

huangapple go评论81阅读模式
英文:

Golang downcasting list of structs

问题

我想要能够更灵活地解析 YAML 文件。也就是说,我的库有一些预定义的选项,YAML 文件必须具备这些选项。然后,用户应该能够扩展这些选项以包含任意自定义选项。

以下是我目前的代码:

package main

import (
    "net/http"

    "yamlcms"

    "github.com/julienschmidt/httprouter"
)

type Page struct {
    *yamlcms.Page
    Title string
    Date  string
}

func getBlogRoutes() {
    pages := []*Page{}
    yamlcms.ReadDir("html", pages)
}

// 这部分代码还在进行中,我只是为了提供上下文而包含它
func main() {
    router := httprouter.New()
    //blogRoutes := getBlogRoutes()
    //for _, blogRoute := range *blogRoutes {
    //  router.Handle(blogRoute.Method, blogRoute.Pattern,
    //      func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {})
    //} 
    http.ListenAndServe(":8080", router)
}

这是 yamlcms 包:

package yamlcms

import (
    "io/ioutil"
    "os"
    "strings"

    "gopkg.in/yaml.v2"
)

type Page struct {
    Slug string `yaml:"slug"`
    File string `yaml:"file"`
}

func (page *Page) ReadFile(file string) (err error) {
    fileContents, err := ioutil.ReadFile(file)
    if err != nil {
        return
    }   

    err = yaml.Unmarshal(fileContents, &page)
    return
}

func isYamlFile(fileInfo os.FileInfo) bool {
    return !fileInfo.IsDir() && strings.HasSuffix(fileInfo.Name(), ".yaml")
}

func ReadDir(dir string, pages []*Page) (err error) {
    filesInfo, err := ioutil.ReadDir(dir)
    if err != nil {
        return
    }   

    for i, fileInfo := range filesInfo {
        if isYamlFile(fileInfo) {
            pages[i].ReadFile(fileInfo.Name())
        }   
    }   

    return
}

这里有一个编译器错误:

src/main.go:19: cannot use pages (type []*Page) as type []*yamlcms.Page in argument to yamlcms.ReadDir

我在这个问题中的主要目的是学习在 Go 中完成这种任务的惯用方式。可能存在其他第三方解决方案,但我暂时对它们不感兴趣,因为在 Go 中我经常遇到与继承等相关的问题。所以,根据我提供的内容,我应该如何最好(符合惯用方式)地实现我的目标?

编辑:

所以我根据建议进行了一些更改。现在我有了这个:

type FileReader interface {
    ReadFile(file string) error
}

func ReadDir(dir string, pages []*FileReader) (err error) {
    filesInfo, err := ioutil.ReadDir(dir)
    if err != nil {
        return
    }   

    for i, fileInfo := range filesInfo {
        if isYamlFile(fileInfo) {
            (*pages[i]).ReadFile(fileInfo.Name())
        }   
    }   

    return
}

然而,我仍然得到类似的编译器错误:

src/main.go:19: cannot use pages (type []*Page) as type []*yamlcms.FileReader in argument to yamlcms.ReadDir

尽管 main.Page 应该是一个 FileReader,因为它嵌入了 yamlcms.Page

英文:

I want to be able to unmarshal yaml files less rigidly. That is, my library has a predefined number of options the yaml file must have. Then, the user should be able to extend this to include any custom options.

Here is what I have

package main

import (
    "net/http"

    "yamlcms"

    "github.com/julienschmidt/httprouter"
)

type Page struct {
    *yamlcms.Page
    Title string
    Date  string
}

func getBlogRoutes() {
    pages := []*Page{}
    yamlcms.ReadDir("html", pages)
}

// This section is a work in progress, I only include it for loose context
func main() {
    router := httprouter.New()
    //blogRoutes := getBlogRoutes()
    //for _, blogRoute := range *blogRoutes {
    //  router.Handle(blogRoute.Method, blogRoute.Pattern,
    //      func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {})
    //} 
    http.ListenAndServe(":8080", router)
}

Here is the yamlcms package:

package yamlcms

import (
    "io/ioutil"
    "os"
    "strings"

    "gopkg.in/yaml.v2"
)

type Page struct {
    Slug string `yaml:"slug"`
    File string `yaml:"file"`
}

func (page *Page) ReadFile(file string) (err error) {
    fileContents, err := ioutil.ReadFile(file)
    if err != nil {
        return
    }   

    err = yaml.Unmarshal(fileContents, &page)
    return
}

func isYamlFile(fileInfo os.FileInfo) bool {
    return !fileInfo.IsDir() && strings.HasSuffix(fileInfo.Name(),     ".yaml")
}

func ReadDir(dir string, pages []*Page) (err error) {
    filesInfo, err := ioutil.ReadDir(dir)
    if err != nil {
        return
    }   

    for i, fileInfo := range filesInfo {
        if isYamlFile(fileInfo) {
            pages[i].ReadFile(fileInfo.Name())
        }   
    }   

    return
}

There is a compiler issue here:

src/main.go:19: cannot use pages (type []*Page) as type []*yamlcms.Page in argument to yamlcms.ReadDir

My main intent in this question is to learn the idiomatic way of doing this kind of thing in Go. Other 3rd-party solutions may exist but I am not immediately interested in them because I have problems like this frequently in Go having to do with inheritance, etc. So along the lines of what I've presented, how can I best (idiomatically) accomplish what I am going for?

EDIT:

So I've made some changes as suggested. Now I have this:

type FileReader interface {
    ReadFile(file string) error
}

func ReadDir(dir string, pages []*FileReader) (err error) {
    filesInfo, err := ioutil.ReadDir(dir)
    if err != nil {
        return
    }   

    for i, fileInfo := range filesInfo {
        if isYamlFile(fileInfo) {
            (*pages[i]).ReadFile(fileInfo.Name())
        }   
    }   

    return
}

However, I still get a similar compiler error:

src/main.go:19: cannot use pages (type []*Page) as type []*yamlcms.FileReader in argument to yamlcms.ReadDir

Even though main.Page should be a FileReader because it embeds yamlcms.Page.

答案1

得分: 1

**编辑:**我忘记了接口的切片不是这样工作的。你需要分配一个新的切片,将所有页面转换为FileReader,调用函数,然后再转换回来。

另一个可能的解决方案是重构yamlcms.ReadDir,使其返回文件的内容,以便稍后进行解组:

// 在yamlcms中。
func ReadYAMLFilesInDir(dir string) ([][]byte, error) { ... }

// 在客户端代码中。
files := yamlcms.ReadYAMLFilesInDir("dir")
for i := range pages {
    if err := yaml.Unmarshal(files[i], &pages[i]); err != nil { return err }
}

原始答案:

Go语言中没有继承或类型转换的概念。在设计中更推荐使用组合和接口。在你的情况下,你可以重新定义yamlcms.ReadDir来接受一个接口FileReader

type FileReader interface {
    ReadFile(file string) error
}

`yamlcms.Page``main.Page`都将实现这个接口因为后者嵌入了前者
英文:

EDIT: I forgot that slices of interfaces don't work like that. You'd need to allocate a new slice, convert all pages to FileReaders, call the function, and convert them back.

Another possible solution is refactoring yamlcms.ReadDir to return the contents of the files, so that they could be unmarshaled later:

// In yamlcms.
func ReadYAMLFilesInDir(dir string) ([][]byte, error) { ... }

// In client code.
files := yamlcms.ReadYAMLFilesInDir("dir")
for i := range pages {
    if err := yaml.Unmarshal(files[i], &pages[i]); err != nil { return err }
}

The original answer:

There are no such things as inheritance or casting in Go. Prefer composition and interfaces in your designs. In your case, you can redefine your yamlcms.ReadDir to accept an interface, FileReader.

type FileReader interface {
    ReadFile(file string) error
}

Both yamlcms.Page and main.Page will implement this, as the latter embeds the former.

huangapple
  • 本文由 发表于 2015年7月1日 23:27:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/31165589.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定