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 孟德尔随机化怎样画共定位分析图
  • ¥18 模拟电路问题解答有偿速度
  • ¥15 CST仿真别人的模型结果仿真结果S参数完全不对
  • ¥15 误删注册表文件致win10无法开启
  • ¥15 请问在阿里云服务器中怎么利用数据库制作网站
  • ¥60 ESP32怎么烧录自启动程序
  • ¥50 html2canvas超出滚动条不显示
  • ¥15 java业务性能问题求解(sql,业务设计相关)
  • ¥15 52810 尾椎c三个a 写蓝牙地址
  • ¥15 elmos524.33 eeprom的读写问题