• 实证与虚无,抽象和具象,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] {  
       /* 方法实现*/  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    具体实现方式:

    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()  
      
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    程序返回:

    I am Android  
    I am Ios
    
    • 1
    • 2

    是的,现在我们可以结构体、函数、以及接口三箭齐发了,这里首先定义好手机接口,并且指定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.  
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

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

    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()  
    	}  
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    程序返回:

    
    
    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
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

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

    也就是说,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()  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    程序返回:

    10个
    
    • 1

    这里使用接口嵌套的形式,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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    程序返回:

    这是可达鸭  
    这是唐老鸭
    
    • 1
    • 2

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

    再来看看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) //因为唐老鸭实现了所有鸭子的函数,所以可以这么用  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    程序返回:

    duck typing  
    this is PsyDuck  
    this is DoningdDuck
    
    • 1
    • 2
    • 3

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

    结语

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

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

  • 相关阅读:
    顶级架构师编写,《DDD领域驱动设计笔记》,看到内容后才明白啥叫顶级,一分钱一分货
    仿真2. 离散事件仿真
    存储区域网络(SAN)之FC-SAN和IP-SAN的比较
    06、HSMS协议介绍
    医院项目-预约挂号-第三部分
    哈工大李治军老师操作系统笔记【7】:多进程图像(Learning OS Concepts By Coding Them !)
    【TcaplusDB知识库】TcaplusDB技术支持介绍
    【架构师视角系列】Apollo配置中心之Client端(二)
    STM32——HAL库中寄存器地址名称映射分析
    如何清理废弃pv和其对应的文件夹
  • 原文地址:https://blog.csdn.net/zcxey2911/article/details/126290922