接口

接口的定义和实现

package main

import "fmt"

/*
[接口]
接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节 在Go语言中接口(interface)是一种类型,一种抽象的类型,interface是一组method(方法)的集合 接口做的事情就像是定义一个协议(规则),只要一台机器有洗衣服和甩干的功能,我就称它为洗衣机。不关心属性(数据),只关心行为(方法)。
为了保护你的Go语言职业生涯,请牢记接口(interface)是一种类型 [为什么要使用接口?]
type Cat struct{}
func (c Cat) Say() string { return "喵喵喵" } type Dog struct{}
func (d Dog) Say() string { return "汪汪汪" } func main() {
c := Cat{}
fmt.Println("猫:", c.Say())
d := Dog{}
fmt.Println("狗:", d.Say())
}
上面的代码中定义了猫和狗,然后它们都会叫,你会发现 main 函数中明显有重复的代码,
如果我们后续再加上猪、青蛙等动物的话,我们的代码还会一直重复下去。
那我们能不能把它们当成“能叫的动物”来处理呢?
Go语言中为了解决类似上面的问题,就设计了接口这个概念。
接口区别于我们之前所有的具体类型,接口是一种抽象的类型。
当你看到一个接口类型的值时,你不知道它是什么,唯一知道的是通过它的方法能做什么。 Go语言提倡面向接口编程
每个接口由数个方法组成,接口的定义格式如下
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2

}
其中:
接口名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,
如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等。
接口名最好要能突出该接口的类型含义。
方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略 [实现接口的条件]
一个对象只要 全部实现 了接口中的方法,那么就实现了这个接口。换句话说,接口就是一个需要实现的方法列表
如果接口中有没被实现的方法,那么就会报错.
结构体实现接口中的方法,要么全部实现,要么就一个别实现,否则报错 [接口类型变量]
那实现了接口有什么用呢?
接口类型变量能够存储所有实现了该接口的实例。 例如上面的示例中,Sayer类型的变量能够存储dog和cat类型的变量。
*/ // 示例
// 由于猫、狗、人都能叫
// 所以定义一个能叫的接口
type speaker interface {
speak() //只要实现了speak方法的变量都是speaker类型, 方法签名
} //定义结构体
type cat struct{} type dog struct{} type person struct{} // 定义相对应的方法,实现接口
func (c cat) speak() {
fmt.Println("喵喵喵~")
} func (d dog) speak() {
fmt.Println("旺旺旺~")
} func (p person) speak() {
fmt.Println("啊啊啊~")
} // 定义一个函数
func da(x speaker) {
// 接收一个参数,传进来什么,我就打什么
x.speak() // 挨打了就要叫
} func main() {
var c1 cat
var d1 dog
var p1 person da(c1)
da(d1)
da(p1) var ss speaker // 定义一个接口类型:speaker 的变量:ss
ss = c1
fmt.Printf("%T\n", ss) //main.cat
ss = d1
fmt.Printf("%T\n", ss) //main.dog
ss = p1
fmt.Printf("%T\n", ss) //main.person
}
package main

import "fmt"

// 定义支付接口
type payer interface {
pay()
} // 定义支付函数
func pay(p payer) {
p.pay()
} // 定义结构体
type weixin struct {
name string
} // 定义结构体方法 实现接口中的方法
func (w weixin) pay() {
fmt.Printf("尊敬的[%s],欢迎使用微信支付~\n", w.name)
} // 定义结构体
type alipay struct {
name string
} // 定义结构体方法 实现接口中的方法
func (a alipay) pay() {
fmt.Printf("欢迎[%s]使用阿里支付~\n", a.name)
} func main() {
var w weixin
var a alipay
w.name = "Alnk"
a.name = "tom"
pay(w)
pay(a) ////实现接口以后就不用每次都这么写重复的代码了
//p1 := weixin{"Alnk"}
//p1.pay()
//p2 := alipay{"tom"}
//p2.pay()
}
package main

import "fmt"

// 接口示例2
// 不管什么牌子的车都能跑 // 定义一个car接口类型
// 不管是什么结构体,只要有run方法都能是carer类型
type carer interface {
run() //只要实现了run方法的变量都是carer类型, 方法签名
} //drive 定义一个函数
func drive(c carer) {
c.run()
} // falali 定义一个结构体
type falali struct {
name string
} // falali 的run方法
func (f falali) run() {
fmt.Printf("%s的速度70\n", f.name)
} // baoshijie 定义一个结构体
type baoshijie struct {
name string
} // baoshijie 的run方法
func (b baoshijie) run() {
fmt.Printf("%s的速度100\n", b.name)
} func main() {
f := falali{"法拉利"}
//f.run()
drive(f) b := baoshijie{"保时捷"}
drive(b)
}
package main

import "fmt"

// 接口的实现

// 定义一个动物接口
type animal interface {
move()
eat(string)
} // 定义一个猫的结构体
type cat struct {
name string
feet int8
} // 猫结构体方法
func (c cat) move() {
fmt.Println("走猫步...")
} // 猫结构体方法
func (c cat) eat(food string) {
fmt.Printf("猫吃%s...\n", food)
} // 定义一个鸡的结构体
type chicken struct {
feet int8
} // 鸡结构体方法
func (c chicken) move() {
fmt.Println("鸡动!")
} // 鸡结构体方法
func (c chicken) eat(food string) {
fmt.Printf("吃%s...\n", food)
} func main() {
var a1 animal //定义一个接口类型的变量
fmt.Printf("%T\n", a1) //<nil> // 定义一个cat类型的变量bc
bc := cat{
name: "淘气",
feet: 4,
}
a1 = bc
a1.eat("小黄鱼") //猫吃小黄鱼...
fmt.Printf("%T\n", a1) //main.cat
fmt.Println(a1) //{淘气 4}
fmt.Println(bc) //{淘气 4} kfc := chicken{feet: 2}
a1 = kfc
fmt.Printf("%T\n", a1) //main.chicken
a1.eat("饲料") //吃饲料...
}

值接收者和指针接收者的区别

package main

import "fmt"

// 使用值接收者和指针接收者的区别?
// 使用值接收者实现接口,结构体类型和结构体指针类型的变量都能存
// 使用指针接收者实现接口,只能存结构体指针类型的变量 // Mover接口和一个dog结构体
type Mover interface {
move()
} type dog struct{} //// 值接收者实现接口
//func (d dog) move() {
// fmt.Println("狗会动")
//}
//// 实现接口的是dog类型
//func main() {
// var x Mover
// var wangcai = dog{} // 旺财是dog类型
// x = wangcai // x可以接收dog类型
// x.move()
// var fugui = &dog{} // 富贵是*dog类型
// x = fugui // x可以接收*dog类型
// x.move()
//}
//
///*
//从上面的代码中我们可以发现,使用值接收者实现接口之后,
//不管是 dog结构体 还是 结构体指针*dog类型 的变量都可以赋值给该接口变量。
//因为Go语言中有对指针类型变量求值的语法糖,
//dog指针fugui内部会自动求值*fugui
//*/ // 指针接收者实现接口
func (d *dog) move() {
fmt.Println("狗会动")
} func main() {
var x Mover
//var wangcai = dog{} // 旺财是dog类型
//x = wangcai // x不可以接收dog类型
var fugui = &dog{} // 富贵是*dog类型
x = fugui // x可以接收*dog类型
x.move()
} // 此时实现Mover接口的是*dog类型,所以不能给x传入dog类型的wangcai,此时x只能存储*dog类型的值fugui
package main

import "fmt"

// 注意:这是一道你需要回答“能”或者“不能”的题!
// 首先请观察下面的这段代码,然后请回答这段代码能不能通过编译? type People interface {
Speak(string) string
} type Student struct{} func (stu *Student) Speak(think string) (talk string) {
if think == "sb" {
talk = "你是个大帅比"
} else {
talk = "您好"
}
return
} func main() {
var peo People
peo = Student{}
//peo = &Student{} //注意这里要返回指针类型才行,因为方法中接收的是指针类型 think := "bitch"
fmt.Println(peo.Speak(think))
}

接口和类型的关系

package main

import "fmt"

// 一个类型可以实现多个接口
// 同一个结构体可以实现多个接口
// 接口还可以嵌套 // 接口
type animal interface {
mover
eater
} // 接口
type mover interface {
move()
} // 接口
type eater interface {
eat(string)
} // cat结构体
type cat struct {
name string
feet int8
} // cat实现了mover接口
func (c *cat) move() {
fmt.Println("走猫步...")
} // cat实现了eater接口
func (c *cat) eat(food string) {
fmt.Printf("猫吃%s...\n", food)
} func main() {
c1 := cat{
name: "tom",
feet: 4,
} var a1 animal a1 = &c1
a1.move() // 走猫步...
a1.eat("小黄鱼") // 猫吃小黄鱼... var m1 mover
m1 = &c1
m1.move() // 走猫步... var e1 eater
e1 = &c1
e1.eat("鱼仔") // 猫吃鱼仔...
}

空接口

package main

import "fmt"

// 空接口
// 空接口是指没有定义任何方法的接口
// 因此任何类型都实现了空接口
// 空接口类型的变量可以存储任意类型的变量 // interface:关键字
// interface{}:空接口类型 // 空接口的应用:
// 1.空接口作为函数的参数
// 2.空接口作为map的值 // 空接口作为函数参数
func show(a interface{}) {
fmt.Printf("type:%T --- value:%v\n", a, a)
} func main() {
// 空接口作为map的值
var m1 map[string]interface{}
m1 = make(map[string]interface{}, 16)
m1["name"] = "周林"
m1["age"] = 9000
m1["merried"] = true
m1["hobby"] = [...]string{"唱", "跳", "rap"}
fmt.Printf("%#v\n", m1) //map[string]interface {}{"age":9000, "hobby":[3]string{"唱", "跳", "rap"}, "merried":true, "name":"周林"} // 空接口作为函数参数
show(false) //type:bool --- value:false
show(nil) //type:<nil> --- value:<nil>
show(m1) //type:map[string]interface {} --- value:map[age:9000 hobby:[唱 跳 rap] merried:true name:周林] // 空接口作为map的键和值
var m2 map[interface{}]interface{}
m2 = make(map[interface{}]interface{}, 5)
m2[1] = "a"
m2["b"] = true
m2[true] = 1
fmt.Printf("%#v\n", m2) //map[interface {}]interface {}{true:1, 1:"a", "b":true}
show(m2) //type:map[interface {}]interface {} --- value:map[true:1 1:a b:true]
}

类型断言

package main

import "fmt"

/*
[类型断言]
空接口可以存储任意类型的值,那我们如何获取其存储的具体数据呢 一个接口的值(简称接口值)是由一个 具体类型 和 具体类型的值 两部分组成的
这两部分分别称为 接口的动态类型 和 接口的动态值 想要判断空接口中的值这个时候就可以使用类型断言,其语法格式:
x.(T)
其中:
x:表示类型为interface{}的变量
T:表示断言x可能是的类型
该语法返回两个参数,第一个参数是x转化为T类型后的变量,第二个值是一个布尔值,若为true则表示断言成功,为false则表示断言失败 因为空接口可以存储任意类型值的特点,所以空接口在Go语言中的使用十分广泛 重点:关于接口需要注意的是,只有当 [有两个或两个以上的具体类型] 必须以相同的方式进行处理时才需要定义接口。
不要为了接口而写接口,那样只会增加不必要的抽象,导致不必要的运行时损耗
*/ // 类型断言1
func assign(a interface{}) {
fmt.Printf("%T\n", a)
str, ok := a.(string)
if !ok {
fmt.Println("猜错了")
} else {
fmt.Println("传进来的是一个字符串: ", str)
}
} // 类型断言2
// x.(type)
// x表示类型为interface{}的变量
// type获取x变量的动态类型
func assign2(a interface{}) {
fmt.Printf("%T\n", a)
switch t := a.(type) {
case string:
fmt.Println("这是一个字符串:", t)
case int:
fmt.Println("这是一个int:", t)
case int64:
fmt.Println("这是一个int64:", t)
case bool:
fmt.Println("这是一个bool:", t)
case []int:
fmt.Println("这是一个slice:", t)
case map[string]int:
fmt.Println("是一个map[string]int:", t)
case func():
fmt.Println("这是一个函数类型:", t)
}
} func main() {
//assign("100")
assign2(true)
assign2("啊哈哈")
assign2(int64(200))
assign2([]int{1, 2, 3})
assign2(map[string]int{"a": 1})
}

接口练习

session中间件开发练习

目录结构

02session中间件开发
├── main.go //测试session中间件
└── session
├── docs
│   ├── README
│   └── 架构图.png
├── memory.go
├── memory_session_mgr.go
├── session.go
└── session_mgr.go

README

设计一个通用的Session服务,支持内存存储和redis存储

session模块设计
本质上k-v系统,通过key进行增删改查
session可以存储在内存或者redis(2个版本) 每一个用户 ---> 一个session ---> 多个key-value
session ---> sessionMgr (多个session对应一个sessionMrg 多对一) 接口设计
Session接口设计:
Set()
Get()
Del()
Save():session存储,redis的实现延迟加载 SessionMgr接口设计:
Init():初始化,加载redis地址
CreateSeesion():创建一个新的session
GetSession():通过sessionId获取对应的session对象 MemorySeesion设计:
定义MemorySeesion对象(字段:sessionId、存kv的map,读写锁)
构造函数,为了获取对象
Set()
Get()
Del()
Save() MemorySeesionMgr设计:
定义MemorySeesionMgr对象(字段:存放所有session的map,读写锁)
构造函数
Init()
CreateSeesion()
GetSession() RedisSession设计:
定义RedisSeesion对象(字段:sessionId,存kv的map,读写锁,redis连接池,记录内存中map是否被修改的标记)
构造函数
Set():将session存到内存中的map
Get():取数据,实现延迟加载
Del()
Save():将session存到redis RedisSessionMgr设计:
定义RedisSessionMgr对象(字段:redis地址、redis密码、连接池、读写锁,大map)
构造函数
Init()
CreateSeesion()
GetSession() 调用示例
如果要在多个函数中使用,设置一个全局的 session.NewMemorySessionMgr() 变量
// 设置session
memSess := session.NewMemorySessionMgr()
s1, id1, err := memSess.CreateSession() // 创建一条session
if err != nil {
panic(err)
}
s1.Set("name", "alnk") // 在一条session中设置值
s1.Set("isLogin", false) // 在一条session中设置值
s1.Set("isVip", false) // 在一条session中设置值 // 获取一条session
ms1, _ := memSess.GetSession(id1) // 获取单个session
// 获取单个session里面的key-value
name1, err := ms1.Get("name")
login1, err := ms1.Get("isLogin")
vip1, err := ms1.Get("isVip")
fmt.Println(name1, login1, vip1) ### redisSession功能未完成

架构图.png

session.go

package session

// 只定义session接口规范
// 其他的模块只要实现这些规范即可 type Session interface {
Set(key string, value interface{}) error
Get(key string) (interface{}, error)
Del(key string) error
Save() error
}

session_mgr.go

package session

// 定义接口规范

// SessionMgr 定义管理者,管理所有的session
type SessionMgr interface {
// 初始化
Init(addr string, options ...string) (err error)
CreateSession() (session Session, sessionId string, err error)
GetSession(sessionId string) (session Session, err error)
}

memory.go

package session

import (
"errors"
"sync"
) // 实现Session接口 type MemorySession struct {
sessionId string
// 存key-value
data map[string]interface{}
rwlock sync.RWMutex // 读写锁,不然如果数据量太大,map读写会出问题
} // 构造函数
func NewMemorySession(id string) *MemorySession {
s := &MemorySession{
sessionId: id,
data: make(map[string]interface{}, 16),
}
return s
} func (m *MemorySession) Set(key string, value interface{}) (err error) {
// 加锁
m.rwlock.Lock()
defer m.rwlock.Unlock()
// 设置值
m.data[key] = value
return
} func (m *MemorySession) Get(key string) (value interface{}, err error) {
m.rwlock.Lock()
defer m.rwlock.Unlock()
value, ok := m.data[key]
if !ok {
err = errors.New("key not exists in session")
return
}
return
} func (m *MemorySession) Del(key string) (err error) {
m.rwlock.Lock()
defer m.rwlock.Unlock()
delete(m.data, key)
return
} func (m *MemorySession) Save() (err error) {
return
}

memory_session_mgr.go

package session

import (
"errors"
"github.com/satori/go.uuid"
"sync"
) // 实现 SessionMgr 接口 // MemorySessionMgr设计:
// 定义MemorySessionMgr对象(字段:存放所有session的map,读写锁)
// 构造函数
// Init()
// CreateSession()
// GetSession() // 定义对象
type MemorySessionMgr struct {
sessionMap map[string]Session
rwlock sync.RWMutex
} // 构造函数
func NewMemorySessionMgr() SessionMgr {
sr := &MemorySessionMgr{
sessionMap: make(map[string]Session, 1024),
}
return sr
} func (s *MemorySessionMgr) Init(addr string, option ...string) (err error) {
return
} // 创建一条session
func (s *MemorySessionMgr) CreateSession() (session Session, sessionId string, err error) {
s.rwlock.Lock()
defer s.rwlock.Unlock()
// go get github.com/satori/go.uuid
// 用uuid作为 sessionId
id := uuid.NewV4()
// 转 string
sessionId = id.String()
// 创建单个session
session = NewMemorySession(sessionId)
// 加入到大map
s.sessionMap[sessionId] = session
return
} // 获取一条session
func (s *MemorySessionMgr) GetSession(sessionId string) (session Session, err error) {
s.rwlock.Lock()
defer s.rwlock.Unlock()
session, ok := s.sessionMap[sessionId]
if !ok {
err = errors.New("session not exists")
return
}
return
}

main.go

package main

import (
"02session中间件开发/session"
"fmt"
) var memSess = session.NewMemorySessionMgr() // 设置为全局变量,好让所以的函数都能调用 func main() {
s, id, err := memSess.CreateSession() // 创建一条session
if err != nil {
panic(err)
}
fmt.Println("id:", id) // 在session中设置值
name := "tom"
isLogin := true
isVip := true
s.Set("name", name) // 在一条session中设置值
s.Set("isLogin", isLogin) // 在一条session中设置值
s.Set("isVip", isVip) // 在一条session中设置值 // 获取一条session
rs, err := memSess.GetSession(id)
fmt.Printf("rs:%#v\n", rs)
fmt.Println(rs)
}

Golang语言系列-09-接口的更多相关文章

  1. Golang语言系列-10-包

    包 自定义包 package _0calc import ( "fmt" ) /* [Go语言的包] 在工程化的Go语言开发项目中,Go语言的源码复用是建立在包(package)基 ...

  2. Golang语言系列-15-数据库

    数据库 MySQL 连接数据库 package main import ( "database/sql" "fmt" _ "github.com/go ...

  3. Golang语言系列-12-网络编程

    网络编程 互联网协议介绍 互联网的核心是一系列协议,总称为"互联网协议"(Internet Protocol Suite),正是这一些协议规定了电脑如何连接和组网.我们理解了这些协 ...

  4. Golang语言系列-08-结构体

    结构体 自定义类型和类型别名的区别 package main import "fmt" // 自定义类型和类型别名的区别 // type后面跟的是类型 type myInt int ...

  5. Golang语言系列-07-函数

    函数 函数的基本概念 package main import ( "fmt" ) // 函数 // 函数存在的意义:函数能够让代码结构更加清晰,更简洁,能够让代码复用 // 函数是 ...

  6. Golang语言系列-01-Go语言简介和变量

    Go语言简介 Go(又称Golang)是Google开发的一种静态强类型.编译型.并发型,并具有垃圾回收功能的编程语言. 罗伯特·格瑞史莫(Robert Griesemer),罗勃·派克(Rob Pi ...

  7. Golang语言系列-16-context上下文

    context上下文 控制子goroutine退出 全局变量方式 package main import ( "fmt" "sync" "time&q ...

  8. Golang语言系列-14-单元测试

    单元测试 字符串切割函数 package split_string import ( "fmt" "strings" ) // Split:切割字符串 // e ...

  9. Golang语言系列-11-goroutine并发

    goroutine 并发 概念 package main import ( "fmt" "time" ) /* [Go语言中的并发编程 goroutine] [ ...

随机推荐

  1. MyBatis:条件构造器QueryWrapper方法详解

    QueryWrapper 说明:      继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件及 LambdaQueryWrapper, 可以通过 n ...

  2. centos下nodejs,npm的安装和nodejs的升级

    安装: sudo yum install epel-release sudo yum install nodejs node -v yum install -y npm --enablerepo=ep ...

  3. mysql中的空值

    空值,即NULL,在数据库中通过is null 和is not null来查询 陷阱一:空值不一定为空 空值是一个比较特殊的字段.在MySQL数据库中,在不同的情形下,空值往往代表不同的含义.这是My ...

  4. 在spring boot使用总结(九) 使用yaml语言来写配置文件

    yaml是专门用来写配置文件的语言.使用yaml来写配置文件扩展性比较强而且十分方便.spring boot支持使用yaml语言来写配置文件,使用snakeyaml库来读取配置文件.spring bo ...

  5. 所有的Java虚拟机必须实现在每个类或接口被Java程序 “ 首次主动使用 ” 时才初始化他们

    原文:https://www.cnblogs.com/fanjie/p/6916784.html Java程序对类的使用方式可分为两种– 主动使用– 被动使用 被动使用以后再讲,这里说说什么是主动使用 ...

  6. 第13次抽考(IO流)

    1.将文本文件a.txt 复制成 b.txt.要求: a. 用逐个字符复制方式: b. 用逐行读写方式: c. 用字符数组方式 2.将压缩包a.rar复制成b.rar. 注意:复制前后手工打开文件,若 ...

  7. Java程序设计(2021春)——第二章课后题(选择题+编程题)答案与详解

    Java程序设计(2021春)--第二章课后题(选择题+编程题)答案与详解 目录 Java程序设计(2021春)--第二章课后题(选择题+编程题)答案与详解 第二章选择题 2.1 面向对象方法的特性 ...

  8. take for granted

    解释含义1 Take for granted是一句地道的英语口语,意思是to expect someone to always be there and do things for you even ...

  9. lucene 入门简介

    Lucene 是一个基于 Java 的全文信息检索工具包,它不是一个完整的搜索应用程序,而是为你的应用程序提供索引和搜索功能.Lucene 目前是 Apache Jakarta 家族中的一个开源项目. ...

  10. informix数据库分页

    需求描述 当查询结果返回大量数据情况下,比如报表查询.需要按一定条件排序提供分页呈现数据. INFORMIX实现方案:Informix 数据库提供了非常便捷.高效的SQL. SELECT SKIP M ...