Go 语言的接口设计是非侵入式的,接口编写者无须知道接口被哪些类型实现。而接口实现者只需知道实现的是什么样子的接口,但无须指明实现哪一个接口。编译器知道最终编译时使用哪个类型实现哪个接口,或者接口应该由谁来实现。
Go 的接口有以下特性:
nil
,此时动态类型和动态值都是nil
。接口定义规则如下:
在Go中,接口是一种类型,它指定了一个方法集:
// 定义一个接口
type Reader interface {
Read(p []byte) (n int, err error)
}
// 另一个包含多个方法的接口
type Shape interface {
Area() float64
Perimeter() float64
}
实现接口的条件如下:
接口的方法与实现接口的类型方法格式一致
在类型中添加与接口签名一致的方法就可以实现该方法。签名包括方法的名称、参数列表、返回参数列表。
接口中所有方法均被实现
当一个接口中有多个方法时,只有这些方法都被实现了,接口才能被正确编译并使用。
Go接口的特别之处在于它们是隐式实现的,即如果一个类型实现了接口中定义的所有方法,它就自动满足了该接口,无需显式声明:
// Circle类型
type Circle struct {
Radius float64
}
// 实现Shape接口的Area方法
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
// 实现Shape接口的Perimeter方法
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
// Rectangle类型
type Rectangle struct {
Width, Height float64
}
// 实现Shape接口的方法
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// 使用接口作为参数
func PrintShapeInfo(s Shape) {
fmt.Printf("面积: %.2f, 周长: %.2f\n", s.Area(), s.Perimeter())
}
func main() {
c := Circle{Radius: 5}
r := Rectangle{Width: 3, Height: 4}
// 都可以作为Shape传递给函数
PrintShapeInfo(c)
PrintShapeInfo(r)
}
**注意:**在 Go 中,不是只有 struct 类型可以实现接口!其他的所有类型,只要实现了接口的所有方法,就自动实现了该接口。
package main
type MyInterface interface {
MyTestFunc(int) int
}
// MyFunc 类型实现了 MyInterface 接口
type MyFunc func(string) string
func (m MyFunc) MyTestFunc(a int) int {
return a
}
// MyInt 类型实现了 MyInterface 接口
type MyInt int
func (i MyInt) MyTestFunc(a int) int {
return a
}
接口可以嵌套其他接口,形成更大的接口:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
// ReadWriter接口嵌套了Reader和Writer
type ReadWriter interface {
Reader
Writer
}
实现ReadWriter
接口的类型必须同时实现Read
和Write
方法。
一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。
网络上的两个程序通过一个双向的通信连接实现数据的交换,连接的一端称为一个 Socket。Socket 能够同时读取和写入数据,这个特性与文件类似。因此,开发中把文件和 Socket 都具备的读写特性抽象为独立的读写器概念。
Socket 和文件一样,在使用完毕后,也需要对资源进行释放。
把 Socket 能够写入数据和需要关闭的特性使用接口来描述:
type Socket struct {
}
func (s *Socket) Write(p []byte) (n int, err error) {
return 0, nil
}
func (s *Socket) Close() error {
return nil
}
Socket 结构的 Write()
方法实现了 io.Writer
接口:
type Writer interface {
Write(p []byte) (n int, err error)
}
同时,Socket 结构也实现了 io.Closer
接口:
type Closer interface {
Close() error
}
使用 Socket 实现的 Writer 接口的代码,无须了解 Writer 接口的实现者是否具备 Closer 接口的特性。同样,使用 Closer 接口的代码也并不知道 Socket 已经实现了 Writer 接口,如下图所示:
例如,现在需要有一个接口,这个接口可以实现服务的开启和日志功能,而日志功能是多个服务通用的,只有开启功能需要根据服务的不同来定义,每个服务都有这两个功能,那么就可以这么去做
package main
import "fmt"
//Service 服务接口
type Service interface {
start()
log()
}
// Logger 通用的日志处理器
type Logger struct{}
func (log *Logger) log() {
fmt.Println("通用日志服务")
}
// GameService 游戏服务
type GameService struct {
//内嵌日志处理器
Logger
}
//实现游戏服务的启动功能
func (gs *GameService) start() {
fmt.Printf("开启游戏服务")
}
func main() {
var service Service = new(GameService)
service.log()
service.start()
}
此时,如果再新增服务的话,只需要内嵌日志处理器然后再实现服务特有的启动功能,就可以了
nil
在 Go语言中只能被赋值给指针和接口。接口在底层的实现有两个部分:type
和 data
。
在源码中,显式地将 nil
赋值给接口时,接口的 type
和 data
都将为 nil
。此时,接口与 nil
值判断是相等的。
但如果将一个带有类型的 nil
赋值给接口时,只有 data
为 nil
,而 type
为 nil
,此时,接口与 nil
判断将不相等。
interface{}
any
//在1.18后,可以使用any来表示空接口,源码中是对空接口做了别名
type any = interface{}
空接口是接口类型的特殊形式,空接口没有任何方法,因此任何类型都无须实现空接口。从实现的角度看,任何值都满足这个接口的需求。因此空接口类型可以保存任何值,也可以从空接口中取出原值。
空接口的内部实现保存了对象的类型和指针。使用空接口保存一个数据的过程会比直接用数据对应类型的变量保存稍慢。因此在开发中,应在需要的地方使用空接口,而不是在所有地方使用空接口。
Go 语言中的空接口在保存不同的值后,可以和其他变量一样使用==
进行比较操作。
类 型 | 说 明 |
---|---|
map | 不可比较,如果比较,程序会报错 |
切片(slice) | 不可比较,如果比较,程序会报错 |
通道(channel) | 可比较,必须由同一个 make 生成,也就是同一个通道才会是true,否则为 false |
数组 | 可比较,编译期知道两个数组是否一致 |
结构体 | 可比较,可以逐个比较结构体的值 |
函数 | 可比较 |
类型断言(Type Assertion)是一个使用在接口值上的操作,用于检查接口类型变量所持有的值是否实现了期望的接口或者具体的类型,也可以作为接口和具体类型直接转换的方法,语法格式如下:
value, ok := x.(T)
x 表示一个接口的类型变量,T 表示一个具体的类型(也可为接口类型)。
该断言表达式会返回 x 的值(也就是 value)和一个布尔值(也就是 ok),可根据该布尔值判断 x 是否为 T 类型
注意:需要注意如果不接收第二个参数也就是上面代码中的 ok,断言失败时会直接造成一个 panic,也就是说,如果断言成功,就可以只接一个值,就是value
package main
import "fmt"
func main() {
var a interface{} = 10
var b interface{} = "hello"
v1, o1 := a.(int)
v2, o2 := b.(int)
fmt.Println(v1, o1) //10 true
fmt.Println(v2, o2) //0 false
}
type-switch 流程控制的语法或许是Go语言中最古怪的语法。 它可以被看作是类型断言的增强版。它和 switch-case 流程控制代码块有些相似。
switch 接口变量.(type) {
case 类型1:
// 变量是类型1时的处理
case 类型2:
// 变量是类型2时的处理
…
default:
// 变量不是所有case中列举的类型时的处理
}
package main
import "fmt"
func main() {
var a interface{} = "hello"
switch a.(type) {
case int:
fmt.Printf("a is int type,value = %s", a)
case string:
fmt.Printf("a is string type,value = %s", a)
case float64:
fmt.Printf("a is float64 type,value = %s", a)
}
//a is string type,value = hello
}
Go标准库中的io
包是接口设计的典范,这种设计允许:
// io包核心接口
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
// 组合接口
type ReadWriter interface {
Reader
Writer
}
type ReadCloser interface {
Reader
Closer
}
// 更多组合...
通过结构体组合可以轻松实现多个接口:
// 实现Reader接口
type StringReader struct {
data string
pos int
}
func (r *StringReader) Read(p []byte) (n int, err error) {
// 实现细节...
return len(p), nil
}
// 实现Writer接口
type StringWriter struct {
data string
}
func (w *StringWriter) Write(p []byte) (n int, err error) {
// 实现细节...
return len(p), nil
}
// 通过组合实现ReadWriter接口
type StringReadWriter struct {
*StringReader
*StringWriter
}
// 可以使用工厂函数创建组合结构
func NewStringReadWriter(data string) *StringReadWriter {
return &StringReadWriter{
StringReader: &StringReader{data: data},
StringWriter: &StringWriter{},
}
}
// 标准错误接口
type error interface {
Error() string
}
// 自定义错误类型
type NetworkError struct {
Code int
Message string
}
func (e NetworkError) Error() string {
return fmt.Sprintf("网络错误 [%d]: %s", e.Code, e.Message)
}
// 错误断言
func handleError(err error) {
if netErr, ok := err.(NetworkError); ok {
fmt.Printf("处理网络错误,代码: %d\n", netErr.Code)
} else {
fmt.Println("处理其他错误")
}
}
// 插件接口
type Plugin interface {
Name() string
Initialize() error
Execute(ctx context.Context) error
Shutdown() error
}
// 插件管理器
type PluginManager struct {
plugins map[string]Plugin
}
func NewPluginManager() *PluginManager {
return &PluginManager{
plugins: make(map[string]Plugin),
}
}
func (pm *PluginManager) Register(p Plugin) error {
name := p.Name()
if _, exists := pm.plugins[name]; exists {
return fmt.Errorf("插件 %s 已注册", name)
}
if err := p.Initialize(); err != nil {
return fmt.Errorf("插件 %s 初始化失败: %v", name, err)
}
pm.plugins[name] = p
return nil
}
func (pm *PluginManager) ExecuteAll(ctx context.Context) error {
for name, p := range pm.plugins {
if err := p.Execute(ctx); err != nil {
return fmt.Errorf("插件 %s 执行失败: %v", name, err)
}
}
return nil
}
func (pm *PluginManager) ShutdownAll() {
for name, p := range pm.plugins {
if err := p.Shutdown(); err != nil {
fmt.Printf("插件 %s 关闭失败: %v\n", name, err)
}
}
}
// 示例插件
type LoggingPlugin struct{}
func (p *LoggingPlugin) Name() string {
return "logging"
}
func (p *LoggingPlugin) Initialize() error {
fmt.Println("日志插件初始化")
return nil
}
func (p *LoggingPlugin) Execute(ctx context.Context) error {
fmt.Println("日志插件执行")
return nil
}
func (p *LoggingPlugin) Shutdown() error {
fmt.Println("日志插件关闭")
return nil
}