Go 編程:圖解反射

原文發(fā)布在個人站點: GitDiG.com, 原文鏈接:https://www.gitdig.com/go-reflect/

1. 圖解反射

在使用反射之前痰洒,此文The Laws of Reflection必讀醋寝。網(wǎng)上中文翻譯版本不少,可以搜索閱讀带迟。

開始具體篇幅之前音羞,先看一下反射三原則:

  • Reflection goes from interface value to reflection object.
  • Reflection goes from reflection object to interface value.
  • To modify a reflection object, the value must be settable.

在三原則中,有兩個關(guān)鍵詞 interface valuereflection object仓犬。有點難理解嗅绰,畫張圖可能你就懂了。

reflect.png

先看一下什么是反射對象 reflection object搀继? 反射對象有很多窘面,但是其中最關(guān)鍵的兩個反射對象reflection object是:reflect.Typereflect.Value.直白一點,就是對變量類型的抽象定義類叽躯,也可以說是變量的元信息的類定義.

再來财边,為什么是接口變量值 interface value, 不是變量值 variable value 或是對象值 object value 呢?因為后兩者均不具備廣泛性点骑。在 Go 語言中酣难,空接口 interface{}是可以作為一切類型值的通用類型使用。所以這里的接口值 interface value 可以理解為空接口變量值 interface{} value黑滴。

結(jié)合圖示憨募,將反射三原則歸納成一句話:

通過反射可以實現(xiàn)反射對象 reflection object接口變量值 interface value之間的相互推導(dǎo)與轉(zhuǎn)化, 如果通過反射修改對象變量的值,前提是對象變量本身是可修改的袁辈。

2. 反射的應(yīng)用

在程序開發(fā)中是否需要使用反射功能菜谣,判斷標(biāo)準(zhǔn)很簡單,即是否需要用到變量的類型信息晚缩。這點不難判斷尾膊,如何合理的使用反射才是難點。因為荞彼,反射不同于普通的功能函數(shù)冈敛,它對程序的性能是有損耗的,需要盡量避免在高頻操作中使用反射卿泽。

舉幾個反射應(yīng)用的場景例子:

2.1 判斷未知對象是否實現(xiàn)具體接口

通常情況下莺债,判斷未知對象是否實現(xiàn)具體接口很簡單滋觉,直接通過 變量名.(接口名) 類型驗證的方式就可以判斷。但是有例外齐邦,即框架代碼實現(xiàn)中檢查調(diào)用代碼的情況椎侠。因為框架代碼先實現(xiàn),調(diào)用代碼后實現(xiàn)措拇,也就無法在框架代碼中通過簡單額類型驗證的方式進(jìn)行驗證我纪。

看看 grpc 的服務(wù)端注冊接口就明白了。

grpcServer := grpc.NewServer()
// 服務(wù)端實現(xiàn)注冊
pb.RegisterRouteGuideServer(grpcServer, &routeGuideServer{})

當(dāng)注冊的實現(xiàn)沒有實現(xiàn)所有的服務(wù)接口時丐吓,程序就會報錯浅悉。它是如何做的,可以直接查看pb.RegisterRouteGuideServer的實現(xiàn)代碼券犁。這里簡單的寫一段代碼术健,原理相同:


//目標(biāo)接口定義
type Foo interface {
    Bar(int)
}
  
dst := (*Foo)(nil)
dstType := reflect.TypeOf(dst).Elem()

//驗證未知變量 src 是否實現(xiàn) Foo 目標(biāo)接口
srcType := reflect.TypeOf(src)
if !srcType.Implements(dstType) {
        log.Fatalf("type %v that does not satisfy %v", srcType, dstType)
}

這也是grpc框架的基礎(chǔ)實現(xiàn),因為這段代碼通常會是在程序的啟動階段所以對于程序的性能而言沒有任何影響粘衬。

2.2 結(jié)構(gòu)體字段屬性標(biāo)簽

通常定義一個待JSON解析的結(jié)構(gòu)體時荞估,會對結(jié)構(gòu)體中具體的字段屬性進(jìn)行tag標(biāo)簽設(shè)置,通過tag的輔助信息對應(yīng)具體JSON字符串對應(yīng)的字段名稚新。JSON解析就不提供例子了勘伺,而且通常JSON解析代碼會作用于請求響應(yīng)階段,并非反射的最佳場景褂删,但是業(yè)務(wù)上又不得不這么做飞醉。

這里我要引用另外一個利用結(jié)構(gòu)體字段屬性標(biāo)簽做反射的例子,也是我認(rèn)為最完美詮釋反射的例子屯阀,真的非常值得推薦缅帘。這個例子出現(xiàn)在開源項目github.com/jaegertracing/jaeger-lib中。

用過 prometheus的同學(xué)都知道蹲盘,metric探測標(biāo)量是需要通過以下過程定義并注冊的:

var (
    // Create a summary to track fictional interservice RPC latencies for three
    // distinct services with different latency distributions. These services are
    // differentiated via a "service" label.
    rpcDurations = prometheus.NewSummaryVec(
        prometheus.SummaryOpts{
            Name:       "rpc_durations_seconds",
            Help:       "RPC latency distributions.",
            Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
        },
        []string{"service"},
    )
    // The same as above, but now as a histogram, and only for the normal
    // distribution. The buckets are targeted to the parameters of the
    // normal distribution, with 20 buckets centered on the mean, each
    // half-sigma wide.
    rpcDurationsHistogram = prometheus.NewHistogram(prometheus.HistogramOpts{
        Name:    "rpc_durations_histogram_seconds",
        Help:    "RPC latency distributions.",
        Buckets: prometheus.LinearBuckets(*normMean-5**normDomain, .5**normDomain, 20),
    })
)

func init() {
    // Register the summary and the histogram with Prometheus's default registry.
    prometheus.MustRegister(rpcDurations)
    prometheus.MustRegister(rpcDurationsHistogram)
    // Add Go module build info.
    prometheus.MustRegister(prometheus.NewBuildInfoCollector())
}

這是 prometheus/client_golang 提供的例子股毫,代碼量多,而且需要使用init函數(shù)召衔。項目一旦復(fù)雜,可讀性就很差祭陷。再看看github.com/jaegertracing/jaeger-lib/metrics提供的方式:

type App struct{
    //attributes ...
    //metrics ...
    metrics struct{
        // Size of the current server queue
            QueueSize metrics.Gauge `metric:"thrift.udp.server.queue_size"`
    
            // Size (in bytes) of packets received by server
            PacketSize metrics.Gauge `metric:"thrift.udp.server.packet_size"`
    
            // Number of packets dropped by server
            PacketsDropped metrics.Counter `metric:"thrift.udp.server.packets.dropped"`
    
            // Number of packets processed by server
            PacketsProcessed metrics.Counter `metric:"thrift.udp.server.packets.processed"`
    
            // Number of malformed packets the server received
            ReadError metrics.Counter `metric:"thrift.udp.server.read.errors"`
    }
}

在應(yīng)用中首先直接定義匿名結(jié)構(gòu)metrics苍凛, 將針對該應(yīng)用的metric探測標(biāo)量定義到具體的結(jié)構(gòu)體字段中,并通過其字段標(biāo)簽tag的方式設(shè)置名稱兵志。這樣在代碼的可讀性大大增強(qiáng)了醇蝴。

再看看初始化代碼:

import "github.com/jaegertracing/jaeger-lib/metrics/prometheus"

//初始化
metrics.Init(&app.metrics, prometheus.New(), nil)

不服不行,完美想罕。這段樣例代碼實現(xiàn)在我的這個項目中: x-mod/thriftudp悠栓,完全是參考該庫的實現(xiàn)寫的霉涨。

2.3 函數(shù)適配

原來做練習(xí)的時候,寫過一段函數(shù)適配的代碼惭适,用到反射笙瑟。貼一下:

//Executor 適配目標(biāo)接口,增加 context.Context 參數(shù)
type Executor func(ctx context.Context, args ...interface{})

//Adapter 適配器適配任意函數(shù)
func Adapter(fn interface{}) Executor {
    if fn != nil && reflect.TypeOf(fn).Kind() == reflect.Func {
        return func(ctx context.Context, args ...interface{}) {
            fv := reflect.ValueOf(fn)
            params := make([]reflect.Value, 0, len(args)+1)
            params = append(params, reflect.ValueOf(ctx))
            for _, arg := range args {
                params = append(params, reflect.ValueOf(arg))
            }
            fv.Call(params)
        }
    }
    return func(ctx context.Context, args ...interface{}) {
        log.Warn("null executor implemention")
    }
}

僅僅為了練習(xí)癞志,生產(chǎn)環(huán)境還是不推薦使用往枷,感覺太重了。

最近看了一下Go 1.14的提案凄杯,關(guān)于try關(guān)鍵字的引入, try參考错洁。按其所展示的功能,如果自己實現(xiàn)的話戒突,應(yīng)該會用到反射功能屯碴。那么對于現(xiàn)在如此依賴 error 檢查的函數(shù)實現(xiàn)來說,是否合適,挺懷疑的膊存,等Go 1.14出了窿锉,驗證一下。

3 小結(jié)

反射的最佳應(yīng)用場景是程序的啟動階段膝舅,實現(xiàn)一些類型檢查嗡载、注冊等前置工作,既不影響程序性能同時又增加了代碼的可讀性仍稀。最近迷上新褲子洼滚,所以別再問我什么是反射了:)

參考資源:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市技潘,隨后出現(xiàn)的幾起案子遥巴,更是在濱河造成了極大的恐慌,老刑警劉巖享幽,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铲掐,死亡現(xiàn)場離奇詭異,居然都是意外死亡值桩,警方通過查閱死者的電腦和手機(jī)摆霉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奔坟,“玉大人携栋,你說我怎么就攤上這事】缺” “怎么了婉支?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長澜建。 經(jīng)常有香客問我向挖,道長蝌以,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任何之,我火速辦了婚禮跟畅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘帝美。我一直安慰自己碍彭,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布悼潭。 她就那樣靜靜地躺著庇忌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪舰褪。 梳的紋絲不亂的頭發(fā)上皆疹,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機(jī)與錄音占拍,去河邊找鬼略就。 笑死,一個胖子當(dāng)著我的面吹牛晃酒,可吹牛的內(nèi)容都是我干的表牢。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼贝次,長吁一口氣:“原來是場噩夢啊……” “哼崔兴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蛔翅,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤敲茄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后山析,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體堰燎,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年笋轨,在試婚紗的時候發(fā)現(xiàn)自己被綠了秆剪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡翩腐,死狀恐怖鸟款,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情茂卦,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布组哩,位于F島的核電站等龙,受9級特大地震影響处渣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蛛砰,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一罐栈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧泥畅,春花似錦荠诬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至聂抢,卻和暖如春钧嘶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背琳疏。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工有决, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人空盼。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓书幕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親揽趾。 傳聞我的和親對象是個殘疾皇子台汇,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容

  • The Laws of Reflection(https://blog.golang.org/laws-of-re...
    one_zheng閱讀 880評論 1 1
  • 反射是 Go 語言學(xué)習(xí)的一個難點,但也是非常重要的一個知識點但骨。反射是洞悉 Go 語言類型系統(tǒng)設(shè)計的法寶励七,Go 語言...
    趙客縵胡纓v吳鉤霜雪明閱讀 471評論 0 8
  • 整理來自互聯(lián)網(wǎng) 1,JDK:Java Development Kit奔缠,java的開發(fā)和運行環(huán)境掠抬,java的開發(fā)工具...
    Ncompass閱讀 1,537評論 0 6
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,093評論 1 32
  • 今天在《光遠(yuǎn)看經(jīng)濟(jì)》的公眾平臺看到了光遠(yuǎn)先生代言靜寧蘋果的圖文,自從我關(guān)注他起校哎,這已經(jīng)是他第N次宣傳家鄉(xiāng)的蘋果了两波,...
    何立廷閱讀 561評論 1 0