GO语言学习笔记:第六章 面向接口

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)
	}
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值