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