接口
什么是 interface
提到接口,我们会联想到汽车的接口,也就是让司机如何操作汽车按自己意愿使用,用户界面也是接口,会联想到很多很多。接口看了很多,对初学者比较难理解,但是一旦理解了并可以灵活运用了,接口 power 还是很大的。
接口可以理解是规范、协议、用户使用手册和对类型抽象,对行为描述。说了这么一大堆还需要您自己了解。
In object-oriented programming, a protocal or interface is a common means for unrelated objects to communicate with each other
wikipedia
上面的话摘字 wiki,这里传递了两个信息
- communicate 接口是用于通讯,接口就是用来定义通讯遵循的规则
- unrelated objects 没有关系的对象,接口定义通讯规则可以使用两个互不相干的对象。
乐高玩具就是一个好的例子。乐高玩具的一个piece 组合时只要遵守尺寸规则,无论大小和颜色就可以组合在一起进行拼接。
以后兼职工作也是一样只要满足规定的条件,在拼接 Lego 玩具时是否可以拼接是和piece 的颜色和形状没有关系的,只要他们都遵守一定尺寸就可以进行拼接。在软件控制模块搭建和通讯也是通过定义一定接口规范来实现了。我想软件工程也在某些方面借鉴传统的行业。
那么什么是 go 的 interface
- abstract types
- concrete types
当然在 go 语言中有很多种类型,不过我们大致可以将归为两种一种类型属于abstract类型(抽象类型)和concrete类型(实体类型)
concrete 类型
- 用于描述类型在内存中分配情况
int8/int16/int32/int64/struct/float - 使用方法赋予数据一定的行为
type Number int
func (n Number) Positive() bool{
return n >0
}
abstract 类型
抽象类型并没定义描述如何为这种类型分配内存空间,而是描述类型的行为。按行为为类型进行划分。这些抽象类型有io.Reader
、io.Writer
和fmt.String
等等
type Positiver interface{
Positive() bool
}
用来说明 go 语言接口的经典接口 Writer 和 Reader 接口
type Reader interface{
Read(b []byte)(int,error)
}
type Writer interface{
Write(b []byte)(int,error)
}
只要实现了接口的方法的类型就属于接口类型,所以集合是普通类型的集合。我们接口是可以组合,但是接口越详细确定范围也就小 weak
type ReadWriter interface{
Read
Writer
}
这里有一个 interface{}
Rob Pike 指着interface{}
是没有任何意思,因为没有任何限制,没有限制也就是没有意义,这个应该不难理解
type ConsoleWriter struct{
}
继续前面的内容,定义一个 struct,struct 表示数据抽象而 interface 表示对行为抽象
func (cw ConsoleWriter) Write(data []byte)(int, error){
n, err := fmt.Println(string(data))
return n,err
}
上面的代码表示如何让 ConsoleWriter 来实现接口 Writer 实现,go 语言对接口实现与其他语言不通,只要我们 struct 作为接受者具有一个 interface 方法名以及参数和返回值类型都相同的方法,就说明该 struct 实现了该接口。
func main() {
var w Writer = ConsoleWriter{}
w.Write([]byte("Hello Interface"))
}
使用起来也很方便,只要实现了 Write 方法的 struct 都可以看成 Writer 按接口分类的类型。这个可以理解为 java 的向上转型也可以理解为多态一种表现。
在这里 go 语言对接口命名也有一定规则可循,就是如果我们定义一个只有方法接口,接口名称应以 er 结尾 Writer 而方法名为 Write。
type Incrementer interface{
Increment() int
}
type IntCounter int
func (ic *IntCounter) Increment() int {
*ic++
return int(*ic)
}
func main() {
myInt := IntCounter(0)
var inc Incrementer = &myInt
for i := 0; i < 10; i++ {
fmt.Println(inc.Increment())
}
}
接口的组合
在 go 语言中我们可以通过接口组合来实现 struct 实现多接口。这个例子相对复杂并且贴近真实项目。
type Writer interface{
Write([]byte) (int, error)
}
type Closer interface{
Close() error
}
这里定义两个接口 Writer 和 Closer,分别提供了 Write 和 Close 两个方法来实现对文件写入和关闭。
type WriterCloser interface{
Writer
Closer
}
我们可以定义 WriterCloser 接口包含了 Writer 接口同时也包含了 Closer 接口两个接口。
type BufferedWriterCloser struct{
buffer *bytes.Buffer
}
定义 struct BufferedWriterCloser 来实现 WriterCloser 接口。
然后 BufferedWriterCloser 来分别实现 Write 和 Close 方法以实现 WriterCloser 这个接口。
func (bwc *BufferedWriterCloser) Write(data []byte)(int, error){
n, err := bwc.buffer.Write(data)
if err != nil{
return 0, err
}
v := make([]byte,8)
for bwc.buffer.Len() > 8 {
_, err := bwc.buffer.Read(v)
if err != nil{
return 0, err
}
_, err = fmt.Println(string(v))
if err != nil{
return 0, err
}
}
return n, nil
}
func (bwc *BufferedWriterCloser) Close() error{
for bwc.buffer.Len() > 0 {
data := bwc.buffer.Next(8)
_, err := fmt.Println(string(data))
if err != nil{
return err
}
}
return nil
}
func NewBufferedWriterCloser() *BufferedWriterCloser{
return &BufferedWriterCloser{
buffer: bytes.NewBuffer([]byte{}),
}
}
func main() {
myInt := IntCounter(0)
var inc Incrementer = &myInt
for i := 0; i < 10; i++ {
fmt.Println(inc.Increment())
}
var wc WriterCloser = NewBufferedWriterCloser()
wc.Write([]byte("Hello jianshu readers, this is a test"))
wc.Close()
}
接口检查
其实在实际开发中有时候我们不知道某个结构是否已经实现接口所有方法。
通过接口.(接收者)(wc.(*BufferedWriterCloser))
可以检查接收者是否已经实现了该接口的所有方法
bwc := wc.(*BufferedWriterCloser)
fmt.Println(bwc)
如果接收者已经实现了接口的所有的方法会返回一个对象,否则就会panic错误,大家都知道我们在程序运行时是不希望看到panic的,这个代价太expansive。例如我们传入 io.Reader
就会发生错误。
bwc := wc.(io.Reader)
fmt.Println(bwc)
interface conversion: *main.BufferedWriterCloser is not io.Reader: missing method Read
其实检查类型会给我们返回两个类型
r, ok := wc.(io.Reader)
if ok {
fmt.Println(r)
}else{
fmt.Println("Conversion failed")
}
空接口
对于一个空接口来说,可以表示任何类型。所以当我们无法判断变量类型时候可以为其指定 interface{}
类型。但是大家对interface{}
要慎用,因为他代表任何类型没有任何限制同时也就失去意义。下面是一个判断类型例子。
var i interface{} = 0
switch i.(type) {
case int:
fmt.Println("i is an integer")
case string:
fmt.Println("i is a string")
default:
fmt.Println("I don't know what is is")
}
引用和值接受者
type Writer interface{
Write([]byte)(int, error)
}
type Reader interface{
Read([]byte)(int,error)
}
type WriterReader interface{
Writer
Reader
}
type FileReaderWriter struct{
filename string
}
func (frw FileReaderWriter) Write(data []byte)(int,error){
fmt.Println("write...")
return 0,nil
}
func (frw FileReaderWriter) Read(data []byte)(int,error){
fmt.Println("Read...")
return 0, nil
}
func main() {
var fileRW WriterReader = FileReaderWriter{
"angular",
}
bytes := []byte("hell world")
fileRW.Write(bytes)
fileRW.Read(bytes)
}
func (frw *FileReaderWriter) Write(data []byte)(int,error){
fmt.Println("write...")
return 0,nil
}
der in assignment:
FileReaderWriter does not implement WriterReader (Write method has pointer receiver)
var fileRW WriterReader = &FileReaderWriter{
"angular",
}
最佳实践
Use many, small interface
Single method interfaces are some of the most powerful and flexible
io.Writer , io.Reader, interface{}
Don't export interfaces for types that will be consumed