前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang 中的抽象 -- 接口的全面解读

golang 中的抽象 -- 接口的全面解读

作者头像
用户3147702
发布2022-06-27 14:14:42
5600
发布2022-06-27 14:14:42
举报
文章被收录于专栏:小脑斧科技博客

1. 引言

之前的文章中,我们介绍了如何通过 golang 的语法实现面向对象的基本特性。 通过 GoLang 实现面向对象思想

在文章中,我们介绍了 golang 中一个用于实现抽象的组件 — 接口,接口是 golang 中非常强大和重要的组件,本文我们就来详细介绍 golang 中接口的用法。

2. 接口

和其他很多语言一样,接口提供了语言的抽象能力,他用来在不暴露数据的内部结构的前提下声明他能够做什么,提供哪些方法。 接口的定义很简单,他也以关键字 type 开始:

type InterfaceName interface { MethodName(<params>) (<returns>) }

他声明了该接口类型的名称,以及符合该接口类型所必须具有的方法列表。

3. 接口的使用

3.1. fmt.Printf

我们常用的 fmt.Printf 方法,他支持传递各种类型的参数,这在不支持重载的 GoLang 语言中看起来很难实现。 事实上,fmt.Printf 方法的参数列表如下:

代码语言:javascript
复制
package fmt

func Printf(format string, args ...interface{}) (int, error) {
        return Fprintf(os.Stdout, format, args...)
}

可变参数列表 args 的类型声明为 interface{},起到了通配符的作用,表示可以传递任何类型的对象。

3.2. fmt.Fprintf 与 io.Writer

而他调用的是 fmt 包中的另一个方法 Fprintf:

代码语言:javascript
复制
package fmt

func Fprintf(w io.Writer, format string, args ...interface{}) (int, error)

Fprintf 函数的首个参数的类型是 io.Writer:

代码语言:javascript
复制
package io

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

只要实现了 Write 方法的类型都可以作为参数传递给 Fprintf 函数,例如 os.Stdout、os.File、bytes.Buffer 或者我们自己定义的实现了 Write 方法的类型都可以作为参数。 os.Writer、os.Reader 两个接口被广泛应用在包括文件、内存缓冲区、网络连接、HTTP 客户端、打包器、散列器等一系列可以写入或读取字节的类型的抽象,同时,os 包还提供了用于关闭他们的抽象接口 Closer。

3.3. 通过实现 Write 方法构造字符串长度统计类型

结合上面的例子,我们可以构造一个实现字符串长度统计的类型:

代码语言:javascript
复制
type ByteCounter int
func (c *ByteCounter) Write(p []byter) (int, error) {
        *c += ByterCounter(len(p))
        return len(p), nil
}

func main() {
        var c ByteCounter
        name := "Kenny"
        fmt.Frpintf(&c, "hello, %s", name)
        fmt.Println(c)
}

打印出了 12。

4. 接口的嵌入

与 struct 类型一样,接口也支持多个接口的匿名组合生成新的接口类型:

代码语言:javascript
复制
type ReadWriter interface {
        Reader
        Writer
}

他相当于:

代码语言:javascript
复制
type ReadWriter interface {
        Read(p []byte) (n int, err error)
        Write(p []byte) (n int, err error)
}

组合的顺序并不重要,但一个类型必须实现接口中每一个方法,才可以作为该接口类型,这同时也意味着,一个实现了 ReadWriter 接口的类型,同时也是 Reader 和 Writer 类型,而空接口类型 interface{} 对其实现类型没有任何要求,因此我们可以把任何值赋值给空接口类型的变量。

代码语言:javascript
复制
var any interface{}
any = true
any = 12.34
any = make(map[string]interface{})
any["hello"] = new(bytes.Buffer)

5. 类型断言

5.1. 测试接口是否已经被实现

由于接口提供了抽象和动态类型的功能,在代码中动态检测是否符合接口类型是常常会用到的。 下面的代码实现了一个检测一个对象是否实现了指定接口的功能:

代码语言:javascript
复制
package main

import "fmt"

type People interface {
    GetName() string
}

type Tom struct {
    Name string
}

func (t *Tom) GetName() string {
    return t.Name
}

type Dog struct {
    Name string
}

func isPeople(instance interface{}) string {
    if _, ok := instance.(People); ok {
        return " is a People"
    }
    return " is not a People"
}

func main() {
    dog := Dog{Name: "dog"}
    tom := Tom{Name: "tom"}
    fmt.Println(dog.Name, isPeople(&dog))
    fmt.Println(tom.Name, isPeople(&tom))
}

打印出了:

dog is not a People tom is a People

5.2. Comma-ok 断言

上面的例子就展示了 comma-ok 断言:

variable.(type)

他返回两个值,分别是指定类型的原值,例如:

代码语言:javascript
复制
PeopleTom, ok := tom.(People)

执行后,PeopleTom 将是一个 People 类型的值或 nil,ok 则返回了检测是否成功的结果。 comma-ok 断言并不仅仅可以被用在接口类型的检测中:

代码语言:javascript
复制
package main

import (
    "fmt"
)

type Html []interface{}

func main() {
    html := make(Html, 5)
    html[0] = "div"
    html[1] = []byte("script")
    for index, element := range html {
        if value, ok := element.(string); ok {
            fmt.Printf("html[%d] is a string and its value is %s\n", index, value)
        } else if value, ok := element.([]byte); ok {
            fmt.Printf("html[%d] is a []byte and its value is %s\n", index, string(value))
        }
    }
}

打印出了:

代码语言:javascript
复制
html[0] is a string and its value is div
html[1] is a []byte and its value is script

5.3. 类型转换

基于类型断言,我们就可以实现类型转换了:

代码语言:javascript
复制
package main

import (
    "fmt"
    "strings"
)

func main() {
    var a interface{} = "hello world"
    fmt.Println(strings.ToUpper(a.(string)))
}

打印出了:

HELLO WORLD

上面的例子中,由于 strings.ToUpper 只接受一个 string 类型的参数,所以我们不能将 interface{} 类型的变量 a 传递给他,于是我们通过类型断言返回了 string 类型的值。

5.4. 需要注意

  1. 上面类型转换的例子中,由于断言忽略了返回的 bool 值,所以一旦转换失败,将会产生 panic
  2. 无论是否接收返回的 bool 值,一旦企图对一个 nil 值进行断言,就一定会产生 panic
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-12-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小脑斧科技博客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 引言
  • 2. 接口
  • 3. 接口的使用
    • 3.1. fmt.Printf
      • 3.2. fmt.Fprintf 与 io.Writer
        • 3.3. 通过实现 Write 方法构造字符串长度统计类型
        • 4. 接口的嵌入
        • 5. 类型断言
          • 5.1. 测试接口是否已经被实现
            • 5.2. Comma-ok 断言
              • 5.3. 类型转换
                • 5.4. 需要注意
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档