Go语言接口与动态类型
来源:云海天教程
时间:2023-01-08 15:45:55 109浏览 收藏
本篇文章主要是结合我之前面试的各种经历和实战开发中遇到的问题解决经验整理的,希望这篇《Go语言接口与动态类型》对你有很大帮助!欢迎收藏,分享给更多的需要的朋友学习~
在经典的面向对象语言(像 C++,Java 和 C#)中数据和方法被封装为类的概念:类包含它们两者,并且不能剥离。Go语言没有类:数据(结构体或更一般的类型)和方法是一种松耦合的正交关系。
Go语言中的接口跟 Java/C# 类似:都是必须提供一个指定方法集的实现。但是更加灵活通用:任何提供了接口方法实现代码的类型都隐式地实现了该接口,而不用显式地声明。
Go 的动态类型
和其它语言相比,Go语言是唯一结合了接口值,静态类型检查(是否该类型实现了某个接口),运行时动态转换的语言,并且不需要显式地声明类型是否满足某个接口。该特性允许我们在不改变已有的代码的情况下定义和使用新接口。接收一个(或多个)接口类型作为参数的函数,可以被实现了该接口的类型实例调用。实现了某个接口的类型可以被传给任何以此接口为参数的函数。
类似于 Python 和 Ruby 这类动态语言中的动态类型(duck typing);这意味着对象可以根据提供的方法被处理(例如,作为参数传递给函数),而忽略它们的实际类型:它们能做什么比它们是什么更重要。
这在程序 duck_dance.go 中得以阐明,函数 DuckDance 接受一个 IDuck 接口类型变量。仅当 DuckDance 被实现了 IDuck 接口的类型调用时程序才能编译通过。
package mainimport "fmt"type IDuck interface { Quack() Walk()}func DuckDance(duck IDuck) { for i := 1; i 输出:
I am quacking!
I am walking!
I am quacking!
I am walking!
I am quacking!
I am walking!
cannot use b (type *Bird) as type IDuck in function argument:
*Bird does not implement IDuck (missing Walk method)
动态方法调用
像 Python,Ruby 这类语言,动态类型是延迟绑定的(在运行时进行):方法只是用参数和变量简单地调用,然后在运行时才解析(它们很可能有像 responds_to 这样的方法来检查对象是否可以响应某个方法,但是这也意味着更大的编码量和更多的测试工作)。Go语言的实现与此相反,通常需要编译器静态检查的支持:当变量被赋值给一个接口类型的变量时,编译器会检查其是否实现了该接口的所有函数。如果方法调用作用于像 interface{} 这样的“泛型”上,可以通过类型断言来检查变量是否实现了相应接口。
例如,用不同的类型表示 XML 输出流中的不同实体。然后我们为 XML 定义一个如下的“写”接口(甚至可以把它定义为私有接口):
type xmlWriter interface {
WriteXML(w io.Writer) error
}
// Exported XML streaming function.
func StreamXML(v interface{}, w io.Writer) error {
if xw, ok := v.(xmlWriter); ok {
// It’s an xmlWriter, use method of asserted type.
return xw.WriteXML(w)
}
// No implementation, so we have to use our own function (with perhaps reflection):
return encodeToXML(v, w)
}
// Internal XML encoding function.
func encodeToXML(v interface{}, w io.Writer) error {
// ...
}
因此 Go 提供了动态语言的优点,却没有其他动态语言在运行时可能发生错误的缺点。对于动态语言非常重要的单元测试来说,这样即可以减少单元测试的部分需求,又可以发挥相当大的作用。
Go 的接口提高了代码的分离度,改善了代码的复用性,使得代码开发过程中的设计模式更容易实现。用 Go 接口还能实现依赖注入模式 。
接口的提取
提取接口是非常有用的设计模式,可以减少需要的类型和方法数量,而且不需要像传统的基于类的面向对象语言那样维护整个的类层次结构。Go 接口可以让开发者找出自己写的程序中的类型。假设有一些拥有共同行为的对象,并且开发者想要抽象出这些行为,这时就可以创建一个接口来使用。假设我们需要一个新的接口 TopologicalGenus,用来给 shape 排序(这里简单地实现为返回 int)。我们需要做的是给想要满足接口的类型实现 Rank() 方法:
//multi_interfaces_poly.gopackage mainimport "fmt"type Shaper interface { Area() float32}type TopologicalGenus interface { Rank() int}type Square struct { side float32}func (sq *Square) Area() float32 { return sq.side * sq.side}func (sq *Square) Rank() int { return 1}type Rectangle struct { length, width float32}func (r Rectangle) Area() float32 { return r.length * r.width}func (r Rectangle) Rank() int { return 2}func main() { r := Rectangle{5, 3} // Area() of Rectangle needs a value q := &Square{5} // Area() of Square needs a pointer shapes := []Shaper{r, q} fmt.Println("Looping through shapes for area ...") for n, _ := range shapes { fmt.Println("Shape details: ", shapes[n]) fmt.Println("Area of this shape is: ", shapes[n].Area()) } topgen := []TopologicalGenus{r, q} fmt.Println("Looping through topgen for rank ...") for n, _ := range topgen { fmt.Println("Shape details: ", topgen[n]) fmt.Println("Topological Genus of this shape is: ", topgen[n].Rank()) }}输出:
Looping through shapes for area ...
Shape details: {5 3}
Area of this shape is: 15
Shape details: &{5}
Area of this shape is: 25
Looping through topgen for rank ...
Shape details: {5 3}
Topological Genus of this shape is: 2
Shape details: &{5}
Topological Genus of this shape is: 1
接口,它本身不用改变,只需要在这个类型上实现新的方法。
显式地指明类型实现了某个接口
如果希望满足某个接口的类型显式地声明它们实现了这个接口,可以向接口的方法集中添加一个具有描述性名字的方法。例如:type Fooer interface {
Foo()
ImplementsFooer()
}
type Bar struct{}
func (b Bar) ImplementsFooer() {} func (b Bar) Foo() {}
空接口和函数重载
在 Go语言中函数重载可以用可变参数 ...T 作为函数最后一个参数来实现。如果我们把 T 换为空接口,那么可以知道任何类型的变量都是满足 T (空接口) 类型的,这样就允许我们传递任何数量任何类型的参数给函数,即重载的实际含义。函数 fmt.Printf 就是这样做的:
fmt.Printf(format string, a ...interface{}) (n int, errno error)
这个函数通过枚举 slice 类型的实参动态确定所有参数的类型。并查看每个类型是否实现了 String() 方法。接口的继承
当一个类型包含(内嵌)另一个类型(实现了一个或多个接口)的指针时,这个类型就可以使用(另一个类型)所有的接口方法。例如:
type Task struct {
Command string
*log.Logger
}
func NewTask(command string, logger *log.Logger) *Task {
return &Task{command, logger}
}
task.Log()
类型可以通过继承多个接口来提供像 多重继承一样的特性:type ReaderWriter struct {
*io.Reader
*io.Writer
}
有用的接口可以在开发的过程中被归纳出来。添加新接口非常容易,因为已有的类型不用变动(仅仅需要实现新接口的方法)。已有的函数可以扩展为使用接口类型的约束性参数:通常只有函数签名需要改变。对比基于类的 OO 类型的语言在这种情况下则需要适应整个类层次结构的变化。
文中关于golang的知识介绍,希望对你的学习有所帮助!若是受益匪浅,那就动动鼠标收藏这篇《Go语言接口与动态类型》文章吧,也可关注golang学习网公众号了解相关技术文章。
-
250 收藏
-
462 收藏
-
394 收藏
-
256 收藏
-
282 收藏
-
165 收藏
-
473 收藏
-
377 收藏
-
384 收藏
-
246 收藏
-
110 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习
-
- 饱满的手套
- 这篇技术贴真及时,很详细,写的不错,已加入收藏夹了,关注作者大大了!希望作者大大能多写Golang相关的文章。
- 2023-02-12 17:02:37
-
- 开心的棒棒糖
- 很棒,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢楼主分享博文!
- 2023-02-10 14:01:22
-
- 舒服的裙子
- 这篇技术文章出现的刚刚好,很详细,很棒,码起来,关注师傅了!希望师傅能多写Golang相关的文章。
- 2023-01-30 07:53:28
-
- 包容的蓝天
- 很有用,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢楼主分享技术贴!
- 2023-01-20 22:08:13
-
- 舒服的荔枝
- 这篇博文真及时,太详细了,感谢大佬分享,收藏了,关注老哥了!希望老哥能多写Golang相关的文章。
- 2023-01-19 22:12:11
-
- 孝顺的橘子
- 这篇文章太及时了,作者加油!
- 2023-01-11 13:05:20
-
- 务实的鸭子
- 很详细,码住,感谢大佬的这篇博文,我会继续支持!
- 2023-01-10 17:42:34