面向对象的优点这里不过多的赘述,感兴趣的自己看下
举个最简单的例子:
func 吃饭(){}
func 睡觉(){}
func 打豆豆(){}
// 如果是小明要吃饭,睡觉、打豆豆,如果用函数的话只能传参!来表示吃饭的是谁、睡觉的是谁,通过函数操作
// 如果是通过对象和方法呢?
xiaohong.吃饭()、xiaohong.睡觉()、xiaohong.打豆豆() // 通过对象来触发动作、区别于过程和函数,它的操作是某一个对象
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它是一个函数,仔细看下它和函数有什么区别
// ShowString 普通的函数接收一个ShowString的类型参数
func showString(m MyInt) {
fmt.Printf("当前对象的值是:%d\n", m)
}
// ShowString MyInt的ShowString方法根据对象值输出指定字符串
func (m MyInt) ShowString() {
fmt.Printf("当前对象的值是:%d\n", m)
}
func (接收器变量 接收器类型) 方法名(参数列表) (返回参数) {
函数体
}
备注:
例子:
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中没有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语言中接口(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)
}
一般情况下慎用,如果用不好他会使你的程序非常脆弱
package main
import "fmt"
func main() {
// 可以传递任意类型的值
xt("Hello World!")
xt(100)
}
func xt(x interface{}) {
fmt.Printf("x的类型是: %T, x的值是:%v\n", x, x)
}
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.(T)语法后返回两个参:
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 修改为自己的路径
当我们定义了一个空接口:
原文:https://www.cnblogs.com/luotianshuai/p/14311122.html