0x00 接口的概念
例如:我们现在有两个团队:infra 团队专门做基础设施 testing团队写测试环境需要的代码
这两个团队都写了一个 Retriever 类型,并且这个类型都具有一个Get方法
testing
package testing
type Retriever struct{}
func (Retriever) Get(url string) string {
return "fake content"
}
infra
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)
}
main中如何调用才能保证耦合度最低呢?
func getRetriever() infra.Retriever {
// return testing.Retriever{}
return infra.Retriever{}
}
func main() {
var r infra.Retriever = getRetriever()
fmt.Println(r.Get("https://xdu.databankes.cn"))
}
对于弱类型语言来说,其实写到这种程度就已经解耦合了,但是因为go是一个强类型语言,返回值类型 和 main函数中变量r的类型仍然处于耦合状态,如果我将infra.Restriever替换为testing.Retriever,那么getRetriever和r 的类型都需要做修改,
所以这时就需要定义一个接口,这个接口指明r 是一个能调用Get方法的东西 就行。这样,我们由测试环境 切换到 线上环境时,只需要将testing 修改 infra 即可,不需要改动其他任何代码
package main
import (
"fmt"
"learngo/interface/infra"
)
func getRetriever() retriever {
// return testing.Retriever{}
return infra.Retriever{}
}
//? : Something that can "Get"
type retriever interface {
Get(string) string
}
func main() {
var r retriever = getRetriever()
fmt.Println(r.Get("https://xdu.databankes.cn"))
}
0x01 duck type 的概念
go语言是一个面向接口的编程语言,没有传统的那种复杂的继承和多态,go语言的面向对象只是支持封装,靠面向对象完成事情在go语言中通过接口来完成
什么是duck typing?
大黄鸭是不是鸭子?
从传统生物学分类来说,大黄鸭不是鸭子
从duck typing 来说,大黄鸭就是鸭子,因为"像鸭子走路,像鸭子叫(长得像鸭子),那么就是鸭子"
duck typing 描述事物的外部行为而非内部结构
严格来说go语言属于结构化类型系统,类似duck typing
python中的duck typing:
def download(retriever):
return retriever.get("https://xdu.databankes.cn")
这段代码中retriever就是一个 duck typing,这个duck typing is something that can get, download是一个使用者,使用了duck typing。但是python只有在运行时才知道传入的retriever有没有get方法。
假想这样一种情况:
乙团队需要调用download ,给download传入一个 retriever 。
而download是由甲团队开发的,是一个非常复杂的函数(乙团队不愿意详细阅读其具体代码),其中调用了retriever的 get,put,delete...等一堆方法,乙团队怎么知道 传入的retriever 需要具备哪些方法呢?这时就必须要求写download的函数的甲团队通过详细的注释来说明对传入的retriever的要求
总结缺点:
1.运行时才知道传入的retriever有没有get
2.需要注释来说明接口
C++中的duck typing
template <class R>
string download(const R& retriever){
return retriever.get("https://xdu.databankes.cn")
}
缺点:
1.编译时才知道传入的retriever有没有get
2.需要注释来说明接口
go语言的duck typing
download 是 使用者
retriever 是 实现者
go语言的接口由使用者定义,驱动实现者去开发
接口变量中有什么?
或者 实现者的值 也可以是 实现者的指针,指向实现者
查看接口变量:
空接口 interface{} 可以接收任何类型
Type Assertion 类型断言
Type Switch 类型判断
/*
* @Author: your name
* @Date: 2020-10-24 15:43:22
* @LastEditTime: 2020-10-24 17:01:59
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: /learngo/retriever/main.go
*/
package main
import (
"fmt"
"learngo/retriever/mock"
"learngo/retriever/real"
"time"
)
type Retriever interface {
Get(url string) string
}
func download(r Retriever) string {
return r.Get("https://xdu.databankes.cn")
}
func inspect(r Retriever) {
switch v := r.(type) {
case mock.Retriever:
fmt.Println("Contents:", v.Contents)
case *real.Retriever:
fmt.Println("UserAgent:", v.UserAgent)
}
}
func main() {
var r Retriever //接口变量
r = mock.Retriever{"fake Content"}
//接口变量被赋值后由两部分组成:实例类型 实例的值
fmt.Printf("%T %v\n", r, r)
//获取接口变量中实例的类型 r.(type) 只能用在switch中
inspect(r)
//获取接口变量中实例的值
mockRetriever := r.(mock.Retriever)
fmt.Println(mockRetriever)
//如果将一个实例的指针赋值给接口变量
//那么接口变量就由:实例类型 实例指针组成
r = &real.Retriever{
UserAgent: "Mozilla/5.0",
TimeOut: time.Minute,
}
fmt.Printf("%T %v\n", r, r) //%T 类型 %v 值
//获取类型
inspect(r)
//Type assertion 类型断言 通过(类型名) 获取该类型肚子中的实例的值
realRetriever := r.(*real.Retriever)
fmt.Println(realRetriever)
}
mockretrieve.go
package mock
type Retriever struct {
Contents string
}
func (r Retriever) Get(url string) string {
return r.Contents
}
retrieve.go
package real
import (
"net/http"
"net/http/httputil"
"time"
)
type Retriever struct {
UserAgent string
TimeOut time.Duration
}
func (r *Retriever) Get(url string) string {
resp, err := http.Get(url)
if err != nil {
panic(err)
}
result, err := httputil.DumpResponse(resp, true)
resp.Body.Close()
if err != nil {
panic(err)
}
return string(result)
}
0x02 接口的组合
type Retriever interface {
Get(url string) string
}
type Poster interface {
Post(url string, form map[string]string) string
}
type RetrieverPoster interface {
//将这两个接口的要求的方法组合
Retriever
Poster
//要求的其他方法
// Connect(host string)
}
匿名组合:
结构体之间的组合
0x03 接口赋值的一些注意点:
package main
type Integer int
//指针接受者
func (this *Integer) Add(a, b int) {
println(a + b)
}
//值接受者
func (self Integer) Sub(a, b int) {
println(a - b)
}
//GoLang 可以根据值接受者函数自动生成指针接受者函数
// func (self *Integer) Sub(a, b int) {
// println(a - b)
// }
type AddSub interface {
Add(a, b int)
Sub(a, b int)
}
func main() {
var i Integer = 123
var as AddSub = &i //正确的写法
// var as AddSub = i 错误的写法
as.Add(1, 2)
as.Sub(2, 1)
}
0x04 接口查询
接口查询:查询某个对象是否实现了鸭子类型要求的所有方法
package main
import "fmt"
type Integer int
//指针接受者
func (this Integer) Add(a, b int) {
println(a + b)
}
func (this Integer) Sub(a, b int) {
println(a - b)
}
type AddSub interface {
Add(a, b int)
Sub(a, b int)
}
type Add interface {
Add(a, b int)
}
type Sub interface {
Sub(a, b int)
}
func main() {
var i Integer = 123
var as AddSub = i
if a, ok := as.(Add); ok { //判断as是否实现了鸭子类型Add的所有方法
fmt.Println(a)
}
}
0x06 类型查询
变量名.(type) 可以获取变量的类型,只能用在switch case结构中
泛型的强制类型转化前,必须进行类型查询
类型查询之后,才能通过变量名.(其他类型) 将泛型interface{} 转化为其他类型
package main
import "fmt"
func main() {
var v1 interface{} = 123
switch v1.(type) { //获取v1的类型,只能用在switch case结构中
case int:
v := v1.(int) //由泛型强制转化为int类型,必须先进行查询才能进行转化
fmt.Println("int", v)
case string:
v := v1.(string) //由泛型强制转化为string类型
fmt.Println("string", v)
}
}