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?