大家好,我是谢伟,是一名程序员。
下面的学习是一个系列,力求从初学者的角度学会go 语言,达到中级程序员水平。
这一系列是我的输出总结,同时我还推出了视频版。正在制作过程。
为写出这些文章,我阅读了网上诸多热门的教程和纸质书籍。内容的实质都是那些,要区分出差异的话,只能表现在具体实例层面。所以,实例我会选取自己在工作中的项目实例抽取出来。希望对大家有所帮助。
我们已经研究了:
Golang 环境的搭建、设置GOPATH、GOROOT 参数,Govendor 包管理, Goland 集成开发环境
Golang 语言学习专栏 -- 第一期Golang 的基础知识:变量声明、基本数据类型、基本数据结构(map、数组、切片、结构体)、流程控制、循环操作等
Golang 语言学习专栏 -- 第二期Golang 函数:入参、返回值、匿名函数、函数作为参数、函数作为返回值
Golang 语言学习专栏 -- 第三期Golang 结构体:声明和定义、组合、格式化显示、访问字段、方法定义
Golang 语言学习专栏 -- 第四期Golang 错误处理机制
Golang 语言学习专栏 -- 第五期Golang 结构体
Golang 语言学习专栏 -- 第六期Github Trending
Golang 语言学习专栏 -- 第七期
本节的主题是:接口
接口是 golang 中最值得强调的特性。它让面向对象,内容组织实现非常的方便。
- 接口的定义
- 接口的使用:赋值、嵌入接口
- 内置常用接口
- 任意(Any)类型、类型判断
1. 接口的定义
接口在 go 语言中是一系列方法的集合,原则上方法可以有很多个,但建议4个左右。
type HttpClient interface {
Get(string) ([]byte, error)
Post(string, map[string]interface{}) ([]byte, error)
Put(string, map[string]interface{}) ([]byte, error)
Delete(string) (int, error)
}
- type 和 interface 关键字组成 接口定义
- 接口是一系列方法的合集
- 方法参数和返回值只需指定类型,无需参数名称
- 只定义要干什么,而不关注实现
上文中定义了一个 httpClient 的接口,指定了这个接口可以干这些活:Get、Post、Put、Delete
2. 接口的使用:赋值、嵌入接口
上文中指定了 httpClient 接口,指定了这个接口需要干的活是:Get、Post、Put、Delete
, 具体的实现需要靠其他结构体来实现。
type HttpImpl struct {
}
func (this *HttpImpl) Get(url string) ([]byte, error) {
var (
resq *http.Response
err error
)
if resq, err = http.Get(url); err != nil {
return nil, ErrorHttpNil
}
defer resq.Body.Close()
var (
resp []byte
)
if resp, err = ioutil.ReadAll(resq.Body); err != nil {
return nil, ErrorByteNil
}
return resp, nil
}
func (this *HttpImpl) Post(url string, body map[string]interface{}) ([]byte, error) {
return nil, nil
}
func (this *HttpImpl) Put(url string, body map[string]interface{}) ([]byte, error) {
return nil, nil
}
func (this *HttpImpl) Delete(url string) (int, error) {
return 0, nil
}
- 上文定义一个结构体
httpImpl
, 这个结构体存在Get、Post、Put、Delete
四个方法,参数和返回值的类型和步骤一定义的接口的方法的参数和返回值一致。 - 上文定义的结构体
httpImpl
还可以有其他的方法,但不重要,重要的是存在接口中定义的那四个方法 - 那么我们就说这个结构体
httpImpl
实现了接口HttpClient
一个结构体实现了接口要求的所有的方法(方法的参数和返回值一致),那么就说这个结构体实现了这个接口
接口可以屏蔽内部细节,和具体的实现方法。只关注我需要做什么,而不关注怎么做。
func main() {
var httpImpl = &HttpImpl{} // 结构体
var httpClient HttpClient // 接口
httpClient = httpImpl // 接口赋值
responseOne, _ := httpClient.Get("https://www.jianshu.com/u/58f0817209aa")
fmt.Println(string([]byte(responseOne)))
}
上文中的使用:httpClient
屏蔽了 httpImpl
的内部细节,而依然可以使用 Get
方法,去完成任务。
当然接口可以被诸多结构体实现,只需存在接口定义的几种方法即可。
接口和结构体的定义很相似,也可以完成嵌入接口的功能,嵌入的匿名的接口,可以自动的具备被嵌入的接口的方法。
type InterHttpClient interface {
HttpClient
}
func main() {
var httpImpl = &HttpImpl{}
var httpClient HttpClient
httpClient = httpImpl
responseOne, _ := httpClient.Get("https://www.jianshu.com/u/58f0817209aa")
fmt.Println(string([]byte(responseOne)))
// interHttpClient
var interHttpClient InterHttpClient
interHttpClient = httpImpl
responseTwo, _ := interHttpClient.Get("http://www.gitbub.com")
fmt.Println(string([]byte(responseTwo)))
}
-
InterHttpClient
嵌入接口HttpClient
, 而自动具备方法:Get、Post、Put、Delete
- 同时
HttpImpl
结构体也实现了接口InterHttpClient
3. 内置常用接口
type Stringer interface {
String() string
}
结构体实现 String
方法即可实现结构化输出结构体。
type error interface {
Error() string
}
实现Error 方法即可自定义错误类型。
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type ReadWriter interface {
Reader
Writer
}
这几个读写接口在好些库中实现了,后续我们再讨论。
4. 任意(Any)类型、类型断言
Any 类型
interface{}
空接口在 go 里,可以当成任意类型,意味着,比如你的函数或者方法不知道传入的参数的类型,可以直接定义为 interface{}
func Say(name interface{}) {
fmt.Println(name)
}
类型断言
类型断言的使用场景是:接口类型的变量可以包含任何类型的值。如何判断变量的真实类型?
比如解析一个不知道字段类型的 json, 常常需要使用到类型断言。
可以使用:
- ok...idiom 模式进行类型判断
- switch... case 推断匹配
ok...idiom
varInterface.(T), varInterface 必须是接口、T 则是具体的实现接口的结构体
func main() {
var httpImpl = &HttpImpl{}
var httpClient HttpClient
httpClient = httpImpl
responseOne, _ := httpClient.Get("https://www.jianshu.com/u/58f0817209aa")
fmt.Println(string([]byte(responseOne)))
// interHttpClient
var interHttpClient InterHttpClient
interHttpClient = httpImpl
responseTwo, _ := interHttpClient.Get("http://www.gitbub.com")
fmt.Println(string([]byte(responseTwo)))
// ok..idiom
if n, ok := httpClient.(*HttpImpl); ok {
fmt.Println(n)
}
if m, ok := interHttpClient.(*HttpImpl); ok {
fmt.Println(m)
}
}
switch ..case...
.(type) 只在 switch 语句里才能使用。
func Show(param interface{}) {
switch param.(type) {
case *HttpImpl:
fmt.Println("1")
default:
fmt.Println("0")
}
}
func main() {
var httpImpl = &HttpImpl{}
var httpClient HttpClient
httpClient = httpImpl
responseOne, _ := httpClient.Get("https://www.jianshu.com/u/58f0817209aa")
fmt.Println(string([]byte(responseOne)))
// interHttpClient
var interHttpClient InterHttpClient
interHttpClient = httpImpl
responseTwo, _ := interHttpClient.Get("http://www.gitbub.com")
fmt.Println(string([]byte(responseTwo)))
Show(httpClient) // 1
Show(interHttpClient) // 1
以上就是接口的全部内容,接口是go 中最特别的特性。借助 接口, go 实现面向对象中的继承和多态。
接口是方法的集合,只定义具体要干什么,而怎么干,则由其他的结构体的方法实现。这样不同的结构体的方法的具体处理不同,实现的接口的功能就不一样。
尽管如此,接口并不意味着可以随意滥用。我们最好是根据面向对象的客观实体,抽象出接口和方法。
本节完,再会。