Go进阶之路——接口

接口

接口类型是一种抽象类型,是方法的集合,其他类型实现了这些方法就是实现了这个接口。

接口类型的值可以存放实现这些方法的任何值。

/* 定义接口 */
type interface_name interface {
   method_name1 [return_type]
   method_name2 [return_type]
   method_name3 [return_type]
   ...
   method_namen [return_type]
}

简单示例:打印不同几何图形的面积和周长

package main

import (
    "fmt"
    "math"
)

type geometry interface {
    area() float32
    perim() float32
}

type rect struct {
    len, wid float32
}

func (r rect) area() float32 {
    return r.len * r.wid
}

func (r rect) perim() float32 {
    return 2 * (r.len + r.wid)
}

type circle struct {
    radius float32
}

func (c circle) area() float32 {
    return math.Pi * c.radius * c.radius
}

func (c circle) perim() float32 {
    return 2 * math.Pi * c.radius
}

func show(name string, param interface{}) {
    switch param.(type) {
    case geometry:
        // 类型断言
        fmt.Printf("area of %v is %v \n", name, param.(geometry).area())
        fmt.Printf("perim of %v is %v \n", name, param.(geometry).perim())
    default:
        fmt.Println("wrong type!")
    }
}

func main() {
    rec := rect{
        len: 1,
        wid: 2,
    }
    show("rect", rec)

    cir := circle{
        radius: 1,
    }
    show("circle", cir)

    show("test", "test param")
}

输出结果:

对于函数 show(name string, param interface{}),其传入的参数param可以是任意类型,只有实现了对应接口中的所有方法,才算是实现了该接口(即存在该类型为接收者的接口中对应的所有方法)。param.(type)获得接口类型名称。

package main

import (
	"fmt"
	"math"
)

type Abser interface {
	Abs() float64
}

func main() {
	var a Abser
	f := MyFloat(-math.Sqrt2)
	v := Vertex{3, 4}

	a = f  // a MyFloat 实现了 Abser
	fmt.Println(a.Abs())

	a = &v // a *Vertex 实现了 Abser
	// 下面一行,v 是一个 Vertex(而不是 *Vertex)
	// 所以没有实现 Abser。
	//a = v
	fmt.Println(a.Abs())
}

type MyFloat float64

func (f MyFloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}

type Vertex struct {
	X, Y float64
}

func (v *Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

注意: 如上代码中, 由于 Vertex的 Abs 只定义在 *Vertex(指针类型) 上, 所以 Vertex(值类型) 不满足 `Abser`。

接口中可以内嵌接口(隐式接口)

类型通过实现那些方法来实现接口。 没有显式声明的必要;所以也就没有关键字“implements“。

隐式接口解藕了实现接口的包和定义接口的包:互不依赖。

因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义。

对第一个例子做以下修改:

  • 首先添加 tmp 接口,该接口定义了 area() 方法
  • 将 tmp 作为 geometry 接口中的匿名成员,并且将 geometry 接口中原本定义的 area() 方法删除

完成以上两步后,geometry 接口将会拥有 tmp 接口所定义的所有方法。运行结果和上述例子相同。

package main

import (
	"fmt"
	"math"
)

type tmp interface{
	area() float32
}

type geometry interface {
	// area() float32
	tmp
	perim() float32
}

type rect struct {
	len, wid float32
}

func (r rect) area() float32 {
	return r.len * r.wid
}

func (r rect) perim() float32 {
	return 2 * (r.len + r.wid)
}

type circle struct {
	radius float32
}

func (c circle) area() float32 {
	return math.Pi * c.radius * c.radius
}

func (c circle) perim() float32 {
	return 2 * math.Pi * c.radius
}

func show(name string, param interface{}) {
	switch param.(type) {
	case geometry:
		// 类型断言
		fmt.Printf("area of %v is %v \n", name, param.(geometry).area())
		fmt.Printf("perim of %v is %v \n", name, param.(geometry).perim())
	default:
		fmt.Println("wrong type!")
	}
}

func main() {
	rec := rect{
		len: 1,
		wid: 2,
	}
	show("rect", rec)

	cir := circle{
		radius: 1,
	}
	show("circle", cir)

	show("test", "test param")
}

Stringers

一个普遍存在的接口: fmt 包中定义的 Stringer

type Stringer interface {
    String() string
}

Stringer 是一个可以用字符串描述自己的类型。`fmt`包 (还有许多其他包)使用这个来进行输出。

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func main() {
	a := Person{"Arthur Dent", 42}
	z := Person{"Zaphod Beeblebrox", 9001}
	fmt.Println(a, z)
}

输出结果:

实现Stringer接口

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func (p Person) String() string {
	return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
	a := Person{"Arthur Dent", 42}
	z := Person{"Zaphod Beeblebrox", 9001}
	fmt.Println(a, z)
}

输出结果:

如此实现了自定义的输出方式。

注:Sprintf 根据格式说明符设置格式,并返回结果字符串。

小练习:

让 IPAddr 类型实现 fmt.Stringer 以便用点分格式输出地址。

例如,`IPAddr{1,`2,`3,`4}` 应当输出 `"1.2.3.4"`。

package main

import "fmt"

type IPAddr [4]byte

func (a IPAddr)String() string{
	return fmt.Sprintf("%v.%v.%v.%v",a[0],a[1],a[2],a[3])
}

func main() {
	addrs := map[string]IPAddr{
		"loopback":  {127, 0, 0, 1},
		"googleDNS": {8, 8, 8, 8},
	}
	for n, a := range addrs {
		fmt.Printf("%v: %v\n", n, a)
	}
}

错误error

Go 程序使用 error 值来表示错误状态。

与 fmt.Stringer 类似,`error` 类型是一个内建接口:

type error interface {
    Error() string
}

(与 fmt.Stringer 类似,`fmt` 包在输出时也会试图匹配 `error`。)

package main

import (
	"fmt"
	"time"
)

type MyError struct {
	When time.Time
	What string
}

func (e *MyError) Error() string {
	return fmt.Sprintf("at %v, %s",
		e.When, e.What)
}

func run() error {
	return &MyError{
		time.Now(),
		"it didn't work",
	}
}

func main() {
	if err := run(); err != nil {
		fmt.Println(err)
	}
}

输出结果:

通常函数会返回一个 error 值,调用的它的代码应当判断这个错误是否等于 `nil`, 来进行错误处理。

i, err := strconv.Atoi("42")
if err != nil {
    fmt.Printf("couldn't convert number: %v\n", err)
}
fmt.Println("Converted integer:", i)

error 为 nil 时表示成功;非 nil 的 error 表示错误。

注:strconv 包中结构体 NumError 实现了 error 接口,Atoi 函数将字符串转换为对应数字。

package main

import (
	"fmt"
	"strconv"
)

func main() {
	i, err := strconv.Atoi("42")
	if err != nil {
		fmt.Printf("couldn't convert number: %v\n", err)
	}
	fmt.Println("Converted integer:", i)

	i, err = strconv.Atoi("i")
	if err != nil {
		fmt.Printf("couldn't convert number: %v\n", err)
	}
	fmt.Println("Converted integer:", i)
}

 输出结果:

小练习:

从之前的练习中复制 Sqrt 函数,并修改使其返回 error 值。

Sqrt 接收到一个负数时,应当返回一个非 nil 的错误值。复数同样也不被支持。

创建一个新类型

type ErrNegativeSqrt float64

为其实现

func (e ErrNegativeSqrt) Error() string

使其成为一个 `error`, 该方法就可以让 ErrNegativeSqrt(-2).Error() 返回 `"cannot Sqrt negative number: -2"`。

注意: 在 Error 方法内调用 fmt.Sprint(e) 将会让程序陷入死循环。可以通过先转换 e 来避免这个问题:`fmt.Sprint(float64(e))`。


解释:

因为 ErrNegativeSqrt 实现了 error 接口,e也是error类型,故 fmt.Sprint(e)将调用e.Error()e转换为字符串。如果Error()方法调用fmt.Sprint(e),则程序将递归直到内存溢出。相当于:

func (e ErrNegativeSqrt) Error() string {
    return fmt.Sprint(e.Error())
}

故通过将e转换成一个非error类型(未实现Error接口)的值可以避免这种情况。

实际上在Error方法中把error值直接传递给fmt包中Print相关的函数都会导致无限循环。

看源码:print.go 中,Sprint 方法和 Sprintf 方法都用到了 printArg 方法,printArg 方法中用到了 handleMethods方法,该方法部分源码如下:

else {
		// If a string is acceptable according to the format, see if
		// the value satisfies one of the string-valued interfaces.
		// Println etc. set verb to %v, which is "stringable".
		switch verb {
		case 'v', 's', 'x', 'X', 'q':
			// Is it an error or Stringer?
			// The duplication in the bodies is necessary:
			// setting handled and deferring catchPanic
			// must happen before calling the method.
			switch v := p.arg.(type) {
			case error:
				handled = true
				defer p.catchPanic(p.arg, verb, "Error")
				p.fmtString(v.Error(), verb)
				return

			case Stringer:
				handled = true
				defer p.catchPanic(p.arg, verb, "String")
				p.fmtString(v.String(), verb)
				return
			}
		}
	}
可见 p.fmtString(v.Error(), verb),如果传入的接口是error类型,则会调用其Error方法。

修改 Sqrt 函数,使其接受一个负数时,返回 ErrNegativeSqrt 值。

package main

import (
	"fmt"
)
type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string{
	return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
}

func Sqrt(x float64) (float64, error) {
	if x<0 {
		return 0, ErrNegativeSqrt(x)
	}
	z := float64(1)
	for {
		temp := z - (z*z-x)/(2*z)
		if temp==z || (temp>z && temp-z<0.00000001) || (temp<z && z-temp<0.00000001) {
			z = temp
			break
		}
		z = temp
	}
	return z, nil
}

func main() {
	fmt.Println(Sqrt(2))
	fmt.Println(Sqrt(-2))
}

输出结果:

Readers

io 包指定了 io.Reader 接口, 它表示从数据流结尾读取。

Go 标准库包含了这个接口的许多实现, 包括文件、网络连接、压缩、加密等等。

io.Reader 接口有一个 Read 方法:

func (T) Read(b []byte) (n int, err error)

Read 用数据填充指定的字节 slice,并且返回填充的字节数和错误信息。 在遇到数据流结尾时,返回 io.EOF 错误。

例子代码创建了一个 strings.Reader。 并且以每次 8 字节的速度读取它的输出。

package main

import (
	"fmt"
	"io"
	"strings"
)

func main() {
	r := strings.NewReader("Hello, Reader!")

	b := make([]byte, 8)
	for {
		n, err := r.Read(b)
		fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
		fmt.Printf("b[:n] = %q\n", b[:n])
		if err == io.EOF {
			break
		}
	}
}

输出结果:

源码如下:

func (r *Reader) Read(b []byte) (n int, err error) {
	if r.i >= int64(len(r.s)) {
		return 0, io.EOF
	}
	r.prevRune = -1
	n = copy(b, r.s[r.i:])
	r.i += int64(n)
	return
}

Web 服务器

包 http 通过任何实现了 http.Handler 的值来响应 HTTP 请求:

package http

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

在这个例子中,类型 Hello 实现了 `http.Handler`。

访问 http://localhost:4000/ 会看到来自程序的问候。

package main

import (
	"fmt"
	"log"
	"net/http"
)

type Hello struct{}

func (h Hello) ServeHTTP(
	w http.ResponseWriter,
	r *http.Request) {
	fmt.Fprint(w, "Hello!")
}

func main() {
	var h Hello
	err := http.ListenAndServe("localhost:4000", h)
	if err != nil {
		log.Fatal(err)
	}
}

注:

Fprint 采用默认格式将其参数格式化并写入w。如果两个相邻的参数都不是字符串,会在它们的输出之间添加空格。返回写入的字节数和遇到的任何错误。

Fatal 等同于Print(),然后调用os.Exit(1)。

 

练习:HTTP 处理

实现下面的类型,并在其上定义 ServeHTTP 方法。在 web 服务器中注册它们来处理指定的路径。

type String string

type Struct struct {
    Greeting string
    Punct    string
    Who      string
}

例如,可以使用如下方式注册处理方法:

http.Handle("/string", String("I'm a frayed knot."))
http.Handle("/struct", &Struct{"Hello", ":", "Gophers!"})
package main

import (
	"fmt"
	"log"
	"net/http"
)

type String string

func (s String) ServeHTTP(
	w http.ResponseWriter,
	r *http.Request) {
	fmt.Fprint(w, s)
}

type Struct struct {
	Greeting string
	Punct    string
	Who      string
}
func (s *Struct) ServeHTTP(
	w http.ResponseWriter,
	r *http.Request) {
	fmt.Fprint(w, s.Greeting,s.Punct,s.Who)
}

func main() {
	http.Handle("/string", String("I'm a frayed knot."))
	http.Handle("/struct", &Struct{"Hello", ":", "Gophers!"})

	//http.Handle calls here
	log.Fatal(http.ListenAndServe("localhost:4000", nil))
}

图片

Package image 定义了 Image 接口:

package image

type Image interface {
    ColorModel() color.Model
    Bounds() Rectangle
    At(x, y int) color.Color
}

*注意*:`Bounds` 方法的 Rectangle 返回值实际上是一个 image.Rectangle, 其定义在 image 包中。

(参阅文档了解全部信息。)

color.Color 和 color.Model 也是接口,但是通常因为直接使用预定义的实现 image.RGBA 和 image.RGBAModel 而被忽视了。这些接口和类型由image/color 包定义。

package main

import (
	"fmt"
	"image"
)

func main() {
	m := image.NewRGBA(image.Rect(0, 0, 100, 100))
	fmt.Println(m.Bounds())
	fmt.Println(m.At(0, 0).RGBA())
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值