dqnqpqv3841 2014-06-15 17:44
浏览 33
已采纳

惯用地干燥Go中的常见字段

I'm writing a client for an API. One method, posts, returns an array of users' posts.

  • Each post is one of eight different types. Clearly, an "is-a" relationship.
  • Many of the fields of the post, including (among others) the ID, URL and time stamp are common to every type of post.
  • Each type of post has fields unique to its type. For example, a photo post will have a resolution and a caption.

In a language with inheritance, I would make an abstract base class Post and then subclass that to make one concrete class for each type of post. I would have a constructor or factory method in the base Post, maybe fromJson(), that takes a JSON object and extracts all the common fields. Then I would override that in each subclass to extract the specialized fields, making sure to call the base implementation to DRY up the extraction of the common fields.

Go does not have inheritance, but it has composition. I defined a Post struct having all the common fields, then I made a struct for each type which has an anonymous Post field so that it includes all the Post fields. For example,

type PhotoPost struct {
    Post // which contains: ID int; URL string; etc
    Caption string
    Height int
    Width int
    /// etc
}

One of my goals is that I want to make it easy for users of my client to access the common fields of the Post. So I definitely don't want to just have the Posts() method that I am writing return interface{}, because then anytime someone wants to get the IDs of all the posts, for example, they would have to make a horrible type switch which would be a pattern used over and over and makes me cringe:

func GetIDs(posts []interface{}) []int {
    var ids []int
    for _, p := range posts {
        switch p.(type) {
            case PhotoPost:
                ids = append(ids, p.(PhotoPost).ID)
            //... repeat for the 7 other kinds of posts, and don't forget a default!
        }
    }
}

This is just awful. But I can't have Posts return []Post, because then when the more specialized data is needed (for use cases like "give me all the photo captions from this user's posts"), it won't be there (because in Go, a PhotoPost is not a Post, it has a Post and its fields.

At the moment, I'm contemplating having Posts() return a PostCollection, which would be a struct that would look like this, so that at least I would avoid the type switch monstrosity above:

type PostCollection struct {                                                                                                                           
        PhotoPosts   []PhotoPost
        // ...repeat for the others
}

but the use case for "get all IDs of all the posts into a slice" or something similar is still very cumbersome. Can someone suggest an idiomatic way to deal with this problem? Preferably one that doesn't require reflection?

I've been pondering having each type of Post implement a PostData() method in a TypedPost interface that returns its own Post, but it doesn't look like that exists unless I have both a named and an anonymous type which seems strange (anonymous so that I can say somePhotoPost.ID when I know I have a PhotoPost want to, and someTypedPost.PostData().ID when I just know that I'm dealing with a TypedPost of some kind. Then I'd have Posts() return []TypedPost. Is there a better way?

  • 写回答

2条回答 默认 最新

  • dongpin1850 2014-06-15 18:29
    关注

    Define an interface for a Post - don't access common data elements except through an interface.

    Here is an example. Note the Post interface which defines what all posts can do (but not what data they have in). playground

    // Basic information about a post
    type PostInfo struct {
        ID  int
        URL string
    }
    
    // To satisfy the post interface
    func (p *PostInfo) Info() *PostInfo {
        return p
    }
    
    // Interface that defines what a Post can do
    type Post interface {
        Info() *PostInfo
    }
    
    type PhotoPost struct {
        PostInfo // which contains: ID int; URL string; etc
        Caption  string
        Height   int
        Width    int
        /// etc
    }
    
    func GetIDs(posts []Post) []int {
        var ids []int
        for _, p := range posts {
            ids = append(ids, p.Info().ID)
        }
        return ids
    }
    
    func main() {
        p0 := &PostInfo{1, "url0"}
        p1 := &PhotoPost{PostInfo{2, "url1"}, "img", 16, 32}
        posts := []Post{p0, p1}
        fmt.Printf("Post IDs %v
    ", GetIDs(posts))
    }
    

    If your code has a type switch to switch over your own objects then you've gone wrong with defining interfaces.

    Note that you can define interfaces which a subset of your posts satisfy and use a type cast to see if they implement it.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 PointNet++的onnx模型只能使用一次
  • ¥20 西南科技大学数字信号处理
  • ¥15 有两个非常“自以为是”烦人的问题急期待大家解决!
  • ¥30 STM32 INMP441无法读取数据
  • ¥15 R语言绘制密度图,一个密度曲线内fill不同颜色如何实现
  • ¥100 求汇川机器人IRCB300控制器和示教器同版本升级固件文件升级包
  • ¥15 用visualstudio2022创建vue项目后无法启动
  • ¥15 x趋于0时tanx-sinx极限可以拆开算吗
  • ¥500 把面具戴到人脸上,请大家贡献智慧,别用大模型回答,大模型的答案没啥用
  • ¥15 任意一个散点图自己下载其js脚本文件并做成独立的案例页面,不要作在线的,要离线状态。