使用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类的对象。使用接口能够大大减少代码冗余!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Everything Store
Brad Stone / Little, Brown and Company / 2013-10-22 / USD 28.00
The definitive story of Amazon.com, one of the most successful companies in the world, and of its driven, brilliant founder, Jeff Bezos. Amazon.com started off delivering books through the mail. Bu......一起来看看 《The Everything Store》 这本书的介绍吧!