Go语言的method和interface(下)

目录

写在前面

  • 在上篇中,基本介绍好了method和interface
  • 这一篇中,将介绍一些与之相关的技术
  • 这一篇有很多干货,因为遇到一些问题,为此我去翻了源码

类型断言(type assertion)

  • 既然可以用interface来代替具体的receiver来执行method,那么我们又怎么确保interface代替的receiver就是我们想要的类型呢?
  • 这个时候就用到了类型断言了
  • 断言的普通格式(i是interface,T是猜测的类型,t就是变量了):t := i.(T)
  • 如果断言正确,那么t会获得相应的T类型的值;否则,会触发一个panic(就像报错一样)
  • 断言的完整格式(i是interface,T是猜测的类型,t就是值变量了,ok是用来判断断言正确与否):t, ok := i.(T)
  • 如果断言正确,那么t会获得相应的T类型的值,ok将为true;否则,t获取相应的零值,而ok为false
package main

import (
        "fmt"
        "math"
)

func main() {
        var i interface{} = float64(math.Pi)

        f := i.(float64)
        fmt.Println(f)

        //var ok bool
        //var a int
        a, ok1 := i.(int)
        fmt.Println(a, ok1)

        //var b string
        b, ok2 := i.(string)
        fmt.Println(b, ok2)

        b = i.(string)
        fmt.Println(b)
}
  • 这个机制就像map中判断值是否存在的操作
    • 查找map中是否存在某个key值的映射
    m := make(map[string]int)
    m["apple"] = 2
    elem, ok := m["apple"]  //在能找到相应值的时候elem为相应的value,ok为true;否则,elem为相应的零值,而ok为false
  • 类型断言还能配合switch语句来使用
package main

import (
        "fmt"
        "math"
)

func do(i interface{}) {
        switch v := i.(type) {
        case int:
                fmt.Printf("%T %v\n", v, v)
        case string:
                fmt.Printf("%T %v\n", v, v)
        default:
                fmt.Printf("%T %v\n", v, v)
        }
}

func main() {
        do(math.Pi)
        do("hello")
        do(false)
        do(21)
}
  • 注意:type是关键词

Stringer

  • 这可以说是用得最广泛的一个interface了
  • Stringer interface是内置的interface
type Stringer interface {
    String() string
}
  • 官方原话:A Stringer is a type that can describe itself as a string. The fmt package (and many others) look for this interface to print values
  • 当使用类似fmt.Println来输出某个string类型的值,会自动调用String()
  • 官方原话: The fmt package (and many others) look for this interface to print values.
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.String(), z.String())
       //等价 fmt.Println(a.String(), z.String())
}
  • Stringer是interface,这也意味着,你可以自己实现String()
package main

import "fmt"

type IPAddr [4]byte

// TODO: Add a "String() string" method to IPAddr.
func (ip IPAddr) String() string {
    return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])
}

func main() {
    hosts := map[string]IPAddr{
        "loopback":  {127, 0, 0, 1},
        "googleDNS": {8, 8, 8, 8},
    }

    for name, ip := range hosts {
        fmt.Printf("%v: %v\n", name, ip)
    }
}

Error

  • 关于错误处理,也是用到了interface
  • 和Stringer一样,error也是内置的interface
type error interface {
    Error() string
}
  • 当使用类似fmt.Println来输出某个error interface类型的值,会自动调用Error()
  • 官方原话:As with fmt.Stringer, the fmt package looks for the error interface when printing values
package main

import "fmt"

type MyError struct {
        content string
}

func (e *MyError) Error() string {
        return fmt.Sprintf("the error is %s", e.content)
}

func main() {
        e := &MyError{"hello"}
        fmt.Println(e)
}
  • 一个更加复杂的例子
package main

import "fmt"

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string {
    //这样是不行的,因为每次用e赋值给error时又会进行Error(),导致死循环
        //return fmt.Sprintf("cannot Sqrt negative number: %v", e)

        //下面是允许的
    //return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
    return fmt.Sprintf("cannot Sqrt negative number: %f", e)
}

func Sqrt(x float64) (float64, error) {
    if x < 0 {
        return 0, ErrNegativeSqrt(x)
    }

    z := 1.0
    for i := 0; i<10; i++ {
        z -= (z*z - x) / (2*z)
    }
    return z, nil

}

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

Reader

  • Reader也是interface
  • 以官方教程提供的为例,
  • 用package io中的io.Reader来作为interface(它的实现是多种多样的,在各种package中都有,例如例子中的strings)
  • io.Reader interface里提供了Read()这个成员,而例子中用到的形式是这样的:
func (T) Read(b []byte) (n int, err error)
  • 在下面的例子中T就是r的类型了,这样r才能调用Read
  • 当读数据到末尾时,将传递一个error:io.EOF
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
        }
    }
}
  • 其实这里面并没有说明白,看了这个例子,可能大多数人会选择死记硬背
  • 这个需要查看源码
    io.go
    io,go
  • 在io.go中,可以看到提供了相应的interface,而且名字是Reader
    strings.go
    strings.go
    strings.go
  • 而在strings.go中,可以看到它也是import了io这个package,
  • 而且还定义了Reader结构体,注释那里提示说这个结构体实现了io.Reader(这个操作有点骚。。)
  • 但是就我看代码的过程中并不认为这种所谓的实现,只是strings里自己做了receiver:Reader和method: Read而已
  • 我的看法:用io package中的Reader接口定义一个变量后,再用strings中的Reader结构体类型变量进行赋值,这时才能算是用了strings的Reader来实现了io.Reader这个interface
  • 例子中给出的官方解释感觉是挖了坑,实际上,只是因为用上了io.EOF,才需要import io
  • 这里貌似体现了软件工程中的弱耦合。并不要求一定要去用strings.Reader struct来实现io.Reader interface,不实现也能单独使用strings.Reader struct
  • 那么例子中提到的用io.Reader又有何用?或者说接口的意义又何在?
  • 这里仅给出其中一点,也是教程练习中的例子
/*
  Exercise: Readers
  Implement a Reader type that emits an infinite stream of the ASCII character 'A'.
*/
package main

import "golang.org/x/tour/reader"

type MyReader struct{}

// TODO: Add a Read([]byte) (int, error) method to MyReader.
func (r MyReader) Read(bytes []byte) (int, error) {
    for i := range bytes {
        bytes[i] = 65
    }
    return len(bytes), nil
}

func main() {
    reader.Validate(MyReader{})
}
  • 这个例子中,编程人员可以根据interface来自定义MyReader及相关method
  • 还有一个比较有趣的联系,讲的是rot13这种简单的加密算法,这里也顺带说一下
/*
Exercise: rot13Reader
A common pattern is an io.Reader that wraps another io.Reader, modifying the stream in some way.

For example, the gzip.NewReader function takes an io.Reader (a stream of compressed data) and returns a *gzip.Reader that also implements io.Reader (a stream of the decompressed data).

Implement a rot13Reader that implements io.Reader and reads from an io.Reader, modifying the stream by applying the rot13 substitution cipher to all alphabetical characters.

The rot13Reader type is provided for you. Make it an io.Reader by implementing its Read method.
*/
package main

import (
    "io"
    "os"
    "strings"
)

type rot13Reader struct {
    r io.Reader
}

func (rot *rot13Reader) Read(b []byte) (n int, err error) {
    n, err = rot.r.Read(b)   //this Read() is implemented by strings.Reader
    for i,v := range b {
        if 'a' <= v && v <= 'z' {
            b[i] = 'a' + (((b[i] - 'a' + 13)) % 26)
        }else if 'A' <= v && v <= 'Z' {
            b[i] = 'A' + (((b[i] - 'A' + 13)) % 26)
        }
    } 
    return
}

func main() {
    s := strings.NewReader("Lbh penpxrq gur pbqr!")
    r := rot13Reader{s}   // rot13Reader.r := s
    io.Copy(os.Stdout, &r)
}
  • 问题说的是rot13这种加密方式,对于字母而言,简单记忆就是加13模26
  • 解密方法一样
  • 难度在于搞清楚interface和实现它的recciver的关系,我的代码注释中已经给出了很大的提示了
  • main()中,一开始创建了strings.Reader,接着用它给自定义的rot13Reader类型变量初始化
  • 初始化过程是用s strings.Reader赋值给rot13Reader类型中成员r io.Reader(r io.Reader这个接口类型变量被s strings.Reader实现了)
  • io,Copy是做什么的呢?我也去翻了源码,篇幅较长,这里只给出一小部分。可以确定的是它用了Read()
func Copy(dst Writer, src Reader) (written int64, err error) {
    return copyBuffer(dst, src, nil)
}
  • 由于源码中用了src,Read(),所以如果src这个receiver没有去实现method:Read()的话,程序是无法进行的

Image

  • Image interface:
package image

type Image interface {
    ColorModel() color.Model
    Bounds() Rectangle
    At(x, y int) color.Color
}
  • image里规定了显示图片需要的数据,例如像素,颜色等

学会看文档

  • 这是本篇的最后了
  • 因为教程中最后给的习题真的需要去看文档才能解决
  • 这里稍微介绍一下
  • 先看问题:
/*
Exercise: Images
Remember the picture generator you wrote earlier? Let's write another one, but this time it will return an implementation of image.Image instead of a slice of data.

Define your own Image type, implement the necessary methods, and call pic.ShowImage.

Bounds should return a image.Rectangle, like image.Rect(0, 0, w, h).

ColorModel should return color.RGBAModel.

At should return a color; the value v in the last picture generator corresponds to color.RGBA{v, v, 255, 255} in this one.

*/

package main

import "golang.org/x/tour/pic"

type Image struct{}

func main() {
    m := Image{}
    pic.ShowImage(m)
}
  • 这个问题就是要你实现Image interface,并且出题者已经准备好了显示图片的方法恶劣:pic.ShowImage
  • 前面的教程已经给出提示了,在文档中查到的Image interface具体如下:
type Image interface {
        // ColorModel returns the Image's color model.
        ColorModel() color.Model
        // Bounds returns the domain for which At can return non-zero color.
        // The bounds do not necessarily contain the point (0, 0).
        Bounds() Rectangle
        // At returns the color of the pixel at (x, y).
        // At(Bounds().Min.X, Bounds().Min.Y) returns the upper-left pixel of the grid.
        // At(Bounds().Max.X-1, Bounds().Max.Y-1) returns the lower-right one.
        At(x, y int) color.Color
}
  • 这里埋一个坑:实现他们的receiver是pointer receiver还是value receiver呢?
  • 根据文档注释,要实现这个interface,就要实现三个method(注意到返回值有三个,这点很重要):
func (myImage Image) ColorModel() color.Model {
}

func (myImage Image) Bounds() Rectangle {
}

func (myImage Image) At(x, y int) color.Color {
}
  • 首先是color.Model这个,直接把这串字符输入文档搜索栏寻找,找到了标准的color type:
//Models for the standard color types.
var (
        RGBAModel    Model = ModelFunc(rgbaModel)
        RGBA64Model  Model = ModelFunc(rgba64Model)
        NRGBAModel   Model = ModelFunc(nrgbaModel)
        NRGBA64Model Model = ModelFunc(nrgba64Model)
        AlphaModel   Model = ModelFunc(alphaModel)
        Alpha16Model Model = ModelFunc(alpha16Model)
        GrayModel    Model = ModelFunc(grayModel)
        Gray16Model  Model = ModelFunc(gray16Model)
)
  • 注意到,这个类型是在image/color中的,所以要import。之后选一个标准的color type,完成ColorModel():
func (myImage Image) ColorModel() color.Model {
        return color.RGBAModel
}
  • 同样的,搜索Rectangle,找到了:
type Rectangle struct {
        Min, Max Point
}
  • Point又是不知道的数据类型,再查:
type Point struct {
        X, Y int
}
  • 也就是连个简单的int类型,想办法初始化一下就行了
  • 注意到 Rectangle是在image package中的,所以也应该改一下(随便给点数据)
func (myImage Image) Bounds() image.Rectangle {
   return image.Rectangle{Min{0, 0}, Max{100, 100}}
}
  • 继续查看color.Color:
type Color interface {
        // RGBA returns the alpha-premultiplied red, green, blue and alpha values
        // for the color. Each value ranges within [0, 0xffff], but is represented
        // by a uint32 so that multiplying by a blend factor up to 0xffff will not
        // overflow.
        //
        // An alpha-premultiplied color component c has been scaled by alpha (a),
        // so has valid values 0 <= c <= a.
        RGBA() (r, g, b, a uint32)
}
  • 这是怎么回事?这是个接口,要实现了才能用?怎么解?
  • 只能慢慢看文档了,在image/color package中找到了个RGBA struct可以作为 receiver用来实现Color interface:
    RGBA
  • 不难看出,RGBA receiver实现了RGBA method (这里的命名真奇怪。。。)
  • 于是完成:
func (myImage Image) At(x, y int) color.Color {
        return color.RGBA{255, 0, 0, 255}
}
  • 好了,1.0版本完成:
package main

import (
        "image"
        "image/color"
        "golang.org/x/tour/pic"
)

type Image struct{
}

func (myImage Image) ColorModel() color.Model {
        return color.RGBAModel
}

func (myImage Image) Bounds() image.Rectangle {
        return image.Rectangle{image.Point{0, 0}, image.Point{100, 100}}
}

func (myImage Image) At(x, y int) color.Color {
        return color.RGBA{255, 0, 0, 255}
}

func main() {
        m := Image{}
        pic.ShowImage(m)
}
  • 结果:
    result
  • 通过文档,我们发现可以简化Bounds(),在image package中有:
func Rect(x0, y0, x1, y1 int) Rectangle
  • 为了使显示的图片富有变化,我们给Image结构体添加成员,由这些成员来决定显示怎么样的图片
  • 完善之后的2.0:
package main

import (
        "image"
        "image/color"
        "golang.org/x/tour/pic"
)

type Image struct{
        x, y int
}

func (myImage Image) ColorModel() color.Model {
        return color.RGBAModel
}

func (myImage Image) Bounds() image.Rectangle {
        return image.Rect(0, 0, myImage.x, myImage.y)
}

func (myImage Image) At(x, y int) color.Color {
        return color.RGBA{uint8(x*y), 0, 0, 255}
}

func main() {
        m := Image{100, 200}
        pic.ShowImage(m)
}
  • 结果:

猜你喜欢

转载自www.cnblogs.com/laiyuanjing/p/10883375.html