3

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

 1 year ago
source link: https://v3u.cn/a_id_231
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
实证与虚无,抽象和具象,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)的抽象性,就是从表面看到本质,从片面看到整体,然后抽出那些稳定的、共有的特性。平时我们会考虑代码的重用性,组件的复用性,同一个功能对不同场景的复用性,有了复用的能力,就能够用更少的开发去满足更多场景的同类需求问题。从而能够从一个具体的需求,看到一类的需求,看到衍生的相关的需求,甚至再对需求进行分类,看到更高层面的需求。进而才能够系统性解决同类的需求而不是就事论事点对点解决问题。

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK