1、概述

接口是计算机系统中多个组件共享的边界,不同的组件能够在边界上交换信息。接口的本质是引入一个新的中间层,调用方可以通过接口与具体实现分离,解除上下游的耦合,上层的模块不再需要依赖下层的具体模块,只需要依赖一个约定好的接口

GoGoGo
USBUSBMP3USB

接口表示调用者和设计者的一种约定,在多人合作开发同一个项目时,事先定义好相互调用的接口可以大大提高开发的效率。接口是用类来实现的,实现接口的类必须严格按照接口的声明来实现接口提供的所有功能。有了接口,就可以在不影响现有接口声明的情况下,修改接口的内部实现,从而使兼容性问题最小化

2、接口的隐式实现

JavaGointerface
type error interface {
	Error() string
}
errorError() stringRPCErrorerror 
type RPCError struct {
	Code    int64
	Message string
}

func (e *RPCError) Error() string {
	return fmt.Sprintf("%s, code=%d", e.Message, e.Code)
}
errorGo

3、接口定义和声明

接口是自定义类型,是对其他类型行为的抽象(定义一个接口类型,把其他类型的值赋值给自定义的接口)

interface
nil
package main

import "fmt"

type Sender interface {
	Send(to string, msg string) error
	SendAll(tos []string, msg string) error
}

func main()  {
	var sender Sender
	fmt.Printf("%T %v\n", sender, sender)  // <nil> <nil>
}

4、接口类型赋值

为接口类型方法赋值,一般是定义一个结构体,需要保证结构体方法(方法名、参数)均与接口中定义相同

package main

import "fmt"

type Sender interface {
	Send(to string, msg string) error
	SendAll(tos []string, msg string) error
}

type EmailSender struct {
}

func (s EmailSender) Send(to, msg string) error {
	fmt.Println("发送邮件给:", to, ",消息内容是:", msg)
	return nil
}

func (s EmailSender) SendAll(tos []string, msg string) error {
	for _, to := range tos {
		s.Send(to, msg)
	}
	return nil
}

func main() {
	var sender Sender = EmailSender{}
	fmt.Printf("%T %v\n", sender, sender) // <nil> <nil>
	sender.Send("geek", "早上好")
	sender.SendAll([]string{"aa","bb"}, "中午好")
}

使用接口的好处,概念上可能不好理解,来一个实际例子

package main

import "fmt"

type Sender interface {
	Send(to string, msg string) error
	SendAll(tos []string, msg string) error
}

type EmailSender struct {
}

func (s EmailSender) Send(to, msg string) error {
	fmt.Println("发送邮件给:", to, ",消息内容是:", msg)
	return nil
}

func (s EmailSender) SendAll(tos []string, msg string) error {
	for _, to := range tos {
		s.Send(to, msg)
	}
	return nil
}

type SmsSender struct {
}

func (s SmsSender) Send(to, msg string) error {
	fmt.Println("发送短信给:", to, ", 消息内容是:", msg)
	return nil
}

func (s SmsSender) SendAll(tos []string, msg string) error {
	for _, to := range tos {
		s.Send(to, msg)
	}
	return nil
}

//func do(sender EmailSender) {
func do(sender Sender) {
	sender.Send("领导", "工作日志")
}

func main() {
	var sender Sender = EmailSender{}
	fmt.Printf("%T %v\n", sender, sender) // <nil> <nil>
	sender.Send("geek", "早上好")
	sender.SendAll([]string{"aa","bb"}, "中午好")
	do(sender)
	sender = SmsSender{}
	do(sender)
}
senderSenderSendSendAll
var sender Sender = EmailSender{}
// 或
var sender Sender = SmsSender{}
// 单独定义go函数调用
func do(sender Sender) {
	sender.Send("领导", "工作日志")
}

如果没有接口,那么最终调用时,还需要对应上其具体的结构体类型,写法为

var sender EmailSender = EmailSender{}
// 或
var sender SmsSender = SmsSender{}
// 单独定义go函数调用
func do(sender EmailSender) {
// func do(sender SmsSender) {
	sender.Send("领导", "工作日志")
}

很明显,前者使用接口定义变量,在传参时也使用接口类型定义,在使用上更为简单,仅仅只需要调整初始化的结构体类型即可

5、接口类型对象

当自定义类型实现了接口类型中声明的所有函数时,则该类型的对象可以赋值给接口变量,并使用接口变量调用实现的接口

  • 方法接收者全为值类型
    如上面的例子

  • 方法接收者全为指针类型

package main

import "fmt"

type Sender interface {
	Send(to string, msg string) error
	SendAll(tos []string, msg string) error
}

type SmsSender struct {
}

func (s *SmsSender) Send(to, msg string) error {
	fmt.Println("发送短信给:", to, ", 消息内容是:", msg)
	return nil
}

func (s *SmsSender) SendAll(tos []string, msg string) error {
	for _, to := range tos {
		s.Send(to, msg)
	}
	return nil
}

func do(sender Sender) {
	sender.Send("领导", "工作日志")
}

func main() {
	var sender Sender = &SmsSender{}  // 指针类型
	do(sender)
}
  • 方法接收者既有值类型又有指针类型
WechatSendersendsendAllsendsendAll
package main

import "fmt"

type Sender interface {
	Send(to string, msg string) error
	SendAll(tos []string, msg string) error
}

type WechatSender struct {
}

// Send 接收者为值对象
func (s WechatSender) Send(to, msg string) error {
	fmt.Println("发送微信给:", to, ", 消息内容是:", msg)
	return nil
}

// SendAll 接收者为指针对象
func (s *WechatSender) SendAll(tos []string, msg string) error {
	for _, to := range tos {
		s.Send(to, msg)
	}
	return nil
}

//func do(sender EmailSender) {
func do(sender Sender) {
	sender.Send("领导", "工作日志")
}

func main() {
	var sender Sender = &WechatSender{}
	do(sender)
}

当接口(A)包含另外一个接口(B)中声明的所有函数时(A接口函数是B接口函数的父集,B是A的子集),接口(A)的对象也可以赋值给其子集的接口(B)变量

package main

import "fmt"

type SignalSender interface {
	Send(to, msg string) error
}

type Sender interface {
	Send(to string, msg string) error
	SendAll(tos []string, msg string) error
}

...

func main() {
	var ssender SignalSender = sender  // 以接口的变量初始化另外一个接口
	ssender.Send("aa", "你好")
}

若两个接口声明同样的函数签名,则这两个接口完全等价
当类型和父集接口赋值给接口变量时,只能调用接口变量定义接口中声明的函数(方法)

6、接口应用举例

实际的生产例子,可以加深对接口的理解。例如多个数据源推送和查询数据

package main

import (
	"fmt"
	"log"
)

/*
1、多个数据源
2、query方法查询数据
3、pushdata方法写入数据
 */

type DataSource interface {
	PushData(data string)
	QueryData(name string) string
}

type redis struct {
	Name string
	Addr string
}

func (r *redis) PushData (data string) {
	log.Printf("pushdata,name:%s,data:%s\n", r.Name,data)
}
func (r *redis) QueryData (name string) string {
	log.Printf("querydata,name:%s,data:%s\n", r.Name,name)
	return name + "redis"
}

type kafka struct {
	Name string
	Addr string
}

func (k *kafka) PushData (data string) {
	log.Printf("pushdata,name:%s,data:%s\n", k.Name,data)
}
func (k *kafka) QueryData (name string) string {
	log.Printf("querydata,name:%s,data:%s\n", k.Name,name)
	return name + "kafka"
}

var Dm = make(map[string]DataSource)

func main()  {
	r:=redis{
		Name: "redis",
		Addr: "127.0.0.1",
	}
	k:=kafka{
		Name:"kafka",
		Addr:"192.169.0.1",
	}
	// 注册数据源到承载的容器中
	Dm["redis"] = &r
	Dm["kafka"] = &k
	// 推送数据
	for i:=0;i<5;i++{
		key:=fmt.Sprintf("key_%d", i)
		for _,ds:=range Dm{
			ds.PushData(key)
		}
	}
	// 查询数据
	for i:=0;i<5;i++{
		key:=fmt.Sprintf("key_%d", i)
		//r:=Dm["redis"]
		//r.QueryData(key)
		for _,ds:=range Dm{
			res:=ds.QueryData(key)
			log.Printf("query_from_ds,res:%s", res)
		}
	}
}

See you ~

关注公众号加群,更多原创干货与你分享 ~