直接上代码:
首先,我定了三个接口、一个结构和三个方法:
type DeptModeFull interface {
Name() string
SetName(name string)
Relocate(building string, floor uint8)
}
type DeptModeA interface {
Name() string
SetName(name string)
}
type DeptModeB interface {
Relocate(building string, floor uint8)
}
type Dept struct {
name string
building string
floor uint8
Key string
}
func (self Dept) Name() string {
return self.name
}
func (self Dept) SetName(name string) {
self.name = name
}
func (self *Dept) Relocate(building string, floor uint8) {
self.building = building
self.floor = floor
}
而后我写了一些测试代码:
dept1 :=
Dept{
name: "MySohu",
building: "Internet",
floor: 7}
switch v := interface{}(dept1).(type) {
case DeptModeFull:
fmt.Printf("The dept1 is a DeptModeFull.\n")
case DeptModeB:
fmt.Printf("The dept1 is a DeptModeB.\n")
case DeptModeA:
fmt.Printf("The dept1 is a DeptModeA.\n")
default:
fmt.Printf("The type of dept1 is %v\n", v)
}
deptPtr1 := &dept1
if _, ok := interface{}(deptPtr1).(DeptModeFull); ok {
fmt.Printf("The deptPtr1 is a DeptModeFull.\n")
}
if _, ok := interface{}(deptPtr1).(DeptModeA); ok {
fmt.Printf("The deptPtr1 is a DeptModeA.\n")
}
if _, ok := interface{}(deptPtr1).(DeptModeB); ok {
fmt.Printf("The deptPtr1 is a DeptModeB.\n")
}
打印出的内容:
The dept1 is a DeptModeA.
The deptPtr1 is a DeptModeFull.
The deptPtr1 is a DeptModeA.
The deptPtr1 is a DeptModeB.
我的问题:
为什么Dept的实例的指针被判定为全部三个接口的实现,而Dept的实例只是DeptModeA接口的实现?
哪位大牛给讲讲?
回答1:
假设T是struct,那么Go里面遵循下面几个原则:
- T的方法集仅拥有 T Receiver 方法。
- *T 方法集则包含全部方法 (T + *T)。
所以你上面的例子dept1应该是拥有方法:Name和SetName
而&dept1拥有方法:Name、SetName和Relocate
这个就是Go里面在设计方法的时候需要注意Receiver的类型
回答2:
谢谢以上几位高手的解答,我又仔细看了下官方规范文档(http://tip.golang.org/ref/spec)上的相关内容,就上面的例子我再自我总结一下(包含了上面几位提供的答案):
1、结构Dept
的方法集中仅包含方法接收者为Dept的方法,即:Name()
和SetName()
)。所以,结构Dept的实例仅为DeptModeA
的实现。
2、结构的指针*Dept
的方法集包含了方法接受者为Dept
和*Dept
的方法,即:Name()
、SetName()
和Relocate()
。所以,接口Dept的实例的指针为全部三个接口——DeptModeFull
、DeptModeA
和DeptModeB
的实现。
但是我觉得上述设计和原则与方法调用的设计有些不一致,请看下面的代码:
dept1.Relocate("Media", 12)
fmt.Printf("Dept: %v\n", dept1)
fmt.Printf("Dept name: %v\n", deptPtr1.Name())
打印的内容:
Dept: {MySohu Media 12 }
Dept name: MySohu
这说明:结构Dept
的实例可以调用其指针方法集中的方法。
我觉得设计不一致的地方是:既然结构的访法集不包含接收者为其指针的方法,那又为什么让在结构的实例上可以调用其指针访法集中的方法呢?反过来看,既然结构的实例可以调用其接口访法集上的方法,那为什么不让结构的方法集直接包含这些方法呢?
不过,不管怎样,这引出了第三条规则(官方规范的意译):
3、如果结构的实例x
是“可被寻址的”,且&x
的方法集中包含方法m
,则x.m()
为(&x).m()
的速记(快捷方式)。
所以其实
dept1.Relocate("Media", 12)
被自动转化成了?
(&dept1).Relocate("Media", 12)
学习了。
可寻址的意思大概就是已经实例话了。如果你直接
var deptPtr2 *Dept
deptPtr2.Relocate("Media", 12)
就不行。反之:
var deptPtr2 Dept
deptPtr2.Relocate("Media", 12)
就可以。区别就在这里了。
另外一个例子
type Reader interface {
read()
}
type Writer interface {
write()
}
//定义上述两个接口的实现类
type MyReadWrite struct{}
func (mrw *MyReadWrite) read() {
fmt.Println("MyReadWrite...read")
}
func (mrw *MyReadWrite) write() {
fmt.Println("MyReadWrite...write")
}
//定义一个接口,组合了上述两个接口
type ReadWriter interface {
Reader
Writer
}
//上述接口等价于:
type ReadWriterV2 interface {
read()
write()
}
//ReadWriter和ReadWriterV2两个接口是等效的,因此可以相互赋值
func interfaceTest0104() {
mrw := MyReadWrite{}
mrw.read()
mrw.write()
//mrw对象实现了read()方法和write()方法,因此可以赋值给ReadWriter和ReadWriterV2
var rw1 ReadWriter = mrw
rw1.read()
rw1.write()
fmt.Println("------")
var rw2 ReadWriterV2 = mrw
rw2.read()
rw2.write()
//同时,ReadWriter和ReadWriterV2两个接口对象可以相互赋值
rw1 = rw2
rw2 = rw1
}
func main(){
interfaceTest0104()
}
标红的两行会报错
main\interface.go:43:6: cannot use mrw (type MyReadWrite) as type ReadWriter in assignment:
MyReadWrite does not implement ReadWriter (read method has pointer receiver)
main\interface.go:48:6: cannot use mrw (type MyReadWrite) as type ReadWriterV2 in assignment:
MyReadWrite does not implement ReadWriterV2 (read method has pointer receiver)
因为ReadWriter 和ReadWriterV2 的实现都是接受的指针参数,就是*T receiver,而mrw只有T receiver,并没有实现参数为指针的方法(这里的mrw可能没有实现任何方法,mrw.read()能成功是因为上面提到的第三点),所以无法赋值
改成 mrw := &MyReadWrite{} 就不会出错