我有一个试图在"子类"上实现功能的程序,其中父级可以检查接口是否已实现。对于透视图,它基于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()
}
答案 0 :(得分:9)
您似乎缺少的一件事是嵌入接口如何影响Go中的结构。请参阅嵌入促进嵌入类型的所有方法(结构或接口,无关紧要)是父类型的方法,但使用嵌入对象作为接收器调用。
这样做的实际副作用是将接口嵌入到结构中可以保证该结构满足其嵌入的接口,因为它根据定义具有该接口的所有方法。尝试调用任何这些方法而不定义某些东西来填充结构中的那个接口字段,但是,恐慌,因为该接口字段默认为nil
。
因此,您的类型断言始终为真。 BaseAppController
嵌入了IGet
和IList
接口,因此始终满足这两个接口。
如果您想要鸭子类型,根据类型上方法的存在与否选择性地启用行为,您需要使用类似于标准库io.WriterTo
interface的工作方式。此接口和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
}
现在,您可以在结构的sql.DB
位置存储sql.Tx
或dber
,使用DBHandle
存储任何内容(以及{{}的所有方法1}}本身)可以在DBHandle
上调用Query()
,QueryRow()
和Exec()
,而无需知道它们是否在交易范围内被调用(但请记住,必须首先初始化接口字段!)
这种类型的功能是嵌入真正开始闪耀的地方,因为它允许功能和灵活性接近完全多态继承系统,而不需要显式的“implements”或“extends”语句。对于你想要的动态鸭子类型行为,它并没有什么用处。
答案 1 :(得分:3)
请勿将界面嵌入与 struct embedding 混合使用。
如果在结构上嵌入接口,实际上是在接口名称中添加新字段,所以如果不进行初始化,则会因为它们为零而感到恐慌。
您的BaseAppController
希望有人在IGet
和IList
字段中填写分别满足IGet
和IList
界面的内容。
这就是你的BaseAppController
结构体的真实含义:
type BaseAppController struct {
Application *Application
IGet IGet
IList IList
}
您似乎正在尝试在Go中进行Java风格的编程,但这并不能很好地结束。
答案 2 :(得分:1)
将其命名为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
答案 3 :(得分:1)
如果你想继承任何带有基本结构的结构,你可以编写这个(丑陋的)代码,它就像一个反射包:
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
}