接口与反射
go 中不需要声明 接口 与相关赋值接口和结构体的关系
只要调用了对应的接口里面所有的方法,我们就认为实现了接口
面向对象:
-
封装:将属性隐藏(结构体属性),提供相关接口供调用者访问和修改,调用的人不用关心我内部实现。
- 比如
User{}
结构体里面,我们在设置 age 的时候,不可能说超过 200 岁,所以这个时候我们就要给他加一个SetAge
方法
// 伪代码 User { name string age int } func (u *User) SetAge(age int) { if age < 200 && age > 0 { u.age = age } else { fmt.Println("age 输入错误") } }
- 比如
-
继承:为了解决代码复用性问题,通过组合
- 匿名组合 和 命名组合
-
多态:同一个对象在不同的条件下有不同的行为。
- 通过接口来实现的
// 伪代码 // 定义 sender 变量,类型是个 Sender 接口,赋值 EmailSender 结构体 // 假如赋值为一个 dingding 结构体,那他就是 dingding 的行为 var sender Sender = EmailSender sender.Send("xxx")
-
可见性:go 里面的代码分为包内和包外
-
接口名称:
-
大写:包外可见(公有)
-
小写:包内可见(私有)
-
方法名称一般大写。
-
接口时自定义类型,是对其他类型的方法的抽象
为什么会使用到接口:
usb插槽就是现实中的接口。
你可以把手机,相机, u盘都插在usb插槽上,而不用担心那个插槽是专门插哪个的,原因是做usb插槽的厂家和做各种设备的厂家都遵守了统一的规定包括尺寸,排线等等。
因为代码要通用所以需要接口
接口:
接口:
方法 => 行为
两个对象,之间是有相同行为的,或者说是有相同方法的
方法 => 集合 => 接口
io.Copy(dst , src)
dst = hash.Hash
dst = os.File
dst 接口 赋值的对象需要满足(实现 dst 接口) dst 接口的所有方法
接口的默认类型为 nil ,是指针类型
1 接口的使用
接口定义:
type 接口名 interface {
// 在接口里面方法可以是多个的
定义方法名1(参数列表)返回值列表
定义方法名2(参数列表)返回值列表
...
}
/* 实现接口,所谓实现有两层含义。
1.把方法具体的类容写出来
2.把所有方法写出来(不能只写接口中单独一个或多个方法)
3.接口变量声明后,类型为 nil ,值为 nil
4.接口类型不能实例化(不能直接通过接口类型创建变量)
5.只能由其他实现了接口的对象进行赋值
*/
实现接口:
func (t 自定义类型) 方法名1 (参数列表) 返回值列表 {
// 方法实现
}
func (t 自定义类型) 方法名2 (参数列表) 返回值列表 {
// 方法实现
}
// 对象有接口中声明的所有方法。
// func 中的 参数列表 和 返回值列表 要和在 interface 中定义的一样
注意:
对象是值还是指针,需要看他的方法接收者
1.如果是值的话,方法接收者必须是值类型
2.如果是指针类型的话,可以是值类型,也可以是指针类型
总结:
-
接口里所有的方法都没有方法体
-
接口方法都是没有实现方法,接口体现了程序设计的多态和高内聚低耦合的思想(高内聚:把所有代码写道函数里面去,低耦合:程序之间的耦合性降低了)
-
Golang 中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,在 Golang 中没有 implement 这样的关键字
1.1 定义
当自定义类型实现了接口类型中声明的所有函数时,则该类型的对象可以赋值给接口变量,并使用接口变量调用实现的方法
结构体能赋值给接口,接口不能赋值给结构体
接口的定义使用范例如下代码
package main
import "fmt"
// 定义了 witer 接口
type Writer interface {
// 在接口里面定义了 witer 方法
Writer([]byte) (int, error)
}
func main() {
// 赋值,这里将 Writer 接口赋值给了 writer 变量
var writer Writer
fmt.Printf("%T,%#v\n", writer, writer)
}
执行
[17:09:32 root@go codes]#go run interface.go
<nil>,<nil>
因为接口现在还没有进行初始化,所以接口现在没有实例化,必须通过赋值
而且这个赋值必须要满足这个接口他所定义的这个方法。包括形参和返回值
1.2 声明
声明接口变量值需要定义变量类型为接口名,此时变量被初始化为
满足接口的方法其实就是调用了接口,用短声明只是赋值变量成为了结构体,但是结构体里面的方法,满足了接口的方法,其实也是间接的调用了接口
1.2.1 结构体指针接收者实现接口
package main
import "fmt"
// 定义了 witer 接口
type Writer interface {
// 在接口里面定义了 witer 方法
Writer([]byte) (int, error)
}
type MyFile struct {
}
// 给 MyFile 接口提绑定 writer 方法但是没有实现接口
func (file *MyFile) Writer() {
}
type MyFile2 struct {
}
// 给 MyFile2 接口提绑定 writer 方法,新参和返回值与 writer 一样实现了方法
func (f *MyFile2) Writer(ctx []byte) (int, error) {
return 0, nil
}
func main() {
// 赋值,这里将 Writer 接口赋值给了 writer 变量
var writer Writer
var MyFile2Pointer = new(MyFile2)
writer = MyFile2Pointer
fmt.Printf("%T,%#v\n", writer, writer)
}
执行
[17:22:08 root@go codes]#go run interface.go
<nil>,<nil>
*main.MyFile2,&main.MyFile2{}
# 这里可以看到 writer 明明是接口类型但是由于 myfile2 方法满足了 writer 接口中的条件,所以 writer = MyFile2Pointer = *main.MyFile2,&main.MyFile2{}
但是我要是使用的是 MyFiel2 的值类型的话就会报错如下范例:
package main
import "fmt"
// 定义了 witer 接口
type Writer interface {
// 在接口里面定义了 witer 方法
Writer([]byte) (int, error)
}
type MyFile2 struct {
}
// 这里声明得 writer 方法是给 MyFile2 结构体的指针声明的,所以下面代码中的值类型并没有 writer 方法
func (f *MyFile2) Writer(ctx []byte) (int, error) {
return 0, nil
}
func main() {
// 赋值,这里将 Writer 接口赋值给了 writer 变量
var writer Writer
// 定义 myfile2 变量接收 MyFile2 的值
var myfile2 MyFile2
writer = myfile2
fmt.Printf("%T,%#v\n", writer, writer)
}
执行:直接报错
[17:39:13 root@go codes]#go run interface.go
# command-line-arguments
./interface.go:39:9: cannot use myfile2 (type MyFile2) as type Writer in assignment:
MyFile2 does not implement Writer (Writer method has pointer receiver)
在我们的 myfile2 值类型中没有实现该接口的方法。
因为我们的值类型是会单独开辟一块内存空间,而单独另外开辟的那块内存空间就没有 Writer
方法从而实现不了接口的调用
1.2.2 结构体值接收者实现接口
package main
import "fmt"
// 定义了 witer 接口
type Writer interface {
// 在接口里面定义了 witer 方法
Writer([]byte) (int, error)
}
type MyFile3 struct {
}
func (f MyFile3) Writer(data []byte) (int, error) {
return 0, nil
}
func main() {
// 赋值,这里将 Writer 接口赋值给了 writer 变量
var writer Writer
// 定义 myfile3 变量接收 MyFile3 的值
var myfile3 MyFile3
// 实现方法
writer = myfile3
fmt.Printf("%T,%#v\n", writer, writer)
}
执行
[17:33:12 root@go codes]#go run interface.go
main.MyFile3,main.MyFile3{}
因为在我们这个 MyFile3
结构体里面实现了 writer 接口的方法方法,所以能够实现接口,假如定义了一个 MyFile3 的指针类型也能够实现调用该接口
因为结构体值类型他会自动生成一个指针接收者的,如果是值类型当我们使用指针类型的时候其实是在对同一个内存空间进行操作
package main
import "fmt"
// 定义了 witer 接口
type Writer interface {
// 在接口里面定义了 witer 方法
Writer([]byte) (int, error)
}
type MyFile3 struct {
}
func (f MyFile3) Writer(data []byte) (int, error) {
return 0, nil
}
func main() {
// 赋值,这里将 Writer 接口赋值给了 writer 变量
var writer Writer
// 定义 myfile3 变量接收 MyFile3 的指针类型
var myfile3 = new(MyFile3)
writer = myfile3
fmt.Printf("%T,%#v\n", writer, writer)
}
执行
[17:46:32 root@go codes]#go run interface.go
*main.MyFile3,&main.MyFile3{}
1.3 赋值
当自定义类型实现了接口类型中声明的所有函数时,则该类型的对象可以赋值给接口变量,并使用接口变量调用实现的接口
比如在工作中我们需要定义业务告警功能,不仅有钉钉、微信、邮箱、短信 所以为了满足所有的告警媒介,我们不得不使用接口,从而减少代码的冗余,以后我们修改的话只需要修改接口的初始化即可。
从下面代码中我们可以看到,定义了 email 和 钉钉 两种告警媒介
package main
import "fmt"
// 定义 Email 结构体,并且定义 Send 方法
type EmailSender struct {
}
func (s *EmailSender) Send(msg string) error {
fmt.Println("email sender:" + msg)
return nil
}
func test(sender *EmailSender) {
sender.Send("yy")
}
// 定义 DingDing 结构体,并且定义 Send 方法
type DingDingSender struct {
}
func (d *DingDingSender) Send(msg string) error {
fmt.Println("dingding sender " + msg)
return nil
}
func test1(dingding *DingDingSender) {
dingding.Send("dingding")
}
func main() {
// 定义了 sender 变量赋值了 DingDingSender 结构体
// 假如我们想使用 email 的话,就需要重新赋值一个 Email 的结构体
sender := DingDingSender{}
sender.Send("xx")
test1(&sender)
}
执行
[11:20:00 root@go codes]#go run sender.go
dingding sender xx
dingding sender dingding
我们从上面代码中可以看到,其实 dingding 和 email 两个结构体中的 sender 方法完全类似,这样的代码就没有冗余,显得浪费,这时候我们就可以使用到我们的接口
如下代码:
package main
import "fmt"
// 定义 sender 接口
type Sender interface {
// 定义 Send(string) error 方法
Send(string) error
}
type EmailSender struct {
}
// 定义 Email 结构体中的 Send 方法,满足 Sender 接口
func (s *EmailSender) Send(msg string) error {
fmt.Println("email sender:" + msg)
return nil
}
// test 函数传承是一个 Sender 接口
func test(sender Sender) {
sender.Send("yy")
}
type DingDingSender struct {
}
func (d *DingDingSender) Send(msg string) error {
fmt.Println("dingding sender " + msg)
return nil
}
func test1(dingding Sender) {
dingding.Send("dingding")
}
func main() {
// 定义 send 接口变量,在赋值的时候赋值成了 EmailSender 结构体的 send 方法
// 以后如果想调用 dingding 的 Send 只需将 EmailSender 改为 DingDingSender 即可
var send Sender = new(EmailSender)
// 实现 send 接口的 Send 方法,调用的其实是 email 结构体的 send 方法
send.Send("email")
// 调用 test 函数,传入 email 结构体的 Send 方法
test(send)
}
执行
[11:38:18 root@go codes]#go run sender.go
email sender:email
email sender:yy
在赋值的时候将 EmailSender 改为 DingDingSender
package main
import "fmt"
// 定义 sender 接口
type Sender interface {
// 定义 Send(string) error 方法
Send(string) error
}
type EmailSender struct {
}
// 定义 Email 结构体中的 Send 方法,满足 Sender 接口
func (s *EmailSender) Send(msg string) error {
fmt.Println("email sender:" + msg)
return nil
}
// test 函数传承是一个 Sender 接口
func test(sender Sender) {
sender.Send("yy")
}
type DingDingSender struct {
}
func (d *DingDingSender) Send(msg string) error {
fmt.Println("dingding sender " + msg)
return nil
}
func test1(dingding *DingDingSender) {
dingding.Send("dingding")
}
func main() {
// 定义 send 接口变量,在赋值的时候改为了 DingDingSender 结构体
var send Sender = new(DingDingSender)
// 实现 send 接口的 Send 方法,调用的其实是 DingDingSender 结构体的 send 方法
send.Send("dingding")
// 调用 test 函数,传入 DingDingSender 结构体的 Send 方法
test(send)
}
执行
[11:43:49 root@go codes]#go run sender.go
dingding sender dingding
dingding sender yy
接口定义以后,用了一个结构体或者说自定义类型去赋值,这个自定义类型的对象需要实现满足接口的所有方法。我们用接口对象其实就是为了调用这些结构体或者说自定义类型的方法。
1.3.1 短声明赋值案例
package main
import "fmt"
// 定义 Sender 接口,抽象方法为 send(string)error
type Sender interface {
send(string) error
}
// 定义 dingding 结构体
type DingDing struct {
}
// 定义 send 方法,实现接口
func (d *DingDing) send(msg string) error {
fmt.Println("dd msg " + msg)
return nil
}
// 定义 test 函数,形参为 Sender 接口
func test(send Sender) {
send.send("interface")
}
func main() {
// 短声明定义 send 结构体变量
DingSend := new(DingDing)
DingSend.send("结构体")
// test 函数中形参是一个 Sender 接口,由于在 DingSend 结构体中 send 方法满足了 Sender 结构体中的 send 方法,所以 test 函数能够直接引用 DingSend 结构体
test(DingSend)
}
执行
[12:24:08 root@go codes]#go run duan.go
dd msg 结构体
dd msg interface
1.3.2 注意接口只是一个方法行为的抽象
接口只是一个方法行为的抽象,当我们赋值了一个变量,然后定义为了接口方法是不能去访问或者修改结构体的属性
package main
import "fmt"
type Sender interface {
send(string) error
}
// 定义 dingding 结构体,定义了以下几个属性
type DingDing struct {
addr string
user string
to string
}
func (d *DingDing) send(msg string) error {
fmt.Println("dd msg " + msg)
return nil
}
func test(send Sender) {
send.send("interface")
}
func main() {
// 赋值了 send 变量为 Sender 接口
var send Sender = new(DingDing)
// send 接口不能调用结构体属性
fmt.Println(send.addr)
}
执行
[12:24:26 root@go codes]#go run duan.go
# command-line-arguments
./duan.go:26:18: send.addr undefined (type Sender has no field or method addr)
# 类型Sender没有字段或方法Addr
1.3.3 接口类型之间的变量赋值
当我们把一个接口赋值给另外一个接口的时候,需要提前实现所有的方法
接口在赋值的时候会隐藏一些属性
package main
import "fmt"
// 定义 Sender 接口,抽象的方法为 send(string) error
type Sender interface {
send(string) error
}
// 定义 SenderAll 接口,抽象的方法有两个,其中一个实现了 Sender 接口的 send(string) error 方法
type SenderAll interface {
send(string) error
sendAll([]string) error
}
// 定义 DingDing 结构体,两个方法都满足了 SenderAll 接口
type DingDing struct {
addr string
user string
to string
}
func (d *DingDing) send(msg string) error {
fmt.Println("dingding msg " + msg)
return nil
}
func (d *DingDing) sendAll(msg []string) error {
fmt.Println("dingding sender all", msg)
return nil
}
func main() {
// 将 new(DingDing) 结构体赋值给 sendAll 和 sender 两个方法,满足 Sender 和 SenderAll 接口
var sendAll SenderAll = new(DingDing)
var sender Sender = new(DingDing)
// 这里将 sender 接口赋值给 sendAll 接口,是错误的,因为在 sender 接口中没有满足 sendAll([]string) error 抽象方法
sendAll = sender
fmt.Println(sendAll, sender)
}
执行
[13:09:39 root@go codes]#go run duan.go
# command-line-arguments
./duan.go:64:10: cannot use sender (type Sender) as type SenderAll in assignment:
Sender does not implement SenderAll (missing sendAll method)
# 无法将发件人(类型sender)用作分配中的类型senderal:发送方未实现senderal(缺少sendAll方法)
但是我们将 sendAll 接口赋值给 sender 接口就可以,因为在 sendAll 接口里面实现了 sender 接口的方法
package main
import "fmt"
// 定义 Sender 接口,抽象的方法为 send(string) error
type Sender interface {
send(string) error
}
// 定义 SenderAll 接口,抽象的方法有两个,其中一个实现了 Sender 接口的 send(string) error 方法
type SenderAll interface {
send(string) error
sendAll([]string) error
}
// 定义 DingDing 结构体,两个方法都满足了 SenderAll 接口
type DingDing struct {
addr string
user string
to string
}
func (d *DingDing) send(msg string) error {
fmt.Println("dingding msg " + msg)
return nil
}
func (d *DingDing) sendAll(msg []string) error {
fmt.Println("dingding sender all", msg)
return nil
}
func main() {
// 将 new(DingDing) 结构体赋值给 sendAll 和 sender 两个方法,满足 Sender 和 SenderAll 接口
var sendAll SenderAll = new(DingDing)
var sender Sender = new(DingDing)
// 将 sendAll 赋值给了 sender 接口
sender = sendAll
// 在调用的时候只能调用 send
sender.send("SENDER 方法")
fmt.Println(sendAll, sender)
}
执行
[13:12:40 root@go codes]#go run duan.go
&{ } &{ }
1.4 接口继承
所谓的接口继承其实就是我们的一个接口继承了另一个接口的使用的方法,也就说说当我们有多个结构体,满足不同接口的时候,既可以通过接口继承来满足。
1.5 类型断言
- 当父接口或者类型对象赋值给接口变量后,需要将接口变量重新转换为原来的类型,需要使用类型断言/查询
- 类型断言是一个使用在接口值上的操作
- 有时候,我们可能需要知道某个接口类型的实际类型,比如某个方法需要接收多种类型的数据并需要做分别处理,我们可以把形参设为空接口类型并接收任意类型的值,但是我们怎么反向知道里面实际保存的是哪个类型的对象呢?
接口是没有办法调用结构体里面的属性,也没有办法去调用该结构体其他的方法,接口只能调用它自己定义的方法。
断言在转换的时候有两个返回值,第一个是断言成功后的对象,还有一个是 OK ,Ok 用来判断是否断言成功了还是断言失败了
范例:
package main
import "fmt"
type Sender interface {
Send(string) error
}
type EmailSender struct {
Addr string
To string
}
func (e *EmailSender) Send(msg string) error {
fmt.Println("email sender : ", msg)
return nil
}
func (e *EmailSender) Test() {
fmt.Println("test !")
}
func main() {
// 定义接口,同时将 EmailSender 结构体里面的属性进行初始化
var sender Sender = &EmailSender{"xxx", "yyy"}
// 调用接口 Send 方法
sender.Send("zzz")
// 类型断言,由于接口不能直接访问到结构体的属性,所以需要类型断言来判断是否断言成功
obj, ok := sender.(*EmailSender)
fmt.Printf("%T\n%T\n", obj, ok)
fmt.Println("断言成功后的值", obj, ok)
}
执行
[16:17:17 root@go codes]#go run asset.go
email sender : zzz
*main.EmailSender # 现在 obj 变量断言成功就是 *main.EmailSender 结构体类型
bool # ok 是一个 bool 类型
断言成功后的值 &{xxx yyy} true # 断言成功后就可以正常访问该结构体
1.5.1 类型断言后访问数据
当我们类型断言成功之后就可以正常访问该结构体里面的属性。
package main
import "fmt"
type Sender interface {
Send(string) error
}
type EmailSender struct {
Addr string
To string
}
func (e *EmailSender) Send(msg string) error {
fmt.Println("email sender : ", msg)
return nil
}
func (e *EmailSender) Test() {
fmt.Println("test !")
}
func main() {
// 定义接口,同时将 EmailSender 结构体里面的属性进行初始化
var sender Sender = &EmailSender{"xxx", "yyy"}
// 类型断言
obj, ok := sender.(*EmailSender)
// 也可以直接访问,不用通过 if 进行判断
fmt.Println(obj.Addr)
// ok 为真就访问 obj.Addr, obj.To 两个属性
if ok {
fmt.Println(obj.Addr, obj.To)
// 访问 Test() 方法
obj.Test()
}
}
执行
[16:57:46 root@go codes]#go run asset.go
xxx
xxx yyy
test !
1.5.2 类型断言注意事项
当我们在有的多个结构体使用接口方法的时候,但是有的结构体没有被赋值,所以就会类型断言错误
在进行类型断言的时候,必须要提前把结构体进行初始化赋值
package main
import "fmt"
type Sender interface {
Send(string) error
}
type EmailSender struct {
Addr string
To string
}
func (e *EmailSender) Send(msg string) error {
fmt.Println("email sender : ", msg)
return nil
}
func (e *EmailSender) Test() {
fmt.Println("test !")
}
type DingDing struct {
}
func (d *DingDing) Send(msg string) error {
fmt.Println("dingding ", msg)
return nil
}
func main() {
// 定义接口,同时将 EmailSender 结构体里面的属性进行初始化
var sender Sender = &EmailSender{"xxx", "yyy"}
// 调用接口 Send 方法
// sender.Send("zzz")
// 类型断言,由于接口不能直接访问到结构体的属性
obj, ok := sender.(*EmailSender)
if ok {
fmt.Println(obj.Addr, obj.To)
obj.Test()
}
// 没有对 DingDing 结构体初始化赋值,这调用的还是 EmailSender 结构体的赋值变量 sender
// DingDing 结构体没有被赋值
dingding, ok := sender.(*DingDing)
fmt.Println(dingding, ok)
}
执行
[16:58:32 root@go codes]#go run asset.go
xxx yyy
test !
<nil> false # false 因为我们没有对 dingding 结构体进行初始化赋值
1.5.3 通过类型断言调用结构体
package main
import "fmt"
type Sender interface {
Send(string) error
}
type EmailSender struct {
Addr string
To string
}
func (e *EmailSender) Send(msg string) error {
fmt.Println("email sender : ", msg)
return nil
}
func (e *EmailSender) Test() {
fmt.Println("test !")
}
type DingDing struct {
}
func (d *DingDing) Send(msg string) error {
fmt.Println("dingding ", msg)
return nil
}
func test(sender Sender) {
// 类型断言,如果 sender 是 EmailSender 打印 email sender
if obj, ok := sender.(*EmailSender); ok {
fmt.Println("email sender", obj.Addr) // 调用 obj.Addr 属性
// 类型断言,如果 sender 是 DingDing 打印 dingding sender
} else if _, ok := sender.(*DingDing); ok {
fmt.Println("dingding sender")
} else {
fmt.Println("error")
}
}
func main() {
// 定义 sender 接口变量,赋值 EmailSender
var sender Sender = &EmailSender{"xxx", "yyy"}
test(sender)
// 传入 test 函数的 DingDing 结构体
test(new(DingDing))
}
执行
[17:27:46 root@go codes]#go run asset.go
email sender xxx
dingding sender
断言错误的话就不会执行成功如下代码
package main
import (
"fmt"
)
type Sender interface {
Send(string) error
}
type Emai struct {
Add string
}
func (e *Emai) Send(msg string) error {
fmt.Println("emai" + msg)
return nil
}
type QQ struct{}
func (q *QQ) Send(msg string) error {
fmt.Println("ding" + msg)
return nil
}
// 类型断言
func Assert(send Sender) {
// 这里通过 send 接口判断传入的类型是否为 Emai
if obj, ok := send.(*Emai); ok {
fmt.Println("emai")
obj.Send("yayayay")
} else {
fmt.Println("struct err")
}
}
func main() {
// 这里传入的是 QQ
Assert(new(QQ))
}
输出
[17:20:38 root@go day1]#go run main.go
struct err
1.5.4 switch 类型查询
可以通过 switch case + 接口变量.(type)
查询变量类型,并选择对应的分支块
语法
switch case + 接口变量.(type)
// 是固定语法,只能用在 switch 里面
范例
package main
import "fmt"
type Test_Send interface {
Send(string) error
}
type Emai struct{}
func (e *Emai) Send(msg string) error {
fmt.Println("emai" + msg)
return nil
}
type Ding struct{}
func (d *Ding) Send(msg string) error {
fmt.Println("ding" + msg)
return nil
}
type QQ struct{}
func (d *QQ) Send(msg string) error {
fmt.Println("ding" + msg)
return nil
}
func Switch_func(send Test_Send) {
// 断言判断
// switch case + 接口变量.(type)
// 判断这个接口的类型,是否为 Emai 或者 Ding
switch send.(type) {
case *Emai:
{
fmt.Println("Emai")
}
case *Ding:
{
fmt.Println("ding")
}
default:
{
fmt.Println("err")
}
}
}
func main() {
// QQ 类型没有在 Switch_func 做断言判断所以会直接执行 default 的 err
Switch_func(new(QQ))
// 通过 new 一个 Ding 并将返回值传递给 Switch_func 函数
Switch_func(new(Ding))
}
执行
[17:46:32 root@go day1]#go run main.go
err # QQ 没有做类型断言执行 default 语句块
ding # 有类型断言执行 Ding 语句块
1.5.4.1 接口查询
接口查询其实就是满足就近原则,顺序是由 switch - case
来决定
package main
import "fmt"
type Sender interface {
Send(string) error
}
type AllSender interface {
Send(string) error
SendAll([]string) error
}
type EmailSender struct {
Addr string
To string
}
func (e *EmailSender) Send(msg string) error {
fmt.Println("email", msg)
return nil
}
func (e *EmailSender) SendAll(msg []string) error {
fmt.Println("emaill All sender", msg)
return nil
}
func main() {
// EmailSender 中满足了 Sender 和 AllSender 两个接口的方法所以能够赋值
var sender Sender = &EmailSender{"xxx", "zzz"}
var allSender AllSender = &EmailSender{"111", "222"}
// 由于 allsender 接口变量中满足 Send() 方法所以能赋值给 sender
sender = allSender
// 通过 switch 对 sender 类型进行查询
switch sender.(type) {
case *EmailSender: // 我们把 *EmailSender 结构体写在最近的位置输出 email
fmt.Println("email")
case AllSender:
fmt.Println("allsender")
default:
fmt.Println("default")
}
}
执行
[18:50:49 root@go codes]#go run assetinteface.go
email
假如我把 AllSender 写在最近的位置输出的就是 allsender
执行
[18:50:55 root@go codes]#go run assetinteface.go
allsender
所以由此可见,接口查询其实就是满足就近原则
1.5.5 类型查询调用结构体属性
package main
import "fmt"
type Sender interface {
Send(string)
}
type Email struct {
user string
addr string
}
func (e *Email) Send(msg string) {
fmt.Println("email=" + msg)
}
type Ding struct{}
func (d *Ding) Send(msg string) {
fmt.Println("ding=" + msg)
}
func qurey(s Sender) {
// 类型查询
// switch case + 接口变量.(type),定义 obj 变量接收接口的结构体类型
switch obj := s.(type) {
// 如果 obj 为 Email 类型,这里输出 obj.addr 也就是 obj=Email
case *Email:
fmt.Println(obj.addr)
obj.Send("eee")
// 如果 obj 为 Ding 类型,这里输出 obj.Send 也就是 obj=Ding
case *Ding:
obj.Send("ddd")
}
}
func main() {
// e 变量赋值 Email
e := &Email{"zzz", "CQ"}
// send 接口赋值 e
var send Sender = e
// 传递初始化后的 Email
qurey(send)
// 直接传递 Ding 结构体的指针
qurey(new(Ding))
}
执行
root@consul-3:~/go/src/2022/day2# go run main.go
CQ
email=eee
ding=ddd
# 通过这种方式就能够实现对不同数据类型的处理
1.5.6 接口断言接口
通过接口的断言,实现对接口的类型访问
package main
import "fmt"
type Sender interface {
Send(string) error
}
type AllSender interface {
Send(string) error
SendAll([]string) error
}
type EmailSender struct {
Addr string
To string
}
func (e *EmailSender) Send(msg string) error {
fmt.Println("email", msg)
return nil
}
func (e *EmailSender) SendAll(msg []string) error {
fmt.Println("emaill All sender", msg)
return nil
}
func main() {
// 定义 sender 的 Sender 接口变量,类型是 EmailSender 结构体
var sender Sender = &EmailSender{"xxx", "zzz"}
// 定义 allsend 的 AllSender 接口变量,类型也是 EmailSender 结构体
var allsend AllSender = &EmailSender{"111", "222"}
// 因为 sender 和 allsend 接口里面都有 Send(string) error 方法
// 所以 allsend 能赋值给 sender
sender = allsend
sender.Send("xxxx")
// 接口的类型断言,因为 allsend 已经赋值给了 sender 所以 sender 变量就有 AllSender 接口
// allsend1 为 EmailSender 结构体类型
allsend1, _ := sender.(AllSender)
fmt.Printf("%T\n", allsend1)
}
执行
[17:56:08 root@go codes]#go run assetinteface.go
email xxxx
*main.EmailSender # 是一个 EmailSender 结构体类型
当然由于现在 allsend1 是 EmailSender 结构体,所以也能够实现对 EmailSender 结构体方法的访问
1.5.7 接口赋值原理图
我们从上图可以看到,当我们需要赋值的时候,赋值给被赋值的变量时,赋值的这个变量必须满足被赋值变量的方法。
如下范例
package main
import "fmt"
type Sender interface {
Send(string) error
}
type AllSender interface {
Send(string) error
SendAll([]string) error
}
type EmailSender struct {
Addr string
To string
}
func (e *EmailSender) Send(msg string) error {
fmt.Println("email", msg)
return nil
}
func (e *EmailSender) SendAll(msg []string) error {
fmt.Println("emaill All sender", msg)
return nil
}
func main() {
// EmailSender 中满足了 Sender 和 AllSender 两个接口的方法所以能够赋值
var sender Sender = &EmailSender{"xxx", "zzz"}
var allSender AllSender = &EmailSender{"111", "222"}
// 由于 allsender 接口变量中满足 Send() 方法所以能赋值给 sender
sender = allSender
sender.Send("sender")
// 通过类型断言,将 EmailSender 结构体类型赋值给 esend ,此时 esend 就是 EmailSender 结构体类型
fmt.Println()
esend, ok := sender.(*EmailSender)
fmt.Println("esend 变量:", esend, ok)
fmt.Println(esend.Addr, esend.To)
esend.Send("esend")
esend.SendAll([]string{"aaa", "bbb"})
fmt.Printf("%T\n", esend)
}
执行
[18:42:20 root@go codes]#go run assetinteface.go
email sender
esend 变量: &{111 222} true # 输出了 EmailSender 中 Addr 和 To 属性值, ok 为 true 为真
111 222 # 通过 esned 单独访问 Addr 和 To 属性
email esend # 调用 EmailSender 中的 Send 方法
emaill All sender [aaa bbb] # 调用 EmailSender 中的 SendAll 方法
*main.EmailSender # 类型为 EmailSender 结构体
1.6 接口组合
接口之中也可以嵌入已存在的接口,从而实现接口的扩展,组合是为了实现代码的复用性
如下面例子中,有一个网络连接的一个接口,对于网络连接来说一般有以下几个过程
- Open(打开)
- Send(发送消息)
- Recive(接收)
- Close(关闭)
package main
import "fmt"
type Sender interface {
Send(string) error
}
// 编写一个网络连接接口
type Connection interface {
Sender // 匿名组合 Sender 接口,复用了 Sender 接口
Open() error
Recive() (string, error)
Close() error
}
// 假如我们需要将 Connection 赋值给 TcpConnection 结构体
// TcpConnection 就需要满足 Connection 的 4 种方法
type TcpConnection struct {
}
func (c *TcpConnection) Open() error {
return nil
}
func (c *TcpConnection) Send(msg string) error {
return nil
}
func (c *TcpConnection) Recive() (string, error) {
return "", nil
}
func (c *TcpConnection) Close() error {
return nil
}
func main() {
var conn Connection = new(TcpConnection)
fmt.Printf("%T\n,%#v\n", conn, conn)
// 现在 TcpConnection 结构体赋值给了 conn 接口变量,所以 conn 能够调用 TcpConnection 结构体的方法
conn.Open()
conn.Send("xxx")
conn.Recive()
conn.Close()
}
执行
# 通过执行 TcpConnection 结构体满足了 Connection 接口的 4 个方法所以能够赋值
[09:50:50 root@go codes]#go run combind.go
*main.TcpConnection
,&main.TcpConnection{}
1.6.1 结构体组合匿名接口(不常用)
我们使用结构体组合接口的时候就可以通过实例变量来访问该结构体里面接口对应的满足了该接口的结构体方法
package main
import "fmt"
type Sender interface {
Send(string) error
}
// 编写一个网络连接接口
type Connection interface {
Sender // 匿名组合 Sender 接口,复用了 Sender 接口
Open() error
Recive() (string, error)
Close() error
}
// 假如我们需要将 Connection 赋值给 TcpConnection 结构体
// TcpConnection 就需要满足 Connection 的 4 种方法
type TcpConnection struct {
}
func (c *TcpConnection) Open() error {
return nil
}
func (c *TcpConnection) Send(msg string) error {
return nil
}
func (c *TcpConnection) Recive() (string, error) {
return "", nil
}
func (c *TcpConnection) Close() error {
return nil
}
// 结构体组合接口
type Client struct {
Connection // 这里嵌入的而是一个 Connection 的接口
Name string
}
func main() {
// 定义 conn 接口变量,赋值 TcpConnection 结构体
var conn Connection = new(TcpConnection)
// 在赋值的时候需要满足匿名嵌入接口方法的结构体进行赋值
// 如这的 conn 变量,由于赋值的是 TcpConnection 结构体,满足了 Connection 接口定义的方法所以能够初始化
client := Client{conn, "zzz"}
client.Open()
client.Send("xxx")
client.Recive()
fmt.Printf("%T\n,%#v\n", client, client)
}
1.6.2 结构体组合命名接口
package main
import "fmt"
type Sender interface {
Send(string) error
}
// 编写一个网络连接接口
type Connection interface {
Sender // 匿名组合 Sender 接口,复用了 Sender 接口
Open() error
Recive() (string, error)
Close() error
}
// 假如我们需要将 Connection 赋值给 TcpConnection 结构体
// TcpConnection 就需要满足 Connection 的 4 种方法
type TcpConnection struct {
}
func (c *TcpConnection) Open() error {
return nil
}
func (c *TcpConnection) Send(msg string) error {
return nil
}
func (c *TcpConnection) Recive() (string, error) {
return "", nil
}
func (c *TcpConnection) Close() error {
return nil
}
// 结构体组合接口
type Client struct {
c Connection // 命名组合,嵌入 Connection 接口
Name string
}
func main() {
// 定义 conn 接口变量,赋值 TcpConnection 结构体
var conn Connection = new(TcpConnection)
// 在赋值的时候需要满足匿名嵌入接口方法的结构体进行赋值
// 如这的 conn 变量,由于赋值的是 TcpConnection 结构体,满足了 Connection 接口定义的方法所以能够初始化
client := Client{conn, "zzz"}
client.c.Open() // 访问的时候需要指定命名组合的字段名
client.c.Send("xxx")
client.c.Recive()
fmt.Printf("%T\n,%#v\n", client, client)
}
执行
[10:08:55 root@go codes]#go run combind.go
main.Client
,main.Client{c:(*main.TcpConnection)(0x57b400), Name:"zzz"}
1.7 匿名接口
在定义变量时将类型指定为接口的函数签名的接口,此时叫匿名接口。匿名接口常用于初始化一次接口变量的场景
匿名(接口、函数、结构体):无非就是在定义的时候不定义名称,而是直接定义一个变量。
package main
import "fmt"
type EmailSender struct {
}
// EmailSender 结构体满足 Send 方法
func (e *EmailSender) Send(msg string) error {
fmt.Println("emailSender", msg)
return nil
}
func main() {
// 定义匿名接口
// 直接定义了一个 sender 变量,类型为 interface
var sender interface {
Send(msg string) error
}
// 在赋值的时候,如果实现了 Send(msg string) error 方法就能赋值
// 将 EmailSender 结构体指针赋值给 sender 变量
sender = new(EmailSender)
sender.Send("email")
}
执行
[10:13:33 root@go codes]#go run lambda.go
emailSender email
1.8 空接口
不包含任何函数签名的接口叫空接口,空接口声明的变量可以赋值为任何类型的变量(任意接口)
package main
import "fmt"
// 全局变量定义空接口
type Empty interface{}
func main() {
// 定义 empty 变量,类型为 Empty
var empty Empty
empty = 1
fmt.Printf("type = %T value = %v\n", empty, empty)
empty = "str"
fmt.Printf("type = %T value = %v\n", empty, empty)
empty = true
fmt.Printf("type = %T value = %v\n", empty, empty)
empty = struct {
Name string
Password string
}{"x", "y"}
fmt.Printf("type = %T value = %v\n", empty, empty)
}
执行
[10:51:45 root@go codes]#go run empty.go
type = int value = 1
type = string value = str
type = bool value = true
type = struct { Name string; Password string } value = {x y}
# 空接口可以赋值成任何类型
1.8.1 空接口操作
当我们需要对空接口进行运算,或者字符串链接等操作的时候就需要使用到类型断言。
package main
import "fmt"
// 全局变量定义空接口
type Empty interface{}
type User struct {
Name string
Password string
}
func main() {
// 定义 empty 变量,类型为 Empty
var empty Empty
// 空接口运算操作:类型断言,v 是断言后的值
empty = 1
if v, ok := empty.(int); ok {
fmt.Println("运算:", v+2)
}
// 空接口字符串链接操作:类型断言,v 是断言后的值
empty = "str"
if v, ok := empty.(string); ok {
fmt.Println("字符串拼接:", v+"xixi")
}
// 空接口对结构体操作,类型断言,v 是断言后的值,也就是 User 结构体
empty = User{"xx", "yy"}
if v, ok := empty.(User); ok {
fmt.Println("结构体:", v.Name, v.Password)
}
}
执行
[11:08:53 root@go codes]#go run empty.go
运算: 3
字符串拼接: strxixi
结构体: xx yy
1.8.2 匿名空接口(常用)
package main
import "fmt"
type User struct {
Name string
Password string
}
func main() {
// 定义 empty 变量为匿名空接口
var empty interface{}
// empty 匿名空接口赋值为 int 类型
empty = 1
fmt.Println(empty)
// empty 匿名空接口赋值为 string 类型
empty = "xxxx"
fmt.Println(empty)
// empty 匿名空接口赋值为 User 结构体类型
empty = User{"xx", "111"}
fmt.Println(empty)
}
执行
[11:14:20 root@go codes]#go run empty_v1.go
1
xxxx
{xx 111}
1.8.2.1 匿名空接口使用
匿名接口的访问,以及对数据的操作,就需要使用到类型断言
package main
import "fmt"
type User struct {
Name string
Password string
}
func main() {
// 定义 empty 变量为匿名空接口
var empty interface{}
// empty 匿名空接口赋值为 int 类型
empty = 1
// 类型断言
if v, ok := empty.(int); ok {
fmt.Println(v + 2)
}
// empty 匿名空接口赋值为 string 类型
empty = "xxxx"
// 类型断言
if v, ok := empty.(string); ok {
fmt.Println(v + "12")
}
// empty 匿名空接口赋值为 User 结构体类型
empty = User{"xx", "111"}
// 类型断言
if v, ok := empty.(User); ok {
fmt.Println(v.Name, v.Password)
}
}
执行
[11:15:38 root@go codes]#go run empty_v1.go
3
xxxx12
xx 111
1.9 接口的应用
1.9.1 String 方法
结构体中定义 string 方法,就可以自动打印
package main
import "fmt"
// 定义结构体 User
type User struct {
Name string
Password string
}
// 定义一个 String 方法
func (u User) String() string {
return fmt.Sprintf("User[Name=%s]", u.Name)
}
func main() {
// 定义 user 变量类型为 User 结构体
user := User{
Name: "xxx",
Password: "xxx",
}
// 输出 user 会自动调用 String() 方法
fmt.Println(user)
// 指针类型的 puser 也是会自动调用 String() 方法
puser := &User{"xxx", "xxx"}
fmt.Println(puser)
}
执行
# 可以看到这里并没有调用 string() 方法他就自己调用了
[16:19:44 root@go codes]#go run string.go
User[Name=xxx]
User[Name=xxx]
1.9.2 sort.Slice 切片方法
该方法可以通过内置函数将其字段的大小值进行排序
package main
import (
"fmt"
"sort"
)
type User struct {
Id int
Name string
Password string
}
// 调用官方的 String() 方法,这里的结构体不能传递指针类型
func (user User) String() string {
return fmt.Sprintf("user[Id = %d],Name = %s]\n", user.Id, user.Name)
}
// 定义 Users 类型是 User 结构体的切片类型
type Users []User
func main() {
// 定义 users 变量并赋值
users := []User{
{1, "z21", "pppp"},
{20, "z222", "pppp"},
{187, "zads", "pppp"},
{230, "z1", "pppp"},
{100, "zyy", "pppp"},
{98, "zgg", "pppp"},
}
// 通过 Slice 进行排序
sort.Slice(users, func(i, j int) bool {
return users[i].Id < users[j].Id
})
fmt.Println(users)
}
1.9.3 自定义类型的时候进行排序
package main
import (
"fmt"
"sort"
)
// 定义 User 结构体
type User struct {
Id int
Name string
Password string
}
// 定义 String 方法进行默认输出
func (user User) String() string {
return fmt.Sprintf("user[Id = %d],Name = %s]\n", user.Id, user.Name)
}
// 定义 Users 类型为 User 结构体的切片
type Users []User
// 定义 Len() 、 Less(i, j int) bool、Swap(i, j int) 方法实现 sort.Slice()
func (users Users) Len() int {
return len(users)
}
func (users Users) Less(i, j int) bool {
return users[i].Id < users[j].Id
}
func (users Users) Swap(i, j int) {
users[i], users[j] = users[j], users[i]
}
func main() {
// 强制类型为 Users 类型将 User 结构体转为切片
users := Users([]User{
{1, "z21", "pppp"},
{20, "z222", "pppp"},
{187, "zads", "pppp"},
{230, "z1", "pppp"},
{100, "zyy", "pppp"},
{98, "zgg", "pppp"},
})
// 然后再通过 sort.Sort() 进行排序
sort.Sort(users)
fmt.Println(users)
}
root@consul-3:~/go/src/2022/day2# go run main.go
[user[Id = 1],Name = z21]
user[Id = 20],Name = z222]
user[Id = 98],Name = zgg]
user[Id = 100],Name = zyy]
user[Id = 187],Name = zads]
user[Id = 230],Name = z1]
]