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 類型,值是 &{}
*/