GO接口-1
概要
go语言是面向接口编程的语言,与传统意义上的面向对象有很大的不同,没有传统很复杂的继承、多态。go语言的面向对象只支持封装。那些依靠继承和多态完成的事情依赖于go的接口完成的。go语言的接口要比传统的语言灵活很多。
#实现fly这个方法的就是实现了这个接口
type Bird interface{
Fly()
}
duck typing概念
上图一个萌萌的鸭子,那么这个大黄鸭到底是不是一个鸭子?需要从不同的角度看待这个问题:
- 传统类型系统:脊索动物门,脊椎动物亚门,鸟纲雁形目,鸭科鸭属动物,是由野生绿头鸭和斑嘴鸭驯化而来
- duck typing 系统:它走起步来像鸭子,并且叫声像鸭子, 那个它一定是一只鸭子,描述事物的外部行为而非内部结构
从传统类型系统来讲,上图的鸭子就不是鸭子。从duck typing 角度理解,上图长得像鸭子,黄色的、尖嘴巴的、小小的圆眼睛,就是鸭子。同样一个东西,要从使用的角度来看,判断它是不是鸭子。严格说go属于结构化类型,类似于duck typing
其它语言的duck typing
#python中的duck typing
def Download(retriever)
return retriever.get("www.baidu.com")
Download函数是使用者,retriever是实现duck typing的对象。调用Download必须传递一个retriever给函数,retriever对象需要实现get方法
- 运行的时候才知道retriever 是否具有 get方法(python没有编译)
- 实现retriever的人,怎么才知道要实现get方法呢?** 通过注释来说明需要实现哪些接口**
#c++中的duck typing
template<class R>
string download(const R& retriever){
return retriever.get("www.baidu.com")
}
c++中的duck typing是通过模板来支持的,R是template,是个class。这段代码和python类似,retriever什么类型都可以, 没有要实现的get的一个基类。什么类型只要实现了get方法,就能作为download函数的参数传递过去使用。
- 编译的是时候才知道retriever有没有 get方法。没有get方法编译报错
- 通过注释来说明需要实现哪些接口
java中没有duck typing 只有类似的代码
<R extends Retriever>
String Download(R r){
return r.get("www.baidu.com")
}
java也是使用了模板,R extends Retriever 即 R继承了Retriever,实现了Retriever的接口,这样 Download 函数里面就可以 r.get(“www.baidu.com”),java解决了需要通过注释来说明需要实现哪些接口的缺点。现在是传入的参数R对象必须实现Retriever接口,就不会出现编译或运行时才出现没有get方法的错误。
- 传入的参数必须实现Retriever接口
- 不是 duck typing 因为它必须实现Retriever接口,比如一个任意类型实现了get方法,但是还是不能使用,必须实现Retriever接口,才能传递给Download作为参数
- 实现多个接口就做不到了 ,同时需要实现Readable、Appendable 怎么处理?
go语言的duck typing
- 一个对象可以同时实现两个接口 接口的组装
- 同时具备python、c++的duck typing灵活性,不管什么类型只要实现了这个接口的所有方法,就是实现了这个接口
- 具有java的类型检查机制,不希望通过注释来说明需要什么类型
接口的定义:
使用者(download)------> 实现者(retriever)
- go语言中接口的定义是由使用者来定义的,比如上图的小黄鸭,小孩子就是认为是个小黄鸭,动物研究员就认为不是鸭子
- 接口的实现是隐式的,不需要说我实现了retriever,只需要实现接口的方法
- 实现接口的方法
Go语言接口示例:
使用者
package main
import (
"ccmouse/interface/duck/mock"
"fmt"
)
type Retriever interface {
Get(url string) string
}
//使用者决定实现者需要实现哪些接口,使用者规定需要实现哪些方法
func download(r Retriever) string{
return r.Get("www.bai.com")
}
func main(){
var r Retriever
r = mock.Retrievermock{"this is Retriever class"}
str := download(r)
fmt.Println(str)
}
实现者
package mock
type Retrievermock struct {
Contents string
}
func (r Retrievermock) Get(str string) string {
return r.Contents
}
接口的值类型
接口变量里面到底是什么
在其他语言中,r可能就是引用指针,指向真实的对象。但是go语言类型都是值类型,所有r并不是指针这么简单,接口变量包含类型和值,这个是可以是真实的值或是指针类型和指向值得指针
类型和值
func main(){
var r Retriever
r = mock.Retrievermock{"this first retriever"}
fmt.Printf("类型:%T,值:%v\n",r,r)
r = retrieverclass.Retrieverclass{"Mozilla/5.0",time.Minute}
fmt.Printf("类型:%T,值:%v\n",r,r)
//str := download(r)
//fmt.Println(str)
}
指针类型和指向值得指针
func main(){
var r Retriever
r = mock.Retrievermock{"this first retriever"}
fmt.Printf("类型:%T,值:%v\n",r,r)
r = &retrieverclass.Retrieverclass{"Mozilla/5.0",time.Minute}
fmt.Printf("类型:%T,值:%v\n",r,r)
//str := download(r)
//fmt.Println(str)
}
总结:
- 接口变量自带指针
- 接口变量同样采用值传递,几乎不需要使用接口的指针
- 指针接收者实现只能以指针方式方式使用,值接收者都可以
接口变量的类型判断
type swich
switch case + 变量名.(type) 判断变量的类型
// r.(type)是获取变量类型
func main(){
var r Retriever
r = mock.Retrievermock{"this first retriever"}
r = &retrieverclass.Retrieverclass{"Mozilla/5.0",time.Minute}
switch v := r.(type) {
case mock.Retrievermock:
fmt.Printf("r type is mock.Retrievermock,contents:%s\n",v.Contents)
case *retrieverclass.Retrieverclass:
fmt.Printf("r type is *isretrieverclass.Retrieverclass,value:%v\n",v)
}
}
类型断言 type assert
变量本身,ok := 变量.(指定类型), ok bool 是该类型true 否则 false,将接口变量转换成指定类型
var r Retriever
r = mock.Retrievermock{"this first retriever"}
r = &retrieverclass.Retrieverclass{"Mozilla/5.0",time.Minute}
//type assert
retri := r.(*retrieverclass.Retrieverclass)
fmt.Println(retri.UserAgent)
//或者是下面的写法
if retri1 ok := r.(*retrieverclass.Retrieverclass) ; ok {
fmt.Println(retri1.Contents)
}
将接口转换为其他接口
实现某个接口的类型同时实现了另外一个接口,此时可以在两个接口间转换。鸟和狗具有不同的特性,鸟可以飞,狗不能飞,但两种动物都可以行走。如果使用结构体同时实现鸟和狗,让它们具备自己特性的 Fly() 和 Walk() 方法就让鸟和狗各自实现了飞行动物接口(Flyer)和行走动物接口(Walker)。将鸟和狗的实例创建后,被保存到 interface{} 类型的 map 中interface{} 类型表示空接口,意思就是这种接口可以保存为任意类型。对保存有鸟或狗的实例interface{} 变量进行断言操作,如果断言对象是断言指定的类型,则返回转换为断言对象类型的接口;如果不是指定的断言类型时,断言的第二个参数将返回 false。
接口类型,ok := 变量.(接口类型)
package main
import "fmt"
// 定义飞行动物接口
type Flyer interface {
Fly()
}
// 定义行走动物接口
type Walker interface {
Walk()
}
// 定义鸟类
type bird struct {
}
// 实现飞行动物接口
func (b *bird) Fly() {
fmt.Println("bird: fly")
}
// 为鸟添加Walk()方法, 实现行走动物接口
func (b *bird) Walk() {
fmt.Println("bird: walk")
}
// 定义猪
type dog struct {
}
// 为猪添加Walk()方法, 实现行走动物接口
func (d *dog) Walk() {
fmt.Println("dog: walk")
}
func main() {
// 创建动物的名字到实例的映射
animals := map[string]interface{}{
"bird": new(bird),
"dog": new(dog),
}
// 遍历映射
for name, obj := range animals {
// 判断对象是否为飞行动物
f, isFlyer := obj.(Flyer)
// 判断对象是否为行走动物
w, isWalker := obj.(Walker)
fmt.Printf("name: %s isFlyer: %v isWalker: %v\n", name, isFlyer, isWalker)
// 如果是飞行动物则调用飞行动物接口
if isFlyer {
f.Fly()
}
// 如果是行走动物则调用行走动物接口
if isWalker {
w.Walk()
}
}
}
空接口 interface{}
空接口是指没有定义任何接口方法的接口,没有定义任何接口方法,意味着Go中的任意对象都可以实现空接口(因为没方法需要实现),任意对象都可以保存到空接口实例变量中。
func main() {
any := make([]interface{}, 5)
any[0] = "age"
any[1] = 12
any[2] = [3]string{1,2,3}
for _, value := range any {
//可以进行类型断言 判断类型
fmt.Println(value)
}
}
接口组合
在 Go语言中,不仅结构体与结构体之间可以嵌套,接口与接口间也可以通过嵌套创造出新的接口。一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。只要接口的所有方法被实现,则这个接口中的所有嵌套接口的方法均可以被调用。示例代码
package main
//定义只能Get请求的接口
type Geter interface {
Get(url string) string
}
//定义只能Post请求的接口
type Poster interface {
Post(url string,params map[string]interface{}) string
}
//定义一个组合接口
type CurlTool interface {
Geter
Poster
}
CurlTool 接口嵌套组合了 Geter、Poster,也就是说CurlTool 同时拥有了 Geter 和 Poster 的特性。
在代码中使用嵌套组合接口,只需要按照接口实现的规则实现 Geter 接口和 Poster 接口即可。而 CurlTool 接口在使用时,编译器会根据接口的实现者确认它们是否同时实现了 Geter 接口和 Poster 接口,详细实现代码如下:
package objects
import "fmt"
//定义Curl结构体
type Curl struct {
}
//Curl结构体实现Get方法 即是Geter接口的实例
func (c *Curl)Get(url string) string {
return fmt.Sprintf("请求的地址:%s,get方式请求成功!",url)
}
//Curl结构体实现Post方法 即是Poster接口的实例
func (c *Curl) Post(url string,params map[string]interface{}) string {
return fmt.Sprintf("请求的地址:%s,提交的参数:%v,post方式提交成功!",url,params)
}
//使用者
func DownLoad(tool CurlTool){
res := tool.Get("http://www.baidu.com")
fmt.Println(res)
}
//使用者
func Submit(tool CurlTool){
res := tool.Post("http://www.baidu.com", map[string]interface{}{"username":"jack","age":20,"email":"1811125@163.com"})
fmt.Println(res)
}
func main(){
//定义变量ct CurlTool接口
var ct CurlTool
//objects.Curl 实现者
ct = &objects.Curl{}
Submit(ct)
}