2014-06-15 10 views
2

我正在編寫一個API的客戶端。一種方法,posts,返回一組用戶的帖子。在自己的地方乾燥了共同的領域

  • 每篇文章是八種不同類型之一。顯然,一種「是 - 一種」關係。
  • 帖子的許多字段,包括(其中包括)ID,URL和時間戳對於每種類型的帖子都是通用的。
  • 每種類型的帖子都有其類型唯一的字段。例如,一個照片文章將有一個分辨率和一個標題。

在一種具有繼承性的語言中,我將抽象基類Post,然後子類化,爲每種類型的帖子創建一個具體類。我會在基礎Post,也許fromJson()有一個構造函數或工廠方法,它需要一個JSON對象並提取所有的公共字段。然後我會覆蓋每個子類中的提取專用字段,確保調用基本實現來幹提取常用字段。

Go沒有繼承,但它有構成。我定義了一個具有所有公共字段的Post結構,然後我爲每個類型都創建了一個結構,其中包含一個匿名Post字段,以便它包含所有Post字段。例如,

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

我的目標之一是我希望讓我的客戶的用戶能夠輕鬆訪問郵政的公共字段。所以我絕對不想讓我寫的Posts()方法返回interface{},因爲隨時有人想要得到所有帖子的ID,例如,他們將不得不做一個可怕的類型開關,這將是一個圖案一遍又一遍地讓我畏縮:

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! 
     } 
    } 
} 

這太糟糕了。但我不能讓Posts返回[]Post,因爲那時需要更專門化的數據(對於像「給我所有來自該用戶帖子的照片字幕」這樣的用例),它不會在那裏(因爲在Go中,a PhotoPost不是Post,它一個Post及其字段。

目前,我考慮有Posts()返回PostCollection,這將是一個結構,將這個樣子,這樣至少我將避免上述類型開關怪物:

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

但用例「將所有帖子的所有ID都放到一個切片中」或類似的東西仍然非常麻煩。有人可以建議一種慣用的方式來處理這個問題嗎?最好是不需要反射的一個?

我一直在琢磨讓每個Post類型實現一個TypedPost界面,返回自己的發帖PostData()方法,但它並不像存在,除非我有兩個名爲匿名類型的似乎很奇怪(匿名,所以我可以說somePhotoPost.ID當我知道我有一個PhotoPost想和someTypedPost.PostData().ID,當我只知道我正在處理某種TypedPost。然後我有Posts()返回[]TypedPost。有沒有一個更好的方法?

回答

6

定義帖子的interface - 不要訪問,除了通過接口通用數據元素。

這裏是一個例子。請注意0​​界面,它定義了什麼所有職位可以做(但不是他們有什麼數據)。 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\n", GetIDs(posts)) 
} 

如果你的代碼中有一類開關切換自己的對象,那麼你已經走了錯誤的定義接口。

請注意,您可以定義您的帖子子集滿足的接口,並使用類型轉換來查看它們是否實現它。

1

一個更簡單的方法是使用接口playground

type PostInterface interface { 
    Id() int 
} 
type Post struct { 
    ID int 
} 

func (p Post) Id() int { 
    return p.ID 
} 

type PhotoPost struct { 
    Post 
} 

func GetIDs(posts ...PostInterface) (ids []int) { 
    ids = make([]int, len(posts)) 
    for i := range posts { 
     p := posts[i] 
     ids[i] = p.Id() 
     switch pt := p.(type) { 
     case PhotoPost: 
      fmt.Println("PhotoPost, width =", pt.Width) 
     } 
    } 
    return 
} 

func main() { 
    pp := []PostInterface{ 
     PhotoPost{Post: Post{10}, Width: 20}, 
     PhotoPost{Post: Post{20}}, 
     PhotoPost{Post: Post{30}}, 
     PhotoPost{Post: Post{40}}, 
    } 
    fmt.Println(GetIDs(pp...)) 
}