写点什么

实证与虚无, 抽象和具象,Go lang1.18 入门精炼教程,由白丁入鸿儒,Go lang 接口 (interface) 的使用 EP08

  • 2022 年 8 月 11 日
    北京
  • 本文字数:3743 字

    阅读完需:约 12 分钟

实证与虚无,抽象和具象,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang接口(interface)的使用EP08

看到接口这两个字,我们一定会联想到面向接口编程。说白了就是接口指定执行对象的具体行为,也就是接口表示让执行对象具体应该做什么,所以,普遍意义上讲,接口是抽象的,而实际执行行为,则是具象的。

接口(interface)的定义

在 Go lang 中,接口是一组方法签名,当类型为接口中的所有方法提供定义时,它被称为实现接口。和面向接口的思想非常类似,接口指定了类型应该具有的方法,类型决定了到底该怎么实现这些方法:


/* 定义接口 */  type interface_name interface {     method_name1 [return_type]     method_name2 [return_type]     method_name3 [return_type]     ...     method_namen [return_type]  }    /* 定义结构体 */  type struct_name struct {     /* variables */  }    /* 实现接口方法 */  func (struct_name_variable struct_name) method_name1() [return_type] {     /* 方法实现 */  }  ...  func (struct_name_variable struct_name) method_namen() [return_type] {     /* 方法实现*/  }
复制代码


具体实现方式:


package main    import (    "fmt"  )    type Phone interface {    call()  }    type Android struct {  }    func (android Android) call() {    fmt.Println("I am Android")  }    type Ios struct {  }    func (ios Ios) call() {    fmt.Println("I am Ios")  }    func main() {    var phone Phone      phone = new(Android)    phone.call()      phone = new(Ios)    phone.call()    }
复制代码


程序返回:


I am Android  I am Ios
复制代码


是的,现在我们可以结构体、函数、以及接口三箭齐发了,这里首先定义好手机接口,并且指定 call()方法,意思是我在抽象层面拥有一个手机,手机应该具有打电话的功能。


随后分别定义结构体和函数(也是方法),分别具现化的实现接口的指定行为,精神上大家是一样的,但肉体上,一个是安卓,另一个则是苹果。


Go lang 中,接口可以被任意的对象实现,同样地,一个对象也可以实现任意多个接口,任意的类型都实现了空接口(interface{}),也就是包含 0 个 method 的 interface。


诚然,如果单独使用结构体,我们也可以,实现类似多态的结构:




package main import "fmt" type Human struct { name string age int phone string } type Student struct { Human //匿名字段 school string loan float32 } type Employee struct { Human //匿名字段 company string money float32 } //Human实现Sayhi方法 func (h Human) SayHi() { fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) } //Human实现Sing方法 func (h Human) Sing(lyrics string) { fmt.Println("。。。。。。。。", lyrics) } //Employee重写Human的SayHi方法 func (e Employee) SayHi() { fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, e.company, e.phone) //Yes you can split into 2 lines here. }
复制代码


可以单独为结构体定义方法,但如果接口参与逻辑:


type Men interface {    SayHi()    Sing(lyrics string)  }    
func main() { mike := Student{Human{"Mike", 10, "1"}, "MIT", 0.00} paul := Student{Human{"Paul", 20, "2"}, "Harvard", 100} sam := Employee{Human{"Sam", 30, "3"}, "Golang Inc.", 1000} Tom := Employee{Human{"Tom", 40, "4"}, "Things Ltd.", 5000} //定义Men类型的变量i var i Men //i能存储Student i = mike fmt.Println("This is Mike, a Student:") i.SayHi() i.Sing("song") //i也能存储Employee i = Tom fmt.Println("This is Tom, an Employee:") i.SayHi() i.Sing("song") //定义了slice Men fmt.Println("Let's use a slice of Men and see what happens") x := make([]Men, 3) //T这三个都是不同类型的元素,但是他们实现了同一个接口 x[0], x[1], x[2] = paul, sam, mike for _, value := range x { value.SayHi() } }
复制代码


程序返回:




This is Mike, a Student: Hi, I am Mike you can call me on 1 。。。。。。。。 song This is Tom, an Employee: Hi, I am Tom, I work at Things Ltd.. Call me on 4 。。。。。。。。 song Let's use a slice of Men and see what happens Hi, I am Paul you can call me on 2 Hi, I am Sam, I work at Golang Inc.. Call me on 3 Hi, I am Mike you can call me on 1
复制代码


由此可见,接口的出现,把本来不相关的结构体类型以抽象的形式结合了起来,不同的类型实现内容不同的共性方法。


也就是说,Men 接口类型的变量 i,那么 i 里面可以存 Human、Student 或者 Employee 值,所以 i 是抽象的,而 Human、Student 或者 Employee 就是 i 的具象化操作。

接口指定函数参数

接口不仅仅可以指定无参方法,也可以指定具体的参数,让函数接受各种类型的参数:


package main    import "fmt"    type Human interface {    Len()  }  type Student interface {    Human  }    type Test struct {  }    func (h *Test) Len() {    fmt.Println("10个")  }  func main() {    var s Student    s = new(Test)    s.Len()  }
复制代码


程序返回:


10个
复制代码


这里使用接口嵌套的形式,Human 接口定义了 Len 方法,结构体 Test 实现了所有的 Len 接口方法,当结构体 s 中调用 Test 结构体的时候,s 就相当于 Python 中的继承,s 继承了 Test,因此,s 可以不用重写所有的 Human 接口中的方法,因为父构造器已经实现了接口。

鸭子类型(ducktyping)

什么是鸭子类型?当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。


所谓远看山有色,近听水无声,春去花还在,人来鸟不惊,意象上来讲,一个事物究竟是不是某一种类型,取决于它具不具备这个类型的特性,这就是鸭子类型的本质。


所以鸭子类型主要描述事物的外部行为而非内部构造,在面向对象的编程语言中,比如 Python 中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定。


编写 test.py 文件:


class PsyDuck():      def gaga(self):          print("这是可达鸭")      # 使用的对象和方法  class DoningdDuck():      def gaga(self):          print("这是唐老鸭")      # 被调用的函数  def duckSay(func):      return func.gaga()      # 限制调用方式  if __name__ != '__main__':      print("must __main__")    if __name__ == "__main__":        # 实例化对象      duck = PsyDuck()      person = DoningdDuck()      # 调用函数      duckSay(duck)      duckSay(person)
复制代码


程序返回:


这是可达鸭  这是唐老鸭
复制代码


所以到底是什么鸭子不重要,重要的是调用了谁的实例。


再来看看 go lang 的手笔:


package main    import "fmt"    //定义一个鸭子接口  //Go 接口是一组方法的集合,可以理解为抽象的类型。它提供了一种非侵入式的接口。任何类型,只要实现了该接口中方法集,那么就属于这个类型。  type Duck interface {    Gaga()  }    //假设现在有一个可达鸭类型  type PsyDuck struct{}    //可达鸭声明方法-满足鸭子会嘎嘎叫的特性  func (pd PsyDuck) Gaga() {    fmt.Println("this is PsyDuck")  }    //假设现在有一个唐老鸭类型  type DonaldDuck struct{}    //唐老鸭声明方法-满足鸭子会嘎嘎叫的特性  func (dd DonaldDuck) Gaga() {    fmt.Println("this is DoningdDuck")  }    //要调用的函数 - 负责执行鸭子能做的事情,注意这里的参数,有类型限制为Duck接口  func DuckSay(d Duck) {    d.Gaga()  }    func main() {    //提示开始打印    fmt.Println("duck typing")      //实例化对象    var pd PsyDuck    //可达鸭类型    var dd DonaldDuck //唐老鸭类型      //调用方法    DuckSay(pd) //因为可达鸭实现了所有鸭子的函数,所以可以这么用    DuckSay(dd) //因为唐老鸭实现了所有鸭子的函数,所以可以这么用  }
复制代码


程序返回:


duck typing  this is PsyDuck  this is DoningdDuck
复制代码


这里首先定义抽象的鸭子接口,指定 gaga 方法,不同的结构体:可达鸭、唐老鸭分别绑定并且实现了鸭子接口的方法,然后声明一个调用函数,在执行的时候,将结构体变量传递给调用函数,动态地实现了不同类型的方法。

结语

所谓接口(interface)的抽象性,就是从表面看到本质,从片面看到整体,然后抽出那些稳定的、共有的特性。平时我们会考虑代码的重用性,组件的复用性,同一个功能对不同场景的复用性,有了复用的能力,就能够用更少的开发去满足更多场景的同类需求问题。从而能够从一个具体的需求,看到一类的需求,看到衍生的相关的需求,甚至再对需求进行分类,看到更高层面的需求。进而才能够系统性解决同类的需求而不是就事论事点对点解决问题。


所以,总的来说,接口的极致就是抽象,而抽象的极致,则是格局,接口,可以更好的帮我们扩大程序视野的格局。

发布于: 刚刚阅读数: 3
用户头像

专注技术,凝聚意志,解决问题 v3u.cn 2020.12.21 加入

还未添加个人简介

评论

发布
暂无评论
实证与虚无,抽象和具象,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang接口(interface)的使用EP08_Go_刘悦的技术博客_InfoQ写作社区