Golang 接口(interface)

Go 语言的接口遵守LSP(里氏替换原则),即 一个类型可以自由地被另一个满足相同接口的类型替换。

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

io.Writer类型是用得最广泛的接口之一,因为它提供了所有类型的写入bytes的抽象,包括文件类型,内存缓冲区,网络链接,HTTP客户端,压缩工具,哈希等等。io包中定义了很多其它有用的接口类型。Reader可以代表任意可以读取bytes的类型,Closer可以是任意可以关闭的值,例如一个文件或是网络链接。

package io
type Reader interface {
    Read(p []byte) (n int, err error)
}
type Closer interface {
    Close() error
}

type ReadWriter interface {
    Reader
    Writer
}
type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

一个类型如果拥有一个接口需要的所有方法,那么这个类型就实现了这个接口。
特例 空接口类型(interface{}),对实现它的类型没有要求,可以将任意一个值赋给空接口类型。

var any interface{}
any = true
any = 12345
any = map[string]int{}
var flagValue = flag.Duration("test", 10*time.Second, "study flag")

func main() {
    flag.Parse()
    fmt.Printf("flagValue init %v...", *flagValue)
    fmt.Println()
}

go run main.go
flagValue init 10s...

go run main.go -test 20s
flagValue init 20s...

go run main.go -test1 10s
flag provided but not defined: -test1
Usage of /tmp/go-build3985799119/b001/exe/main:
  -test duration
        study flag (default 10s)
exit status 2

需要实现 flag.Value接口的类型

package flag

// Value is the interface to the value stored in a flag.

type Value interface {
    String() string
    Set(string) error
}

具体代码如下

package main

import (
    "flag"
    "fmt"
    "time"
)

var flagValue = flag.Duration("test", 10*time.Second, "study flag")
var diyValue = diyFlagValueFunc("diyValue", "hello flag", "study diy flag")

func main() {
    flag.Parse()
    fmt.Printf("flagValue init %v...", *flagValue)
    fmt.Println()

    fmt.Printf("diyValue init %v...", *diyValue)
    fmt.Println()

}

type diyFlagValue struct {
    X string
}

func (d *diyFlagValue) String() string {
    return d.X
}

func (d *diyFlagValue) Set(s string) error {
    d.X = s + "flagSet"
    return nil
}

func diyFlagValueFunc(name string, value string, usage string) *string {
    d := &diyFlagValue{X: value}
    flag.CommandLine.Var(d, name, usage)
    return &d.X
}

go run main.go -test 10s -diyValue test
flagValue init 10s...

diyValue init testflagSet...

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

var w io.Writer

w.Write([]byte("hello")) // panic: nil pointer dereference
w = os.Stdout

调用一个包含*os.File类型指针的接口值的Write方法,使得(*os.File).Write方法被调用。这个调用输出“hello”
w.Write([]byte("hello")) // "hello"

效果和下面这个直接调用一样:
os.Stdout.Write([]byte("hello")) // "hello"
w = new(bytes.Buffer)

Write方法的调用也使用了和之前一样的机制:
w.Write([]byte("hello")) // writes "hello" to the bytes.Buffers

这次类型描述符是*bytes.Buffer,所以调用了(*bytes.Buffer).Write方法,并且接收者是该缓冲区的地址。这个调用把字符串“hello”添加到缓冲区中。
将nil赋给了接口值

接口值可以使用和!=来进行比较。两个接口值相等仅当它们都是nil值,或者它们的动态类型相同并且动态值也根据这个动态类型的操作相等。因为接口值是可比较的,所以它们可以用在map的键或者作为switch语句的操作数。

然而,如果两个接口值的动态类型相同,但是这个动态类型是不可比较的(比如切片),将它们进行比较就会失败并且panic:

var x interface{} = []int{1, 2, 3}
fmt.Println(x == x) // panic: comparing uncomparable type []int

安全的可比较类型(如基本类型和指针),不可比较的类型(如切片,映射类型,和函数),在比较接口值或者包含了接口值的聚合类型时,必须要意识到潜在的panic。同样的风险存在于使用接口作为map的键或者switch的操作数。只能比较你非常确定它们的动态值是可比较类型的接口值。

type IBird interface {
    hello()
}
type Bird struct {
}

func (p *Bird) hello() {
    fmt.Println("BirdBirdBirdBird")
}

func getBird() *Bird {
    fmt.Println("getBird  return nil")
    return nil
}

func main() {
    var i IBird
    i = getBird()

    if i == nil {
        fmt.Println("nil")
    } else {//包含nil 指针的接口值
        fmt.Println("not nil")
        fmt.Printf("%v, %T\n", i, i)
    }
}

go run main.go
getBird  return nil
not nil
<nil>, *main.Bird

</nil>
package sort

type Interface interface {
    Len() int
    Less(i, j int) bool // i, j are indices of sequence elements
    Swap(i, j int)
}

对自定义类型进行排序需要实现上述三个接口

type StringSlice []string
func (p StringSlice) Len() int           { return len(p) }
func (p StringSlice) Less(i, j int) bool { return p[i] < p[j] }
func (p StringSlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

sort.Sort(StringSlice(names))
package http

type Handler interface {
    ServeHTTP(w ResponseWriter, r *Request)
}

func ListenAndServe(address string, h Handler) error

只要实现了 Handler 的ServeHTTP 方法,就可以添加进Http 函数处理逻辑,对于更复杂的应用,一些ServeMux可以通过组合来处理更加错综复杂的路由需求

func main() {
    mux := http.NewServeMux()
    mux.Handle("/list", http.HandlerFunc(handleList))
    mux.Handle("/price", http.HandlerFunc(handlePrice))
    log.Fatal(http.ListenAndServe("localhost:8000", mux))
}

http.HandlerFunc(handleList)&#x662F;&#x4E00;&#x4E2A;&#x8F6C;&#x6362;&#x800C;&#x975E;&#x4E00;&#x4E2A;&#x51FD;&#x6570;&#x8C03;&#x7528;

package http
type HandlerFunc func(w ResponseWriter, r *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

实现了接口http.Handler的方法的函数类型。ServeHTTP方法的行为是调用了它的函数本身。因此HandlerFunc是一个让函数值满足一个接口的适配器,这里函数和这个接口仅有的方法有相同的函数签名。

type error interface {
    Error() string
}
var w io.Writer
w = os.Stdout
f := w.(*os.File)
c := w.(*bytes.Buffer)
var w io.Writer
w = os.Stdout
rw := w.(io.ReadWriter) // success
w = new(ByteCounter)
rw = w.(io.ReadWriter) // panic
func IsNotExist(err error) bool {
    return underlyingErrorIs(err, ErrNotExist)
}
ErrNotExist   = errNotExist()   // "file does not exist"

func underlyingErrorIs(err, target error) bool {
    // Note that this function is not errors.Is:
    // underlyingError only unwraps the specific error-wrapping types
    // that it historically did, not all errors implementing Unwrap().
    err = underlyingError(err)
    if err == target {
        return true
    }
    // To preserve prior behavior, only examine syscall errors.
    e, ok := err.(syscallErrorType)
    return ok && e.Is(target)
}

// underlyingError returns the underlying error for known os error types.

func underlyingError(err error) error {
    switch err := err.(type) {
    case *PathError:
        return err.Err
    case *LinkError:
        return err.Err
    case *SyscallError:
        return err.Err
    }
    return err
}

// PathError records an error and the operation and file path that caused it.

type PathError struct {
    Op   string
    Path string
    Err  error
}

func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
func writeString(w io.Writer, s string) (n int, err error) {
    type stringWriter interface {
        WriteString(string) (n int, err error)
    }
    if sw, ok := w.(stringWriter); ok { //&#x5224;&#x65AD;&#x662F;&#x5426;&#x5C5E;&#x4E8E;&#x8FD9;&#x4E2A;&#x7C7B;&#x578B;
        return sw.WriteString(s)
    }
    return w.Write([]byte(s))
}

Original: https://www.cnblogs.com/arvinhuang/p/15216477.html
Author: 平凡键客
Title: Golang 接口(interface)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/620161/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • leetcode 208. Implement Trie (Prefix Tree) 实现 Trie (前缀树) (中等)

    Trie(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼…

    数据库 2023年6月16日
    092
  • 数据库相关工作流程与工具

    在工作过程中分享数据库相关工作的过程: [En] Share the process of database-related work during the work proces…

    数据库 2023年5月24日
    095
  • MySQL——基础查询与条件查询

    基础查询 /* 语法: select 查询列表 from 表名; 类似于:System.out.println(打印东西); 1、查询列表可以是:表中的字段、常量值、表达式、函数 …

    数据库 2023年5月24日
    0105
  • java扫描某个包下的所有java类并加载

    最近在学习java的反射和注解,实际情景中需要扫描某个包下的所有java类,然后使用类加载器加载类。 基本思路,获得程序的路径扫描src下某个包内的子包和java类,实现也比较简单…

    数据库 2023年6月11日
    092
  • Mybatis-Spring源码分析

    Mybatis-Spring 博主技术有限,本文难免有错误的地方,如果您发现了欢迎评论私信指出,谢谢JAVA技术交流群:737698533 当我们使用mybatis和spring整…

    数据库 2023年6月16日
    090
  • 事务的ACID特性

    技术是为了解决问题而生的,通过事务我们可以解决以下问题: 多个操作不是一个整体操作,出现了部分执行成功的情况,导致数据的状态不一致问题(原子性) 一组操作只有部分完成,没有全部完成…

    数据库 2023年6月11日
    083
  • asp.net(C#)接MYSQL8.0版本报错的处理方法

    由于新的操作系统没有安装mysql,项目中需要使用到,于是安装了最新版本的mysql8.0.28(我安装的是社区版),如果你的项目是老项目,使用的mysql插件是比较老的版本,安装…

    数据库 2023年5月24日
    0186
  • Goroutines (一)

    Goroutines CSP communicating sequential processes Go 语言中,每一个并发执行单元叫做一个goroutine,语法上仅需要在一个普…

    数据库 2023年6月16日
    079
  • JAVA中如何取得一个数组中最大值和最小值呢?

    数组是日常开发中,常用的数据结构, 它可用于存储同一类型的数据,如:(基础类型,引用类型) 那么我们如何获取一个数组中的最大值和最小值呢? 对一些基础类型,我们可以直接使用比较, …

    数据库 2023年6月11日
    078
  • Node版本更新及切换

    Node版本升级 &#x6E05;&#x9664;npm&#x7F13;&#x5B58; npm cache clean -f n&#x6A…

    数据库 2023年6月16日
    0113
  • 自然对数

    https://zhuanlan.zhihu.com/p/71928040自然对数 https://www.youtube.com/watch?v=mZE0RmCbDe8 本文来自…

    数据库 2023年6月11日
    0109
  • 设计模式之(11)——享元模式

    继续把我们的设计模式捡起,希望我能坚持完这个系列吧,下面我们就进入正题吧。 在软件开发过程中,我们需要重复使用某个对象的时候,如果重复地new这个对象,不停地申请内存空间,会造成内…

    数据库 2023年6月14日
    064
  • 【黄啊码】教你用python画冰墩墩

    python;gutter:true; import turtle</p> <p>turtle.title('PythonBingDwenDwen…

    数据库 2023年6月16日
    055
  • MySQL 同步复制及高可用方案总结

    mysql作为应用程序的数据存储服务,要实现mysql数据库的高可用。必然要使用的技术就是数据库的复制,如果主节点出现故障可以手动的切换应用到从节点,这点相信运维同学都是知道,并且…

    数据库 2023年6月9日
    070
  • Redis-持久化

    因为Redis是内存操作,意味着掉电就GG, 所以为了保证异常重启等问题后能尽快恢复服务,还是需要一定的持久化机制来保证。Redis提供了两种持久化机制: AOF Append O…

    数据库 2023年6月11日
    099
  • 启程——博客之路

    憋了这么久还是忍不住开始写自己的博客了。。。之前总是看别人的博客,伸手党一个,但是时间久了,总有一些自己想说的话,想想分享一些技术、经验,也能记录自己的学习历程,毕竟编程这条路还很…

    数据库 2023年6月9日
    071
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球