Go In Action --- 类型的本质、接口、多态、嵌入类型、标识符

类型

在声明一个新类型之后,声明一个该类型的方法之前,需先确定:这个类型的本质是什么?如果给这个类型增加或删除某个值,是要创建一个新值,还是要更改当前的值?如果要新值,就选择值接收者;如果要修改当前值,就选择指针接收者。这决定了 程序内部传递这个类型的值的方式:是按值做传递,还是按指针做传递。

1. 内置类型

包含数值类型、字符串类型和布尔类型
这些类型本质上都是原始类型,在操作其值时,应该传递其对应的值的副本。

2. 引用类型

包含切片、映射、通道、接口和函数类型
每个引用类型创建的标头值(引用类型创建的变量),都包含一个指向底层数据结构的指针。
所以通过复制来传递一个引用类型的值的副本,本质上就是在共享底层数据结构

3. 结构类型

待补充
结构类型的本质既可以是原始的,也可以是非原始的。

4. 看一个标准库中的接口 及 实现例子

4.1 io.Writer接口

// Writer is the interface that wraps the basic Write method.
//
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// Write must return a non-nil error if it returns n < len(p).
// Write must not modify the slice data, even temporarily.
//
// Implementations must not retain p.
type Writer interface {
    Write(p []byte) (n int, err error)
}

4.2 bytes.Buffer实现了该接口

// Write appends the contents of p to the buffer, growing the buffer as
// needed. The return value n is the length of p; err is always nil. If the
// buffer becomes too large, Write will panic with ErrTooLarge.
func (b *Buffer) Write(p []byte) (n int, err error) {
    b.lastRead = opInvalid
    m := b.grow(len(p))
    return copy(b.buf[m:], p), nil
}

4.3 接口实现多态

当方法的接收参数是某个接口时,可以将不同的实现该接口的值作为参数传递给方法,从而实现采取不同行为的能力。
方法fmt.Fprintf的第一个参数是io.Writer接口

// Fprintf formats according to a format specifier and writes to w.
// It returns the number of bytes written and any write error encountered.
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
    p := newPrinter()
    p.doPrintf(format, a)
    n, err = w.Write(p.buf)
    p.free()
    return
}

方法io.Copy的第一个参数也是io.Writer接口

func Copy(dst Writer, src Reader) (written int64, err error) {
    return copyBuffer(dst, src, nil)
}

4.4 简单的例子展示io多态

func ByteBuffer()  {
    var b bytes.Buffer

    //将字符串写入Buffer
    b.Write([]byte("Hello go"))

    //使用Fprintf将字符串拼接到Buffer
    fmt.Fprintf(&b, ", nice to meet you")

    //将Buffer的内容写到Stdout
    io.Copy(os.Stdout, &b)
}

最终输出结果:
Hello go, nice to meet you

5. Go语言的接口规则

因为涉及到指针,规则相比Java来说略微复杂一些。

Go语言的方法有接收者这一说词,从Java的角度来看,就是方法的调用者。在Java中,调用者就是调用者。但是Go语言中,分值接收者指针接收者。但规则不外乎如下两种:

  1. 如果一个接口的实现类型,用指针接收者来作为方法的接收者(其实就是Java的调用者),那么只能用该类型的指针对象来调用该接口。

  2. 如果一个接口的实现类型,用值接收者来作为方法的接收者,那么既可以用指针对象也可以用值的副本来调用该接口。

以下是Go语言给出的方法集规则:

Values Methods Receivers remark
T (t T) 如2
*T (t T) and (t *T) 如1
Methods Receivers Values remark
(t T) T and *T 如2
(t *T) *T 如1

5. 多态

如下实现了一个简单的多态:

//person接口
type personInter interface {
    sayName()
}

//用户
type user struct {
    name string
}
func (u *user) sayName()  {
    fmt.Printf("User name is %s \n", u.name)
}

//管理员
type admin struct {
    name string
}
func (u *admin) sayName()  {
    fmt.Printf("Admin name is %s \n", u.name)
}

func Test()  {
    logger := user{"logger"}
    //letsSayYourName(logger)  //这是错误调用,不能用值的副本logger去调用接收者为指针接收者的实现方法
    letsSayYourName(&logger)

    admin := admin{"admin"}
    letsSayYourName(&admin)
}

//方法入参为接口,只要实现了该接口的实现类型都可作为入参
func letsSayYourName(person personInter)  {
    person.sayName()
}

最终输出:
User name is logger
Admin name is admin

6. 嵌入类型

先介绍一下Go语言所谓的嵌入类型:
嵌入类型是将已有的类型直接声明在新的结构类型中,被嵌入的类型被称为新的外部类型的内部类型。
个人理解Go语言中的嵌入类型类似于Java中的组合

6.1 示例1 -- 外部类型调用内嵌类型的方法

type animal struct {
    name string
}

func (u *animal) sayName() {
    fmt.Printf("name is %s \n", u.name)
}

//管理员类型,内嵌一个user类型
type person struct {
    animal  //嵌入类型
    name string
}

func TestInnerType() {
    ad := person{animal{"logger"}, "admin"}
    ad.animal.sayName()
    ad.sayName()
}

输出:
name is logger
name is logger

小结:

  • 可以将内嵌类型的方法提升到外部类型
  • ad.sayName()调用内嵌类型的方法输出的还是内嵌类型的属性

6.2 示例2 -- 外部类型调用内嵌类型的实现接口

//接口
type foodInter interface {
    sayFoodName()
}

//苹果
type apple struct {
    name string
}

//超市
type market struct {
    apple  //内嵌类型 -- 苹果
}

func (a *apple) sayFoodName()  {
    fmt.Printf("food name is %s \n", a.name)
}

func TestInnerTypeInterface()  {
    walmart := market{ apple{"ApplyX"}}

    //重点是这句
    //用于实现接口的内部类型的方法,被提升到了外部类型
    commonSayName(&walmart)
}

func commonSayName(food foodInter)  {
    food.sayFoodName()
}

输出:
food name is ApplyX

小结:

  • 由于内部类型的提升,内部类型实现的接口会自动提升到外部类型。这意味着由于内部类型的实现,外部类型也同样实现了这个接口。

6.3 示例3 -- 外部类型覆盖内嵌类型的实现接口

相比于示例2,增加了类型market自己的实现方法以此覆盖内嵌类型的实现接口,并在类型market中添加了属性marketName以便输出展示。

//接口
type foodInter interface {
    sayFoodName()
}

//苹果
type apple struct {
    name string
}

//超市
type market struct {
    marketName string
    apple  //内嵌类型 -- 苹果
}

func (a *apple) sayFoodName()  {
    fmt.Printf("food name is %s \n", a.name)
}

func (m *market) sayFoodName()  {
    fmt.Printf("market name is %s \n", m.marketName)
}

func TestInnerTypeInterface()  {
    walmart := market{ "walmart", apple{"ApplyX"}}

    //重点是这句
    //用于实现接口的内部类型的方法,被提升到了外部类型
    commonSayName(&walmart)
}

func commonSayName(food foodInter)  {
    food.sayFoodName()
}

输出:
market name is walmart

小结:

  • 由于内部类型的提升,内部类型实现的接口会自动提升到外部类型。这意味着由于内部类型的实现,外部类型也同样实现了这个接口。

7. 公开或未公开的标识符

类似于Java中的public、private等,你可以控制方法或变量的访问权限。Go语言当然也提供了这种功能。
Go语言支持从包里公开或隐藏标识符。如果一个标识符,如下示例中的counter以小写字母开头,即包外代码不可见(类似Java的private),如果一个标识符,如下示例中的PublicCounter以大写字母开头,这个标识符就是公开的,即包外代码可见(类似Java的public)。

7.1 原始类型

//声明一个未公开的计数器
type counter int

//声明一个公开的计数器
type PublicCounter int

当然,我们也可以给未公开的标识符,如counter写一个公开的取值函数,或者写一个工厂函数新建一个counter

//取值函数
func GetCounter(value int) counter {
  return counter
}

//工厂函数
func New(value int) counter {
  return counter(value)
}

工厂函数命名为New只是Go语言中的一个习惯。

7.2 结构类型中的未公开字段

结构类型中带有未公开类型的字段

//公开的用户类型
type Admin struct {
    Name string    //公开的Name字段
    email string    //未公开的email字段
}

如果在其它包中创建该类型,并赋值email字段,在定义阶段是不会报错的,如下不会直接报错(identifier是包名):

ad := identifier.Admin{"admin", "admin@email.com"}

但是在运行的时候,会报错,错误如下:

# command-line-arguments
.\main.go:43: implicit assignment of unexported field 'email' in identifier.Admin literal

7.3 访问未公开的内嵌对象中的公开字段

identifier包中定义如下结构类型,apple是未公开的结构类型,但是其Name字段是公开的。

type apple struct {
    Name string
}

//公开类型的Tree对象
type Tree struct {
    TreeName string //公开类型的TreeName
    apple           //未公开的apple类型
}

在其它包中,可以利用内部对象属性升级为外部对象的特性,为tree的未公开内嵌类型apple的公开字段Name设值。

tree := identifier.Tree{TreeName: "MoneyTree"}
tree.Name = "apply"
fmt.Printf("%v \n", tree)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,505评论 6 13
  • 忘了有多久,没有抬起过头去仰望天空。所以,才会惊㤉的发现,盛夏的夜里,竟然没有星星。 似乎,永远都...
    掬水浅吟阅读 233评论 0 2
  • 我决定了,今天要讲的是我亲亲亲爱的大表姐———刘雯。 首先附送麻豆工作照。 以及大表姐拍过的一丢丢广告大片。 可能...
    筱帅GG阅读 238评论 0 2