接口(interface)
[TOC]
接口类型是对其它类型行为的抽象和概括, 接口把实现分离, 可以此来实现面向对象编程的多态.
Golang接口独特之处在于它是隐式实现的, 具体类型只需实现接口定义的方法,而不用在定义时指明满足的接口类型, 好处之一就是对已经存在的类型, 可根据其方法定义某种接口, 从而让该类型自动满足接口定义而无需改变类型定义.
接口是契约
接口类型是一种抽象类型, 隐藏内部实现而只暴露方法, 这种封装和抽象是面向对象编程思想的精髓之一. 而就算是对面向对象编程的批判, 也说明了这一点. 对对象的抽象, 可能并不在乎它是什么, 而在于它能做什么, 比如我们说到"杯子", 不需要定义内部细节, 只需具有盛水喝水功能, 定义一个用于喝水的方法, 就可以称为杯子, 也许长得像碗.
- 一个常见的例子就是格式化, 以下两个函数分别格式化输出到标准输出和字符串, 它们调用了同一个函数
Fprintf
package fmt
func Fprintf(w io.Writer, format string, args ...interface{}) (int, error)
func Printf(format string, args ...interface{}) (int, error) {
return Fprintf(os.Stdout, format, args...)
}
func Sprintf(format string, args ...interface{}) string {
var buf bytes.Buffer
Fprintf(&buf, format, args...)
return buf.String()
}
- 而调用的左边第一个参数分别为
os.Stdout
和bytes.Buffer
,这里借助接口io.Writer
实现, 使得fmt.Fprintf
仅仅通过io.Writer接口来保证合约约束行为, 因而可以输出到不同类型
package io
// Writer is the interface that wraps the basic Write method.
type Writer interface {
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// Write must return a non-nil error if it returns n < len(p).
// Write must not modify the slice data, even temporarily.
//
// Implementations must not retain p.
Write(p []byte) (n int, err error)
}
- io.Writer接口定义了函数Fprintf和这个函数调用者之间的约定,无论传递的是何种类型, 它都必须要实现这个接口, 因内部用到该接口
Write
签名方法
接口类型
接口类型是描述一系列方法的集合,实现了这些方法的类型就是该接口的实例.
-
io.Writer
提供了所有的类型写入bytes的抽象,包括文件类型,内存缓冲区,网络链接,HTTP客户端,压缩工具,哈希等等 -
io.Reader
可以代表任意可以读取bytes的类型 -
io.Closer
可以是任意可以关闭的值,例如一个文件或是网络链接 - 可组合这些接口得到新的接口定义:
// 嵌套的形式
type ReadWriter interface {
Reader
Writer
}
type ReadWriteCloser interface {
Reader
Writer
Closer
}
// 非嵌套的形式
type ReadWriter interface {
Read(p []byte) (n int, err error)
Write(p []byte) (n int, err error)
}
- 赋值指定接口
var w io.Writer
w = os.Stdout // OK: *os.File has Write method
w = new(bytes.Buffer) // OK: *bytes.Buffer has Write method
w = time.Second // compile error: time.Duration lacks Write method
var rwc io.ReadWriteCloser
rwc = os.Stdout // OK: *os.File has Read, Write, Close methods
rwc = new(bytes.Buffer) // compile error: *bytes.Buffer lacks Close method
//接口赋值给接口
w = rwc
- 方法的指针接收者(
func(*T) method()
的*T
), 在T类型的参数上调用一个*T
的方法是合法的- 前提这个参数是一个变量,可以寻址, 编译器隐式的获取了它的地址
- 只是语法糖: T并不具有 *T的方法, T实现的接口少于 *T
- godoc -analysis=type tool 分析展示类型方法等
type IntSet struct { /* ... */ }
func (*IntSet) String() string
var _ = IntSet{}.String() // compile error: String requires *IntSet receiver
// equals to &s.String()
var s IntSet
var _ = s.String() // OK: s is a variable and &s has a String method
var _ fmt.Stringer = &s // OK
var _ fmt.Stringer = s // compile error: IntSet lacks String method
- interface{}, 空接口, 任何类型的对象都可以赋值给空接口类型变量
- 非空的接口类型比如io.Writer经常被指针类型实现, 特别是改变接收者时
- flag.Value接口, 定义此接口可用于flag命令行参数解析
package flag
// Value is the interface to the value stored in a flag.
type Value interface {
String() string
Set(string) error
}
接口值
- 接口零值nil, 可比较:
if reader !=nil
type | nil |
---|---|
value | nil |
- 赋值os.File
type | *os.File |
---|---|
value | pointer |
- 接口值可以使用
==
和!=
比较:- 两个接口值相等仅当它们都是nil值或者它们的动态类型相同并且动态值也根据这个动态类型的
==
操作相等 - 使用fmt包的%T动作可打印动态类型
- 两个接口值相等仅当它们都是nil值或者它们的动态类型相同并且动态值也根据这个动态类型的
// 动态类型不可比较会panic
var x interface{} = []int{1, 2, 3}
fmt.Println(x == x) // panic: comparing uncomparable type []int
- 陷阱! 一个包含nil指针值的接口不是nil接口
func print(writer io.Writer) {
if writer != nil {
writer.Write("done\n")
}
}
var buf *bytes.Buffer
//panic, buf不是nil interface, 是一个value为nil的Writer, 函数中if writer != nil
print(buf)
sort.Interafce接口
内置的排序算法需要知道以下三点:
- 序列的长度
- 元素比较方法
- 交换两个元素的方式
package sort
type Interface interface {
Len() int
Less(i, j int) bool // i, j are indices of sequence elements
Swap(i, j int)
}
实现以上接口就可以用内置的sor.Sort
排序
http.Handler接口
package http
type Handler interface {
ServeHTTP(w ResponseWriter, r *Request)
}
func ListenAndServe(address string, h Handler) error
- 实现该接口
func main() {
db := database{"shoes": 50, "socks": 5}
log.Fatal(http.ListenAndServe("localhost:8000", db))
}
type dollars float32
func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) }
type database map[string]dollars
func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {
for item, price := range db {
fmt.Fprintf(w, "%s: %s\n", item, price)
}
}
- 简单的按照路径路由:
func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {
switch req.URL.Path {
case "/list":
for item, price := range db {
fmt.Fprintf(w, "%s: %s\n", item, price)
}
case "/price":
item := req.URL.Query().Get("item")
price, ok := db[item]
if !ok {
w.WriteHeader(http.StatusNotFound) // 404, 先写Header
fmt.Fprintf(w, "no such item: %q\n", item)
return
}
fmt.Fprintf(w, "%s\n", price)
default:
w.WriteHeader(http.StatusNotFound) // 404
fmt.Fprintf(w, "no such page: %s\n", req.URL)
}
}
- 请求多路器ServeMux, 替换以上
case
更加清晰实用
func main() {
db := database{"shoes": 50, "socks": 5}
mux := http.NewServeMux()
mux.Handle("/list", http.HandlerFunc(db.list))
mux.Handle("/price", http.HandlerFunc(db.price))
log.Fatal(http.ListenAndServe("localhost:8000", mux))
}
type database map[string]dollars
func (db database) list(w http.ResponseWriter, req *http.Request) {
for item, price := range db {
fmt.Fprintf(w, "%s: %s\n", item, price)
}
}
func (db database) price(w http.ResponseWriter, req *http.Request) {
item := req.URL.Query().Get("item")
price, ok := db[item]
if !ok {
w.WriteHeader(http.StatusNotFound) // 404
fmt.Fprintf(w, "no such item: %q\n", item)
return
}
fmt.Fprintf(w, "%s\n", price)
}
price
和list
类似于http.Handler的方法, 但本身不能直接作为Handler接口参数调用, http.HandlerFunc(db.list)
是强制转换成http.HandlerFunc
, 其中http.HandleFunc
定义如下:
package http
type HandlerFunc func(w ResponseWriter, r *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
精髓来了: 这是个实现了http.Handler接口的函数类型, ServeHTTP 调用它本身的函数, HandleFunc
是一个适配器, 将与接口方法具有相同函数签名的函数转换成一个接口实例, database也得以用两种方式满足http.Handler
接口
另外, ServeMux可用HandleFunc方法简化调用:
mux.HandleFunc("/price", db.price)
error接口
type error interface {
Error() string
}
创建error的方法, errors.New
和fmt.Errorf
:
package fmt
import "errors"
func Errorf(format string, args ...interface{}) error {
return errors.New(Sprintf(format, args...))
}
syscall的Errno
package syscall
type Errno uintptr // operating system error code
var errors = [...]string{
1: "operation not permitted", // EPERM
2: "no such file or directory", // ENOENT
3: "no such process", // ESRCH
// ...
}
func (e Errno) Error() string {
if 0 <= int(e) && int(e) < len(errors) {
return errors[e]
}
return fmt.Sprintf("errno %d", e)
}
类型断言
语法: x.(T)被称为断言类型
- 断言接口是否某种动态类型, 检查接口的动态类型
var w io.Writer
w = os.Stdout
f := w.(*os.File) // success: f == os.Stdout
c := w.(*bytes.Buffer) // panic: interface holds *os.File, not *bytes.Buffer
- 断言是否满足某接口类型
var w io.Writer
w = os.Stdout
rw := w.(io.ReadWriter) // success: *os.File has both Read and Write
w = new(ByteCounter)
rw = w.(io.ReadWriter) // panic: *ByteCounter has no Read method
- bool返回代替panic
var w io.Writer = os.Stdout
f, ok := w.(*os.File) // success: ok, f == os.Stdout
b, ok := w.(*bytes.Buffer) // failure: !ok, b == nil
基于类型断言判断错误类型
- 大部分情况不要根据error.String()去判断错误类型, 而应该使用专门的类型来描述结构化的错误。如os.PathError
import (
"errors"
"syscall"
)
var ErrNotExist = errors.New("file does not exist")
// IsNotExist returns a boolean indicating whether the error is known to
// report that a file or directory does not exist. It is satisfied by
// ErrNotExist as well as some syscall errors.
func IsNotExist(err error) bool {
if pe, ok := err.(*PathError); ok {
err = pe.Err
}
return err == syscall.ENOENT || err == ErrNotExist
}
通过类型断言查询行为
以下Write时强制类型转换发生临时copy行为
func writeHeader(w io.Writer, contentType string) error {
if _, err := w.Write([]byte("Content-Type: ")); err != nil {
return err
}
if _, err := w.Write([]byte(contentType)); err != nil {
return err
}
// ...
}
而某些io.Writer
类型具有WriteString()
方法会避免这种拷贝, 但不能假设所有Writer都具有这个方法, 这时候可以加入一个接口类型断言判断 (标准库有io.WriteString
)
// writeString writes s to w.
// If w has a WriteString method, it is invoked instead of w.Write.
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) // avoid a copy
}
return w.Write([]byte(s)) // allocate temporary copy
}
func writeHeader(w io.Writer, contentType string) error {
if _, err := writeString(w, "Content-Type: "); err != nil {
return err
}
if _, err := writeString(w, contentType); err != nil {
return err
}
// ...
}
另外一个例子, fmt.Fprintf区分error
和Stringer
package fmt
func formatOneValue(x interface{}) string {
if err, ok := x.(error); ok {
return err.Error()
}
if str, ok := x.(Stringer); ok {
return str.String()
}
// ...all other types...
}
type switch
switch x.(type) {
case nil: // ...
case int, uint: // ...
case bool: // ...
case string: // ...
default: // ...
}
或者扩展形式, 这时候通过类型断言访问提取的值:
// x可判断类型, 且通过此变量访问实际类型的值
// switch x := x.(type) { /* ... */ }
func sqlQuote(x interface{}) string {
switch x := x.(type) {
case string:
return sqlQuoteString(x) // (not shown)
default:
panic(fmt.Sprintf("unexpected type %T: %v", x, x))
}
}
建议
- 不做不必要的抽象, 大部分情况下, 只有一种实现时无需定义接口
- 例外: 需要解耦具体实现的依赖时, 一种实现也可定义接口(实现不能存在于定义该接口的包中时)
- 一个原则, 不定义过多方法, 小接口更容易实现, 只要需要的