7. 接口(interface)
接口本身是调用方和实现放均需要遵守的一种协议,按统一方法命名参数类型和数量来协调逻辑处理。Go语言中使用组合实现对象特性的描述,对象内部使用结构体内嵌组合对象应具有的特性,对外通过接口暴露能使用的特性。Go语言的接口设计是非入侵式的,开发者无需知道哪些类型被实现。而接口实现者只需要知道实现的是什么样子的接口,但无需指明实现哪一个接口。编译器知道接口应该由谁来实现。
- 非入侵式的设计让接口与实现者真正解耦。编译速度提高。
1.声明接口
+ 接口是一种类型,也是一种抽象结构,不会暴露所含数据的格式、类型及结构
```
//1.1 接口声明格式
//每个接口类型由数个方法组成
type 接口类型名 interface {
方法名1(args[]) 返回值列表1
方法名2(args[]) 返回值列表2
}
// 例如
type writer interface {
Write([]byte) error
}
//1.2 常见的接口及写法
type Writer interface {
Write(p []byte)(n int, err error)
}
type Stringer interface {
String() string
}
```
2.实现接口的条件
+ 实现接口的条件一: 接口的方法与实现接口的类型方法格式一致。
+ 在类型中添加与接口签名一致的方法就可以实现该方法。签名包括方法中的名称,参数列表,返回参数列表。只要有一项不一致,接口的这个方法就不会被实现。
```
type DataWriter interface {
WriteData(data interface{}) error
}
type file struct {
}
func (d *file) WriteData(data interface{}) error {
fmt.Println("WriteData: ", data)
return nil
}
func main() {
f := new(file)
var writer DataWriter
writer = f
writer.WriteData("data")
}
```
+ 几种接口无法实现的错误
+ 函数名不一致导致的报错
+ 实现接口的方法签名不一致导致的报错
-
条件二:接口中所有方法均被实现
-
当一个接口中由多个方法时,只有方法都被实现了,接口才能被正确的编译
type DataWriter interface{ WriteDta(data interface{}) error CanWrite() bool }
- Go语言中接口的实现是隐式的,无须让实现接口的类型写出实现了哪些接口。这种设计成为非侵入式设计。
-
3.理解类型与接口的关系
+ 类型与接口之间有一对多和多对一的关系。
+ 3.1 一个类型可以实现多个接口
+ 一个类型可以实现多个接口,而接口之间彼此独立,不知道对方的实现
+ 网络上两个程序通过一个双向的通信连接实现数据交换,连接的一端称为一个Socket。Socket能同时读取和写入数据,开发中,把文件和Socket都具备的读写特性抽象为独立的读写器概念。
```
type Socket struct {
}
func (s *Socket) Write(p []byte) (n int, err error) {
return 0, nil
}
func (s *Socket) Close() error {
return nil
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
// Closer不知道实现者是否具备Writer接口的特性,反之亦然。
func usingWrite(writer io.Writer) {
writer.Write(nil)
}
func usingCloser(closer io.Closer) {
closer.Closer()
}
func main() {
s := new(Socket)
usingWriter(s)
usingCloser(s)
}
```
+ 3.2多个类型可以实现相同的接口
+ 一个接口的方法,不一定由一个类型完全实现,接口方法可以通过在类型中嵌入其他类型或结构体来实现。使用者不关心某个接口的方法式通过一个类型完全实现的,还是通过多个结构嵌入到一个结构体中拼凑起来共同实现的。
+ Service接口定义两个方法,一个是开启服务Start(),一个是输出日志的方法Log().使用GameService结构体来实现Service,GameService自己的结构只能实现Start()方法,而Service接口中,Log()方法已经被一个能输出日志的日志器Logger实现了,无须再进行GameService封装,或者重新实现一边,所以将Logger嵌入到GameService能最大程度的避免代码冗余,简化代码结构。
```
type Service interface {
Start()
Log(string)
}
type Logger struct {
}
func (g *Logger) Log(l string) {
}
type GameService struct {
Logger
}
func (g *GameService) Start() {
}
func main() {
var s Service = new(GameService)
s.Start()
s.Log("hello")
}
```
4.便于扩展输出方式的日志系统
+ 日志一般支持多种形式
```
// 日志对外接口logger.go
//声明日志写入器接口
type LogWriter interface {
Write(data interface{}) errorString
}
type Logger struct {
writerList []LogWriter
}
func (l *Logger) RegisterWriter(writer LogWriter) {
l.writerList = append(l.writerList, writer)
}
//将一个data类型的数据写入日志
func (l *Logger) Log(data interface{}) {
for _, writer := range l.writerList {
// 将日志输出到每一个写入器中
writer.Write(data)
}
}
func NewLogger() *Logger {
return &Logger{}
}
// 文件写入器
type fileWriter struct {
file *os.File
}
func (f *fileWriter) SetFile(filename string) (err error) {
if f.file != nil {
f.file.Close()
}
f.file, err = os.Create(filename)
return err
}
func (f *fileWriter) Write(data interface{}) error {
if f.file == nil {
return errors.new("file not created")
}
str := fmt.Sprintf("%v\n", data)
_, err := f.file.Write([]byte(str))
return err
}
func newFileWriter() *fileWriter {
return &fileWriter{}
}
// 命令行写入器
// 命令行在Go中也是一种文件,os.Stdout对应标准输出,一般表示屏
// 幕。os.Stderr对应标准错误输出,一般将错误输出到日志中,不过大
// 多数情况,os.Stdout和os.Stderr合并输出;os.Stdin对应标准输入
// 这几个都是*os.File类型
type consoleWriter struct {
}
func (f *consoleWriter) Write(data interface{}) error {
str := fmt.Sprintf("%v\n", data)
_, err := os.Stdout.Write([]byte(str))
return err
}
func newConsoleWrite() *consoleWrite {
return &consoleWrite{}
}
// 使用日志
// 一般先创建日志器,为日志器添加输出设备
func creatLogger() *Logger {
l := NewLogger()
cw := newConsoleWriter()
l.RegisterWriter(cw)
fw := newFileWriter()
if err := fw.SetFile("log.log"); err != nil {
fmt.Println(err)
}
l.RegisterWriter(fw)
return l
}
func main() {
l := createLogger()
l.Log("hello")
}
```
5示例,使用接口进行数据排序
-
排序是常见的算法之一,Go中排序,使用sortInterface接口提供数据的一些特性和操作方法。
type Interface interface { Len() int Less(i, j int) bool //交换元素 Swap(i, j int) }
-
5.1 使用sort.Interface接口进行排序
-
对一系列字符串排序时,用字符串切片([]string)承载多个字符串。只有让定义的自定义类型实现sort.Interface接口,才能让sort包识别自定义类型。
// 将[]string 定义为MystringList类型 type MyStringList []string func (m MyStringList) Len() int { return len(m) } func (m MyStringList) Less(i, j int) bool { return m[i] < m[j] } func (m MyStringList) Swap(i, j int) { m[i], m[j] = m[j], m[i] } func main() { names := MyStringList{ "3. Triple kill", "5. Penta kill", "4. Quadra kill", "1. First blood", "2. Double kill", } sort.Sort(names) for _, v := range names { fmt.Printf("%s\n", v) } }
-
-
5.2 常见的便捷排序
-
go语言中的快速排序
//sort包中StringSlice类型 type StringSlice []string func (p StringSlice) Len() int { return len(p) } func (p StringSlice) Less(i, j int) bool { return p[i] < p[j] } func (p StringSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } func (p StringSlice) Sort() { Sort(p) } // 简化代码 func main() { names := sort.StringSlice{ "3. Triple kill", "5. Penta kill", "4. Quadra kill", "1. First blood", "2. Double kill", } sort.Sort(names) for _, v := range names { fmt.Printf("%s\n", v) } }
-
2.对整型切片进行排序
// sort.IntSlice类型 type IntSlice []int func (p IntSlice) Len(0 int {return len(p)}) func (p IntSlice) Less(i, j int) bool {return p[i] < p][j]} func (p IntSlice) Swap(i, j int) {p[i], p[j] = p[j], p[i]} func (p IntSlice) Sort() {Sort(p)}
- 还有更进一步简化的sort.String
-
sort包内建的类型排序接口
+ 字符串型 | StringSlice | sort.Strings(a []string) | 按ASCII值升序 + 整型 | IntSlice | sort.Ints(a []int) | 数值升序 + 双精度浮点 | Float64Slice | sort.Float64s(a []float64) | 数值升序
-
-
5.3对结构体数据进行排序
- 结构体排序一般有多种规则,需要确定规则的优先度
-
1.实现sort.Interface进行结构体排序
+ 给英雄结构体排序 ``` type HeroKind int const ( None HeroKind = iota Tank Assasin Mage ) type Hero struct { Name string Kind HeroKind } // 将英雄指针的切片定义为Heros类型 type Heros []*Hero func (s Heros) Len() int { return len(s) } func (s Heros) Less(i, j int) bool { if s[i].Kind != s[j].Kind { return s[i].Kind < s[j].Kind } return s[i].Name < s[j].Name } func (s Heros) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func main() { heros := Heros { &Hero{"吕布", Tank}, &Hero{"李白", Assassin}, &Hero{"妲己", Mage}, &Hero{"貂蝉", Assassin}, &Hero{"关羽", Tank}, &Hero{"诸葛亮", Mage}, } sort.Sort(heros) for _, v := range heros { fmt.Printf("%+v\n", v) } } ```
-
2.使用sort.Slice进行切片元素排序
//函数定义 func Slice(slice interface{}, less func(i, j int) bool) // 优化英雄排序代码 type HeroKind int const ( None = iota Tank Assassin Mage ) type Hero struct { Name string Kind HeroKind } func main() { heros := []*Hero { Hero{"吕布", Tank}, Hero{"李白", Assassin}, Hero{"妲己", Mage}, Hero{"貂蝉", Assassin}, Hero{"关羽", Tank}, Hero{"诸葛亮", Mage}, } sort.Slice(heros, func(i, j int) bool { if heros[i].Kind != heros[j].Kind { return heros[i].Kind < heros[j].Kind } return heros[i].Name < heros[j].Name }) for _, v := range heros { fmt.Printf("%+v\n", v) } }
-
6.接口的嵌套组合-将多个接口放在一个接口内
+ 1.系统包中的接口嵌套组合
```
// 写入器
type Writer interface {
Write(p []byte) (n int, err error)
}
// 关闭器
type Closer interface {
Close() error
}
// 写入关闭器
type WriteCloser interface {
Writer
Closer
}
```
+ 2.使用接口嵌套组合
```
// 声明一个设备结构
type device struct {
}
func (d *device) Write(p []byte) (n int, err error) {
return 0, nil
}
func (d *device) Close() error {
return nil
}
func main() {
var wc io.WriteCloser = new(device)
wc.Write(nil)
wc.Close()
var writeOnly io.Writer = new(device)
writeOnly.Write(nil)
}
```
7.在接口和类型间转换
+ 7.1 类型断言的格式
```
//基本格式
t := i.(T) // i接口变量,T为目标类型,t代表转换后的变量
t, ok := i.(T) // ok被认为是i接口是否实现T类型的结果
```
+ 7.2 将接口转换为其他接口
```
var obj interface = new(bird)
f, isFlyer := obj.(Flyer)
type Flyer interface {
Fly()
}
type Walker interface {
Walk()
}
type bird struct {
}
func (b *bird) Fly() {
fmt.Println("bird: fly")
}
func (b *bird) Walk() {
fmt.Println("bird: walk")
}
type pig struct {
}
func (p *pig) Walk() {
fmt.Println("pig: walk")
}
func main() {
animals := map[string]interface{} {
"bird": new(bird),
"pig": new(pig),
}
for name, obj := range animals {
f, isFlyer := obj.(Flyer)
w, isWalker := obj.(Walker)
fmt.Printf("name: %s isFlyer: %v isWalker: %v\n", name, isFlyer, isWalker)
if ifFlyer {
f.Fly()
}
if isWalker {
w.Walk()
}
}
}
```
+ 7.3 将接口转换为其他类型
```
p1 := new(pig)
var a Walker = p1
p2 := a.(*pig)
fmt.Printf("p1=%p p2=%p", p1, p2)
// 接口转换时,main.Walker接口内部包存的是*main.pig,所以如果让Walker类型转换成*bird时会报错。
```
8.空接口类型(interface{})--能保存所有值类型
+ 空接口的内部实现保存了对象的类型和指针,空接口速度稍慢,不应所有地方都用空接口
+ 8.1将值保存到空接口
```
var any interface{}
any = 1
fmt.Println(any)
any = "hello"
fmt.Println(any)
any = false
fmt.Println(any)
```
+ 8.2从空接口获取值
```
// 保存到空接口的值,直接取出指定类型的值会发生编译错误
var a int = 1
var i interface{} = a
wrong:
var b int = i
right:
var b int = i.(int)
```
+ 8.3空接口的值比较
+ 1.类型不同的空接口的比较结果不同
```
var a interface{} = 100
var b interface{} = "hi"
fmt.Println(a==b) // false
```
+ 2.不能比较空姐口中的动态值
```
var c interface{} = []int{10}
var d interface{} = []int{20}
fmt.println(c==d) // 报错,[]int时不可比较类型
```
+ map | 宕机错误, 不可比较 |
+ 切片[]T | 宕机错误,不可比较
+ 通道channel | 可比较,必须由同一个make生成,同一个通道true,否则为false
+ 数组 | 可比较,编译期知道两个数组是否一致
+ 结构体 | 可比较,可以逐个比较结构体的值
+ 函数 | 可比较
有疑问加站长微信联系(非本文作者)