编程方式

  • 上面的文章通过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\n", m)
}

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

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

  • 函数定义: func 函数名(参数列表) (返回参数) {函数体}
  • 方法定义: func (接收器变量 接收器类型) 方法名(参数列表) (返回参数) {函数体}
// ShowString 普通的函数接收一个ShowString的类型参数
func showString(m MyInt) {
fmt.Printf("当前对象的值是:%d\n", m)
} // ShowString MyInt的ShowString方法根据对象值输出指定字符串
func (m MyInt) ShowString() {
fmt.Printf("当前对象的值是:%d\n", 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正在吃饭....\n", p.name)
} // Sleep Person的睡觉方法
func (p *Person) Sleep() {
fmt.Printf("%s正在睡觉....\n", p.name)
} // Play Person的玩游戏方法
func (p *Person) Play(game string) {
fmt.Printf("%s正在玩:%s....\n", 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 把告警写入数据库\n", s.ModuleName)
} // IsAlarm 短信告警判断这个模块告警是否关闭(需要发送)
func (s *SmsAlarms) IsAlarm() bool {
fmt.Printf("伪代码逻辑:短信告警--->模块:%s 判断模块是否关闭告警\n", s.ModuleName)
return true
} // SendAlarm 短信告警发送
func (s *SmsAlarms) SendAlarm() {
fmt.Printf("伪代码逻辑:短信告警--->模块:%s 告警发送完毕....\n", s.ModuleName)
} // WechatAlarms 微信告警
type WechatAlarms struct {
ModuleName string
Account string
} // InputAlarm 微信告警的写库方法
func (s *WechatAlarms) InputAlarm() {
fmt.Printf("伪代码逻辑:微信告警--->模块:%s 把告警写入数据库\n", s.ModuleName)
} // IAlarm 短信告警判断这个模块告警是否关闭(需要发送)
func (s *WechatAlarms) IAlarm() bool {
fmt.Printf("伪代码逻辑:微信告警--->模块:%s 判断模块是否关闭告警\n", s.ModuleName)
return true
} // SAlarm 短信告警发送
func (s *WechatAlarms) SAlarm() {
fmt.Printf("伪代码逻辑:微信告警--->模块:%s 告警发送完毕....\n", 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("不要发送告警\n")
} // 统一的告警写库方法
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 把告警写入数据库\n", s.ModuleName)
} // IsAlarm 短信告警判断这个模块告警是否关闭(需要发送)
func (s *SmsAlarms) IsAlarm() bool {
fmt.Printf("伪代码逻辑:短信告警--->模块:%s 判断模块是否关闭告警\n", s.ModuleName)
return true
} // SendAlarm 短信告警发送
func (s *SmsAlarms) SendAlarm() {
fmt.Printf("伪代码逻辑:短信告警--->模块:%s 告警发送完毕....\n", s.ModuleName)
} // WechatAlarms 微信告警
type WechatAlarms struct {
ModuleName string
Account string
} // InsertAlarm 微信告警的写库方法
func (s *WechatAlarms) InsertAlarm() {
fmt.Printf("伪代码逻辑:微信告警--->模块:%s 把告警写入数据库\n", s.ModuleName)
} // IsAlarm 微信告警判断这个模块告警是否关闭(需要发送)
func (s *WechatAlarms) IsAlarm() bool {
fmt.Printf("伪代码逻辑:微信告警--->模块:%s 判断模块是否关闭告警\n", s.ModuleName)
return true
} // SendAlarm 微信告警发送
func (s *WechatAlarms) SendAlarm() {
fmt.Printf("伪代码逻辑:微信告警--->模块:%s 告警发送完毕....\n", 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\n", c.Name, food)
} // Walk 小猫的Walk方法
func (c *Cat) Walk(place string) {
fmt.Printf("小猫:%s正在%s行走....\n", 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\n", s, x, x) i := 100
x = i
fmt.Printf("s的类型是: %T, x的类型是: %T, x的值是: %v\n", 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\n", x, x)
}

切片或者map的元素类型

package main

import "fmt"

func main() {
list := []interface{}{10, "a", []int{1, 2, 3}}
fmt.Printf("%v\n", list) info := map[string]interface{}{"age": 18, "addr": "河北", "hobby": []string{"篮球", "旅游"}}
fmt.Printf("%v\n", 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\n", v)
} else {
fmt.Printf("%v\n", ok)
}
}

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

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

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

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

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

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

Go语言学习之路-11-方法与接口的更多相关文章

  1. 12天学好C语言——记录我的C语言学习之路(Day 11)

    12天学好C语言--记录我的C语言学习之路 Day 11: 因为指针部分比较的难,所以我们花费的时间也是最长的,希望大家耐的住性子,多多理解,多多打代码.好了,废话不多说,来看第11天的学习. //编 ...

  2. 12天学好C语言——记录我的C语言学习之路(Day 12)

    12天学好C语言--记录我的C语言学习之路 Day 12: 进入最后一天的学习,用这样一个程序来综合考量指针和字符串的关系,写完这个程序,你对字符串和指针的理解应该就不错了. //输入一个字符串,内有 ...

  3. 12天学好C语言——记录我的C语言学习之路(Day 10)

    12天学好C语言--记录我的C语言学习之路 Day 10: 接着昨天的指针部分学习,有这么一个题目: //还是四个学生,四门成绩,只要有学生一门功课没及格就输出这个学生的所有成绩 /*//progra ...

  4. 12天学好C语言——记录我的C语言学习之路(Day 9)

    12天学好C语言--记录我的C语言学习之路 Day 9: 函数部分告一段落,但是我们并不是把函数完全放下,因为函数无处不在,我们今后的程序仍然会大量运用到函数 //转入指针部分的学习,了解指针是什么 ...

  5. 12天学好C语言——记录我的C语言学习之路(Day 7)

    12天学好C语言--记录我的C语言学习之路 Day 7: 昨天进行了一天的数组学习,今天大家可以先写几个昨天的程序热热身,回顾回顾,然后今天第一个新程序也是关于数组的,比较难,准备好就开始啦! //输 ...

  6. 12天学好C语言——记录我的C语言学习之路(Day 5)

    12天学好C语言--记录我的C语言学习之路 Day 5: 第五天的学习开始了,今天我们主要对几个程序进行编写,让自己充分的熟练编程语言,大量的题目会让自己变的精炼.以一个程序(program 5.1) ...

  7. 12天学好C语言——记录我的C语言学习之路(Day 3)

    12天学好C语言--记录我的C语言学习之路 Day 3: 不知不觉到了第三天的学习,我们前两天学习的东西很杂乱,各个方面都有学习.我觉得这不是不系统,也不是学的不扎实,这种学习对于初学者而言我认为是很 ...

  8. 12天学好C语言——记录我的C语言学习之路(Day 8)

    12天学好C语言--记录我的C语言学习之路 Day 8: 从今天开始,我们获得了C语言中很有力的一个工具,那就是函数.函数的魅力不仅于此,一个程序到最后都是由众多函数组成的,我们一定要用好函数,用熟练 ...

  9. 12天学好C语言——记录我的C语言学习之路(Day 6)

    12天学好C语言--记录我的C语言学习之路 Day 6: 今天,我们要开始学习数组了. //①数组部分,数组的大小不能够动态定义.如下: //int n;   scanf("%d,& ...

随机推荐

  1. PTA甲级—STL使用

    1051 Pop Sequence (25分) [stack] 简答的栈模拟题,只要把过程想清楚就能做出来. 扫描到某个元素时候,假如比栈顶元素还大,说明包括其本身的在内的数字都应该入栈.将栈顶元素和 ...

  2. Educational Codeforces Round 43

    Educational Codeforces Round 43  A. Minimum Binary Number 显然可以把所有\(1\)合并成一个 注意没有\(1\)的情况 view code / ...

  3. java——接口、多态性、对象转型

    接口定义:  默认方法: 默认方法的作用: 如果在你的接口已经投入使用了,这个时候你想要在接口里面加一个方法,这个时候如果你加一个抽象方法的话,所有实现类都要改变源代码(因为实现类要把接口中的所有抽象 ...

  4. CodeForces - 721C 拓扑排序+dp

    题意: n个点m条边的图,起点为1,终点为n,每一条单向边输入格式为: a,b,c     //从a点到b点耗时为c 题目问你最多从起点1到终点n能经过多少个不同的点,且总耗时小于等于t 题解: 这道 ...

  5. Codeforces Round #479 (Div. 3) D. Divide by three, multiply by two (DFS)

    题意:给你一个长度为\(n\)的序列\(a\).对它重新排列,使得\(a_{i+1}=a_{i}/3\)或\(a_{i+1}=2*a_{i}\).输出重新排列后的序列. 题解:经典DFS,遍历这个序列 ...

  6. Educational Codeforces Round 56 (Rated for Div. 2) D. Beautiful Graph (二分图染色)

    题意:有\(n\)个点,\(m\)条边的无向图,可以给每个点赋点权\({1,2,3}\),使得每个点连的奇偶不同,问有多少种方案,答案对\(998244353\)取模. 题解:要使得每个点所连的奇偶不 ...

  7. Kafka官方文档V2.7

    1.开始 1.1 简介 什么是事件流? 事件流相当于人体的中枢神经系统的数字化.它是 "永远在线 "世界的技术基础,在这个世界里,业务越来越多地被软件定义和自动化,软件的用户更是软 ...

  8. Leetcode(27)-移除元素

    给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度. 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成 ...

  9. IPC$入侵

    一 唠叨一下: 网上关于ipc$入侵的文章可谓多如牛毛,而且也不乏优秀之作,攻击步骤甚至可以说已经成为经典的模式,因此也没人愿意再把这已经成为定式的东西拿出来摆弄. 二 什么是ipc$ IPC$(In ...

  10. Web 前端如何优雅的处理海量数据

    Web 前端如何优雅的处理海量数据 Q: 如何在 Web 页面上处理上亿条后端返回的数据,并且保证 UI 展示的流畅性 A: 思路: 时间分片, 批处理,Buffer 缓存,虚拟滚动,Web Work ...