zoukankan      html  css  js  c++  java
  • Go语言基础之18--接口编程

    一、接口介绍和定义

    1.1 接口定义了一个对象的行为规范

    A. 只定义规范,不实现

    B. 具体的对象需要实现规范的细节

    葵花宝典:

    接口就是一层封装,1个例子,封装一个返还浏览器内容的接口。为什么不直接面向对象呢。你封装成一个接口的话,不论是返回文件或者图片或者html都可以通过接口进行返回,不用接口的话,你需要为每一种返回类型写函数。

    1.2 Go中接口定义

    A. type 接口名字 interface

    B. 接口里面是一组方法签名的集合(后面的调用参数和返回值都要和接口中的方法一模一样)

    C.接口是引用类型(指针);注意:函数传递接口类型参数时,一定不要加*,因为接口本身就是引用类型,如果加*就报错了

    type Animal interface {
        Talk()
        Eat() int
        Run()
    }

    1.3 Go中接口的实现

    A. 在Go语言中,一个类只要实现了接口要求的所有函数,我们就说这个类实现了该接口

    B. 接口类型的变量可以保存实现该接口的任何具体类型的实例。

    实例:

    package main
    
    import (
        "fmt"
    )
    
    type Animal interface { //定义了动物的规范(接口定义的一组方法)
        Eat()
        Talk()
        Run()
    }
    
    type Dog struct { //狗如果能够满足了动物的规范(接口方法),那其就是动物
        name string
    }
    
    func (d *Dog) Eat() { //目前狗还不是动物,因为其只实现了Eat,还需要实现Talk和Run,其才算是动物
        fmt.Printf("%s is eating
    ", d.name)
    }
    
    func (d *Dog) Talk() {
        fmt.Printf("%s is talking
    ", d.name)
    }
    
    func (d *Dog) Run() {
        fmt.Printf("%s is runing
    ", d.name)
    }
    
    func (d *Pig) Eat() {
        fmt.Printf("%s is eating
    ", d.name)
    }
    
    func (d *Pig) Talk() {
        fmt.Printf("%s is talking
    ", d.name)
    }
    
    func (d *Pig) Run() {
        fmt.Printf("%s is runing
    ", d.name)
    }
    
    func main() {
        var dog = &Dog{
            name: "旺财",
        }
    
        var a Animal
        fmt.Printf("a:%v dog:%v
    ", a, dog) //接口底层就是指针,指向的就是一个空对象,如果字节调用就会panic
    
        a = dog //dog满足了接口所有方法,所以我们可以直接将其复制给Animal,对应的理论就是接口类型的变量可以保存实现该接口的任何具体类型的实例。
        a.Eat()
        a.Run()
        a.Talk()
    
        var pig = &Pig {
            name:"佩奇",
        }
    
        a = pig
        a.Eat()
        a.Run()
        a.Talk()
    }

     执行结果:

    上面这个例子中,Pig和Dog实现了Animal的所有方法,所以Cat和Dog都是动物

    小结一下:

    Go中的接口只要一个对象实现了接口类型中的所有方法,那么这个对象就实现了这个接口,当然如果一个对象实现了多个interface类型的方法,那么这个对象就实现了多个接口

    1.4 接口实例

    A. 一个公司需要计算所有职员的薪水

    B. 每个职员的薪水计算方式不同

    如何解答呢?

    我们先单纯通过结构体进行解答:

    结构体示例

    package main
    
    import (
        "fmt"
    )
    
    type Developer struct { //开发
        Name string
        Base int
    }
    
    func (d *Developer) Calc() int {
        return d.Base
    }
    
    type PM struct { //pm
        Name   string
        Base   int
        Option int
    }
    
    func (p *PM) Calc() int {
        return p.Base + p.Option
    }
    
    type YY struct { //运营
        Name   string
        Base   float32
        Option float32
        Rate   float32 //0.6 ~ 3
    }
    
    func (p *YY) Calc() float32 {
        return p.Base + p.Option*p.Rate
    }
    
    type EmployeeMgr struct { //最终汇总
        devList []*Developer //用切片存
        pmList  []*PM
        yyList  []*YY
    }
    
    func (e *EmployeeMgr) Calc() float32 { //进行计算
        var sum float32
        for _, v := range e.devList { //计算程序员
            sum += float32(v.Calc())
        }
    
        for _, v := range e.pmList { //计算pm
            sum += float32(v.Calc())
        }
    
        for _, v := range e.yyList { //计算运营
            sum += float32(v.Calc())
        }
    
        return sum
    }
    
    func (e *EmployeeMgr) AddDev(d *Developer) { //人从哪来,要添加人,添加到列表的函数
        e.devList = append(e.devList, d)
    }
    
    func (e *EmployeeMgr) AddPM(d *PM) {
        e.pmList = append(e.pmList, d)
    }
    
    func (e *EmployeeMgr) AddYY(d *YY) {
        e.yyList = append(e.yyList, d)
    }
    
    func main() {
        var e = &EmployeeMgr{}
    
        dev := &Developer{ //添加具体人
            Name: "develop",
            Base: 10000,
        }
        e.AddDev(dev)
    
        pm := &PM{
            Name:   "pm",
            Base:   10000,
            Option: 12000,
        }
        e.AddPM(pm)
    
        yy := &YY{
            Name:   "yy",
            Base:   10000,
            Option: 12000,
            Rate:   1.2,
        }
        e.AddYY(yy)
    
        sum := e.Calc() //计算所有人
        fmt.Printf("sum:%f
    ", sum)
    }

     执行结果:

    解释:

    单纯用结构体实现有一个很大的弊端就是如果我们要添加一个职位的话,非常不方便,要改动地方太多,比如:增加职位结构体(类)、结构体方法、计算方式添加、增加进总列表,十分的不灵活,下面我们来看看通过接口实现怎么样

    接口实例:

    package main
    
    import (
        "fmt"
    )
    
    type Employee interface {  //定义1个雇员的接口,其规定的方法是calc(计算工资)
        Calc() float32   //接下来的各种职位要想使用这个接口,就需要有该接口规定的方法,并且类型也要一致
    }
    
    type Developer struct {
        Name string
        Base float32
    }
    
    func (d *Developer) Calc() float32 {
        return d.Base
    }
    
    type PM struct {
        Name   string
        Base   float32
        Option float32
    }
    
    func (p *PM) Calc() float32 {
        return p.Base + p.Option
    }
    
    type YY struct {
        Name   string
        Base   float32
        Option float32
        Rate   float32 //0.6 ~ 3
    }
    
    func (p *YY) Calc() float32 {
        return p.Base + p.Option*p.Rate
    }
    
    type EmployeeMgr struct {
        employeeList []Employee   //员工管理列表不需要在区分职位了,只需要1个职员列表即可(因为无论是dev还是pm、yy都实现了employee的接口)
    }
    
    func (e *EmployeeMgr) Calc() float32 {  //计算也是都统一一个了
        var sum float32
        for _, v := range e.employeeList {
            sum += v.Calc()
        }
    
        return sum
    }
    
    func (e *EmployeeMgr) AddEmpoyee(d Employee) {   //追加也是都只有1个了,并且这里参数也不需要加*,因为interface自身就是引用类型。
        e.employeeList = append(e.employeeList, d)
    }
    
    func main() {
        var e = &EmployeeMgr{}
    
        dev := &Developer{
            Name: "develop",
            Base: 10000,
        }
        e.AddEmpoyee(dev)
    
        pm := &PM{
            Name:   "pm",
            Base:   10000,
            Option: 12000,
        }
        e.AddEmpoyee(pm)
    
        yy := &YY{
            Name:   "yy",
            Base:   10000,
            Option: 12000,
            Rate:   1.2,
        }
        e.AddEmpoyee(yy)
    
        sum := e.Calc()
        fmt.Printf("sum:%f
    ", sum)
    }

    执行结果:

     

    解释:

    可以发现用接口来实现,非常方便,新增加一个职位,只需要增加一个职位的类和方法以及该职位职员相关信息即可,十分方便了。

    1.5 接口类型变量

    A. var a Animal

    B. 那么a能够存储所有实现Animal接口的对象实例

    实例参考Dog和Pig的实例。 

    二、空接口

    2.1 空接口

    A. 空接口没有定义任何方法

    B. 所以任何类型都实现了空接口,所以空接口就可以用来存储任何类型的实例。

    有一个思考,空接口这么牛逼,可以存储任何类型,那我岂不是可以随意用空接口了?

    答:这样不好,首先go语言本身是一个强类型语言,规定传什么类型参数就传什么类型,强类型语言通过规定了,能够提高性能,这也是其的一大特点,而如果不规定,用途就模糊不清了,那样维护性差,可操作性低,就丧失了go的优势了。

    interface {
    
    }

    实例如下:

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        var a interface{} //定义1个空接口
        var b int
    
        a = b //a是空接口,所以可以存储任何类型
        fmt.Printf("a=%v a:%T
    ", a, a)
        var c float32
    
        a = c
        fmt.Printf("a=%v a:%T
    ", a, a)
    }

    执行结果:

    实例2:

    package main
    
    import (
        "fmt"
    )
    
    func describe(i interface{}) {
        fmt.Printf("Type = %T, value = %v
    ", i, i)
    }
    
    func main() {
        s := "hello world"
        describe(s) //空接口可以存string
        i := 55
        describe(i) //空接口可以存int
        strt := struct {
            name string
        }{
            name: "Naveen R",
        }
        describe(strt) //空接口可以存struct,可以证明其可以存储任意类型
    }

     执行结果:

    三、类型断言

    3.1 如何获取接口类型里面存储的具体的值呢?

    实例1:

    package main
    
    import (
        "fmt"
    )
    
    type Animal interface {
        Eat()
    }
    
    type Dog struct {
        name string
    }
    
    func (d *Dog) Eat() {
        fmt.Printf("%s is eating
    ", d.name)
    }
    
    type Pig struct {
        name string
    }
    
    func (d *Pig) Eat() {
        fmt.Printf("%s is eating
    ", d.name)
    }
    
    //类型断言
    func Describe(a Animal) { //注意Animal(接口)本身就是引用类型,这里不能加*,加上会报错
        dog := a.(*Dog) //将animal指定为dog(但是animal中不一定只有dog)
        dog.Eat()
    }
    
    func main() {
        var dog = &Dog{
            name: "旺财",
        }
    
        var a Animal
        a = dog
        fmt.Printf("I am dog
    ")
        Describe(a) //dog调用类型断言的describe函数
    
        var pig = &Pig{
            name: "佩奇",
        }
    
        a = pig
        fmt.Printf("I am pig
    ")
        Describe(a) //pig也调用类型断言的describe函数
    }

     执行结果:

    解释:

    可以发现直接报错了,这是因为我们强行将animal定为dog,但是animal中还有pig,所以pig在调用describe函数时,就冲突了,就报错了。这是一个很大的坑,如何解决呢?

    解决办法:

    引入 ok判断机制!

    实例如下:

    package main
    
    import (
        "fmt"
    )
    
    type Animal interface {
        Eat()
    }
    
    type Dog struct {
        name string
    }
    
    func (d *Dog) Eat() {
        fmt.Printf("%s is eating
    ", d.name)
    }
    
    type Pig struct {
        name string
    }
    
    func (d *Pig) Eat() {
        fmt.Printf("%s is eating
    ", d.name)
    }
    
    //类型断言
    func Describe(a Animal) { //注意Animal(接口)本身就是引用类型,这里不能加*,加上会报错
        dog, ok := a.(*Dog) //引入ok判断机制
        if !ok {
            fmt.Printf("convert to dog failed
    ")
            return
        }
        fmt.Printf("describe suncc
    ")
        dog.Eat()
        fmt.Printf("describe suncc----------
    ")
    }
    
    func main() {
        var dog = &Dog{
            name: "旺财",
        }
    
        var a Animal
        a = dog
        fmt.Printf("I am dog
    ")
        Describe(a)
    
        var pig = &Pig{
            name: "佩奇",
        }
    
        a = pig
        fmt.Printf("I am pig
    ")
        Describe(a)
    }

     执行结果:

    解释:

    我们可以发现引入ok判断机制后,将animal定为dog,但是animal中还有pig,当确定dog时就会执行成功,而是pig则就会失败。

    实例2:

    package main
    
    import (
        "fmt"
    )
    
    func assert(i interface{}) {
        s := i.(int)  //将空接口传入的类型定为int
        fmt.Println(s)
    }
    
    func main() {
        var s interface{} = "harden"
        assert(s)
    }

     执行结果:

    解决上述报错:

    引入ok机制:

    package main
    
    import (
        "fmt"
    )
    
    func assert(i interface{}) {
        v, ok := i.(int) //将空接口传入的类型定为int
        fmt.Println(v, ok)
    }
    
    func main() {
        var s interface{} = 56
        assert(s)
        var i interface{} = "harden"
        assert(i)
    }

     执行结果:

    3.2 type switch

    方法1:问题:需要转2次

    实例1:

    package main
    
    import (
        "fmt"
    )
    
    type Animal interface {
        Eat()
    }
    
    type Dog struct {
        name string
    }
    
    func (d *Dog) Eat() {
        fmt.Printf("%s is eating
    ", d.name)
    }
    
    type Pig struct {
        name string
    }
    
    func (d *Pig) Eat() {
        fmt.Printf("%s is eating
    ", d.name)
    }
    
    //types switch
    func DescribeSwitch(a Animal) {
        fmt.Printf("DescribeSwitch(a) begin
    ")
        switch a.(type) { //格式是固定的,type是一个关键字  //强制转第一次type关键字
        case *Dog:  //强制转换成dog(强制转第二次)
            dog := a.(*Dog)
            dog.Eat()
        case *Pig: //强制转成pig(强制转第二次)
            pig := a.(*Pig)
            pig.Eat()
        }
        fmt.Printf("DescribeSwitch(a) end
    ")
    }
    
    func main() {
        var dog = &Dog{
            name: "旺财",
        }
    
        var a Animal
        a = dog
        fmt.Printf("I am dog
    ")
        DescribeSwitch(a)
    
        var pig = &Pig{
            name: "佩奇",
        }
    
        a = pig
        fmt.Printf("I am pig
    ")
        DescribeSwitch(a)
    }

    执行结果:

    实例2:

    package main
    
    import (
        "fmt"
    )
    
    func findType(i interface{}) {
        switch i.(type) {
        case string:
            fmt.Printf("I am a string and my value is %s
    ", i.(string))
        case int:
            fmt.Printf("I am a int and my value is %d
    ", i.(int))
        default:
            fmt.Printf("Unknown type
    ")
        }
    }
    
    func main() {
        findType("hello")
        findType(77)
        findType(88.98)
    }

     执行结果:

    方法2:(推荐) 解决需要转2次问题

    实例1:

    package main
    
    import (
        "fmt"
    )
    
    type Animal interface {
        Eat()
    }
    
    type Dog struct {
        name string
    }
    
    func (d *Dog) Eat() {
        fmt.Printf("%s is eating
    ", d.name)
    }
    
    type Pig struct {
        name string
    }
    
    func (d *Pig) Eat() {
        fmt.Printf("%s is eating
    ", d.name)
    }
    
    //types switch
    func DescribeSwitch(a Animal) {
        fmt.Printf("DescribeSwitch(a) begin
    ")
        switch v := a.(type) { //格式是固定的,type是一个关键字
        case *Dog:
            v.Eat() //v就是断言之后的具体类型
        case *Pig:
            v.Eat()
        }
        fmt.Printf("DescribeSwitch(a) end
    ")
    }
    
    func main() {
        var dog = &Dog{
            name: "旺财",
        }
    
        var a Animal
        a = dog
        fmt.Printf("I am dog
    ")
        DescribeSwitch(a)
    
        var pig = &Pig{
            name: "佩奇",
        }
    
        a = pig
        fmt.Printf("I am pig
    ")
        DescribeSwitch(a)
    }

     执行结果:

    实例2:

    package main
    
    import (
        "fmt"
    )
    
    func findType(i interface{}) {
        switch v := i.(type) {
        case string:
            fmt.Printf("I am a string and my value is %s
    ", v)
        case int:
            fmt.Printf("I am a int and my value is %d
    ", v)
        default:
            fmt.Printf("Unknown type
    ")
        }
    }
    
    func main() {
        findType("hello")
        findType(77)
        findType(88.98)
    }

     执行结果:

     

    四、指针接收和值接收

    葵花宝典:

    指针类型实现的接口,值类型是赋值不了的。但是值类型实现的接口,指针类型依然可以赋值

    例子如下:

    比如说Dog是通过指针类型实现的接口:

    当我们进行值类型赋值就会报错:

    我们可以通过传入地址来解决:

    但如果Dog是值类型实现的接口:

    我们的指针类型对其进行赋值依然没问题:

    这是因为go在传入时帮我们将指针类型转换为了值类型。

    五、多接口

    5.1 多接口

    同一个类型可以实现多个接口。

    比如说狗是一个动物,它可以实现动物的接口,狗同时还是一个哺乳动物,他也可以实现哺乳动物的接口。重要的是:只要把多个接口的方法都实现了,那么其就可以实现多个接口。

    package main
    
    import (
        "fmt"
    )
    
    //定义2个接口
    type Animal interface {
        Eat()
    }
    
    type BuRuAnimal interface {
        ChiNai()
    }
    
    type Dog struct {
        name string
    }
    
    //Dog实现了上述2个接口的方法,所以其也实现了上述2个接口
    func (d *Dog) Eat() {
        fmt.Printf("%s is eating
    ", d.name)
    }
    
    func (d *Dog) ChiNai() {
        fmt.Printf("%s is ChiNai
    ", d.name)
    }
    
    func main() {
        var dog = &Dog{
            name: "旺财",
        }
    
        var a Animal
        fmt.Printf("a:%v dog:%v
    ", a, dog)
        a = dog
        a.Eat()
    
        var b BuRuAnimal
        b = dog
        b.ChiNai()
    }

     执行结果:

    5.2 接口嵌套

    package main
    
    import (
        "fmt"
    )
    
    //定义2个接口
    type Animal interface {
        Eat()
    }
    
    type BuRuAnimal interface {
        ChiNai()
    }
    
    //接口嵌套
    type AdvanceAnimal interface { //要想实现AdvanceAnimal接口,那么就需要满足嵌套的接口的所有方法
        Animal
        BuRuAnimal
    }
    
    type Dog struct {
        name string
    }
    
    func (d *Dog) Eat() {
        fmt.Printf("%s is eating
    ", d.name)
    }
    
    func (d *Dog) ChiNai() {
        fmt.Printf("%s is ChiNai
    ", d.name)
    }
    
    func main() {
        var dog = &Dog{
            name: "旺财",
        }
    
        var a AdvanceAnimal
        fmt.Printf("a:%v dog:%v
    ", a, dog)
        a = dog
        a.Eat()
        a.ChiNai()
    }

     执行结果:

    六、接口实例讲解

    6.1 io包中的writer接口

    底层writer接口的规定的方法:

     

    实例:

    package main
    
    import (
        "fmt"
        "os"
    )
    
    type Test struct {
        data string
    }
    
    //这里实现1个wirter接口的方法
    func (t *Test) Write(p []byte) (n int, err error) {
        t.data = string(p)
        return len(p), nil
    }
    
    func main() {
        file, _ := os.Create("c:/tmp/c.txt")
        fmt.Fprintf(os.Stdout, "hello world
    ") //输出到终端 (这里FPrintf函数要传入一个writer类型接口,把具体实例os.Stdout传给writer接口)
        fmt.Fprintf(file, "hello world
    ")
        /* 因为writer接口的存在,我们可以省去下面的步骤:
        fmt.FPtrintfConsole()
        fmt.FPtrintfFile()
        fmt.FPtrintfNet()
        */
        var t *Test = &Test{}
        fmt.Fprintf(t, "this is a test inteface:%s", "?akdfkdfjdkfk
    ") //存入到data中了
    
        fmt.Printf("t.data:%s
    ", t.data)
    }

     执行结果:

    6.2 fmt包中的Stringer接口

    底层stringer接口

    实例:

    package main
    
    import (
        "encoding/json"
        "fmt"
    )
    
    type Student struct {
        Name string
        Age  int
    }
    
    func (s *Student) String() string {
        data, _ := json.Marshal(s)
        return string(data)
    }
    
    func main() {
        var a = &Student{
            Name: "hell",
            Age:  12,
        }
        fmt.Printf("a = %v
    ", a)  //fmt包调Print相关函数时,看传进去的变量是否实现了stringer接口
    }

     执行结果:

    解释:

    Printf函数会判断传入的变量a是否实现了stringer接口,stringer接口会调其中的string方法去格式化输出字符串

    6.3 error包中的error接口

    底层error接口:

    type error interface {
        Error() string
    }

    实例1:

    package main
    
    import (
        "fmt"
        "time"
    )
    
    type MyError struct {
        When time.Time
        What string
    }
    
    func (e MyError) Error() string {
        str := fmt.Sprintf("at %v, %s", e.When, e.What)
        fmt.Printf("1:%T
    ", str)
        return str
    }
    func run() error {
        fmt.Println("0")
        str := MyError{time.Now(), "it didn't work"}
        fmt.Printf("2:%T
    ", str)
        fmt.Println(MyError{time.Now(), "it didn't work"})
        return str
    }
    func main() {
        if err := run(); err != nil {
            fmt.Printf("3:%T
    ", err)
            fmt.Println(err)
        }
    }

     执行结果:

    解释:

    因为error接口只有1个Error方法,所以我们就可以实现自己的错误类型,此处我们就是定义了myerror结构体,但是我们实现了error方法,在函数中返回时返回error,就把我们自己的错误类型反回给了error接口了

    实例2:

    package main
    
    import (
        "fmt"
    )
    
    type ErrNegativeSqrt float64
    
    func (e ErrNegativeSqrt) Error() string {
    
        return fmt.Sprintf("cannot Sqrt negative number:%v", float64(e))
    }
    
    func Sqrt(x float64) (float64, error) {
        if x < 0 {
            return 0, ErrNegativeSqrt(x)   //如果小于0,则返回我们自定义的错误
        }
    
        return x, nil
    }
    
    func main() {
        fmt.Println(Sqrt(2))
        _, err := Sqrt(-2)
        if err != nil {
            switch err.(type) { //类型断言,获取错误码
            case ErrNegativeSqrt:
                fmt.Printf("ErrNegativeSqrt
    ")
            default:
    
            }
        }
    }

     执行结果:

    6.4 Reader接口

     实例:

    package main
    
    import (
        "fmt"
        _ "strings"
        "time"
    )
    
    type MyReader struct{}
    
    // TODO: Add a Read([]byte) (int, error) method to MyReader.
    func (r MyReader) Read(b []byte) (int, error) {
        b[0] = 'A'
        return 1, nil
    }
    func main() {
        var myre MyReader
        b := make([]byte, 1)
        //for {
        //r := strings.NewReader(b)
        myre.Read(b)
        fmt.Printf("%c
    ", b[0])
        time.Sleep(1 * time.Second)
        myre.Read(b)
        fmt.Println(b[0])
        //}
    }

     执行结果:

    解释:

    定义1个Myreader结构体,下面实现reader方法,实例实现了reader方法,按照定义的read方法去操作。

    6.5 Image接口

    image接口底层:

    type Image interface {
        ColorModel() color.Model
        Bounds() Rectangle
        At(x, y int) color.Color
    }

    实例:

    package main
    
    import (
        "image"
        "image/color"
    
        "golang.org/x/tour/pic"
        //"fmt"
    )
    
    type Image struct {
        weight int
        height int
    }
    
    func (c Image) ColorModel() color.Model {
        return color.RGBAModel
    }
    func (b *Image) Bounds() image.Rectangle {
        return image.Rect(0, 0, b.weight, b.height)
    }
    func (a *Image) At(x, y int) color.Color {
        //fmt.Println(x, y)
        return color.RGBA{uint8(x), uint8(y), 255, 255}
    }
    func main() {
        m := &Image{700, 50}
        //m.At(225, 0)
        pic.ShowImage(m) //m.At(x, y)的参数由pic传入,传入了所有情况
    }

    解释:

    定义了image的结构体,下面实现了底层image接口的方法,所以我们就可以传入实例进行操作。

  • 相关阅读:
    【转】框架集中framespacing、border和frameborder属性的关系
    使用ArcGIS GP服务之二手工建模
    使用ArcGIS GP服务之五 JavaScript的调用
    使用ArcGIS GP服务之三发布前的准备
    计算GPS WGS_84 两点的距离
    使用ArcGIS GP服务之四GP服务发布
    计算GPS WGS_84 两点的距离 更加细腻的算法
    QueryPerformanceFrequency
    cocos2dx App 图标
    cocos2dx CCTimer::timerWithTarget回调
  • 原文地址:https://www.cnblogs.com/forever521Lee/p/9494953.html
Copyright © 2011-2022 走看看