2018年4月19日星期四

golang struct中内部类型embedding Type用法

    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》

没有评论:

发表评论