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())
}
