分享
  1. 首页
  2. 文章

Go语言==接口(interface)

默之 · · 1453 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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
 + 数组 | 可比较,编译期知道两个数组是否一致
 + 结构体 | 可比较,可以逐个比较结构体的值
 + 函数 | 可比较

有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:默之

查看原文:Go语言==接口(interface)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
1453 次点击
1 回复 | 直到 2025年04月30日 12:00:05
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏