這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
近期在公司實習,參與了公司的一個分布式的應用服務系統。系統採用Golang語言作為系統的開發語言,在開發過程中採用了Go語言的反射函數的特性來取代了以前常使用的switch文法。
switch-case是一種多種選擇的文法,其本質與if-else方法差不多,都是通過判斷條件來執行不同的方法。而Go提供了一種機制在運行時更新變數和檢查它們的值,調用它們的方法,和它們支援的內在操作,但是在編譯時間並不知道這些變數的類型。這種機制被稱為反射,反射也可以讓我們將類型本身作為第一類的實值型別處理。
Web應用路由問題
在我們編寫Web應用過程中,常常會遇到一個路由需要對應一個方法,我們會選擇使用switch的方法來進行路由的匹配,若是路由匹配成功,我們會調用一個方法,這種方法能夠很簡便的完成我們的工作,也便於程式員在編寫代碼過程中釐清思路。
問題:
在一個URL的路由中,我們在request中通過cmd的參數來對應一個方法,這樣我們要如何根據一個cmd對應一個方法?
可能針對這個問題有人會說,我們為什麼不把cmd放到URL裡面,這樣的話就是一個方法對應一個路由,而且大多數的Web架構會通過回呼函數來進行函數調用,針對於這個問題,我只能說大部分的都是將cmd放到http的request裡面的,具體的好處可能就是減少了對API的監管,以及當路由比較多的時間能夠減少麻煩吧。
用switch方法實現:
cmd := this.GetString("cmd")switch cmd{ case "ls": ls() case "cd": cd() default: fmt.Println("cmd method missing")}
上述的方法先通過URL的參數擷取到cmd,然後通過cmd來調用相對應的方法。在傳統的MVC的設計模式中,需要在Controller中添加switch方法,同時需要在Model中實現相對應的方法,總計修改了2個檔案。
go語言反射函數
reflect包
在reflect包中,主要通過Typeof()和Valueof()兩個方法來實現反射。兩個方法相互結合,能夠反射出被反射函數的全部資訊。
package mainimport ( "fmt" "reflect")type Ref struct { id int name string}func (ref *Ref)GetName(){ fmt.Println("getName()函數")}func (ref *Ref)GetNameById(){ fmt.Println("getNameById()函數")}func main(){ t := reflect.TypeOf(&Ref{}) v := reflect.ValueOf(new(Ref)) fmt.Println(t) fmt.Println(v) for i:= 0; i< t.NumMethod();i++{ fmt.Println(t.Method(i).Name) v.Method(i).Call(nil) }}
TypeOf()
TypeOf()函數主要是列印出被反射函數的類型,其返回結果是reflect.Type類型。
在上面的樣本中,通過Method().Name能夠反射其方法的函數名。
常用的方法:
- func (t *rtype)String() string
- func (t *rtype)Name() string
- func (t *rtype)Kind() reflect.kind
- func (t *rtype)Method(int) reflect.Method
- func (t *rtype)Elem() reflect.Type
- func (t *rtype)In(int) reflect.Type
ValueOf()
ValueOf()函數主要是列印出被反射函數的類型,其返回結果是reflect.Value類型。
在上面的樣本中,通過Method().Call()能夠反射出其函數並執行。
常用的方法:
- func (v Value)String() string
- func (v Value)Elem() reflect.Value
- func (v Value)Method(int) reflect.Value
- func (v Value)Call(in []Value) (r []Value)
反射的實現過程
由於有反射的存在,因此在傳統的MVC的設計模式中,當我們添加服務時,不需要修改Controller端的代碼,Controller只需要維持一個map的表,裡面的就來儲存需要被反射的models。
package server import( "reflect" "fmt")// ReServer 來儲存map的結構體type ReServer struct { m map[string]interface{}}// RegisterService 註冊服務func (this *ReServer)RegisterService(service interface{})(err error){ serviceType := reflect.TypeOf(service).Elem() ServiceName := serviceType.Name() if _,ok := this.m[ServiceName] if ok { fmt.Println("service has been registered") }else{ this.m[ServiceName] = service } return}// Start 服務啟動func (this *ReServer)Start(){ for k,v := range this.m { // 裡面根據商務邏輯執行想要的方法 }}
在上訴的例子中,通過對services的服務註冊,就能夠通過Start()函數探索服務,並且根據業務來實現自己的代碼。
package mainimport ( "server")type Server struct {}func (server *Server)funOne(){ fmt.Println("Server FunOne")} func main(){ reServer := &server.ReServer{ m: make(map[string]interface{}) } err := reServer.RegisterService(new(Server)) reServer.Start()}
因此在我們主函數中,匯入封裝好的包,只需要註冊一個結構體,就能夠將自己的方法反射出來實現。
對應上面的Web的路由問題,我們將Controller進行封裝,然後將Model進行反射,當我們業務增加時,我們在Model裡面添加就可以了,不需要修改Controller。