![](/img/trans.png)
[英]How GoLang's typecast to interface implemented by a struct and embedded struct works
[英]Golang embedded interface on parent struct
我有一個試圖在“子類”上實現函數的程序,其中父進程可以檢查接口是否已實現。 對於透視圖,它實際上是基於if方法存在來處理REST URL生成。
我遇到的是基於以下模式,當實現僅1時,IList和IGet接口都可以在TestController對象上找到。 當調用IGet接口時,我感到恐慌。
我寧願不在基礎結構上做出Get / List的具體定義,然后必須覆蓋它們,更願意進行存在的測試,然后從那里開始。
這是一個游樂場鏈接以及https://play.golang.org/p/5j58fejeJ3
package main
import "fmt"
type IGet interface {
Get(int)
}
type IList interface {
List(int)
}
type Application struct {
name string
}
type BaseAppController struct {
*Application
IGet
IList
}
type TestController struct {
*BaseAppController
}
func (ctrl *BaseAppController) Init() {
fmt.Println("In Init")
if f, ok := interface{}(ctrl).(IGet); ok {
fmt.Println("Controller Found GET", f)
} else {
fmt.Println("Controller NOT Found GET", f)
}
if f, ok := interface{}(ctrl).(IList); ok {
fmt.Println("Controller Found LIST", f)
} else {
fmt.Println("Controller NOT Found LIST", f)
}
}
func (ctrl *BaseAppController) Call() {
fmt.Println("In Call")
if f, ok := interface{}(ctrl).(IGet); ok {
fmt.Println("Controller Found GET - going to call", f)
f.Get(7)
} else {
fmt.Println("Controller NOT Found GET - can't call", f)
}
}
// Test controller implements the Get Method
func (ctrl *TestController) Get(v int) {
fmt.Printf("Hi name=%s v=%d\n", ctrl.name, v)
}
func main() {
app := Application{"hithere"}
ctrl := TestController{&BaseAppController{Application: &app}}
ctrl.Init()
ctrl.Call()
}
您似乎缺少的一件事是嵌入接口如何影響Go中的結構。 請參閱嵌入促進嵌入類型的所有方法(結構或接口,無關緊要)是父類型的方法,但使用嵌入對象作為接收器調用。
這樣做的實際副作用是將接口嵌入到結構中可以保證該結構滿足其嵌入的接口,因為它根據定義具有該接口的所有方法。 然而,試圖調用任何這些方法而不定義某些東西來填充結構中的那個接口字段會引起恐慌,因為該接口字段默認為nil
。
因此,您的類型斷言將始終為真。 BaseAppController
嵌入了IGet
和IList
接口,因此始終滿足這兩者。
如果你想要鴨子類型,根據類型上方法的存在與否選擇性地啟用行為,你需要使用類似於標准庫io.WriterTo
接口的工作方式。 此接口和io.ReaderFrom
是io.Writer
和io.Reader
對象可以實現的可選接口,可以直接寫入或讀取其他源,而不是需要緩沖讀取數據或要自行寫入的數據的io
包。
基本要點是,您可以使用所需的方法定義底層接口,這就是您傳遞的內容。 然后,您有一個或多個可選接口,您可以檢查傳遞的類型以查看它們是否滿足,如果是,則使用該可選接口的方法(如果不是,則恢復為默認行為)。 在這種情況下不需要嵌入。
嵌入接口,而不是用於鴨子打字,更多的是多態性。 例如,如果您想訪問SQL數據庫,但希望能夠處理標准數據庫調用和事務中的調用,您可以創建一個包含兩種類型的聯合方法的結構( sql.DB
和sql.Tx
)像這樣:
type dber interface {
Query(query string, args ...interface{}) (*sql.Rows, error)
QueryRow(query string, args ...interface{}) *sql.Row
Exec(query string, args ...interface{}) (sql.Result, error)
}
然后你做一個像這樣的結構:
type DBHandle struct {
dber
}
現在你可以在結構的dber
槽中存儲sql.DB
或sql.Tx
,使用DBHandle
任何東西(以及DBHandle
本身的所有方法)都可以調用Query()
, QueryRow()
和Exec()
在DBHandle
上,無需知道它們是否在事務范圍內被調用(但請記住,必須首先初始化該接口字段!)
這種類型的功能是嵌入真正開始閃耀的地方,因為它允許功能和靈活性接近完全多態繼承系統,而不需要顯式的“implements”或“extends”語句。 它對你想要的動態鴨子打字行為類型並沒有多大用處。
不要將界面嵌入與struct embedding混合使用。
如果在結構上嵌入接口,實際上是使用接口名稱向結構中添加新字段,因此如果不進行初始化,則會因為它們為零而引起恐慌。
您的BaseAppController
希望有人在IGet
和IList
字段中填寫分別滿足IGet
和IList
接口的內容。
這就是你的BaseAppController
結構的真實情況:
type BaseAppController struct {
Application *Application
IGet IGet
IList IList
}
看來你正在嘗試在Go中進行Java風格的編程,但這並不能很好地結束。
用er命名: Getter
而不是IGet
你不需要在struct中嵌入interface方法,你的struct有Get接收器方法就足夠了。
調用任何nil接口值的方法會導致恐慌 :
你定義了ctrl.IGet
但沒有初始化它,
如果在Init()內部,請在第一行中添加此行:
fmt.Printf("f=%T IGet:T=%T V=%[2]v\n", f, ctrl.IGet)
輸出是:
f=*main.BaseAppController IGet:T=<nil> V=<nil>
變量總是初始化為明確定義的值,接口也不例外。 接口的零值將其類型和值組件設置為nil。
您的BaseAppController
缺少Get接口方法。
並將示例代碼中的最后一個方法編輯為:
func (ctrl *BaseAppController) Get(v int) {
fmt.Printf("Hi name=%s v=%d\n", ctrl.name, v)
}
而你的代碼現在運行。
如果你想用最小的改變來修復你當前的代碼,只需用你的主函數替換它(注意ctrl是這里的指針):
func main() {
app := Application{"hithere"}
ctrl := &TestController{&BaseAppController{Application: &app}}
ctrl.IGet = interface{}(ctrl).(IGet)
ctrl.Init()
ctrl.Call()
}
工作示例代碼(最小變化):
package main
import "fmt"
type IGet interface {
Get(int)
}
type IList interface {
List(int)
}
type Application struct {
name string
}
type BaseAppController struct {
*Application
IGet
IList
}
type TestController struct {
*BaseAppController
}
func (ctrl *BaseAppController) Init() {
fmt.Println("In Init")
if f, ok := interface{}(ctrl).(IGet); ok {
fmt.Println("Controller Found GET", f)
} else {
fmt.Println("Controller NOT Found GET", f)
}
if f, ok := interface{}(ctrl).(IList); ok {
fmt.Println("Controller Found LIST", f)
} else {
fmt.Println("Controller NOT Found LIST", f)
}
}
func (ctrl *BaseAppController) Call() {
fmt.Println("In Call")
if f, ok := interface{}(ctrl).(IGet); ok {
fmt.Println("Controller Found GET - going to call", f)
f.Get(7)
} else {
fmt.Println("Controller NOT Found GET - can't call", f)
}
}
// Test controller implements the Get Method
func (ctrl *TestController) Get(v int) {
fmt.Printf("Hi name=%s v=%d\n", ctrl.name, v)
}
func main() {
app := Application{"hithere"}
ctrl := &TestController{&BaseAppController{Application: &app}}
ctrl.IGet = interface{}(ctrl).(IGet)
ctrl.Init()
ctrl.Call()
}
這也有效(嵌入式界面已刪除):
package main
import "fmt"
type IGet interface {
Get(int)
}
type IList interface {
List(int)
}
type Application struct {
name string
}
type BaseAppController struct {
*Application
}
type TestController struct {
*BaseAppController
}
func (ctrl *TestController) Init() {
fmt.Println("In Init")
if f, ok := interface{}(ctrl).(IGet); ok {
fmt.Println("Controller Found GET", f)
} else {
fmt.Println("Controller NOT Found GET", f)
}
if f, ok := interface{}(ctrl).(IList); ok {
fmt.Println("Controller Found LIST", f)
} else {
fmt.Println("Controller NOT Found LIST", f)
}
}
func (ctrl *TestController) Call() {
fmt.Println("In Call")
if f, ok := interface{}(ctrl).(IGet); ok {
fmt.Println("Controller Found GET - going to call", f)
f.Get(7)
} else {
fmt.Println("Controller NOT Found GET - can't call", f)
}
}
// Test controller implements the Get Method
func (ctrl *TestController) Get(v int) {
fmt.Printf("Hi name=%s v=%d\n", ctrl.name, v)
}
func main() {
app := Application{"hithere"}
ctrl := TestController{&BaseAppController{Application: &app}}
ctrl.Init()
ctrl.Call()
}
輸出:
In Init
Controller Found GET &{0xc082026028}
Controller NOT Found LIST <nil>
In Call
Controller Found GET - going to call &{0xc082026028}
Hi name=hithere v=7
如果你想繼承任何帶有基礎結構的結構,你可以編寫這個(丑陋的)代碼,它就像一個反射包:
package main
import (
"fmt"
"unsafe"
)
type i interface{
i() interface{}
ct(i)
}
type t1 struct{
rptr unsafe.Pointer
}
func(x *t1) i() interface{} {
// parent struct can view child changed value, stored in rptr, as original value type with changes after store, instead of i interface
rv:= *(*i)(x.rptr)
fmt.Printf("%#v %d\n", rv, rv.(*t2).a)
return rv
}
func(x *t1) ct(vv i){
// store pointer to child value of i interface type
// we can store any of types, i is for the sample
x.rptr = unsafe.Pointer(&vv)
}
type t2 struct{
t1
a int
}
func main() {
t:=&t2{}
t.ct(t) // store original
t.a = 123 // change original
ti:=(t.i()).(*t2) // t.i() is a method of parent (embedded) struct, that return stored value as original with changes in interface{}
fmt.Printf("%#v %d\n",ti, ti.a)
}
這個樣本不好。 最佳樣本可包括界面字段:
type t1 struct{
original i
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.