1.25 Go语言之接口

1.25 Go语言之接口

Go语言接口的特点

  • 非侵入式--->编写者无须知道接口被哪些类型实现接口实现者只需知道实现的是什么样子的接口,但无须指明实现哪一个接口。编译器知道最终编译时使用哪个类型实现哪个接口,或者接口应该由谁来实现。

Go语言接口声明(定义)

Go语言不是一种 “传统” 的面向对象编程语言:它里面没有类和继承的概念。

每个接口类型由数个方法组成

Go接口的声明格式:

type 接口类型名 interface{
   方法名1( 参数列表1 ) 返回值列表1
   方法名2( 参数列表2 ) 返回值列表2
   
}

注意:参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以被忽略

type Writer interface{
   Writer([]byte) (n int, err error)
}
/*
这个接口可以调用 Write() 方法写入一个字节数组([]byte)
返回值告知写入字节数(n int)和可能发生的错误(err error)。
*/

Go语言中的String接口

将一个对象以字符串形式展现的接口,只要实现了这个接口的类型,在调用 String() 方法时,都可以获得对象对应的字符串。

type Stringer interface{
   String() string
}
/*
类似java的toString()方法
*/

Go语言实现接口的条件

回顾Java等语言实现接口的条件:

  • 接口的方法与实现接口的类型方法格式一致

  • 接口中所有方法均被实现


  • 一个任意类型T的方法集为一个接口类型的方法集的超集,则我们说类型T实现了此接口类型。

  • T可以是一个非接口类型,也可以是一个接口类型。

Go语言中接口的特点:

  • 隐式的,两个类型之间的实现关系不需要在代码中显式地表示出来

  • Go中没有类似implements的关键字。Go编译器将自动在需要的时候检查两个类型之间的实现关系

  • Go语言中的接口也需要实现

接口的方法与实现接口的类型方法格式一致:

说明:

在类型中添加与接口签名一致的方法就可以实现该方法。(签名包括:方法名、入参、反参) 示例:

package main

import "fmt"

// 定义数据写入器
type DataWriter interface {
   // 定义接口中的方法
   WriterData(data interface{}) error
}

// 定义结构体。用于实现DataWriter接口。类似于java中的类
type file struct {

}

// 单独定义接口中方法的内容(实现接口中定义的方法)
/*
因为没有类似implements这样的关键字,所以在方法名前加入该结构体的指针对象
表示使用该结构体实现接口WriterData
file 的 WriteData() 方法使用指针接收器。输入一个 interface{} 类型的 data,返回 error。
*/
func (d *file) WriterData(data interface{}) error {
   // 写入数据
   fmt.Println("WriterData:", data)
   // 返回值
   return nil
}

func main() {
   // 实例化file
   f := new(file)

   // 声明一个接口变量
   var writer DataWriter

   // 将实现了接口的结构体对象赋值给接口
   /*
   将 *file 类型的 f 赋值给 DataWriter 接口的 writer,虽然两个变量类型不一致。但是 writer 是一个接口,且 f 已经完全实现了 DataWriter() 的所有方法,因此赋值是成功的
    */
   writer = f

   // 使用writer接口变量调用方法进行数据写入
   writer.WriterData("data")
}

调用---实现关系图:

 

几种接口无法实现时的错误:

  • 函数名不一致导致的报错

  • 实现接口的方法签名不一致导致的报错(方法名、入参、反参有一项不一样导致错误)

接口中所有方法均被实现:

接口中有多个方法时,只有这些方法都被实现了接口才能被正确编译并且使用

Go语言的接口实现是隐式的,无须让实现接口的类型写出实现了哪些接口。这个设计被称为非侵入式设计。

示例:

// 定义数据写入器
type DataWriter interface {
   // 定义接口中的方法
   WriterData(data interface{}) error
   
   // 新增一个方法以后下面的实现在运行的时候就会报错
   CanWrite() bool
}

Go语言类型与接口的关系

  • 一对多

  • 多对一


一个类型实现多个接口

特点:

一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现

套接字概念:

网络上的两个程序通过一个双向的通信连接实现数据的交换。连接的一端称为一个 Socket

Socket的特点:

使用完毕后需要对资源进行释放

package main

// 定义结构体
type Socket struct {

}

// 定义该结构体实现了的方法
func (s *Socket) Write(p []byte) (n int, err error) {
   return 0, nil
}

func (s *Socket) Close() error {
   return nil
}

同时定义该结构的方法实现了什么接口:

type Writer interface {
   Write(p []byte) (n int, err error)
}

type Closer interface {
   Close() error
}

特点:

  • 使用 Socket 实现的 Writer 接口的代码,无须了解 Writer 接口的实现者是否具备 Closer 接口的特性

  • 使用 Closer 接口的代码也并不知道 Socket 已经实现了 Writer 接口

实现层级图:

 

使用 Socket 结构实现的 Writer 接口和 Closer 接口示例:

package main

// 定义结构体
type Socket struct {

}

// 定义该结构体实现了的方法
func (s *Socket) Write(p []byte) (n int, err error) {
   return 0, nil
}

func (s *Socket) Close() error {
   return nil
}

type Writer interface {
   Write(p []byte) (n int, err error)
}

type Closer interface {
   Close() error
}

// 使用Writer的代码, 并不知道Socket和Closer的存在
/*
个人理解:
以java的接口为例。我们在实现接口的时候需要重写接口当中的全部的方法。这样才可以实现接口
也就是说在实现一个接口的同时我们就了解到了接口当中的全部的方法信息。这是强耦合性
在Go中定义接口的实现时候结构体本身和该结构体实现的其他方法--->注意看下面方法的入参
*/
func usingWriter(writer Writer) {
   writer.Write(nil)
}

func usingCloser(closer Closer) {
   closer.Close()
}

func main() {
   // 实例化Socket结构体
   s := new(Socket)

   // 使用usingWriter方法注意看入参
   usingWriter(s)

   // 使用usingCloser方法--->我们传入的是实现了方法里面定义的接口的对象,并不是接口对象
   usingCloser(s)
}

这就是Go语言接口的特点:

使用函数时函数本身定义的形参是接口。然而实际使用的时候传入实现了这些接口的对象在编译器层面就会自动的识别该函数使用了该对象实现的哪一个接口。

usingWriter() 和 usingCloser() 完全独立,互相不知道对方的存在,也不知道自己使用的接口是 Socket 实现的。

多个类型可以实现相同的接口

在上面说到的一个接口中定义的方法如果结构体没有全部实现那么在调用的时候就会报错。而一个接口的方法不一定需要由一个类型完全实现,接口中的方法可以通过在类型中嵌入其他类型或者结构体来实现。

示例:

package main

// 定义一个接口,该接口有一个服务需要的开启和输出日志的方法
type Service interface {
// 开启服务
Start()

// 输出日志
Log(string2 string)
}

// 定义一个日志类型
type Logger struct {
}

// 类型实现接口中的函数
func (g *Logger) Log(l string) {
}

// 定义另一个类型,该类型的属性是实现了接口当中方法的类型
type GameService struct {
Logger // 嵌入日志类型
}

// 再用外类型去实现接口当中的另一个函数
func (g *GameService) Start() {
}

方法调用:

// 方法调用
func main() {
// 创建类型对象
var s Service = new(GameService)

// 调用接口方法
s.Start()
s.Log("HelloWorld!")
}
/*
s 就可以使用 Start() 方法和 Log() 方法,其中,Start() 由 GameService 实现,Log() 方法由 Logger 实现
*/

 

posted @   俊king  阅读(208)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示