前言
接口是一种限制一种约定
要求对象类型实现了某些方法
第一个例子
文件1:infra.go
这是自定义的infra包
在Go语言中 结构体其实就是(对象)类的载体
在文件1中
实现了结构体 Retriever
并且实现了方法 Get
Get方法符合规定的参数要求:一个传入string参数 返回一个string参数
package infra
import (
"io/ioutil"
"net/http"
)
type Retriever struct {
}
func (Retriever) Get(url string) string {
resp, err := http.Get(url)
if err != nil {
panic(err)
}
defer resp.Body.Close()
bytes, _ := ioutil.ReadAll(resp.Body)
return string(bytes)
}
文件2: main.go
调用我们定义的infra包
在文件2中
type retriever interface{
Get(string)string
}
这是定义了一个接口
观察发现 infra包中的 Retriever结构体确实按照要求实现了Get方法
因此 可以用接口类型 retriever 定义变量 something
var something retriever
这里要求 something 按照要求实现了Get方法
package main
import (
"fmt"
"infra"
)
func getRetriever() infra.Retriever {
return infra.Retriever{}
}
//接口是一种限制一种约定
//要求接收的对象类型实现了某些方法
type retriever interface {
// Get 在这里指定要求实现哪些具体的方法
Get(string) string
}
func main() {
//Something that can "Get"
var something retriever = getRetriever()
fmt.Println(something.Get("https://www.baidu.com"))
}
运行效果
深入分析
关于接口的讨论:
应当关注 实现类 的外部行为而非内部结构
认识Duck Typing的概念
duck typing
一个东西究竟是不是鸭子取决于它的能力
游泳起来像鸭子(不同的场景有不同的要求)
叫起来也像鸭子(不同的场景有不同的需要)
那么就可以是鸭子
对比不同语言
python 只有在运行时才能知道
使用的对象是否按照接口的要求实现了某些方法
需要通过注释约定接口的要求
对传入的对象参数没有类型检查
C++ 只有在编译时才能知道
使用的对象是否按照接口的要求实现了某些方法
需要通过注释约定接口的要求
对传入的对象参数没有类型检查
JAVA 在写代码时就能发现
使用的对象是否按照接口的要求实现了某些方法
传入的参数类型被确定 有参数类型检查 不必通过注释说明
但并不是duck typing的理想概念 不够灵活
例如JAVA在更新拓展接口时 往往需要大量的代码修改
Golang是面向接口的编程语言
接口由使用者定义而非实现者定义
实现了指定的方法就能直接被接口类型引用(调用)
结论:Go语言的接口是一组方法的集合
Go语言颠覆性的接口设计
认识侵入式接口与非侵入式接口
侵入式:JAVA C++
非侵入式:Golang
侵入式:实现类需要明确指出自己实现了某个接口
C++ 实现的类名 : public 接口名 (虚继承)
JAVA 实现的类名 implements 接口名
非侵入式:只要实现了接口规定的一组方法 就可以直接被接口类型调用实现类不需要显示声明自己实现了某些接口
第二个例子
package main
import (
"fmt"
)
//定义一个对象 实现接口
type implement struct {
Contents string
}
func (i implement) Get(url string) string {
return i.Contents
}
type Retriever interface {
Get(url string) string
}
func download(r Retriever) string {
return r.Get("https:// www.baidu.com")
}
//download是使用者
//r是实现者
func main() {
var r Retriever = implement{"Hello World"}
fmt.Println(download(r))
}
分析程序的执行过程
1.定义了一个implement对象
2.为implement对象绑定了方法Get
传入一个string参数 返回一个string参数
3.定义一个Retriever的接口类型
要求实现Get方法
传入一个string参数 返回一个string参数
4.实现download函数 传入Retriever接口类型的变量
返回Get方法的调用结果
5.在main函数中 初始化了一个implement对象
6.将初始化的implement对象赋值给接口类型(Retriever)变量(r)
7.download函数调用 打印Hello World
有趣之处
Get方法的具体实现 并不是去获取www.baidu.com的相关信息
而是直接打印Hello World
这就印证了:
1.只要实现接口的方法 就可以直接被接口类型调用
2.关注对象的能力 而非内部构造与具体实现
第三个例子
接口类型模拟队列
interface{}在Go语言中可以表示任何类型
类似于C++的模板编程 区别在于 这个Queue切片可以同时存不同的类型
缺点:只有在运行时才能知道是否发生错误
package main
import "fmt"
//interface{}在Go语言中可以表示任何类型
//类似于C++的模板编程 区别在于这个Queue可以存不同的类型
//缺点 只有在运行时才能知道是否发生错误
type Queue []interface{}
func (q *Queue) Push(v interface{}) {
*q = append(*q, v)
}
func (q *Queue) Pop() interface{} {
head := (*q)[0]
*q = (*q)[1:]
return head
}
func (q *Queue) IsEmpty() bool {
return len(*q) == 0
}
func main() {
var q Queue
q.Push("ABC")
q.Push(999)
q.Push(13.14)
fmt.Println(q.Pop())
fmt.Println(q.Pop())
fmt.Println(q.Pop())
}