go接口

简介: go接口

接口类型是对其它类型行为的抽象和概括;因为接口类型不会和特定的实现细节绑定在一起,通过这种抽象的方式我们可以让我们的函数更加灵活和更具有适应能力。

Go语言中接口类型的独特之处在于它是满足隐式实现的。也就是说,如果一个类型实现了一个接口定义的所有方法,那么它就自动地实现了该接口。因此,我们可以通过将接口作为参数来实现对不同类型的调用,从而实现多态。

接口约定

当你有看到一个接口类型的值时,你不知道它是什么,唯一知道的就是可以通过它的方法来做什么。 接口可以让我们将不同的类型绑定到一组公共的方法上,从而实现多态和灵活的设计。

go

复制代码

type ByteCounter int
func (c *ByteCounter) Write(p []byte) (int, error) {
  *c += ByteCounter(len(p)) // convert int to ByteCounter
  return len(p), nil
}
func main() {
  var c ByteCounter
  c.Write([]byte("hello"))
  fmt.Println(c) // "5", = len("hello")
  c = 0          // reset the counter
  var name = "Dolly"
  fmt.Fprintf(&c, "hello, %s", name)
  fmt.Println(c) // "12", = len("hello, Dolly")
}

![[Pasted image 20231127204316.png]] Fprintf 函数接受一个 io.Writer 的接口类型 要让我们的 ByteCounter 类型可以被 Fprintf 函数接受,就需要我们实现 Fprintf 函数的方法(唯一的一个 Write 方法)

接口类型

接口类型具体描述了一系列方法的集合,一个实现了这些方法的具体类型是这个接口类型的实例。

go

复制代码

import "fmt"
type person interface {
  Eat() (msg string)
  Touch() (msg string)
}
type user struct{}
func (p *user) Eat() (msg string) {
  return "full"
}
func (p *user) Touch() (msg string) {
  return "hurt"
}
func main() {
  var cfd user
  fmt.Println(cfd.Eat())   //"full"
  fmt.Println(cfd.Touch()) //"hurt"
}

实现接口的条件

一个类型如果拥有一个接口需要的所有方法,那么这个类型就实现了这个接口。接口指定的规则非常简单:表达一个类型属于某个接口只要这个类型实现这个接口。所以:

go

复制代码

var w io.Writer         // 只用实现 Write 方法
w = os.Stdout           // OK: *os.File 有 Write 方法
w = new(bytes.Buffer)   // OK: *bytes.Buffer 有 Write 方法
w = time.Second         // compile error: time.Duration 没有 Write 方法
var rwc io.ReadWriteCloser // 需要实现 Read, Write, Close 方法
rwc = os.Stdout         // OK: *os.File 有 Read, Write, Close 方法
rwc = new(bytes.Buffer) // compile error: *bytes.Buffer 没有 Close 方法

这个规则甚至适用于等式右边本身也是一个接口类型

go

复制代码

w = rwc                 // OK: io.ReadWriteCloser 有 Write 方法
rwc = w                 // compile error: io.Writer 没有 Close 方法

go

复制代码

type IntSet struct {
}
func (*IntSet) String() string {
  return ""
}
var _ = IntSet{}.String()    // compile error: String 需要 *IntSet 接收
var _ = (&IntSet{}).String() // OK: s 是一个变量而 &s 有 a String 方法

go

复制代码

var s IntSet
var _ fmt.Stringer = &s // OK
var _ fmt.Stringer = s  // compile error: IntSet 没有 String 方法

由于只有 *IntSet 类型有 String 方法,所以也只有 *IntSet 类型实现了 fmt.Stringer 接口:

空接口

go

复制代码

var any interface{}
any = true
any = 12.34
any = "hello"
any = map[string]int{"one": 1}
any = new(bytes.Buffer)

interface{}被称为空接口类型是不可或缺的。 因为空接口类型对实现它的类型没有要求,所以我们可以将任意一个值赋给空接口类型。

接口值

接口值,由两个部分组成,一个具体的类型那个类型的值。它们被称为接口的动态类型动态值

接口值 · Go语言圣经 (studygolang.com)

一个包含nil指针的接口不是nil接口

一个不包含任何值的nil接口值和一个刚好包含nil指针的接口值是不同的。这个细微区别产生了一个容易绊倒每个Go程序员的陷阱。

go

复制代码

package main
import (
  "bytes"
  "fmt"
  "io"
)
const debug = false
func main() {
  var buf *bytes.Buffer = nil
  if debug {
    buf = new(bytes.Buffer) // enable collection of output
  }
  //buf.Write([]byte("hello"))
  f(buf) // NOTE: subtly incorrect!
  if debug {
    // ...use buf...
  }
}
// If out is non-nil, output will be written to it.
func f(out io.Writer) {
  fmt.Printf("%T", out)
  // ...do something...
  if out != nil {
    out.Write([]byte("done!\n"))
  }
}

main函数调用函数f时,它给f函数的out参数赋了一个*bytes.Buffer的空指针,所以out的动态值是nil。然而,它的动态类型是*bytes.Buffer,意思就是out变量是一个包含空指针值的非空接口,所以防御性检查out!=nil的结果依然是true

问题在于尽管一个nil的 *bytes.Buffer 指针有实现这个接口的方法,它也不满足这个接口具体的行为上的要求。

解决方案就是避免一开始就将一个不完整的值赋值给这个接口:

go

复制代码

var buf io.Writer
if debug {
    buf = new(bytes.Buffer) // enable collection of output
}
f(buf) // OK


相关文章
|
设计模式 存储 监控
《Go 简易速速上手小册》第4章:接口与抽象(2024 最新版)(上)
《Go 简易速速上手小册》第4章:接口与抽象(2024 最新版)
142 1
|
5月前
|
存储 Go
Go语言之接口与多态 -《Go语言实战指南》
Go 语言中的接口是实现多态的核心机制,通过一组方法签名定义行为。任何类型只要实现接口的所有方法即视为实现该接口,无需显式声明。本文从接口定义、使用、底层机制、组合、动态行为到工厂模式全面解析其特性与应用,帮助理解 Go 的面向接口编程思想及注意事项(如 `nil` 陷阱)。
154 22
|
5月前
|
存储 JSON Go
Go语言之空接口与类型断言
本文介绍了 Go 语言中空接口(`interface{}`)和类型断言的核心概念及其应用。空接口可存储任意类型数据,适用于通用函数、动态数据结构与 JSON 解析等场景;类型断言用于将接口变量还原为具体类型,推荐使用带 `ok` 的写法以避免程序崩溃。此外,文章通过示例讲解了 `type switch` 类型判断与 JSON 处理技巧,并总结了空接口的注意事项,强调滥用可能导致类型安全性降低。内容深入浅出,帮助开发者灵活运用这些特性。
114 15
|
5月前
|
Go
Go语言接口的定义与实现
Go 语言的接口提供了一种灵活的多态机制,支持隐式实现和抽象编程。本文介绍了接口的基本定义、实现方式、空接口的使用、类型断言以及接口组合等核心概念,并探讨了接口与 nil 的关系及应用场景。通过示例代码详细说明了如何利用接口提升代码的可扩展性和可测试性,总结了接口的关键特性及其在依赖注入、规范定义和多态调用中的重要作用。
195 14
|
10月前
|
Go 开发工具
百炼-千问模型通过openai接口构建assistant 等 go语言
由于阿里百炼平台通义千问大模型没有完善的go语言兼容openapi示例,并且官方答复assistant是不兼容openapi sdk的。 实际使用中发现是能够支持的,所以自己写了一个demo test示例,给大家做一个参考。
|
Go 数据安全/隐私保护
go 基于gin编写encode、decode、base64加密接口
go 基于gin编写encode、decode、base64加密接口
156 2
|
11月前
|
存储 Rust Go
Go nil 空结构体 空接口有什么区别?
本文介绍了Go语言中的`nil`、空结构体和空接口的区别。`nil`是预定义的零值变量,适用于指针、管道等类型;空结构体大小为0,多个空结构体实例指向同一地址;空接口由`_type`和`data`字段组成,仅当两者均为`nil`时,空接口才为`nil`。
220 1
Go nil 空结构体 空接口有什么区别?
|
存储 Go
Go to Learn Go之接口
Go to Learn Go之接口
107 7
|
存储 缓存 NoSQL
在 Go 中使用接口进行灵活缓存
在 Go 中使用接口进行灵活缓存
|
XML 存储 JSON
在Go中使用接口:实用性与脆弱性的平衡
在Go中使用接口:实用性与脆弱性的平衡