在 Go 中,interface與其他語言的寫法稍有不同,除了上一篇提到的可以處理各種資料型別的功能以外,另一個功能就是可以定義行為,也就是說 interface 中可以定義一些方法來表示一個對象的行為,而當我們有自定義的型態假設想要擁有這些行為,就是去實踐 interface 裡面的方法。比方說今天我們想要計算動物園裡頭某種動物的每年開銷,我們可以用以下的方式算得結果
type ZooAnimal interface {
Feed() int
Rest() int
Place() string
}
type Lion struct {
AnimalInfo Animal
}
type Animal struct {
Amount int
FeedDaily int
RestDaily int
Place string
}
func (lion *Lion) Feed() int {
return lion.AnimalInfo.Amount * lion.AnimalInfo.FeedDaily
}
func (lion *Lion) Rest() int {
return lion.AnimalInfo.Amount * lion.AnimalInfo.RestDaily
}
func (lion *Lion) Place() string {
return lion.AnimalInfo.Place
}
func ShowFeedYear(animal ZooAnimal) int {
feed_daily := animal.Feed()
var ratio int
switch animal.Place() {
case "熱帶":
ratio = 1
case "寒帶":
ratio = 2
}
return feed_daily * ratio * 365
}
func ShowRestYear(animal ZooAnimal) int {
rest_daily := animal.Rest()
return rest_daily * 365
}
func ShowTotalYear(animal ZooAnimal, kind string) {
feed_year := ShowFeedYear(animal)
rest_year := ShowRestYear(animal)
fmt.Printf("%s 每年花費為 %d 元 (此為毫無根據的數字) \n", kind, feed_year+rest_year)
}
func main() {
lion := &Lion{AnimalInfo: Animal{Amount: 2, FeedDaily: 500, RestDaily: 50, Place: "熱帶"}}
ShowTotalYear(lion, "獅子")
}
輸出結果為:
獅子 每年花費為 401500 元 (此為毫無根據的數字)
目前還看不出他的好處,反而覺得相對冗長,但是假設動物園的動物又突然多了企鵝,那我們只需要定義企鵝的基本資訊
// type ZooAnimal interface {
// Feed() int
// Rest() int
// Place() string
// }
// type Lion struct {
// AnimalInfo Animal
// }
// type Animal struct {
// Amount int
// FeedDaily int
// RestDaily int
// Place string
// }
// func (lion *Lion) Feed() int {
// return lion.AnimalInfo.Amount * lion.AnimalInfo.FeedDaily
// }
// func (lion *Lion) Rest() int {
// return lion.AnimalInfo.Amount * lion.AnimalInfo.RestDaily
// }
// func (lion *Lion) Place() string {
// return lion.AnimalInfo.Place
// }
// func ShowFeedYear(animal ZooAnimal) int {
// feed_daily := animal.Feed()
// var ratio int
// switch animal.Place() {
// case "熱帶":
// ratio = 1
// case "寒帶":
// ratio = 2
// }
// return feed_daily * ratio * 365
// }
// func ShowRestYear(animal ZooAnimal) int {
// rest_daily := animal.Rest()
// return rest_daily * 365
// }
// func ShowTotalYear(animal ZooAnimal, kind string) {
// feed_year := ShowFeedYear(animal)
// rest_year := ShowRestYear(animal)
// fmt.Printf("%s 每年花費為 %d 元 (此為毫無根據的數字) \n", kind, feed_year+rest_year)
// }
// func main() {
// lion := &Lion{AnimalInfo: Animal{Amount: 2, FeedDaily: 500, RestDaily: 50, Place: "熱帶"}}
// ShowTotalYear(lion, "獅子")
penguin := &Penguin{AnimalInfo: Animal{Amount: 10, FeedDaily: 100, RestDaily: 100, Place: "寒帶"}}
ShowTotalYear(penguin, "企鵝")
// }
func (penguin *Penguin) Feed() int {
return penguin.AnimalInfo.Amount * penguin.AnimalInfo.FeedDaily
}
func (penguin *Penguin) Rest() int {
return penguin.AnimalInfo.Amount * penguin.AnimalInfo.RestDaily
}
func (penguin *Penguin) Place() string {
return penguin.AnimalInfo.Place
}
type Penguin struct {
AnimalInfo Animal
}
輸出的結果為:
獅子 每年花費為 401500 元 (此為毫無根據的數字)
企鵝 每年花費為 1095000 元 (此為毫無根據的數字)
實際上還可以再優化一下,透過 switch 來做物種的分類,那就可以修改成如下資訊
// type ZooAnimal interface {
// Feed() int
// Rest() int
// Place() string
// }
// type Lion struct {
// AnimalInfo Animal
// }
// type Animal struct {
// Amount int
// FeedDaily int
// RestDaily int
// Place string
// }
// func (lion *Lion) Feed() int {
// return lion.AnimalInfo.Amount * lion.AnimalInfo.FeedDaily
// }
// func (lion *Lion) Rest() int {
// return lion.AnimalInfo.Amount * lion.AnimalInfo.RestDaily
// }
// func (lion *Lion) Place() string {
// return lion.AnimalInfo.Place
// }
// func ShowFeedYear(animal ZooAnimal) int {
// feed_daily := animal.Feed()
// var ratio int
// switch animal.Place() {
// case "熱帶":
// ratio = 1
// case "寒帶":
// ratio = 2
// }
// return feed_daily * ratio * 365
// }
// func ShowRestYear(animal ZooAnimal) int {
// rest_daily := animal.Rest()
// return rest_daily * 365
// }
func ShowTotalYear(animal ZooAnimal) {
// feed_year := ShowFeedYear(animal)
// rest_year := ShowRestYear(animal)
fmt.Printf("每年花費為 %d 元 (此為毫無根據的數字) \n", feed_year+rest_year)
// }
// func main() {
// // lion := &Lion{AnimalInfo: Animal{Amount: 2, FeedDaily: 500, RestDaily: 50, Place: "熱帶"}}
// // ShowTotalYear(lion, "獅子")
// // penguin := &Penguin{AnimalInfo: Animal{Amount: 10, FeedDaily: 100, RestDaily: 100, Place: "寒帶"}}
// // ShowTotalYear(penguin, "企鵝")
animals := [...]ZooAnimal{
&Lion{AnimalInfo: Animal{Amount: 2, FeedDaily: 500, RestDaily: 50, Place: "熱帶"}},
&Penguin{AnimalInfo: Animal{Amount: 10, FeedDaily: 100, RestDaily: 100, Place: "寒帶"}},
}
for _, animal := range animals {
switch animal.(type) {
case *Lion:
fmt.Printf("獅子 ")
case *Penguin:
fmt.Printf("企鵝 ")
default:
fmt.Println("是誰偷偷混進來啊~!!")
}
ShowTotalYear(animal)
}
// }
// func (penguin *Penguin) Feed() int {
// return penguin.AnimalInfo.Amount * penguin.AnimalInfo.FeedDaily
// }
// func (penguin *Penguin) Rest() int {
// return penguin.AnimalInfo.Amount * penguin.AnimalInfo.RestDaily
// }
// func (penguin *Penguin) Place() string {
// return penguin.AnimalInfo.Place
// }
// type Penguin struct {
// AnimalInfo Animal
// }
輸出結果如下:
獅子 每年花費為 401500 元 (此為毫無根據的數字)
企鵝 每年花費為 1095000 元 (此為毫無根據的數字)
可能用這樣的解釋,還是會有疑問,就是看不出優勢為何,但實際上這樣做的好處是對於大型程式碼開發上比較好管理,也比較容易劃分工作內容。以上面為例,就可以將其分成兩個工作內容,一個是專門生產各種不同動物的工程師,另一個是專門針對動物類型的會有哪些開銷,比方說,一開始的動物園要管理的動物只有五到十種動物,但我們為他們標上習慣的生活環境、需要吃的食物、休息時間等等,以方便管理並且計算最後這些動物的總開銷,當然很少隻動物時我們可以為每隻動物計算,但假設今天要管理的是一百到兩百種動物,那透過 interface 這種方式就能比較好閱讀,而且好處是,我們可以先定義一種動物,就像我們上面一開始先定義獅子,透過獅子我們就能完成整年的開銷計算,那麼無尾熊、熊貓、袋鼠或是大象等等,我們僅需定義前面的基本資訊,就可以獲得後面整年的開銷,不僅在程式碼上方便閱讀,也好管理,在未來新增的動物種類的擴編上也相較簡單,因此這種的使用時機,建議是在大型專案,以更實際的例子來說,就是電商在處理帳號登入,比方說帳號登入可能有 Google、FaceBook、自家的註冊登入方式,那麼我們今天只需要利用上面 interface 這種方式,就可以將負責帳號登入的工作獨立出來,然後成功登入的後續就可以交由其他人分配,就不需要特別為了 Google 登入的使用者寫一種購買與儲存購物清單方式,FaceBook 登入的使用者再寫另一種方式,然後自家的又要另一種,如此一來就能有效的避免不必要的人力浪費,但如果是小專案的話,就不建議這麼做,因為這樣就是用大砲打小鳥,也是另一種人力浪費,嘻嘻
https://github.com/luckyuho/ithome30-golang/tree/main/day06