实证与虚无,抽象和具象,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天前
|
安全 Go
Golang深入浅出之-Go语言模板(text/template):动态生成HTML
【4月更文挑战第24天】Go语言标准库中的`text/template`包用于动态生成HTML和文本,但不熟悉其用法可能导致错误。本文探讨了三个常见问题:1) 忽视模板执行错误,应确保正确处理错误;2) 忽视模板安全,应使用`html/template`包防止XSS攻击;3) 模板结构不合理,应合理组织模板以提高可维护性。理解并运用这些最佳实践,能提升Go语言模板编程的效率和安全性,助力构建稳健的Web应用。
15 0
|
4天前
|
程序员 Go
Golang深入浅出之-Select语句在Go并发编程中的应用
【4月更文挑战第23天】Go语言中的`select`语句是并发编程的关键,用于协调多个通道的读写。它会阻塞直到某个通道操作可行,执行对应的代码块。常见问题包括忘记初始化通道、死锁和忽视`default`分支。要解决这些问题,需确保通道初始化、避免死锁并添加`default`分支以处理无数据可用的情况。理解并妥善处理这些问题能帮助编写更高效、健壮的并发程序。结合使用`context.Context`和定时器等工具,可提升`select`的灵活性和可控性。
17 2
|
1天前
|
JSON JavaScript 前端开发
Golang深入浅出之-Go语言JSON处理:编码与解码实战
【4月更文挑战第26天】本文探讨了Go语言中处理JSON的常见问题及解决策略。通过`json.Marshal`和`json.Unmarshal`进行编码和解码,同时指出结构体标签、时间处理、omitempty使用及数组/切片区别等易错点。建议正确使用结构体标签,自定义处理`time.Time`,明智选择omitempty,并理解数组与切片差异。文中提供基础示例及时间类型处理的实战代码,帮助读者掌握JSON操作。
12 1
Golang深入浅出之-Go语言JSON处理:编码与解码实战
|
1天前
|
安全 Go 开发者
Golang深入浅出之-Go语言模板(text/template):动态生成HTML
【4月更文挑战第25天】Go语言的`text/template`和`html/template`库提供动态HTML生成。本文介绍了模板基础,如基本语法和数据绑定,以及常见问题和易错点,如忘记转义、未初始化变量、复杂逻辑处理和错误处理。建议使用`html/template`防止XSS攻击,初始化数据结构,分离业务逻辑,并严谨处理错误。示例展示了条件判断和循环结构。通过遵循最佳实践,开发者能更安全、高效地生成HTML。
7 0
|
2天前
|
数据管理 Go 开发者
Golang深入浅出之-Go语言上下文(context)包:处理取消与超时
【4月更文挑战第25天】Go语言中的`context`包在并发、网络请求和长任务中至关重要,提供取消、截止时间和元数据管理。本文探讨`context`基础,如`Background()`、`TODO()`、`WithCancel()`、`WithDeadline()`和`WithTimeout()`。常见问题包括不当传递、过度使用`Background()`和`TODO()`以及忽略错误处理。通过取消和超时示例,强调正确传递上下文、处理取消错误和设置超时以提高应用健壮性和响应性。正确使用`context`是构建稳定高效Go应用的关键。
12 1
|
2天前
|
Unix Linux Go
Golang深入浅出之-信号(Signals)处理与优雅退出Go程序
【4月更文挑战第25天】Go语言中的信号处理关乎程序对外部事件的响应,尤其是优雅地终止进程。本文介绍了信号基础,如SIGINT、SIGTERM等常见信号,以及处理流程:注册处理器、等待信号、执行清理和优雅退出。强调了三个易错点及避免方法,并提供实战代码示例展示如何监听和响应信号。信号处理应简洁高效,确保程序健壮性和用户体验。
10 0
|
4天前
|
Go 开发者
Golang深入浅出之-Go语言上下文(context)包:处理取消与超时
【4月更文挑战第23天】Go语言的`context`包提供`Context`接口用于处理任务取消、超时和截止日期。通过传递`Context`对象,开发者能轻松实现复杂控制流。本文解析`context`包特性,讨论常见问题和解决方案,并给出代码示例。关键点包括:1) 确保将`Context`传递给所有相关任务;2) 根据需求选择适当的`Context`创建函数;3) 定期检查`Done()`通道以响应取消请求。正确使用`context`包能提升Go程序的控制流管理效率。
14 1
|
4天前
|
Go
Golang深入浅出之-信号(Signals)处理与优雅退出Go程序
【4月更文挑战第23天】在Go语言中,使用`os/signal`包处理信号对实现程序优雅退出和响应中断至关重要。本文介绍了如何注册信号处理器、处理常见问题和错误,以及提供代码示例。常见问题包括未捕获关键信号、信号处理不当导致程序崩溃和忽略清理逻辑。解决方案包括注册信号处理器(如`SIGINT`、`SIGTERM`)、保持信号处理器简洁和执行清理逻辑。理解并正确应用这些原则能增强Go程序的健壮性和可管理性。
14 1
|
4天前
|
存储 安全 Go
Golang深入浅出之-原子操作包(sync/atomic)在Go中的应用
【4月更文挑战第23天】Go语言的`sync/atomic`包支持原子操作,防止多线程环境中的数据竞争。包括原子整数和指针操作,以及原子标量函数。常见问题包括误用非原子操作、误解原子操作语义和忽略内存排序约束。解决方法是使用原子函数、结合其他同步原语和遵循内存约束。注意始终使用原子操作处理共享变量,理解其语义限制,并熟悉内存排序约束,以实现并发安全和高效的应用程序。
18 1
|
5天前
|
安全 Go 开发者
Golang深入浅出之-Go语言并发编程面试:Goroutine简介与创建
【4月更文挑战第22天】Go语言的Goroutine是其并发模型的核心,是一种轻量级线程,能低成本创建和销毁,支持并发和并行执行。创建Goroutine使用`go`关键字,如`go sayHello("Alice")`。常见问题包括忘记使用`go`关键字、不正确处理通道同步和关闭、以及Goroutine泄漏。解决方法包括确保使用`go`启动函数、在发送完数据后关闭通道、设置Goroutine退出条件。理解并掌握这些能帮助开发者编写高效、安全的并发程序。
13 1