• Go语言学习之路-11-方法与接口


    编程方式

    • 上面的文章通过func函数,使我们可以重复的使用代码,称之为函数式编程
    • 面向对象编程:通过对象 + 方法 ,让操作基于一个对象,而不只是来回的掉函数(并且可以使用面向对象的其他优点)

    面向对象的优点这里不过多的赘述,感兴趣的自己看下
    举个最简单的例子:

    func 吃饭(){}
    func 睡觉(){}
    func 打豆豆(){}
    
    // 如果是小明要吃饭,睡觉、打豆豆,如果用函数的话只能传参!来表示吃饭的是谁、睡觉的是谁,通过函数操作
    
    // 如果是通过对象和方法呢?
    xiaohong.吃饭()、xiaohong.睡觉()、xiaohong.打豆豆()   // 通过对象来触发动作、区别于过程和函数,它的操作是某一个对象
    
    

    go语言对象方法

    自定义类型和方法

    package main
    
    import "fmt"
    
    func main() {
    	var a MyInt = 1
    	a.ShowString()
    }
    
    // MyInt 自定义的int类型
    type MyInt int
    
    // ShowString MyInt的ShowString方法根据对象值输出指定字符串
    func (m MyInt) ShowString() {
    	fmt.Printf("当前对象的值是:%d
    ", m)
    }
    

    通过上面的方法可以看出,我们自定义了个类型:MyInt , 并给MyInt绑定了一个方法:

    ShowString它是一个函数,仔细看下它和函数有什么区别

    • 函数定义: func 函数名(参数列表) (返回参数) {函数体}
    • 方法定义: func (接收器变量 接收器类型) 方法名(参数列表) (返回参数) {函数体}
    // ShowString 普通的函数接收一个ShowString的类型参数
    func showString(m MyInt) {
    	fmt.Printf("当前对象的值是:%d
    ", m)
    }
    
    // ShowString MyInt的ShowString方法根据对象值输出指定字符串
    func (m MyInt) ShowString() {
    	fmt.Printf("当前对象的值是:%d
    ", m)
    }
    

    接收器: 方法作用的目标(类型和方法的绑定)

    func (接收器变量 接收器类型) 方法名(参数列表) (返回参数) {
        函数体
    }
    

    备注:

    • 接收器变量:接收器中的参数变量名在命名时,官方建议使用接收器类型名的第一个小写字母,而不是 self、this 之类的命名。例如,Socket 类型的接收器变量应该命名为 s,Connector 类型的接收器变量应该命名为 c 等
    • 接收器类型:接收器类型和参数类似,可以是指针类型和非指针类型
    • 方法名、参数列表、返回参数:格式与函数定义一致

    例子:

    package main
    
    import "fmt"
    
    func main() {
    	p1 := &Person{"eson", 2}
    	p1.Eat()
    	p1.Sleep()
    	p1.Play("足球")
    }
    
    // Person 自定义的Person类型
    type Person struct {
    	name string
    	age  uint8
    }
    
    // Eat Person的吃饭方法
    func (p *Person) Eat() {
    	fmt.Printf("%s正在吃饭....
    ", p.name)
    }
    
    // Sleep Person的睡觉方法
    func (p *Person) Sleep() {
    	fmt.Printf("%s正在睡觉....
    ", p.name)
    }
    
    // Play Person的玩游戏方法
    func (p *Person) Play(game string) {
    	fmt.Printf("%s正在玩:%s....
    ", p.name, game)
    }
    
    

    go面向对象总结

    • 任何自定义类型都可以定义方法(内置类型,接口定义方法不可以自定义方法)
    • 方法通过接收者方式和类型进行绑定达到面向对象
    • 一般都用struct类型当做方法的接受者 & 并且通过指针来传递类型的值方便修改

    方法的继承

    在Go中没有extends关键字,也就意味着Go并没有原生级别的继承支持! Go是使用组合来实现的继承,说的更精确一点,是使用组合来代替的继承

    package main
    
    import "fmt"
    
    func main() {
    	cat := &Cat{
    		Animal: &Animal{
    			Name: "cat",
    		},
    	}
    	cat.Eat() // cat is eating
    }
    
    // Animal 动物的结构体
    type Animal struct {
    	Name string
    }
    
    // Eat 方法与Animal结构体绑定
    func (a *Animal) Eat() {
    	fmt.Printf("%v is eating", a.Name)
    	fmt.Println()
    }
    
    // Cat 结构体通过组合的方式实现继承
    type Cat struct {
    	*Animal
    }
    
    

    go语言接口

    在Go语言中接口(interface)是一种类型,一种抽象的类型

    interface是一组方法的集合,它不关心属性(数据),只关心行为(方法),它类似规则标准

    为什么要用接口

    场景: 我有一个发送短信告警的代码如下,现在来个新人要新增微信的告警
    问题:

    • 逻辑代码产生了冗余,逻辑都是一样的:写库、判断是否发送告警、发送告警,每个类型的告警都要写一遍

    • 一点约束都没有,不管是参数还是方法名字(增加了后期阅读和维护成本)

    接口可以搞定上面的问题

    package main
    
    import "fmt"
    
    func main() {
    
    	var input string
    	fmt.Scanln(&input)
    	// 接收一个告警消息,接收到后需要做
    	// 写库
    	// 判断这个模块告警是否关闭(需要发送)
    	// 发送告警
    
    	switch input {
    	case "smse":
    		// 短信告警
    		alarms := &SmsAlarms{ModuleName: "nginx", PhoneNumber: 1234567890}
    		// 写库
    		alarms.InsertAlarm()
    		isSend := alarms.IsAlarm()
    		if isSend {
    			// 如果需要发送告警就发送
    			alarms.SendAlarm()
    		}
    	case "wechat":
    		// 短信告警
    		alarms := &WechatAlarms{ModuleName: "nginx", Account: "test@qq.com"}
    		// 写库
    		alarms.InputAlarm()
    		isSend := alarms.IAlarm()
    		if isSend {
    			// 如果需要发送告警就发送
    			alarms.SAlarm()
    		}
    	}
    
    }
    
    // SmsAlarms 短信告警
    type SmsAlarms struct {
    	ModuleName  string
    	PhoneNumber int
    }
    
    // InsertAlarm 短信告警的写库方法
    func (s *SmsAlarms) InsertAlarm() {
    	fmt.Printf("伪代码逻辑:短信告警--->模块:%s 把告警写入数据库
    ", s.ModuleName)
    }
    
    // IsAlarm 短信告警判断这个模块告警是否关闭(需要发送)
    func (s *SmsAlarms) IsAlarm() bool {
    	fmt.Printf("伪代码逻辑:短信告警--->模块:%s 判断模块是否关闭告警
    ", s.ModuleName)
    	return true
    }
    
    // SendAlarm 短信告警发送
    func (s *SmsAlarms) SendAlarm() {
    	fmt.Printf("伪代码逻辑:短信告警--->模块:%s 告警发送完毕....
    ", s.ModuleName)
    }
    
    // WechatAlarms 微信告警
    type WechatAlarms struct {
    	ModuleName string
    	Account    string
    }
    
    // InputAlarm 微信告警的写库方法
    func (s *WechatAlarms) InputAlarm() {
    	fmt.Printf("伪代码逻辑:微信告警--->模块:%s 把告警写入数据库
    ", s.ModuleName)
    }
    
    // IAlarm 短信告警判断这个模块告警是否关闭(需要发送)
    func (s *WechatAlarms) IAlarm() bool {
    	fmt.Printf("伪代码逻辑:微信告警--->模块:%s 判断模块是否关闭告警
    ", s.ModuleName)
    	return true
    }
    
    // SAlarm 短信告警发送
    func (s *WechatAlarms) SAlarm() {
    	fmt.Printf("伪代码逻辑:微信告警--->模块:%s 告警发送完毕....
    ", s.ModuleName)
    }
    
    

    接口的定义

    type 接口类型名 interface{
        方法名1( 参数列表1 ) (返回值列表1)
        方法名2( 参数列表2 ) 返回值列表2
        …
    }
    
    * 接口名: <p style="color:red">接口是一个类型通过type关键字定义</p>, 一般接口名字是er结尾且具有实际的表现意义,比如我下面的例子
    * 方法名:首字母大写package外可以访问,否则只能在自己的包内访问
    * 参数、返回值名称可以省略,但是类型不能省略比如: call(string) string
    
    ```go
    // Alerter 告警的接口类型
    type Alerter interface {
    	InsertAlarm()
    	IsAlarm() bool
    	SendAlarm()
    }
    

    最终实现例子:

    package main
    
    import "fmt"
    
    func main() {
    
    	var input string
    	fmt.Scanln(&input)
    
    	// 声明告警接口变量
    	var alarms Alerter
    
    	switch input {
    	case "smse":
    		// 短信告警
    		alarms = &SmsAlarms{ModuleName: "nginx", PhoneNumber: 1234567890}
    
    	case "wechat":
    		// 短信告警
    		alarms = &WechatAlarms{ModuleName: "nginx", Account: "test@qq.com"}
    	default:
    		fmt.Printf("不要发送告警
    ")
    	}
    
    	// 统一的告警写库方法
    	alarms.InsertAlarm()
    	// 统一判断是否需要发送告警
    	isSend := alarms.IsAlarm()
    	if isSend {
    		alarms.SendAlarm()
    	}
    }
    
    // Alerter 告警的接口类型
    type Alerter interface {
    	InsertAlarm()
    	IsAlarm() bool
    	SendAlarm()
    }
    
    // SmsAlarms 短信告警
    type SmsAlarms struct {
    	ModuleName  string
    	PhoneNumber int
    }
    
    // InsertAlarm 短信告警的写库方法
    func (s *SmsAlarms) InsertAlarm() {
    	fmt.Printf("伪代码逻辑:短信告警--->模块:%s 把告警写入数据库
    ", s.ModuleName)
    }
    
    // IsAlarm 短信告警判断这个模块告警是否关闭(需要发送)
    func (s *SmsAlarms) IsAlarm() bool {
    	fmt.Printf("伪代码逻辑:短信告警--->模块:%s 判断模块是否关闭告警
    ", s.ModuleName)
    	return true
    }
    
    // SendAlarm 短信告警发送
    func (s *SmsAlarms) SendAlarm() {
    	fmt.Printf("伪代码逻辑:短信告警--->模块:%s 告警发送完毕....
    ", s.ModuleName)
    }
    
    // WechatAlarms 微信告警
    type WechatAlarms struct {
    	ModuleName string
    	Account    string
    }
    
    // InsertAlarm 微信告警的写库方法
    func (s *WechatAlarms) InsertAlarm() {
    	fmt.Printf("伪代码逻辑:微信告警--->模块:%s 把告警写入数据库
    ", s.ModuleName)
    }
    
    // IsAlarm 微信告警判断这个模块告警是否关闭(需要发送)
    func (s *WechatAlarms) IsAlarm() bool {
    	fmt.Printf("伪代码逻辑:微信告警--->模块:%s 判断模块是否关闭告警
    ", s.ModuleName)
    	return true
    }
    
    // SendAlarm 微信告警发送
    func (s *WechatAlarms) SendAlarm() {
    	fmt.Printf("伪代码逻辑:微信告警--->模块:%s 告警发送完毕....
    ", s.ModuleName)
    }
    

    接口的作用总结

    通过上面的例子可以发现,如果想发送告警

    • 首先必须遵循接口定义的方法名称和参数,达到了约束

    • 后面在想增加其他类型的告警比如邮件告警的时候,代码逻辑哪里只需增加一个email告警的赋值即可,接口约束了告警怎么玩,也简化了重复的逻辑NICE

    接口的嵌套

    接口与接口间可以通过嵌套创造出新的接口,看下面的例子

    package main
    
    import "fmt"
    
    func main() {
    	var a Animaler
    	a = &Cat{Name: "小花"}
    	a.Eat("猫粮")
    	a.Walk("花园")
    
    }
    
    // Animaler 定义一动物的接口
    type Animaler interface {
    	Eater
    	Walker
    }
    
    // Eater 定义一个吃的接口
    type Eater interface {
    	Eat(string)
    }
    
    // Walker 定义一个行走的接口
    type Walker interface {
    	Walk(string)
    }
    
    // Cat 定义一个猫的结构体
    type Cat struct {
    	Name string
    }
    
    // Eat 小猫的Eat方法
    func (c *Cat) Eat(food string) {
    	fmt.Printf("小猫:%s正在吃:%s
    ", c.Name, food)
    }
    
    // Walk 小猫的Walk方法
    func (c *Cat) Walk(place string) {
    	fmt.Printf("小猫:%s正在%s行走....
    ", c.Name, place)
    }
    
    

    空接口

    一个类型如果实现了一个 interface 的所有方法就说该类型实现了这个 interface,空的 interface 没有方法,所以可以认为所有的类型都实现了 interface{}
    所以:空接口是指没有定义任何方法的接口,因此任何类型都实现了空接口,如下面例子

    package main
    
    import "fmt"
    
    func main() {
    	var x interface{}
    
    	s := "Hello World"
    	x = s
    	fmt.Printf("s的类型是: %T, x的类型是: %T, x的值是: %v
    ", s, x, x)
    
    	i := 100
    	x = i
    	fmt.Printf("s的类型是: %T, x的类型是: %T, x的值是: %v
    ", s, x, x)
    }
    

    空接口的应用场景

    • 作为函数的参数类型,让函数可以接收任意类型的类型
    • 作为数组、切片、map的元素类型,来增强他们的承载元素的灵活性

    一般情况下慎用,如果用不好他会使你的程序非常脆弱

    空接口作为函数的参数的类型时

    package main
    
    import "fmt"
    
    func main() {
    	// 可以传递任意类型的值
    	xt("Hello World!")
    	xt(100)
    }
    
    func xt(x interface{}) {
    	fmt.Printf("x的类型是: %T, x的值是:%v
    ", x, x)
    }
    

    切片或者map的元素类型

    package main
    
    import "fmt"
    
    func main() {
    	list := []interface{}{10, "a", []int{1, 2, 3}}
    	fmt.Printf("%v
    ", list)
    
    	info := map[string]interface{}{"age": 18, "addr": "河北", "hobby": []string{"篮球", "旅游"}}
    	fmt.Printf("%v
    ", info)
    }
    

    类型断言

    空接口可以存储任意类型的值,如果使用了空接口,如何在运行的时候获取它到底是什么类型的数据呢?

    x.(T)

    • x:表示类型为interface{}的变量
    • T:表示断言x可能是的类型

    调用: x.(T)语法后返回两个参:

    • 数第一个参数是x转化为T类型后的变量
    • 第二个值是一个布尔值(为true则表示断言成功,为false则表示断言失败)
    package main
    
    import "fmt"
    
    func main() {
    	var x interface{}
    	x = "Hello World"
    	// x.(T)
    	v, ok := x.(string)
    
    	if ok {
    		fmt.Printf("类型断言:string, 它的值是:%v
    ", v)
    	} else {
    		fmt.Printf("%v
    ", ok)
    	}
    }
    

    类型断言的本质(感兴趣的可以看下没必要深究)

    静态语言在编写、编译的时候可以准确的知道某个变量的类型,那运行中它是如何获取变量的类型的呢?通过类型元数据

    每个类型都有自己的类型元数据,我们看看空接口它可以存储任意类型的数据,所以只需要知道

    • 存储的类型是是什么
    • 存哪里

    源码在这里: /usr/local/Cellar/go/1.15.8/libexec/src/runtime/type.go 修改为自己的路径

    当我们定义了一个空接口:

    作者:罗天帅
    出处:http://www.cnblogs.com/luotianshuai/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
  • 相关阅读:
    在windows下如何批量转换pvr,ccz为png或jpg
    cocos2d-x 中的 CC_SYNTHESIZE 自动生成 get 和 set 方法
    制作《Stick Hero》游戏相关代码
    触摸事件的setSwallowTouches()方法
    随机生成数(C++,rand()函数)
    随机生成数
    cocos2d-x 设置屏幕方向 横屏 || 竖屏
    Joystick 摇杆控件
    兔斯基 经典语录
    Cocos2d-x 3.2 EventDispatcher事件分发机制
  • 原文地址:https://www.cnblogs.com/luotianshuai/p/14311122.html
Copyright © 2020-2023  润新知