1. 接口(interface)的类型
在Go语言中接口(interface)是一种类型,是一种抽象的类型。
首先我们定义interface类型变量
func main(){
// 定义一个空接口x
var x interface{}
fmt.Printf("type:%T value:%v\n", x, x)
s := "hello world!"
x = s
fmt.Printf("type:%T value:%v\n", x, x)
i := 128
x = i
fmt.Printf("type:%T value:%v\n", x, x)
member := Member{}
x = member
fmt.Printf("type:%T value:%v\n", x, x)
// 打印结果
// type:<nil> value:<nil>
// type:string value:hello world!
// type:int value:128
// type:main.Member value:{ 0}
// 空类型作为map的value值定义类型
var studentInfo = make(map[string]interface{})
}
// 空接口作为函数参数
func show(a interface{}) {
fmt.Printf("type:%T value:%v\n", a, a)
}
从上面的例子中我们可以知道:
1.定义空的interface的它的类型和值都为nil, 那么就可以通过!=nil做interface的判断
2.空接口类型的变量可以存储任意类型的变量。空接口是指没有定义任何方法的接口,因此任何类型都实现了空接口。
3.空接口常用的可以作为map的值定义类型和用户函数参数
2.接口(interface)的定义和使用
和面向对象接口类型相似,接口(interface)是为了不暴露出它所代表的对象的内部值的结构和这个对象支持的基础操作的集合;它们只会展示对外提供操作自己的方法。
- 接口(interface)的定义
??????? Go源码io包下的Reader和Writer接口定义: -
// Reader is the interface that wraps the basic Read method.
type Reader interface {
Read(p []byte) (n int, err error)
}
// Writer is the interface that wraps the basic Write method.
type Writer interface {
Write(p []byte) (n int, err error)
}
// Closer is the interface that wraps the basic Close method.
type Closer interface {
Close() error
} io.Writer类型是用的最广泛的接口之一,因为它提供了所有的类型写入bytes的抽象,包括文件类型,内存缓冲区,网络链接,HTTP客户端,压缩工具,哈希等等 除了直接定义接口方法,Go的接口还能嵌套 // ReadWriter is the interface that groups the basic Read and Write methods.
type ReadWriter interface {
Reader
Writer
}
// ReadCloser is the interface that groups the basic Read and Close methods.
type ReadCloser interface {
Reader
Closer
}
// WriteCloser is the interface that groups the basic Write and Close methods.
type WriteCloser interface {
Writer
Closer
}
// ReadWriteCloser is the interface that groups the basic Read, Write and Close methods.
type ReadWriteCloser interface {
Reader
Writer
Closer
} 接口的定义就是使用interface定义一组方法。这里一般方法名大写,表示其他的包可以使用该接口方法 - 接口(interface)的实现
Go的接口(interface)实现是一种称为鸭子模型的实现。接口(interface)就像是定义一个规则集合,只要实现了接口(interface)的提供的所有方法,那么就表示该实现了该接口,不用像java那种在类上显示的去指定实现的接口。 我们看下bytes.Buffer实现的Reader和Writer接口 package bytes
// A Buffer is a variable-sized buffer of bytes with Read and Write methods.
type Buffer struct {
buf []byte // contents are the bytes buf[off : len(buf)]
off int // read at &buf[off], write at &buf[len(buf)]
lastRead readOp // last read operation, so that Unread* can work correctly.
}
// 实现Write方法
func (b *Buffer) Write(p []byte) (n int, err error) {
b.lastRead = opInvalid
m, ok := b.tryGrowByReslice(len(p))
if !ok {
m = b.grow(len(p))
}
return copy(b.buf[m:], p), nil
}
// 实现Read方法
func (b *Buffer) Read(p []byte) (n int, err error) {
b.lastRead = opInvalid
if b.empty() {
// Buffer is empty, reset to recover space.
b.Reset()
if len(p) == 0 {
return 0, nil
}
return 0, io.EOF
}
n = copy(p, b.buf[b.off:])
b.off += n
if n > 0 {
b.lastRead = opRead
}
return n, nil
} 这里看到Buffer实现了Reader的Read和Write方法,而判断是否实现了某个接口(interface)的是要保证实现的所有方法的签名必须一致。签名一致要求的是实现的接口的方法必须保证名称相同、参数列表相同以及返回值相同。 这里我们可以看到Buffer实现的Read和Write方法和定义的Reader和Writer的一致。 - 接口(interface)的断言和类型选择? ? ? ??
第1部分里面我们看到空的interface的可以赋值给任意类型,那么我们怎么判断实际有没有实现某个接口呢?这里go提供一种断言的方式来判断,语法格式如下: value, ok := x.(T)
// x:表示类型为interface{}的变量
// T:表示断言x可能是的类型。
// value :成功转换后的值,动态类型
// ok:是否为目标类型,bool 类型 -
这里需要注意的是,如果x == nil, 那么断言一定失败。如果在已经确定?x ?就是?T ?类型的前提下,也可以省略?ok ,但是只要类型不匹配,就会报 panic。 -
我们来看看了bytes.Buffer实现了Reader和Writer的接口,下面用断言来判断一下 var r io.Reader
r = bytes.NewBufferString("111")
rw1, ok := r.(io.ReadWriter)
fmt.Printf("result: ok->%v, value->%v\n", ok,rw1)
var w io.Writer
w = bytes.NewBufferString("222")
rw2, ok := w.(io.ReadWriter)
fmt.Printf("result: ok->%v, value->%v\n", ok,rw2)
var stdw io.Writer = os.Stdout
f, ok := stdw.(*os.File)
fmt.Printf("result: ok->%v, value->%v\n",ok,f.Name())
// 打印结果
// result: ok->true, value->111
// result: ok->true, value->222
// result: ok->true, value->/dev/stdout
如果判断一个空接口类型有很多类型需要判断的时候,我们可以使用go的类型分支结构来处理,结构如下: switch x.(type) {
case nil:
// todo...
case int, uint:
// todo...
case bool:
// todo...
case string:
// todo...
default:
// todo...
} ?????????
3.接口(interface)在go-kratos框架中的使用
// 判断config是否实现了Config接口
_ Config = (*config)(nil)
// Config is a config interface.
type Config interface {
Load() error
Value(key string) Value
Watch(key string, o Observer) error
Close() error
}
// 定义具体实现
type config struct {
opts options
reader Reader
cached sync.Map
observers sync.Map
watchers []Watcher
log *log.Helper
}
// 通过options创建Config实例
func New(opts ...Option) Config {
return &config{
opts: o,
reader: newReader(o),
log: log.NewHelper(o.logger),
}
}
func (c *config) Load() error {
// todo: 具体加载配置方法实现
return nil
}
func (c *config) Value(key string) Value {
// todo: 取配置某个key对应的值
return nil
}
func (c *config) Watch(key string, o Observer) error {
// todo: 监听配置值变化
return nil
}
func (c *config) Close() error {
// todo: 关闭配置文件
return nil
}
说明:这里这么实现的几点意图
1. 定义config为小写,外部包不能访问,是为了隐藏包内部具体的实现细节。
2. 对外只提供Config的interface来方法操作,实现与内部实现隔离。
3. _ Config = (*config)(nil),这句代码判断config是否实现了Config接口,如果 *Config 与 config的接口不匹配, 那么语句_ Config = (*config)(nil)将无法编译通过。将实例是否实现接口提前到编译期就表现出来
参考资料:
1.kratos的配置包:https://github.com/go-kratos/kratos/tree/main/config
2.接口 · Go语言中文文档
3. uber手册interface的合法校验:GitHub - keenw/uber_go_guide_cn: Uber Go 语言编码规范中文版. The Uber Go Style Guide .
|