golang接口初解析
golang接口初理解
- golang接口初理解
- 接口是什么
- 接口能做什么
- 为什么要有接口
- 关于duck Typing(鸭子类型)
- 什么时候需要接口
- 如何使用接口
- 需求
- 需求解析
- 面向过程的编程
- 面向”对象”式编程
- 面向接口编程
- 用接口实现功能的扩展
- golang接口的好处
很多golang初学者当学到接口这部分内容的时候,大多很疑惑:
- 接口是什么?
- 接口能做什么?
- 为什么要有接口?
- 什么时候需要接口?
- 接口如何使用?
等等诸如此类的问题,因为笔者在工作中主要使用C语言作为主要开发语言,对于java.C 等等语言不理解,也没接触过面向对象的范式,所以在学习的时候也是一头雾水下来,网上查阅的资料并没有系统的解释好这几个问题,下面笔者就结合实际经验来为大家解析一下golang的接口相关知识
接口是什么
要回答这个问题,先引用圣经中的一段原话:
在Go语言中还存在着另外一种类型:接口类型。接口类型是一种抽象的类型。它不会暴露出它所代表的对象的内部值的结构和这个对象支持的基础操作的集合;它们只会展示出它们自己的方法。也就是说当你有看到一个接口类型的值时,你不知道它是什么,唯一知道的就是可以通过它的方法来做什么。
首先从圣经原文中我们可以明确:接口是一种类型
接口能做什么
从圣经中可以看到:接口内部包含很多方法,你不知道他是什么,你唯一知道的就是可以通过他的方法来做什么,简而言之:接口类型具体描述了一系列方法的集合
为什么要有接口
从这个问题开始才算真正的难以理解的知识点也是最核心的知识点:为什么golang要创造一个接口这个东西?
要理解这个问题,那我们就要提出另一个问题:接口解决了什么问题?
其实要追溯起来,可能要联系到一个概念叫做 duck Typing
关于duck Typing(鸭子类型)
当一个东西,走起来像鸭子,叫起来也像鸭子,游泳也像鸭子,那么我们可以认为他就是一只鸭子
关于鸭子类型的定义不想赘述,很多书上包括文章上都有讲,他的主要思想就是:我们并不关心这个鸭子是什么东西,我们只关心这个鸭子能做什么?也就是只关心行为,结合前面圣经里面的说法: 你不知道它是什么,唯一知道的就是可以通过它的方法来做什么
行为可以理解为动作,比方说如果某个东西实现了手的抓取功能,那么他就是一只手(无论是人手,机械手,重点在于抓取),结合鸭子类型我们不难理解:接口本身并不代表什么,有意义的是他里面包含的方法,如果这个接口实现了一个抓取的方法,那么我们就可以把它当作一只手来处理
那么我们再来看接口解决了什么问题:当某些类有一些共同的方法的时候,怎样把这些类做一个统一操作呢,比方说人可以抓东西,机械手臂也能抓东西,那么我们能否把问题的焦点转化为对这个手的处理而不用关心其他的动作呢?
很幸运,接口可以做到
什么时候需要接口
如果理清了上面那个为什么要有接口的问题,那么这个问题就迎刃而解了:当我们不关心某个类型是什么而只是关心他有什么样的行为的时候,就需要接口了
如何使用接口
接下来这个是重点了,因为毕竟概念理解了但是在实际的工作中如果没法使用,等于是没用,接下来我就通过一个简单的例子解释一下如何在编程中使用接口,建议大家把例子重新自己实现出来,一来加深映像,二来可以帮助自己理解深刻,如有不同意见,欢迎批评
需求
这个是《go语言编程》(许式伟 著)里面的例子,我们来加以变形以便理解接口:假如有这样一个需求,客户需要一个音乐播放器能够播放MP3,ogg,wma等格式的音乐
需求解析
简单的需求解析其实就是:输入->输出的过程,在这个例子里我们的输入一个音乐文件,输出声音,但是这本例中,我们并不输出声音,而是用一个打印信息来标识已经开始播放了,所以输出实际为播放
面向过程的编程
type mp3 struct {
name string
}
type wma struct {
name string
}
func playMp3(mp mp3){
fmt.Println(" is playing mp3")
}
func playWma(wm wma){
fmt.Println("is playing wma")
}
func main() {
file := "let's go.mp3"
if strings.Contains(file,".mp3") {
fmt.Println(" is a mp3")
mu := mp3{file}
playMp3(mu)
}
if strings.Contains(file,".wma") {
fmt.Println(" is a mp3")
wm := wma{file}
playWma(wm)
}
}
在过程式编程我们可以看到,程序一点都不灵活,如果要增加一个新的类型的时候就很繁琐,要改一很多东西,所以接下来我编写一个面向对象式范例
面向”对象”式编程
type mp3 struct {
name string
}
type wma struct {
name string
}
func (m mp3) play() {
fmt.Println(" is playing mp3")
}
func (w wma) play() {
fmt.Println("is playing wma")
}
func main() {
file := "let's go.mp3"
if strings.Contains(file,".mp3") {
fmt.Println(" is a mp3")
mu := mp3{file}
mu.play()
}
if strings.Contains(file,".wma") {
fmt.Println(" is a mp3")
wm := wma{file}
wm.play()
}
}
其实golang自身并不带对象式编程,这个大家都懂,可以看到对象式编程其实就是每个类型自己实现了自己的play方法,这样就比过程式稍显灵活,但是感觉离目标很不够,因为如果我们想增加一个音乐类型的话还是需要在main函数里增加一个类型判断
面向接口编程
type Player interface {
play()
}
type mp3 struct {
name string
}
type wma struct {
name string
}
func (m mp3) play() {
fmt.Println(" is playing mp3")
}
func (w wma) play() {
fmt.Println("is playing wma")
}
var musicType = map[string]Player{
".mp3": new(mp3),
".wma": new(wma),
}
func main() {
file := "let's go.mp3"
var pl Player
for k, v := range musicType {
if strings.Contains(file, k) {
pl = v
}
}
pl.play()
}
可以看到,当我们用面向接口式编程的时候,我们的主函数可以大大简化,几乎都是对接口的操作,而且当我们需要增加一种音乐类型的时候,只需要改音乐类型实现自己的play方法,然后在musicType这个map里增加一项
想一想:如果用之前的两种方式能做到这么简洁吗?
用接口实现功能的扩展
假如现在客户增加这样一个需求,说在播放的时候,显示出当前播放歌曲的名称,因为我们在创建这个Player的时候仅仅new出了一个mp3或者wma,里面并没有填入相关信息,不过没关系,我们有强大的接口,这些都不是问题,由于下面的程序变动比较大,建议读者自己实现一遍,多思考:
package main
import (
"fmt"
"strings"
)
type Player interface {
play()
getName() string
}
type Getter interface {
getName() string
getTime() string
}
type Setter interface {
setName(string)
setTime(string)
}
type mp3 struct {
name string
time string
}
func (m mp3) getName() string {
return m.name
}
func (m mp3) getTime() string {
return m.time
}
func (m *mp3) setName(name string) {
m.name = name
}
func (m *mp3) setTime(time string) {
m.time = time
}
func (m mp3) play() {
fmt.Println(" is playing mp3:", m.name)
}
type wma struct {
name string
time string
}
func (w wma) getName() string {
return w.name
}
func (w wma) getTime() string {
return w.time
}
func (w *wma) setName(name string) {
w.name = name
}
func (w *wma) setTime(time string) {
w.time = time
}
func (w wma) play() {
fmt.Println("is playing wma:", w.name)
}
var musicType = map[string]interface{}{
".mp3": new(mp3),
".wma": new(wma),
}
func main() {
file := "let's go.mp3"
for k, v := range musicType {
if strings.Contains(file, k) {
//因为获得的类型为interface{}
//采用接口查询,看是否实现了Setter接口
if setter, ok := v.(Setter); ok {
fmt.Println("is a setter")
setter.setName(file)
}
if pl, ok := v.(Player); ok {
pl.play()
}
}
}
}
上面的程序增加了两个接口Getter和Setter,顾名思义,就是给类型做读取和设置用的,除此之外,有一种奇怪的用法,map那里返回一个interface{},而不是一个Player,原因在于一个编码原则:单一职责原则,意即一个程序做一件事,所以player只做palyer相关的事,而getter就负责获取
可以看到在程序主题那里我使用了
if setter, ok := v.(Setter); ok {
fmt.Println("is a setter")
setter.setName(file)
}
这不是什么稀奇的用法,而是golang的类型断言,前面说过,接口本质上是一种类型,所以同样能适用于类型断言,书上管这叫:接口查询,利用接口查询的范式可以看对应的数据结构是否实现了某个接口,很实用
golang接口的好处
到这里我的golang剖析就基本完成了,我们可以看到:golang利用接口可以做好多工作:
- 使程序逻辑结构更加清晰
- 符合设计原则:开闭原则
- 使程序的扩展更加灵活
- 减少重复代码量
由于水平有限,文笔拙劣,目前只能讲解到这个地步,一是给自己的认知做一个总结,而是希望能帮到更多的人理解到golang借口的妙用.大家有更好的idea或者批评可以留言给出
到此这篇关于“golang接口初解析”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!您可能感兴趣的文章:
Golang 面试题
golang接口初解析
【Golang 接口自动化00】为什么要用Golang做自动化?
GO接口应用场景说明
golang中接口的内部实现
浅谈golang中的接口
Golang Once源码解析
在一台电脑上同时运行多个MySQL
想系统学习GO语言(Golang
go 获取函数地址_Go语言基础--接口浅析