接口
什么是 interface
提到接口混弥,我們會(huì)聯(lián)想到汽車(chē)的接口雨饺,也就是讓司機(jī)如何操作汽車(chē)按自己意愿使用袭异,用戶(hù)界面也是接口钠龙,會(huì)聯(lián)想到很多很多。接口看了很多御铃,對(duì)初學(xué)者比較難理解碴里,但是一旦理解了并可以靈活運(yùn)用了,接口 power 還是很大的上真。
接口可以理解是規(guī)范咬腋、協(xié)議、用戶(hù)使用手冊(cè)和對(duì)類(lèi)型抽象睡互,對(duì)行為描述根竿。說(shuō)了這么一大堆還需要您自己了解。
In object-oriented programming, a protocal or interface is a common means for unrelated objects to communicate with each other
wikipedia
上面的話(huà)摘字 wiki就珠,這里傳遞了兩個(gè)信息
- communicate 接口是用于通訊寇壳,接口就是用來(lái)定義通訊遵循的規(guī)則
- unrelated objects 沒(méi)有關(guān)系的對(duì)象,接口定義通訊規(guī)則可以使用兩個(gè)互不相干的對(duì)象妻怎。
樂(lè)高玩具就是一個(gè)好的例子壳炎。樂(lè)高玩具的一個(gè)piece 組合時(shí)只要遵守尺寸規(guī)則,無(wú)論大小和顏色就可以組合在一起進(jìn)行拼接逼侦。
以后兼職工作也是一樣只要滿(mǎn)足規(guī)定的條件匿辩,在拼接 Lego 玩具時(shí)是否可以拼接是和piece 的顏色和形狀沒(méi)有關(guān)系的腰耙,只要他們都遵守一定尺寸就可以進(jìn)行拼接。在軟件控制模塊搭建和通訊也是通過(guò)定義一定接口規(guī)范來(lái)實(shí)現(xiàn)了铲球。我想軟件工程也在某些方面借鑒傳統(tǒng)的行業(yè)挺庞。
那么什么是 go 的 interface
- abstract types
- concrete types
當(dāng)然在 go 語(yǔ)言中有很多種類(lèi)型,不過(guò)我們大致可以將歸為兩種一種類(lèi)型屬于abstract類(lèi)型(抽象類(lèi)型)和concrete類(lèi)型(實(shí)體類(lèi)型)
concrete 類(lèi)型
- 用于描述類(lèi)型在內(nèi)存中分配情況
int8/int16/int32/int64/struct/float - 使用方法賦予數(shù)據(jù)一定的行為
type Number int
func (n Number) Positive() bool{
return n >0
}
abstract 類(lèi)型
抽象類(lèi)型并沒(méi)定義描述如何為這種類(lèi)型分配內(nèi)存空間睬辐,而是描述類(lèi)型的行為挠阁。按行為為類(lèi)型進(jìn)行劃分宾肺。這些抽象類(lèi)型有io.Reader
溯饵、io.Writer
和fmt.String
等等
type Positiver interface{
Positive() bool
}
用來(lái)說(shuō)明 go 語(yǔ)言接口的經(jīng)典接口 Writer 和 Reader 接口
type Reader interface{
Read(b []byte)(int,error)
}
type Writer interface{
Write(b []byte)(int,error)
}
只要實(shí)現(xiàn)了接口的方法的類(lèi)型就屬于接口類(lèi)型,所以集合是普通類(lèi)型的集合锨用。我們接口是可以組合丰刊,但是接口越詳細(xì)確定范圍也就小 weak
type ReadWriter interface{
Read
Writer
}
這里有一個(gè) interface{}
Rob Pike 指著interface{}
是沒(méi)有任何意思,因?yàn)闆](méi)有任何限制增拥,沒(méi)有限制也就是沒(méi)有意義啄巧,這個(gè)應(yīng)該不難理解
type ConsoleWriter struct{
}
繼續(xù)前面的內(nèi)容,定義一個(gè) struct掌栅,struct 表示數(shù)據(jù)抽象而 interface 表示對(duì)行為抽象
func (cw ConsoleWriter) Write(data []byte)(int, error){
n, err := fmt.Println(string(data))
return n,err
}
上面的代碼表示如何讓 ConsoleWriter 來(lái)實(shí)現(xiàn)接口 Writer 實(shí)現(xiàn)秩仆,go 語(yǔ)言對(duì)接口實(shí)現(xiàn)與其他語(yǔ)言不通,只要我們 struct 作為接受者具有一個(gè) interface 方法名以及參數(shù)和返回值類(lèi)型都相同的方法猾封,就說(shuō)明該 struct 實(shí)現(xiàn)了該接口澄耍。
func main() {
var w Writer = ConsoleWriter{}
w.Write([]byte("Hello Interface"))
}
使用起來(lái)也很方便,只要實(shí)現(xiàn)了 Write 方法的 struct 都可以看成 Writer 按接口分類(lèi)的類(lèi)型晌缘。這個(gè)可以理解為 java 的向上轉(zhuǎn)型也可以理解為多態(tài)一種表現(xiàn)齐莲。
在這里 go 語(yǔ)言對(duì)接口命名也有一定規(guī)則可循,就是如果我們定義一個(gè)只有方法接口磷箕,接口名稱(chēng)應(yīng)以 er 結(jié)尾 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 語(yǔ)言中我們可以通過(guò)接口組合來(lái)實(shí)現(xiàn) struct 實(shí)現(xiàn)多接口。這個(gè)例子相對(duì)復(fù)雜并且貼近真實(shí)項(xiàng)目岳枷。
type Writer interface{
Write([]byte) (int, error)
}
type Closer interface{
Close() error
}
這里定義兩個(gè)接口 Writer 和 Closer芒填,分別提供了 Write 和 Close 兩個(gè)方法來(lái)實(shí)現(xiàn)對(duì)文件寫(xiě)入和關(guān)閉。
type WriterCloser interface{
Writer
Closer
}
我們可以定義 WriterCloser 接口包含了 Writer 接口同時(shí)也包含了 Closer 接口兩個(gè)接口空繁。
type BufferedWriterCloser struct{
buffer *bytes.Buffer
}
定義 struct BufferedWriterCloser 來(lái)實(shí)現(xiàn) WriterCloser 接口氢烘。
然后 BufferedWriterCloser 來(lái)分別實(shí)現(xiàn) Write 和 Close 方法以實(shí)現(xiàn) WriterCloser 這個(gè)接口。
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()
}
接口檢查
其實(shí)在實(shí)際開(kāi)發(fā)中有時(shí)候我們不知道某個(gè)結(jié)構(gòu)是否已經(jīng)實(shí)現(xiàn)接口所有方法家厌。
通過(guò)接口.(接收者)(wc.(*BufferedWriterCloser))
可以檢查接收者是否已經(jīng)實(shí)現(xiàn)了該接口的所有方法
bwc := wc.(*BufferedWriterCloser)
fmt.Println(bwc)
如果接收者已經(jīng)實(shí)現(xiàn)了接口的所有的方法會(huì)返回一個(gè)對(duì)象播玖,否則就會(huì)panic錯(cuò)誤,大家都知道我們?cè)诔绦蜻\(yùn)行時(shí)是不希望看到panic的饭于,這個(gè)代價(jià)太expansive蜀踏。例如我們傳入 io.Reader
就會(huì)發(fā)生錯(cuò)誤维蒙。
bwc := wc.(io.Reader)
fmt.Println(bwc)
interface conversion: *main.BufferedWriterCloser is not io.Reader: missing method Read
其實(shí)檢查類(lèi)型會(huì)給我們返回兩個(gè)類(lèi)型
r, ok := wc.(io.Reader)
if ok {
fmt.Println(r)
}else{
fmt.Println("Conversion failed")
}
空接口
對(duì)于一個(gè)空接口來(lái)說(shuō),可以表示任何類(lèi)型果覆。所以當(dāng)我們無(wú)法判斷變量類(lèi)型時(shí)候可以為其指定 interface{}
類(lèi)型颅痊。但是大家對(duì)interface{}
要慎用,因?yàn)樗砣魏晤?lèi)型沒(méi)有任何限制同時(shí)也就失去意義局待。下面是一個(gè)判斷類(lèi)型例子斑响。
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",
}
最佳實(shí)踐
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