Go语言==接口(interface)

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

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

881 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传