Go语言开发过程中,什么时候使用方法,什么时候使用interface进行方法封装呢?

刚开始做Go语言开发的过程中,经常会有一个疑问,使用接口封装的方法和不使用接口封装的方法,在本质上是没有区别的,但是在读了io标准库之后,对两者的使用有了进一步的理解,在这里总结一下。

1.什么是方法

在 go语言中可以通过下面的形式定义方法

func (A type) function_name(args) {}

其中A为一种定义好的类型,args为传入的参数,调用方法时,必须使用type.function_name的形式调用,type是定义好的类型,举个例子:

package main

import (
	"fmt"
)
// 定义结构体类型User
type User struct {
	Name string
	Age  int
}
// User类型的方法,用来输出用户名和密码
func (u *User) GetUserInfo() {
	fmt.Println("Name:", u.Name, "Age:", u.Age)
	return
}

func main() {
	var u User
	u.Name = "random"
	u.Age = 18
	// 调用方法
	u.GetUserInfo()
}

Output:

$ go run main.go
Name: random Age: 18

2.如何使用接口封装方法

Go语言通过接口实现了鸭子类型(duck-typing):“当看到一直鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子”。我们并不关心对象是什么类型,到底是不是鸭子,只关心行为,这也就是其他编程语言中的多态。
下面举一个例子:

package main

import (
	"fmt"
)
// 对方法进行封装
type IUser interface {
	GetUserInfo()
}

// 定义结构体类型User
type User struct {
	Name string
	Age  int
}
// User类型的方法,用来输出用户名和密码
func (u *User) GetUserInfo() {
	fmt.Println("Name:", u.Name, "Age:", u.Age)
	return
}

func main() {
	var u User
	u.Name = "random"
	u.Age = 18
	var user IUser = &u
	user.GetUserInfo()
}

Output:

$ go run main.go
Name: random Age: 18

与方法相比这种调用方式显得复杂,那么什么时候使用接口封装方法比较好呢?

3.什么时候使用接口

这里以io标准库为例,在io包中最重要的是两个接口:Reader和Writer接口,首先来介绍这两个接口。

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

Reader 接口包装了基本的 Read 方法。

Read 将 len( p) 个字节读取到 p 中。它返回读取的字节数 n(0 <= n <= len§)以及任何遇到的错误。即使 Read 返回的 n < len§,它也会在调用过程中使用 p的全部作为暂存空间。若一些数据可用但不到 len( p) 个字节,Read 会照例返回可用的东西,而不是等待更多。

当 Read 在成功读取 n > 0 个字节后遇到一个错误或 EOF 情况,它就会返回读取的字节数。它会从相同的调用中返回(非nil的)错误或从随后的调用中返回错误(和 n == 0)。这种一般情况的一个例子就是 Reader 在输入流结束时会返回一个非零的字节数,可能的返回不是 err == EOF 就是 err == nil。无论如何,下一个 Read 都应当返回 0, EOF。

调用者应当总在考虑到错误 err 前处理 n > 0 的字节。这样做可以在读取一些字节,以及允许的 EOF 行为后正确地处理I/O错误。
Read 的实现会阻止返回零字节的计数和一个 nil 错误,调用者应将这种情况视作空操作。

io库中有一个函数: func ReadFull(r Reader, buf []byte) (n int, err error),这个函数可以把对象 r 中的数据读出来,然后存入一个缓冲区 buf 中,以便其它代码可以处理 buf 中的数据,ReadFull 可以读取任何对象的数据,但是有个前提,就是这个对象必须符合 Reader 的标准
读到这里大家应该能看出接口封装的作用了,它可以实现在不知道对象类型的情况下可以对对象进行一些操作。

// 定义一个 str 类型(以 string 为基类型)
type str string

// 实现 str 类型的 Read 方法
func (s str) Read(p []byte) (n int, err error) {
	i, ls, lp := 0, len(s), len(p)
	for ; i < ls && i < lp; i++ {
		// 将小写字母转换为大写字母,然后写入 p 中
		if s[i] >= 'a' && s[i] <= 'z' {
			p[i] = s[i] + 'A' - 'a'
		} else {
			p[i] = s[i]
		}
	}
	// 根据读取的字节数设置返回值
	switch i {
	case lp:
		return i, nil
	case ls:
		return i, io.EOF
	default:
		return i, errors.New("read fail")
	}
}
func main() {
	s := str("Hello World!")      // 创建 str 对象 us
	buf := make([]byte, 32)       // 创建缓冲区 buf
	n, err := io.ReadFull(s, buf) // 将 s 中的数据读取到 buf 中
	if err != nil && err != io.ErrUnexpectedEOF {
		return
	}
	fmt.Printf("%s\nlength:%d", buf, n) // 显示 buf 中的内容
}

Output:

$ go run main.go
HELLO WORLD!
length:12
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章