Golang通过内部类型或者嵌入类型embedding的方式来实现一种代码的复用以及重定义,类似与Java中的继承和重载。
这种内部类有时候也被称为匿名属性(anonymous field,形式上,与其他属性相比,就是没有名字~),这里统一用“内部类型”来指代。
内部类的方式,可以让外部类直接访问和使用内部类中的属性和方法(只要这些语法上是允许访问的!)
我们结合代码来看看内部类型的用法。
1.同package下内部类
package main
import (
"fmt"
)
type user struct {
name string
email string
}
func (u *user) notify() {
fmt.Printf("Send email to %s@<%s>\n", u.name, u.email)
}
type admin struct {
user //设置内部类型
level string //什么外部类的属性
}
func main() {
u1 := admin{
user: user{
"haha",
"haha@evertrain.cn",
},
//name: "lala",
level: "high",
}
u1.user.notify() //既可以通过内部类型调用方法。内部类的属性和方法始终可以通过内部类名进行访问
u1.notify() //也可以直接通过外部类的对象访问
fmt.Println(u1.user.name)
fmt.Println(u1.name)
}
输出是
Send email to haha@<haha@evertrain.cn>
Send email to haha@<haha@evertrain.cn>
haha
haha
2.覆盖内部类的属性和方法
如果在外部类中重新定义name和notify方法,如下:
...同上部分省略...
type admin struct {
user //设置内部类型
name string //外部类新定义
level string //什么外部类的属性
}
func (a *admin) notify() {
fmt.Printf("Send admin email to %s@<%s>\n", a.name, a.email)
}
func main() {
u1 := admin{
user: user{
"haha",
"haha@evertrain.cn",
},
name: "lala",
level: "high",
}
u1.user.notify() //既可以通过内部类型调用方法。内部类的属性和方法始终可以通过内部类名进行访问
u1.notify() //也可以直接通过外部类的对象访问
fmt.Println(u1.user.name)
fmt.Println(u1.name)
}
则输出
Send email to haha@<haha@evertrain.cn>
Send admin email to lala@<haha@evertrain.cn>
haha
lala
3.使用跨package的内部类
在考虑跨package的情况,将user定义放到package user中去,由于小写开头的user变成了user包的私有类,外部包无法访问,将其变成公开类User,其余不变(属性和方法仍然为私有)。
admin定义如下:
import (
"fmt"
u "./user"
)
type admin struct {
u.User //设置内部类型
name string //外部类新定义
level string //什么外部类的属性
}
func (a *admin) notify() {
fmt.Printf("Send admin email to %s\n", a.name)
}
func main() {
u1 := admin{
User: u.User{
"haha",
"haha@evertrain.cn",
},
name: "lala",
level: "high",
}
//u1.User.notify() //无法访问其私有方法
u1.notify() //也可以直接通过外部类的对象访问
//fmt.Println(u1.User.name) //无法访问其私有属性
fmt.Println(u1.name)
}
其中u1创建的时候就报错了,类似如下:
implicit assignment of unexported field 'name' in user.User literal
无法通过这种形式来指定赋值私有变量。为User类新加一个公开的NewUser方法
func NewUser(name string, email string) *User {
return &User{
name,
email,
}
}
然后admin的创建改成:
func main() {
u1 := admin{
User: *u.NewUser("haha", "haha@evertrain.cn"),
name: "lala",
level: "high",
}
//u1.User.notify() //无法访问其私有方法
u1.notify() //也可以直接通过外部类的对象访问
//fmt.Println(u1.User.name) //无法访问其私有属性
fmt.Println(u1.name)
}
则输出变成了
Send admin email to lala
lala
可以看到,跨package后私有属性、方法都不再可见,需要将应该公开的属性和方法改成公开才能达到复用的效果。
4.指针Point形式的内部类型与值Value形式的内部类型的差别
admin还可以用这样的方式定义:
type admin struct {
*u.User //设置内部类型,指针类型
name string //外部类新定义
level string //什么外部类的属性
}
则稍作修改后,返回同样的结果
u1 := admin{
User: u.NewUser("haha", "haha@evertrain.cn"), //保持指针
name: "lala",
level: "high",
}
指针类型的内部类型,保留了指针Pointer的特点,初始化不指定的话,就是nil,所以必须明确的指定。
Pointer形式下,外部类访问内部类的属性和方法的方式与Value一致。不同的是,Pointer形式下,是根据指针操作,操作的是对象本身。而如果是Value形式的,例如赋值就是变成一份拷贝,再更新属性和原对象就已经无关了。示例如下:
package main
import (
"fmt"
)
type user struct {
name string
email string
}
func (u *user) setName(n string) {
u.name = n
}
type admin struct {
user //Value形式内部类
level string
}
type operator struct {
*user //Pointer形式内部类
level string
}
func main() {
u1 := user{"haha1","haha1@evertrain.cn"}
u2 := user{"haha2","haha2@evertrain.cn"}
a1 := admin{
user: u1,
level: "high",
}
o1 := operator{
user: &u2,
level: "middle",
}
a1.setName("new_haha1")
o1.setName("new_haha2")
fmt.Println(a1.user.name)
fmt.Println(a1.name)
fmt.Println(u1.name) //此处无变化,因为a1中的user是u1的一份拷贝,其上的更新不会作用到u1
fmt.Println(o1.user.name)
fmt.Println(o1.name)
fmt.Println(u2.name)
}
输出:
new_haha1
new_haha1
haha1
new_haha2
new_haha2
new_haha2
参考
- https://groups.google.com/forum/#!topic/golang-nuts/ctRUq62cgME
- 《Manning Go in Action》