这是我参与8月更文挑战的第 20 天,活动详情查看: 8月更文挑战
接口的组合
接口的组合就是将一些接口整合在一起,有点像多继承一样。可以直接看下边的例子
type Retriver interface {
Get(url string) string
}
type Poster interface {
Post(url string, form map[string]string) string
}
func download(r Retriver) string {
return r.Get("<http://www.baidu.com>")
}
func post(poster Poster) {
poster.Post("<http://ww.baidu.com>", map[string]string{
"name": "shulv",
"age": "twenty",
})
}
复制代码
上边定义了两个接口,并且分别有这两个接口的调用者,download和post。现在假设有个session函数,它需要一个参数,即是一个Retriver,又是一个Poster。此时就可以用到接口的组合
type RetrieverPoster interface {
Retriver
Poster
//Connect(host string) //当然还可以添加别的方法
}
func session(s RetrieverPoster) string {
s.Post("<http://www.baidu.com>", map[string]string{
"Contents": "facked",
})
return s.Get("<http://www.baidu.com>")
}
复制代码
上边的代码中定义了一个组合接口RetrieverPoster,它直接包含了Retriver和Poster这两个接口,也就是说他可以直接使用这两个接口中的方法。当然,这个组合接口中还可以增加自己的方法。有没有发现它和结构体的嵌套非常的像(不记得结构体嵌套的,可以点这里)
下边为了方便理解,我直接在一个文件中定义结构体,并实现结构体的一些方法
type Mock struct {
Contains string
}
func (r Mock) Get(url string) string {
return r.Contains
}
func (r *Mock) Post(url string, form map[string]string) string {
r.Contains = form["Contents"]
return "ok"
}
type Real struct {
UserAgent string
TimeOut time.Duration
}
func (r Real) Get(url string) string {
resp, err := http.Get(url)
if err != nil {
panic(err)
}
result, err := httputil.DumpResponse(resp, true)
resp.Body.Close()
if err != nil {
panic(err)
}
return string(result)
}
复制代码
上边定义了两个结构体,分别是Mock和Real,并且实现了对应的方法(可以看到,Mock实现了组合接口RetrieverPoster)。下边在main函数中调用session函数
func main() {
retriver := Mock{"just a test"}
fmt.Println(session(&retriever))
}
输出:facked(因为在Post方法中对Mock.Content进行了修改,说明session里边调用了Mock的Post方法)
复制代码
其实在Go语言的库中,有很多接口组合的例子,比如io包里的ReadWriteCloser,它就由多个接口组合而成的一个接口,内容如下:
// ReadWriteCloser is the interface that groups the basic Read, Write and Close methods.
type ReadWriteCloser interface {
Reader
Writer
Closer
}
复制代码
可以看到它是由Reader、Writer和Closer这三个接口组合而成的。当然,你可以进到io这个包里边,里边有很多这样的组合接口
上边就是接口的组合,下边分享一些Go语言中标准的接口
常见系统接口
Stringer
在fmt这个包中,有一个接口叫Stringer,它里边只有一个String函数,所以一个类型只要实现了String函数,就实现了Stringer接口
package fmt
type Stringer interface {
String() string
}
复制代码
下边定义一个结构体类型,该结构体中实现了String方法
package main
import "fmt"
type Mock struct {
Contents string
}
func (m Mock) String() string {
return fmt.Sprintf("Mock: {Contents = %s}", m.Contents)
}
func main() {
m := Mock{"a test, too"}
fmt.Println(m.String())
}
输出:Mock: {Contents = a test, too}
复制代码
Reader
在io这个包中有一个Reader接口,它里边只有一个Read接口。实现Read方法的可以是一个文件,可以从文件中读取内容,放入到byte(当然不止有文件,还有网络、slice等等,都可以)
package io
type Reader interface {
Read(p []byte) (n int, err error)
}
复制代码
可以看一下os.Open方法的内部实现,它的返回值是一个File的结构,然后进入到File中可以看到它是一个结构体类型,并且可以发现这个结构体中内嵌了一个file的结构体,而这个file实际上是 *File的真实表示。而 *File又实现了Read方法,所以它实际上就实现了Reader接口
//file.go
package os
func Open(name string) (*File, error) {
return OpenFile(name, O_RDONLY, 0)
}
//types.go
package os
type File struct {
*file // os specific
}
....
// file is the real representation of *File.
type file struct {
pfd poll.FD
name string
dirinfo *dirInfo // nil unless directory being read
nonblock bool // whether we set nonblocking mode
stdoutOrErr bool // whether this is stdout or stderr
appendMode bool // whether file is opened for appending
}
而*File实现了Read接口,内容如下:
//file.go
package os
func (f *File) Read(b []byte) (n int, err error) {
if err := f.checkValid("read"); err != nil {
return 0, err
}
n, e := f.read(b)
return n, f.wrapErr("read", e)
}
复制代码
在*File中可以发现,它不会说我实现了哪些接口。实现了哪些接口是用的人来说的,它只需要实现某些方法就行了
所以我们在获取文件中的内容的时候调用的bufio.NewScanner()方法,它需要的参数是一个io.Reader类型(所以Open返回的File类型,是可以直接传递给NewScanner,因为它实现了Reader接口)
filename := "a.txt"
file, err := os.Open(filename)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())//逐行读取文件中的内容
}
//下边是NewScanner内部实现
func NewScanner(r io.Reader) *Scanner {
return &Scanner{
r: r,
split: ScanLines,
maxTokenSize: MaxScanTokenSize,
}
}
复制代码
Writer
跟Reader接口一样,它也在io包中,接口中只有一个Write方法。实现Write方法的可以是一个文件,将提供的byte数据写入到文件中
package io
type Writer interface {
Write(p []byte) (n int, err error)
}
复制代码
在fmt包中有一个Fprintf函数(fmt中各种输出函数的使用及作用,可以点这里),Fprintf的作用是返回一个格式化后的字符串。它的第一个参数是一个io.Writer类型,所以任何实现了这个Writer接口的,都能够传给它来用
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
p := newPrinter()
p.doPrintf(format, a)
n, err = w.Write(p.buf)
p.free()
return
}
复制代码
示例:读取文件内容
以读取文件内容为例,通常的做法如下
package main
import (
"bufio"
"fmt"
"os"
)
func printFile(filename string) {
file, err := os.Open(filename)
if err != nil {
panic(err)
}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())//逐行读取文件中的内容
}
}
func main() {
printFile("a.txt")
}
复制代码
这样的做法缺点就是,printFile只能接收一个文件名字符串,只能读取文件中的内容。下边对他进行优化,让他不仅能够打印文件内容,并且还能够通过像打印文件一样打印字符串
package main
import (
"bufio"
"fmt"
"io"
"os"
"strings"
)
func printFile(filename string) {
file, err := os.Open(filename)
if err != nil {
panic(err)
}
printFileContents(file)
}
func printFileContents(read io.Reader) {
scanner := bufio.NewScanner(read)
for scanner.Scan() {
fmt.Println(scanner.Text())//逐行读取文件中的内容
}
}
func main() {
printFile("a.txt")
s := `uiieo
"dgsjg"
s
d
3
`
printFileContents(strings.NewReader(s))
}
复制代码
可以看到将实际的打印内容工作交由printFileContents方法来做,它接收一个Reader类型的参数,所以可以利用它来打印文件内容,也能将字符串类型以文件的方式打印,这样它就变得更加通用