zoukankan      html  css  js  c++  java
  • Golang接口类型-上篇

    1、概述

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

    简单来说,Go语言中的接口就是一组方法的签名。接口是Go语言整个类型系统的基石,其他语言的接口是不同组件之间的契约的存在,对契约的实现是强制性的,必须显式声明实现了该接口,这类接口称之为“侵入式接口”。而Go语言的接口是隐式存在,只要实现了该接口的所有函数则代表已经实现了该接口,并不需要显式的接口声明

    接口的比喻
    ​一个常见的例子,电脑上只有一个USB接口。这个USB接口可以接MP3、数码相机、摄像头、鼠标、键盘等。所有的上述硬件都可以公用这个接口,有很好的扩展性,该USB接口定义了一种规范,只要实现了该规范,就可以将不同的设备接入电脑,而设备的改变并不会对电脑本身有什么影响(低耦合)

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

    2、接口的隐式实现

    Java中实现接口需要显式地声明接口并实现所有方法,而在Go中实现接口的所有方法就隐式地实现了接口
    定义接口需要使用interface关键字,在接口中只能定义方法签名,不能包含成员变量,例如

    type error interface {
    	Error() string
    }
    

    如果一个类型需要实现error接口,那么它只需要实现Error() string方法,下面的RPCError结构体就是 error 接口的一个实现

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

    会发现上述代码根本就没有error接口的影子,这正是因为Go语言中接口的实现都是隐式的

    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
    ", 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
    ", 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
    ", sender, sender) // <nil> <nil>
    	sender.Send("geek", "早上好")
    	sender.SendAll([]string{"aa","bb"}, "中午好")
    	do(sender)
    	sender = SmsSender{}
    	do(sender)
    }
    

    按照上面的示例,最后定义变量sender为接口类型Sender,调用接口方法时,只需要指定接口类型对应的结构体是什么,因为在定义接口时,已经声明了此接口实现了SendSendAll两个方法

    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)
    }
    
    • 方法接收者既有值类型又有指针类型

    WechatSendersendsendAllsend有指针和值,sendAll只有指针,因此初始化的时候只能用指针,不能用值

    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
    ", r.Name,data)
    }
    func (r *redis) QueryData (name string) string {
    	log.Printf("querydata,name:%s,data:%s
    ", 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
    ", k.Name,data)
    }
    func (k *kafka) QueryData (name string) string {
    	log.Printf("querydata,name:%s,data:%s
    ", 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)
    		}
    	}
    }
    

    参考:https://draveness.me/golang/docs/part2-foundation/ch04-basic/golang-interface/

    See you ~

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

  • 相关阅读:
    1058 A+B in Hogwarts (20)
    1046 Shortest Distance (20)
    1061 Dating (20)
    1041 Be Unique (20)
    1015 Reversible Primes (20)(20 分)
    pat 1027 Colors in Mars (20)
    PAT 1008 Elevator (20)
    操作系统 死锁
    Ajax的get方式传值 避免& 与= 号
    让IE浏览器支持CSS3表现
  • 原文地址:https://www.cnblogs.com/ssgeek/p/15433859.html
Copyright © 2011-2022 走看看