go语言的接口(接口属于对象)定义使用了duck typing这个思想。接口做的事情就像是定义一个协议(规则),只要一台机器有洗衣服和甩干的功能,我就称它为洗衣机。不关心属性(数据),只关心行为(方法)。下面来看go语言的一个函数代码:

func isDuck(duck Duck) bool {
	return duck.AmDuck()
}

这里的isDuck函数被称为使用者,它使用了传入duck的amDuck()函数,因此duck被称为实现者。

go语言接口由使用者定义,因为使用者用到了amDuck()这个函数,因此传入的duck接口需要定义amDuck()这个方法,这种思维更符合duck typing的思维,使用者觉得传入的对象拥有我需要的功能,我就认为你是这个接口类型。

而传统的接口定义是反着来的,首先是实现者自己定义方法,使用者需要查看实现者定义了哪些方法,看自己是否能够使用。

go.modmain.go.go
go mod init demo.com

go语言接口定义代码:

type Duck interface {
	AmDuck() bool
}

go语言接口的子类实现:

package yellow 

import "fmt"

type SmallDuck struct {
	Foot int
}

func (smallDuck SmallDuck) AmDuck() bool {
	if smallDuck.Foot == 2 {
		return true
	}
	return false
}

func (smallDuck SmallDuck) String() string {
	return fmt.Sprintf("foot is %d", smallDuck.Foot)
}

struct如果实现了Amduck()的话,它就会被认为是Duck的实现类,这种隐式的接口实现定义,既能检查传入的类是否实现了AmDuck方法,又能实现以接口方式作为传入参数的效果。

需要注意的是实现类的名称和属性以及方法名称都要大写,其它包才可以进行调用,否则将会出现报错

go语言接口的使用:

package main

import "demo.com/yellow"

func isDuck(duck Duck) bool {
	return duck.AmDuck()
}

type Duck interface {
	AmDuck() bool
}

func main() {
	duck := yellow.SmallDuck{2}
	print(isDuck(duck))
}

isDuck传入的参数被定义为一个接口,而这里传入的是我们的实现类,实现了解耦的效果

如何判断是否实现了接口

package main

import "fmt"

//定义一个People接口
type People interface {
	SayName() string
}

//定义一个学生类型
type Student struct {
	Name string
}

//学生类型实现SayName方法
func (s Student) SayName() string {
	return s.Name
}

//检查结构体都实现了接口了没有
func checkPepole(tmp interface{}) {
	if _, ok := tmp.(People); ok {
		fmt.Println("Student implements People")
	}
}

func main() {
	student := Student{Name: "张三"}
	checkPepole(student)
}
go语言有三大常用的系统接口,Stringer,Reader,Writer。

Stringer接口

Stringer接口包含了String方法,它的作用和java的toString一样,我们在打印重写了String方法这个对象的时候,会打印出返回的string值。定义一个结构体SmallDuck,它重写了String方法。具体的调用:

type UglyDuckling interface{}
func main() {
	var uglyDuckling UglyDuckling
	uglyDuckling = yellow.SmallDuck{Foot: 2}
	fmt.Printf("%T %v \n", uglyDuckling, uglyDuckling)
}

结果如下:

yellow.SmallDuck foot is 2

当使用 fmt.Printf()、fmt.Print() 和 fmt.Println() 会自动使用 String() 方法。输出的SamllDuck的value为自定义的内容,即String方法返回的值。

type ConfigOne struct {
	Daemon string
}

func (c *ConfigOne) String() string {
	return fmt.Sprintf("print: %v", c)
}

func main() {
	c := &ConfigOne{}
	c.String()
}

自定义的String()导致递归调用,死循环最后抛错。推荐使用对象的属性

type Couple struct {
	Husband string
	Wife    string
}

func (self *Couple) String() string {
	return "(husband:" + self.Husband + ", wife:" + self.Wife + ")"
}

func main() {
	couple := Couple{"XuXian", "BaiSuzhen"}
	fmt.Println(&couple)
	fmt.Println(couple)
}

注意

需要注意的是接收者的类型,如果为指针类型,则fmt.Println(指针)时可以调用String()方法,fmt.Println(值)不会调用String()方法。而如果接收者是值类型,则fmt.Println(值)和fmt.Println(指针)都会调用String()方法。原因是fmt.Println()函数其接收的参数是实现了String()方法的接口,接口不保存地址,因此当fmt.Println(值)时得不到地址也就无法调用String()方法。

Writer接口

type Writer interface {   Write(p []byte) (n int, err error)}

go语言的Writer接口定义了一个Write方法,当实现者为文件的时候,通过Write方法可以将byte数组里面内容write进入文件当中。

Reader接口

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

Reader接口定义了一个Read方法,当实现者为文件的时候,通过Read方法可以将文件里面的内容写进byte数组当中。除了文件实现了Reader接口和Writer接口外,byte数组,byte切片,网络流相关的也实现了Reader接口。因此,涉及到读写底层的东西,我们传入的是Writer和Reader接口,而不是File。例如fmt包的Fprintf方法和Fscanf方法

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
}

func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error) {
	s, old := newScanState(r, false, false)
	n, err = s.doScanf(format, a)
	s.free(old)
	return
}

具体的Reader操作例子。定义读取Reader里面内容的方法

func printContents(reader io.Reader) {
	scanner := bufio.NewScanner(reader)
	for scanner.Scan() {
		fmt.Println(scanner.Text())
	}
}

具体调用

func main() {
	s := `1  12!!!`
	printContents(strings.NewReader(s))
}

通过``定义一个跨行的字符串,通过string.NewReader()方法将s转为Reader类型,然后调用定义的方法,通过Scanner读取字符打印在控制台上。

除了Reader和Writer外,还有组合它们的接口ReadWriter

type ReadWriter interface {
	Reader
	Writer
}

实现者只要实现方法即可,具体的组合和调用由使用者决定。

golang的package
  • 1、不限于一个文件,可以多个文件组成一个package
  • 2、不要求package的名称和所在目录名相同,但是最好保持相同,package名称小写
  • 3、每个子目录中只能存在一个package,否则编译报错
  • 4、package是以绝对路径GOPATH来寻址,不要用相对路径来import