使用interface重構代碼,面向接口,減小重複代碼
項目背景
- 須要提供節目,節目集數據的增刪改查,數據庫使用ES(elasticsearch)
重構前 →_→
- 本文着重強調用接口重構的思路,因此只選取讀取功能做爲例子
- 數據結構
type Video struct{
VideoID string `json:"video_id"`
//包含不少節目的屬性,如節目類型,上下線狀態等
}
type Show struct{
ShowID string `json:"show_id"`
//包含不少節目集的屬性,如評分,演員等
}
- ES中對應兩個index,video,show 分別對應上邊的兩個結構。重構前讀取功能實現
package es
import (
"context"
"log"
"encoding/json"
"github.com/olivere/elastic"
)
func(video *Video) ReadVideo(videoID string){
client,_ := elastic.NewClient(elastic.SetURL("http://127.0.0.1:19200"))
defer client.Stop()
if !isVideoExists(videoID){
return
}
esResponse,err := client.Get().Index("video").Type("video").Id(videoID).Do(context.Background())
if err != nil {
log.Println("Failed to read ES data of ID:! ",videoID)
return
}
json.Unmarshal(*esResponse.Source,&video)
}
func isVideoExists(videoID string)bool{
client,_ := elastic.NewClient(elastic.SetURL("http://127.0.0.1:19200"))
defer client.Stop()
exist,_ := client.Exists().Index("video").Type("video").Id(videoID).Do(context.Background())
if !exist{
log.Println("video ID may be incorrect! ",videoID)
return false
}
return true
}
func(show *Show) ReadShow(showID string){
client,_ := elastic.NewClient(elastic.SetURL("http://127.0.0.1:19200"))
defer client.Stop()
if !isShowExists(showID){
return
}
esResponse,err := client.Get().Index("show").Type("show").Id(showID).Do(context.Background())
if err != nil {
log.Println("Failed to read ES data of ID:! ",showID)
return
}
json.Unmarshal(*esResponse.Source,&show)
}
func isShowExists(showID string)bool{
client,_ := elastic.NewClient(elastic.SetURL("http://127.0.0.1:19200"))
defer client.Stop()
exist,_ := client.Exists().Index("show").Type("show").Id(showID).Do(context.Background())
if !exist{
log.Println("show ID may be incorrect! ",showID)
return false
}
return true
}
重構中——分析代碼結構
- 優勢:處理流程比較清晰(沒有複雜的調用邏輯,想不清晰都難......)
- 缺點:
-
- 面向過程!致使每個過程都須要定義新的函數來處理,讀取節目須要 ReadVideo(),讀取節目集須要ReadShow()。不管這個新加的功能是否是已有相似的實現可複用
-
- 代碼冗餘!上邊的代碼有很是嚴重的冗餘,一個是判斷ID是否在ES庫中,一個是讀取功能的實現,除了ES的路徑不一樣,video是 video->video->VIDEO_ID;show 則是 show->show->SHOW_ID;其他的代碼基本一致
重構中——看似可行的方案
- 加一個interface類型參數,再加一個指示類型的參數,只用一個Read函數,一個isExists函數就行。
func Read(media interface{}, mediaType, id string){
client,_ := elastic.NewClient(elastic.SetURL("http://127.0.0.1:19200"))
defer client.Stop()
if !isExists(id){
return
}
esResponse,err := client.Get().Index(mediaType).Type(mediaType).Id(id).Do(context.Background())
if err != nil {
log.Println("Failed to read ES data of ID:! ",id)
return
}
json.Unmarshal(*esResponse.Source,&media)
}
func isExists(mediaType, id string)bool{
client,_ := elastic.NewClient(elastic.SetURL("http://127.0.0.1:19200"))
defer client.Stop()
exist,_ := client.Exists().Index(mediaType).Type(mediaType).Id(id).Do(context.Background())
if !exist{
log.Println("ID may be incorrect! ",id)
return false
}
return true
}
- 增長了參數個數,Read增長了兩個,isExists增長了一個。一個函數的參數天然是越少越好。增長參數,差評+1
- 程序的健壯性下降了!一旦mediaType沒有準確地對應ID,程序就不能正常工做,好比傳了 "video" 和show 的ID ,要在ES的video數據庫裏邊找show數據庫的ID,天然就會失敗。另外,使用interface類型參數,一樣也會形成健壯性的下降
- 使用了interface類型參數。爲了可以傳入自定義的Show類型和Video類型數據,使用了interface。那麼,在調用這個函數的時候,這個參數傳int,string,bool都是合法的,可是,從實際須要的功能來講,這些參數顯然是不合法的。
- 總結 雖然這個實現能夠減小重複代碼,可是反而增長了函數調用的風險,一旦你離職,別人接手你的代碼,這就會成爲別人的災難。程序的健壯性下降了,顯然,這個交換並不划算!那麼,有沒有什麼辦法既能減小冗餘代碼,又能不帶來其餘的負面影響,諸如下降代碼健壯性?
重構後 ヽ( ̄▽ ̄)ノ
- 不少人會問golang是否是一個面向對象的語言,論壇上的答案很玄妙:是也不是。說不是,是由於go裏邊沒有明確的語法聲明類,說是,是由於go也能夠經過struct和interface實現面向對象的特性。How?請看重構以後的代碼!
package es
import (
"context"
"log"
"encoding/json"
"github.com/olivere/elastic"
)
type Video struct{
VideoID string `json:"video_id"`
//包含不少節目的其餘屬性,如節目類型,上下線狀態等,此處省略
}
type Show struct{
ShowID string `json:"show_id"`
//包含不少節目集的其餘屬性,如評分,演員等,此處省略
}
func(video *Video) read(videoID string){}
func(show *Show) read(showID string){}
type reader interface {
read()
}
type esPath struct{
ESIndex string
ESType string
ESID string
}
func Read(reader reader,esPath *esPath){
client ,_:= ESClient()
defer client.Stop()
if !isExists(esPath){
return
}
esResponse,err := client.
Get().Index(esPath.ESIndex).Type(esPath.ESType).Id(esPath.ESID).Do(context.Background())
if err != nil {
logger.LogPrintln("Failed to read ES data of ID:! ",esPath.ESID)
return
}
json.Unmarshal(*esResponse.Source,&reader)
}
func isExists(esPath *esPath)bool{
client,_ := ESClient()
defer client.Stop()
exist,_ := client.Exists().Index(esPath.ESIndex).Type(esPath.ESType).Id(esPath.ESID).Do(context.Background())
if !exist{
logger.LogPrintln("ShowID may be incorrect! ",esPath.ESID)
return false
}
return true
}
- 簡單說明一下爲什麼定義了一個新struct esPath,完整的程序還有修改,新增,刪除等功能,能夠複用這個結構。
- video和show都有read函數,因此實現了reader接口。在調用Read函數的時候只能使用show或者video類型,不然會報類型錯誤。避免了interface類型什麼數都能瞎傳的問題。在某種程度上,能夠說video和show是reader類的對象,均可以用Read()函數讀出reader類的對象。使用接口可以大大減小代碼冗餘!