Loading

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{}

flag.Value

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 类型

需要实现 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...

接口值

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

  1. 定义了变量w ,类型和值都是nil
var w io.Writer

w.Write([]byte("hello")) // panic: nil pointer dereference
  1. 将一个os.File类型的值赋给变量w,这个赋值过程调用了一个具体类型到接口类型的隐式转换,这和显式的使用io.Writer(os.Stdout)是等价的。这个接口值的动态类型被设为os.File指针的类型描述符,它的动态值持有os.Stdout的拷贝
w = os.Stdout

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

效果和下面这个直接调用一样:
os.Stdout.Write([]byte("hello")) // "hello"
  1. 给接口值赋了一个bytes.Buffer类型的值,现在动态类型是bytes.Buffer并且动态值是一个指向新分配的缓冲区的指针
w = new(bytes.Buffer)

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

这次类型描述符是*bytes.Buffer,所以调用了(*bytes.Buffer).Write方法,并且接收者是该缓冲区的地址。这个调用把字符串“hello”添加到缓冲区中。
  1. 将nil赋给了接口值,这个重置将它所有的部分都设为nil值,把变量w恢复到和它之前定义时相同的状态
将nil赋给了接口值

接口值比较

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

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

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

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

一个不包含任何值的nil接口值和一个刚好包含nil指针的接口值是不同的。

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

sort.Interface

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))

http.Handler

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)是一个转换而非一个函数调用

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

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

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

error

type error interface {
    Error() string
}

类型断言

  1. 具体类型的类型断言从它的操作对象中获得具体的值。如果检查失败,接下来这个操作会抛出panic
var w io.Writer
w = os.Stdout
f := w.(*os.File)     
c := w.(*bytes.Buffer)
  1. 对一个接口类型的类型断言改变了类型的表述方式,改变了可以获取的方法集合(通常更大),但是它保留了接口值内部的动态类型和值的部分。
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 { //判断是否属于这个类型 
        return sw.WriteString(s) 
    }
    return w.Write([]byte(s)) 
}

Go语言圣经

posted @ 2021-09-01 21:22  平凡键客  阅读(752)  评论(0编辑  收藏  举报