GO语言实战——第五章

GO语言实战——第五章

Go语言的类型系统

  • 用户定义的类型

GO语言里声明用户定义的类型有两种方法。最常用的方法就是使用关键词struct,它可以让用户创建一个结构类型。

//user在程序里定义一个用户类型
type user struct{
    name string
    email string
    exit int
    privileeged bool
}

//声明user类型的变量
var bill user

使用结构字面量来声明一个结构类型的变量

lisa := user{
    name: "Lisa",
    email: "lisa@emali.com",
    ext: 123,
    privilege: true,
}

不使用字段名,创建结构类型的值,这种形式下,值的顺序很重要,必须要和结构声明中字段的顺序一致。

lisa := user{"Lisa","lisa@email.com",123,true}

当声明结构类型时,字段的类型并不限制在内置类型,也可以使用其他用户定义的类型。

//admin 需要一个user类型作为管理者,并附加权限
type admin struct{
    person user
    level string
}

//这种的结构类型声明
fred := admin{
    person: user{
        name: "Lisa",
        email: "Lisa@email.com",
        ext: 123,
        privileged: true,
    },
    level: "super"
}

基于一个已有的类型,将其作为新类型的类型说明。

type Duration int64

这种类型,GO并不认为Duration和int64是同一种类型。两种类型的值即便互相兼容,也不能互相赋值。

  • 方法

方法能够给用户定义的类型添加新的行为。方法实际上也是函数,只是在声明时,在关键词func和方法名之间增加了一个参数

关键词func和函数名之间参数被称为"接收者",如果一个函数有接收者,这个函数就被称为方法。

package main

import "fmt"

type user struct {
	name string
	email string
}

//notify使用值接收者实现一个方法
func (u user)notify(){
	fmt.Printf("Sending Useer Email To %s<%s>\n",u.name,u.email)
}

//changeEmaill使用柱子很接收者实现一个方法
func (u *user)changeEmail(email string){
	u.email = email
}

//main
func main(){
	//user类型的值可以用来调用
	//使用值接收者声明的方法
	bill := user{"Bill","bill@email.com"}
	bill.notify()

	//指向user类型值的指针也可以用来调用
	//使用值接收者声明的方法
	lisa := &user{"Lisa","Lisa@email.com"}
	lisa.notify()

	//user类型的值可以用来调用
	//使用指针接收者声明的方法
	bill.changeEmail("bill@newdomain.com")
	bill.notify()

	//指向 user 类型值的指针可以用来调用
	// 使用指针接收者声明的方法
	lisa.changeEmail("lisa@newdomain.com")
	lisa.notify()
}

如果使用值接收者声明方法,调用时就会使用这个值的一个副本来执行。

值接收者使用值的副本来调用方法,而指针接受者使用实际值来调用方法。

GO语言即允许使用值,也允许使用指针来调用方法,不必严格符合接收者的类型。

  • 类型的本质

如果要创建一个新值,那就使用值接收者。如果是要修改当前值,那就使用指针接收者。不要关注某个方法是如何处理这个值的,而是要去关注这个值得到本质是什么。

  • 内置类型

当对这些值进行增加或者删除的时候,会创建一个新值。基于这个结论,当把这些类型的值传递给方法或者函数时,应该传递一个对应值的副本。

  • 引用类型

GO语言的引用类型有:切片、映射(map)、通道、接口和函数类型。

  • 结构类型

结构类型可以用来描述一组数据值,这组值的本质即可以是原始的,也可以是非原始的。

struct

是使用值接收者还是指针接收者,不应该由该方法是否修改了接收到的值来决定。这个决策
应该基于该类型的本质。这条规则的一个例外是,需要让类型值符合某个接口的时候,即便类型
的本质是非原始本质的,也可以选择使用值接收者声明方法。这样做完全符合接口值调用方法的
机制。

  • 接口

多态是指代码可以根据类型的具体实现采取不同行为的能力。

package main

import "fmt"

//notifier是定义 通知类行为的接口
type notifieer interface {
	notify()
}

//user在程序里定义为一个用户的类型
type user struct{
	name string
	email string
}

//notify是使用指针接收者实现的方法
func (u *user)notify(){
	fmt.Printf("Sending user emaill to %s<%s>\n",u.name,u.email)
}

//main
func main(){
	u := user{"Bill","bill@email.com"}
	sedNotification(&u)
}
//接受一个实现notifier接口的值并且发送通知
func sedNotification(n notifieer){
	n.notify()
}

可以包含0个或多个方法的签名

只定义方法的签名,不包含实现

实现接口不需要显式的声明,只需实现相应方法即可

(在 go 中实现接口很简单,不需要显式的声明实现了某个接口,想要实现某个接口,只需要实现接口中的所有方法即可。)

  • 多态

实现接口的不同行为。

//这个示例程序使用接口展示多态行为
package main
import(
	"fmt"
)
//notifiere是定义了一个通知类行为的接口
type notifier interface{
	notify()
}
//user在程序了里定义一个用户类型
type user1 struct{
	name string
	email string
}
//notify使用指针接收者实现了notifier接口
func (u *user1) notify(){
	fmt.Printf("Sending user eemail to %s<%s>\n",u.name,u.email)
}
//admin定义了程序里面的管理员
type admin struct{
	name string
	email string
}
//notify使用指针接收者实现了notifier接口
func (a *admin)notify(){
	fmt.Printf("Sending admin email to %s<%s>\n",a.name,a.email)
}
//sendNotification接收一个实现notifier接口的值并发送通知
func sendNotification(n notifier){
	n.notify()
}
//main
func main(){
	//创建一个user值并传给sendNotification
	bill := user1{"Bill","bill@email.com"}
	sendNotification(&bill)

	//创建一个admin值并传给sedNotification
	lisa := admin{"Lisa","lisa@email.com"}
	sendNotification(&lisa)
}

Output:
Sending user eemail to Bill<bill@email.com>
Sending admin email to Lisa<lisa@email.com>
  • 嵌入类型

Go 语言允许用户扩展或者修改已有类型的行为。

嵌入类型是将已有的类型直接声明在新的结构类型里。被嵌入的类型被称为新的外部类型的内部类型。

外部类型也可以通过声明与内部类型标识符同名的标识符来覆盖内部标识符的字段或者方法。这就是扩展或者修改已有类型的方法。

package main
//这个示例程序展示如何将一个类型嵌入了另一个类型,以及内部类型和外部类型之间的关系
import "fmt"

//type user
type user struct{
	name string
	email string
}

//notify 实现了一个可以通过user类型值的指针调用的方法
func (u *user)notify(){
	fmt.Printf("Sending user email to %s<%s>\n",u.name,u.email)
}
//admin代表一个拥有权限的管理员用户
type admin struct {
	user //嵌入类型
	level string
}

//main
func main(){
	ad := admin{
		user: user{
			name: "john smith",
			email: "john@yahoo,com",
		},
		level: "super",
	}
	//直接访问内部类型的方法
	ad.user.notify()

	//内部类型的方法也被提升到外部类型
	ad.notify()
}

Output:
Sending user email to john smith<john@yahoo,com>
Sending user email to john smith<john@yahoo,com>

要嵌入一个类型,只需要声明这个类型的名字就可以了。

嵌入类型直接声明类型名就可以。

(由于内部类型的提升,内部类型实现的接口会自动提升到外部类型。这意味着由于内部类型的实现,外部类型也同样实现了这个接口。)即外部类也可以实现内部类的接口并调用实现的方法。

如果外部类型不需要使用内部类型的实现,而想用自己的一套实现:

package main

import "fmt"

//这个示例战士了当内部类型和外部类型要实现同一个接口时的方法

//notifier定义了一个通知类行为的接口
type notifier interface{
	notify()
}

//user类型
type user struct{
	name string
	email string
}

//通过user类型指针调用的方法
func (u *user)notify(){
	fmt.Printf("Sending user email to %s<%s>\n",u.name,u.email)
}

//admin类型
type admin struct {
	user
	level string
}

//通过admin类型 的指针调用方法
func (a *admin)notify(){
	fmt.Printf("Sending admin email to %s<$s>\n",a.name,a.email)
}
func sendNotification(n notifier){
	n.notify()
}
//main
func main(){
	ad := admin{
		user: user{
			name:"john smith",
			email:"john@yahoo.com",
		},
		level: "supeer",
	}
	//给admin用户发送一个通知接口的嵌入的内部类型实现没有提升到外部类型
    //这里实现了admin指针传入的方法而不是user指针
    sendNotification(&ad) //output: sending admin email ...

	//直接访问内部类型的方法
    ad.user.notify()  //output: sending user email ...

	//内部类型的方法没有提升
    ad.notify()      //output: sending admin email ...

}

这表明,如果外部类型实现了 notify 方法,内部类型的实现就不会被提升。

不过也可以通过直接访问内部类型的值,来调用没有被提升的内部类型实现的方法。

  • 公开或未公开的标识符(变量名、方法名、函数名、接口名)

“当一个标识符的名字以小写字母开头时,这个标识符就是未公开的,即包外的代码不可见。”

package counters
type alterCounter int   //这里的alterCounter是一个未公开的类型

如果一个标识符以大写字母开头,这个标识符就是公开的,可以被包外的代码可见。

永远不能显式创建一个未公开的类型的变量,不过短变量声明操作符可以这么做。

在公开的结构体中小写的字段也是未公开的:

entities/entities.go
-----------------------------------------------------------------------
01 // entities 包包含系统中
02 // 与人有关的类型
03 package entities
04
05 // User 在程序里定义一个用户类型
06 type User struct {
07  Name string
08  email string
09 }
listing71.go
-----------------------------------------------------------------------
01 // 这个示例程序展示公开的结构类型中未公开的字段
02 // 无法直接访问
03 package main
04
05 import (
06  "fmt"
07
08  "github.com/goinaction/code/chapter5/listing71/entities"
09 )
10
11 // main 是应用程序的入口
12 func main() {
13  // 创建 entities 包中的 User 类型的值
14  u := entities.User{
15  Name: "Bill",
16  email: "bill@email.com",
17  }
18
19  // ./example69.go:16: 结构字面量中结构 entities.User
20  // 的字段’email’未知
21
22  fmt.Printf("User: %v\n", u)
23 }

User 类型里声明了两个字段,一个名为 Name 的公开的字段,一个名为 email 的未公开的字段。

当结构体是未公开时,但是内部字段是公开的,可以嵌入到公开的机构体中进行外部调用。

即便内部类型是未公开的,内部类型里声明的字段依旧是公开的。既然内部类型的标识符提升到了外部类型,这些公开的字段也可以通过外部类型的字段的值来访问。

  • 小结

使用关键词struct或者通过指定已经存在的类型,可以声明用户定义的类型。

方法提供了一种用户定义的类型增加行为的方式。

设计类型时需要确认类型的本质是原始的,还是非原始的。

接口是声明了一组行为并支持多态的类型。

嵌入类型提供了扩展类型的能力,而无需使用继承。

标识符要么是从包里公开的,要么是在包里未公开的。

posted @ 2021-04-10 16:25  Gumi-21  阅读(96)  评论(0)    收藏  举报