go面向接口編程知識點
接口定義與格式
接口(interface)是一種類型,用來定義行為(方法)。這句話有兩個重點,類型和定義行為。
首先解釋定義行為:
接口即一組方法定義的集合,定義了對象的一組行為,就是定義了一些函數,由具體的類型實例實現具體的方法。
換句話說,一個接口就是定義(規范或約束),接口並不會實現這些方法,具體的實現由類實現,實現接口的類必須嚴格按照接口的聲明來實現接口提供的所有功能。接口的作用應該是將定義與實現分離,降低耦合度。
在多人合作開發同一個項目時,接口表示調用者和設計者的一種約定,事先定義好相互調用的接口可以大大提高開發的效率。有了接口,就可以在不影響現有接口聲明的情況下,修改接口的內部實現,從而使兼容性問題最小化。
接口的定義格式:
type Namer interface { Method1(param_list) return_type //方法名(參數列表) 返回值列表 Method2(param_list) return_type //方法名(參數列表) 返回值列表
......
}
隱式實現及實現條件
怎么實現接口:
實現接口的類並不需要顯式聲明,只需要實現接口所有的函數就表示實現了該接口,而且類還可以擁有自己的方法。
接口能被哪些類型實現:
接口可以被結構體實現,也可以被函數類型實現。
接口被實現的條件:
接口被實現的條件一:接口的方法與實現接口的類型方法格式一致(方法名、參數類型、返回值類型一致)。
接口被實現的條件二:接口中所有方法均被實現。
package main import "fmt" type Shaper interface { Area() float64 // Perimeter() float64 } type Rectangle struct { length float64 width float64 } // 實現 Shaper 接口中的方法 func (r *Rectangle) Area() float64 { return r.length * r.width } // Set 是屬於 Rectangle 自己的方法 func (r *Rectangle) Set(l float64, w float64) { r.length = l r.width = w } func main() { rect := new(Rectangle) //創建指針類型的結構體實例(類實例) rect.Set(2, 3) areaIntf := Shaper(rect) //這里將指針類型實例賦值給接口,下面會介紹。 fmt.Printf("The rect has area: %f\n", areaIntf.Area()) }
接口賦值
現在來解釋接口是一個類型,本質是一個指針類型,那么什么樣的值可以賦值給接口,有兩種:實現了該接口的類或者接口。
1.將對象賦值給接口
當接口實例中保存了自定義類型的實例后,就可以直接從接口上調用它所保存的實例的方法。
package main import ( "fmt" ) //定義接口 type Testinterface interface{ Teststring() string Testint() int } //定義結構體 type TestMethod struct{ name string age int } //結構體的兩個方法隱式實現接口 func (t *TestMethod)Teststring() string{ return t.name } func (t *TestMethod)Testint() int{ return t.age } func main(){ T1 := &TestMethod{"ling",34} T2 := TestMethod{"gos",43} //接口本質是一種類型 //接口賦值:只要類實現了該接口的所有方法,即可將該類賦值給這個接口 var Test1 Testinterface //接口只能是值類型 Test1 = T1 //TestMethod類的指針類型實例傳給接口 fmt.Println(Test1.Teststring()) fmt.Println(Test1.Testint()) Test2 := T2 //TestMethod類的值類型實例傳給接口 fmt.Println(Test2.Teststring()) fmt.Println(Test2.Testint()) }
2.將接口賦值給另一個接口
1.只要兩個接口擁有相同的方法列表(與次序無關),即是兩個相同的接口,可以相互賦值
2.接口賦值只需要接口A的方法列表是接口B的子集(即假設接口A中定義的所有方法,都在接口B中有定義),那么B接口的實例可以賦值給A的對象。反之不成立,即子接口B包含了父接口A,因此可以將子接口的實例賦值給父接口。
3.即子接口實例實現了子接口的所有方法,而父接口的方法列表是子接口的子集,則子接口實例自然實現了父接口的所有方法,因此可以將子接口實例賦值給父接口。
3.接口類型作為參數
第一點已經說了可以將實現接口的類賦值給接口,而將接口類型作為參數很常見。這時,那些實現接口的實例都能作為接口類型參數傳遞給函數/方法。
package main import ( "fmt" ) //Shaper接口 type Shaper interface { Area() float64 } // Circle struct結構體 type Circle struct { radius float64 } // Circle類型實現Shaper中的方法Area() func (c *Circle) Area() float64 { return 3.14 * c.radius * c.radius } func main() { // Circle的指針類型實例 c1 := new(Circle) c1.radius = 2.5 //將 Circle的指針類型實例c1傳給函數myArea,接收類型為Shaper接口 myArea(c1) } func myArea(n Shaper) { fmt.Println(n.Area()) }
空接口
空接口是指沒有定義任何接口方法的接口。沒有定義任何接口方法,意味着Go中的任意對象都已經實現空接口(因為沒方法需要實現),只要實現接口的對象都可以被接口保存,所以任意對象都可以保存到空接口實例變量中。
空接口的定義方式:
type empty_int interface {}
更常見的,會直接使用interface{}
作為一種類型,表示空接口。例如:
// 聲明一個空接口實例 var i interface{}
再比如函數使用空接口類型參數:
func myfunc(i interface{})
如何使用空接口
可以定義一個空接口類型的array、slice、map、struct等,這樣它們就可以用來存放任意類型的對象,因為任意類型都實現了空接口。
package main import "fmt" func main() { any := make([]interface{}, 5) any[0] = 11 any[1] = "hello world" any[2] = []int{11, 22, 33, 44} for _, value := range any { fmt.Println(value) } }
11 hello world [11 22 33 44] <nil> <nil>
通過空接口類型,Go也能像其它動態語言一樣,在數據結構中存儲任意類型的數據。
接口嵌套
接口可以嵌套,嵌套的內部接口將屬於外部接口,內部接口的方法也將屬於外部接口。
另外在類型嵌套時,如果內部類型實現了接口,那么外部類型也會自動實現接口,因為內部屬性是屬於外部屬性的。
type ReadWrite interface { Read(b Buffer) bool Write(b Buffer) bool } type Lock interface { Lock() Unlock() } type File interface {
//ReadWrite為內部接口 ReadWrite
//Lock為內部接口 Lock Close() }
類型斷言
類型斷言為判斷一個類型有沒有實現接口。
假如我現在寫了一個結構體類型 MyFile
來實現上面的 File
接口,那么我如何知道 MyFile
是否實現了 File
接口呢?
package main import "fmt" type MyFile struct{} func (m *MyFile) Read() bool { fmt.Printf("Read()\n") return true } // ... // 假設我這里相繼實現了 Write(), Lock(),Unlock() 和 Close() 方法 func main() { my := new(MyFile) fIntf := File(my) // 看這里,看這里 if v, ok := fIntf.(*MyFile); ok { v.Read() } }
類型斷言的格式:
if v, ok : = varI.(T) ; ok {
// checked type assertion
//do something return }
如果 v
是 varI
轉換到類型 T
的值,ok
會是 true
;否則 v
是類型 T
的零值,ok
是 false
。
要是多個類型實現了同一個接口,比如前面的 areaIntf
,要如何測試呢?
那就要用 type-switch
來判斷了。
switch t := areaIntf.(type) { case *Rectangle: // do something case *Triangle: // do something default: // do something }
多態
1、多個類型(結構體)可以實現同一個接口。
2、一個類型(結構體)可以實現多個接口。
3、實現接口的類(結構體)可以賦值給接口。
package main import "fmt" type Shaper interface { Area() float64 } // ==== Rectangle ==== type Rectangle struct { length float64 width float64 } // 實現 Shaper 接口中的方法 func (r *Rectangle) Area() float64 { return r.length * r.width } // Set 是屬於 Rectangle 自己的方法 func (r *Rectangle) Set(l float64, w float64) { r.length = l r.width = w } // ==== Triangle ==== type Triangle struct { bottom float64 hight float64 } func (t *Triangle) Area() float64 { return t.bottom * t.hight / 2 } func (t *Triangle) Set(b float64, h float64) { t.bottom = b t.hight = h } // ==== Triangle End ==== func main() { rect := new(Rectangle) rect.Set(2, 3) areaIntf := Shaper(rect) //這種方法只能將指針類型的類示例賦值給接口 fmt.Printf("The rect has area: %f\n", areaIntf.Area()) triangle := new(Triangle) triangle.Set(2, 3) areaIntf = Shaper(triangle) //這種方法只能將指針類型的類示例賦值給接口 fmt.Printf("The triangle has area: %f\n", areaIntf.Area()) }