(七)Go語言學習筆記 - 接口(interface)

1 接口簡單使用場景

package main

import (
	"fmt"
)
// 定義一個 Usb 接口
type Usb interface{
	Start()
	Stop()
}

// 定義手機結構體
type Phone struct{

}
// 讓Phone實現Usb接口的方法
func (p Phone) Start(){
	fmt.Println("手機正在啓動中......")
}
func (p Phone) Stop(){
	fmt.Println("手機正在關閉中......")
}

// 定義相機結構體
type Camera struct{

}
// 讓Camera實現Usb接口的方法
func (c Camera) Start(){
	fmt.Println("相機正在啓動中......")
}
func (c Camera) Stop(){
	fmt.Println("相機正在關閉中......")
}

// 定義一個Computer結構體
type Computer struct{

}
// 編寫一個Working方法,接收一個Usb接口類型變量
// 只要是實現了Usb接口(所謂實現Usb接口,就是指實現了Usb接口聲明的所有方法)
func (c Computer) Working(usb Usb) { // usb變量會根據傳入的實參,來判斷到底是Phone還是Camera

	// 通過usb接口變量來調用Start和Stop方法
	usb.Start()
	usb.Stop()

}

func main()  {

	// 測試,先創建結構體變量
	phone := Phone{}
	camera := Camera{}
	computer := Computer{}
	computer.Working(phone)
	//手機正在啓動中......
	//手機正在關閉中......
	computer.Working(camera)
	//相機正在啓動中......
	//相機正在關閉中......

}

2 什麼是接口

interface類型可以定義一組方法,但是這些不需要實現。並且interface不能包含任何變量。到某個自定義類型(比如結構體Phone)要使用的時候,在根據具體情況把這些方法寫出來(實現)。

基本語法:

	// 定義接口類型
	type 接口名 interface{
		method1(參數列表) 返回值列表
		method2(參數列表) 返回值列表
		...
	}
	
	// 實現接口所有方法
	func (t 自定義類型) method1(參數列表) 返回值列表 {
		// 方法實現
	}
	func (t 自定義類型) method2(參數列表) 返回值列表 {
		// 方法實現
	}
	...

說明:

  • (1)接口裏的所有方法都沒有方法體,即接口的方法都是沒有實現的方法。接口體現了程序設計的多態高內聚低耦合的思想。
  • (2)Golang中的接口,不需要顯示的實現。只需要一個變量,含有接口類型中的所有方法,那麼這個變量就實現這個接口。因此,Golang中沒有implement這樣的關鍵字。

2.1 details

  • (1)接口本身不能創建實例,但是可以指向一個實現了該接口的自定義類型的變量(實例)
package main

import (
	"fmt"
)

type AInterface interface {
	Say()
}
type Student struct {
	Name string
}

func (stu Student) Say() {
	fmt.Println("stu Say()")
}

func main() {

	var stu Student
	var a AInterface = stu
	a.Say()  // stu Say()

}

  • (2)接口中所有的方法都沒有方法體,即都是沒有實現的方法
  • (3)在Golang中,一個自定義類型需要將某個接口的所有方法都實現,我們才說這個自定義類型實現了該接口。
  • (4)一個自定義類型只有實現了某個接口,才能將該自定義類型的實例(變量)賦值給接口類型
  • (5)只要是自定義數據類型,就可以實現接口,不僅僅是結構體類型。
type integer int

func (i integer) Say() {
	fmt.Println("integer i Say() =", i)
}

func main() {
	var i integer = 10
	var b AInterface = i
	b.Say() // integer i Say() = 10
}
  • (6)一個自定義數據類型可以實現多個接口
type AInterface interface {
	Say()
}

type BInterface interface{
	Hello()
}

type Monster struct{

}
func (m Monster) Hello(){
	fmt.Println("Monster hello")
}
func (m Monster) Say(){
	fmt.Println("Monster Say")
}

func main() {
	var a2 AInterface = Monster
	var b2 BInterface = Monster
	a2.Say() // Monster Say
	b2.Hello()  //Monster hello
}
  • (7)Golang接口中不能有常量
type AInterface interface{
	Name string  // 錯誤的寫法
	Test01()
	Test02()
}
  • (8)一個接口(比如A接口)可以繼承多個別的接口(比如B、C接口),這時如果要實現A接口,也必須將B、C接口的方法也全部實現。
package main

import (
	"fmt"
)

type BInterface interface {
	test01()
}

type CInterface interface {
	test02()
}

type AInterface interface {
	BInterface
	CInterface
	test03()
}

type Student struct {

}
func (stu Student) test01(){
	fmt.Println("stu test01()......")
}
func (stu Student) test02(){
	fmt.Println("stu test02()......")
}
func (stu Student) test03(){
	fmt.Println("stu test02()......")
}

func main()  {
	var stu Student
	var a AInterface = stu
	a.test01()  // stu test01()......
	a.test02()  // stu test02()......
	a.test03()  // stu test03()......

}

  • (9)interface類型默認是一個指針(引用類型),如果沒有對interface初始化就使用,那麼會輸出nil。
  • (10)空接口interface{}沒有任何方法,即所有類型都實現了空接口,我麼可以把任何一個變量賦給空接口。

type T interface{

}

type Student struct {
	Name string
}

func (stu Student) Say() {
	fmt.Println("stu Say()")
}

func main() {

	var stu Student
	var t T = stu
	fmt.Println(t)
	var t2 interface{} = stu
	var num1 float64 = 8.8
	t2 = num1
	t = num1
	fmt.Println(t2, t)

}

2.2 經典調用接口的實例

實現對結構體切片的排序 sort.Sort(data Interface)

package main

import (
	"fmt"
	"sort"
	"math/rand"
)

// (1)聲明一個Hero結構體
type Hero struct {
	Name string
	Age int
}

// (2)聲明一個Hero結構體的切片類型
type HeroSlice []Hero

// (3)實現Interface接口
func (hs HeroSlice) Len() int {
	return len(hs)
}

// Less方法就是決定使用什麼標準進行排序
func (hs HeroSlice) Less(i, j int) bool {
	// 如果i 排在j前面,那麼返回true
	return hs[i].Age > hs[j].Age
}

func (hs HeroSlice) Swap(i, j int) {
	hs[i], hs[j] = hs[j], hs[i]
}

func main()  {

	var intSlice = []int{13, 1, -1, 0, 15, 56}

	//對切片進行排序
	sort.Ints(intSlice)
	fmt.Println(intSlice)  // [-1 0 1 13 15 56]

	//對結構體切片進行排序
	var heroes HeroSlice
	for i := 0; i < 10; i++{
		hero := Hero{
			Name : fmt.Sprintf("英雄~%d", rand.Intn(100)),
			Age : rand.Intn(100),
		}
		//	將hero append到heroes切片
		heroes = append(heroes, hero)
	}

	// 排序前 遍歷切片
	for _, v := range(heroes){
		fmt.Println("排序前", v)
	}

	// 調用sort.Sort
	sort.Sort(heroes)
	for _, v := range(heroes){
		fmt.Println("排序後", v)
	}
}

2.3 接口和繼承的關係

  • (1)接口和繼承解決的問題不同
    • 繼承的價值主要在於:解決代碼的複用性和可維護性。
    • 接口的價值主要在於:設計,設計好各種規範(方法),讓其它自定義類型去實現這些方法
  • (2)接口比繼承更加靈活
    • 繼承是滿足 is - a的關係,而接口只需要滿足 like - a的關係即可
  • (3)接口在一定程度上實現代碼解耦
package main

import "fmt"

type BirdFly interface {
	Flying()
}

// 定義一個Monkey猴子結構體
type Monkey struct {
	Name string
}

func (m *Monkey) Climbing() {
	fmt.Println(m.Name, "天生會爬樹...")
}

type LittleMonkey struct {
	Monkey // 繼承
}

func (m *LittleMonkey) Flying() {
	fmt.Println(m.Name, "通過後天學習,會飛翔了....")
}


func main()  {

	monkey := LittleMonkey{
		Monkey{
			Name : "孫悟空",
		},
	}
	monkey.Climbing()  // 孫悟空 天生會爬樹...
	monkey.Flying()

}
  • (1)A結構體繼承了B結構體,那麼A結構體就自動的繼承了B結構體的字段和方法,並且可以直接使用
  • (2)當A結構體需要擴展功能,同時又不希望去破壞繼承關係,則可以去實現某個接口即可,因此可以認爲:實現接口是對繼承機制的補充。

3 多態

變量(實例)具有多種形態。面向對象的第三大特徵,在Go語言中,多態特徵是通過接口實現的。可以按照統一的接口來調用不同的實現。這時接口變量就呈現不同的形態。
在下邊的Usb 的案例就是多態的體現,usb Usb,既可以接收手機變量,又可以接收相機變量,就體現了Usb接口多態特性

  • (1)多態參數
  • (2)多態數組
package main

import (
	"fmt"
)
// 定義一個 Usb 接口
type Usb interface{
	Start()
	Stop()
}

// 定義手機結構體
type Phone struct{
	Name string
}
// 讓Phone實現Usb接口的方法
func (p Phone) Start(){
	fmt.Println("手機正在啓動中......")
}
func (p Phone) Stop(){
	fmt.Println("手機正在關閉中......")
}

// 定義相機結構體
type Camera struct{
	Name string
}
// 讓Camera實現Usb接口的方法
func (c Camera) Start(){
	fmt.Println("相機正在啓動中......")
}
func (c Camera) Stop(){
	fmt.Println("相機正在關閉中......")
}

func main()  {
	// 定義一個Usb接口數組,可以存放Phone和Camera的結構體變量
	// 這裏就體現出多態數組
	var usbArr [3]Usb
	usbArr[0] = Phone{"apple"}
	usbArr[1] = Phone{"華爲"}
	usbArr[2] = Camera{"Nikon"}

	fmt.Println(usbArr)  // [{apple} {華爲} {Nikon}]

}

4 斷言

斷言引出:如何將一個接口變量類型,賦值給自定義類型的變量

package main

import "fmt"

type Point struct {
	x int
	y int
}

func main()  {

	var a interface{}
	var point Point = Point{1, 2}
	a = point

	var b Point
	// 此時如果想讓a賦值給b,不能直接  b = a
	// 要用類型斷言
	b = a.(Point)
	// 表示判斷a是否是指向Point類型的變量,如果是就轉成Point類型並賦給b變量,否則就報錯
	fmt.Println(b)  // 輸出 {1, 2}


	// 類型斷言的其它案例
	var a1 interface{}
	var b1 float64 = 1.1
	a1 = b1
	y := a1.(float64) // 這邊就不能寫 a1.(float32)
	fmt.Println(y)
	// 在進行類型斷言時,如果類型不匹配,就會報panic錯誤


	// 如何在進行斷言時,帶上監測機制,如果成功就ok,否則也不要報panic
	var x interface{}
	var b2 float64 = 1.1
	x = b2
	b2, ok := x.(float32)
	if ok {  // ok爲true
		fmt.Println("轉換成功......")
		fmt.Printf("b2 的類型是%T 值是%v", b2, b2)
	} else {
		fmt.Println("轉換失敗")
	}

	// 上邊的判斷可以簡寫爲
	// if b2, ok := x.(float32); ok {}
	fmt.Println("繼續執行")
}

4.1 示例

在之前USB實例做改進,給Phone結構體增加一個特有的方法call(),當Usb接口接收的是Phone變量時,還需要調用call方法

package main

import (
	"fmt"
)

type Usb interface {
	Start()
	Stop()
}

// 定義手機結構體
type Phone struct {
	Name string
}
func (p *Phone) Start()  {
	fmt.Println("Phone正在啓動中......")
}
func (p *Phone) Stop()  {
	fmt.Println("Phone正在關閉中......")
}

func (p *Phone) Call() {
	fmt.Println("Phone正在打電話")
}

// 定義相機結構體
type Camera struct {
	Name string
}
func (c *Camera) Start()  {
	fmt.Println("Camera正在啓動中......")
}
func (c *Camera) Stop()  {
	fmt.Println("Camera正在關閉中......")
}

type Computer struct {

}

func (c *Computer) Working(usb Usb) {
	usb.Start()
	// 如果usb指向Phone結構體變量,則還需要調用Call方法
	// 類型斷言
	if phone, ok := usb.(*Phone); ok {
		phone.Call()
	}
	usb.Stop()
}

func main()  {

	var usbArr [3]Usb
	usbArr[0] = &Phone{"vivo"}
	usbArr[1] = &Phone{"小米"}
	usbArr[2] = &Camera{"Nikon"}

	var computer Computer
	for _, v := range usbArr {
		computer.Working(v)
		fmt.Println()
	}
	// fmt.Println(usbArr)
}

4.2 示例

package main

import (
	"fmt"
)

type Student struct {

}

// 寫一個函數,循環判斷傳入參數的類型

func JudgeType(items... interface{}) {

	for index, x := range items {
		switch x.(type) {  // 這裏type是一個關鍵字,固定寫法
		case bool:
			fmt.Printf("第%v個參數是 bool 類型,值是 %v \n", index + 1, x)
		case float64, float32:
			fmt.Printf("第%v個參數是 浮點類型 類型,值是 %v \n", index + 1, x)
		case int, int32, int64:
			fmt.Printf("第%v個參數是 整型 類型,值是 %v \n", index + 1, x)
		case string:
			fmt.Printf("第%v個參數是 string 類型,值是 %v \n", index + 1, x)
		case Student :
			fmt.Printf("第%v個參數是 Student 類型,值是 %v \n", index + 1, x)
		case *Student :
			fmt.Printf("第%v個參數是 *Student 類型,值是 %v \n", index + 1, x)
		default:
			fmt.Println("第%v個參數是 類型不確定,值是 %v \n", index + 1, x)
		}
	}

}

func main() {
	var n1 float32 = 1.1
	var n2 float64 = 2.2
	var n3 int = 10
	var str string = "Hello"
	stu := Student{}
	stu1 := &Student{}

	JudgeType(n1, n2, n3, str, stu, stu1)
}
/*
第1個參數是 浮點類型 類型,值是 1.1
第2個參數是 浮點類型 類型,值是 2.2
第3個參數是 整型 類型,值是 10
第4個參數是 string 類型,值是 Hello
第5個參數是 Student 類型,值是 {}
第6個參數是 *Student 類型,值是 &{}
 */
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章