Go核心开发学习笔记(廿四) —— 接口,多态,断言

接口 Interface概述

  1. 在Golang中大量存在,面向对象的核心与灵魂一定是接口编程,且相对于其他OOP语言也是极具特色。
  2. 手机,相机连接计算机USB接口,都是统一制式,不用担心到底什么设备连接计算机,插上什么就是什么,从而引出多态概念。
  3. 多态主要是依赖接口实现的,低耦合高内聚。
  4. 接口定义了几个未实现的方法,创建其他结构体,补全各自方法,从而实现不同结构体调用类似"集线器"完成接口所有方法。
  5. ★★★接口中所有方法都没有方法体,即接口都是未实现的方法,接口体现了程序设计的多态和高内聚低耦合的思想。
  6. ★★★Golang接口不需要显式的实现,只要一个变量包含接口所有方法,那么变量就实现了这个接口,没有implement关键字。

举例说明,定义一个接口

package main
import "fmt"

type Usb interface {
	/*
	声明了两个没有实现的方法
	 */
	Start()                  //定义接口类型与结构体类似
	Stop()                   //接口类型中只能定义方法!不能定义属性!
}

type Usb2 interface {
	/*
	声明了两个没有实现的方法
	 */
	Start()                  
	Stop()   
	Test()                   //下面所有结构体方法都没有Test(),没有实现就不可使用Usb2接口,一定会报错,必须要完全实现方法                
}

type Phone struct {

}

func (p Phone) Start() {
	fmt.Println("手机开始工作")
}

func (p Phone) Stop() {
	fmt.Println("手机停止工作")
}

type Camera struct {

}

func (c Camera) Start() {
	fmt.Println("相机开始工作")
}

func (c Camera) Stop() {
	fmt.Println("相机停止工作")
}

type Computer struct {

}

func (c1 Computer) Connect(usb Usb) {
	usb.Start()
	usb.Stop()
}

func main() {
	//创建结构体变量
	computer := Computer{}
	phone := Phone{}
	camera := Camera{}
	//重点
	computer.Connect(phone)
	computer.Connect(camera)
}

接口应用场景

  1. 开发过程中不可能由一个程序员完成所有工作,项目经理就可以提供接口,所有接口内容一致,由程序员实现各自负责变量的所有方法。
  2. 可以很好地控制和管理软件开发进度。

接口的事项和细节

  1. 接口本身不能创建实例,但可以指向一个实现了该接口的自定义类型的变量。 //上面示例中 Usb接口本身没有实例,但是Phone和Camera可以创建实例使用接口方法
  2. 接口中所有方法都没有方法体,即都是没有实现的方法。
  3. Golang中,一个自定义类型需要将某个接口的!所有方法!都实现,我们才说这个自定义类型实现了该接口。
  4. 只要是自定义类型,都可以实现接口,不仅仅是结构体,int,float都可以的,前提是只要实现了接口所有方法!
  5. 一个自定义类型可以实现多个接口,只要实现了所有方法! //A接口方法1,B接口方法2,一个结构体变量实现了方法1和方法2,那么A和B中接口全部实现并且可以使用方法。
  6. 定义接口类型一定不能包含任何变量!只能是未实现的方法!
  7. 接口也可以继承,继承的前提是实现所有继承被继承的方法!
  8. 接口是一个引用类型,与结构体不同,如果没有初始化使用,输出会为nil。
  9. 空接口interface{}没有任何方法,所以所有数据类型都实现了空接口,即我们可以将任何变量赋值给空接口。
  10. ★★★在实现接口方法时,一定要注意,方法绑定变量 func (p phone) xx() {} 和func (p *phone) xx() {} 是两个完全不一样的概念,一个是phone类型实现方法,一个是phone对应的指针类型实现了方法,后面如果涉及接口变量,一定要对应上,不然肯定编译错误。

针对第九条举例说明,看看空接口接收值打印出来什么样。

package main
import "fmt"
type T1 interface {

}

func main() {
	//没有初始化使用,打印出来是nil<nil>
	var t1 T1
	fmt.Println(t1)

	//给空接口传入一个整型
	num := 666
	var t2 T1 = num
	fmt.Println(t2)   //666 

	//直接定义空接口类型,直接实例化出一个t3空接口赋予num1
	var num1 = 8.8
	var t3 iterface{} = num1
	fmt.Println(t3)            //8.8
}

接口和继承的关系

  1. 接口可以通过定义未实现方法,作为继承后的新变量的扩展,例如剑豪继承了剑圣的所有剑法,同时又开眼悟出新的技能,新的技能就可以通过接口方式实现。
  2. 实现接口对继承机制的补充,接口提供共性规范和标准。
  3. 继承主要解决代码复用性和可维护性问题;接口主要在于设计好各种规范,让其他自定义类型去实现这种规范方法。
  4. 接口比继承更加灵活,不需要类似继承是百分百契合not is a,只需要like a即可。
  5. 接口一定程度上实现了代码解耦。

多态

  1. 变量具有多种形态,面向对象的第三大特征,Golang中多态是通过接口实现的。
  2. 可以按照统一的接口调用不同的实现,这时接口变量就呈现不同的形态。

接口体现多态特征

  1. 多态参数:通过传入形参来决定哪个变量实现接口方法。
  2. 多态数组:在一个数组中存放多态参数涉及到的各种结构体,其中结构体还有自己独特方法可以被调用,详情见下面断言

断言 assert

  1. 一个结构体变量赋值给一个空接口,再指定一个同类型结构体变量,将空接口赋值给这个结构体变量,这样直接做是不行的(need type assertion)

    package main
    import "fmt"
    type Zuobiao struct {
    	x int
    	y int
    }
    
    type A interface {
    
    }
    
    func main() {
    	zuobiao := Zuobiao{3,5}
    	var a A
    	a = zuobiao
    	fmt.Println(a)
    	var zuobiao1 Zuobiao
    	//zuobiao1 = a            //返回来赋值是不可以的,
    	//这时候需要使用断言判断是否可以转换,如果不能转换就会报错,因为a指向zuobiao,所以可以转换成功。
    	zuobiao1 = a.(Zuobiao)    //如果希望把空接口重新转换成原类型,那么就要使用断言 <接口变量>.(<回转类型名称>)
    	fmt.Println(zuobiao1)
    }
    
  2. 类型断言:由于接口是一般类型,不是具体类型,要转换成具体类型,就需要使用类型断言。

  3. 断言不是胡乱转换,而是空接口变量指向的是什么类型,就必须使用该类型。

  4. 如何进行断言时进行检查,带上检测机制,如果成功就ok,否则也不会报panic

    //带检测类型的断言写法1

    var b A
    var zb2 = Zuobiao{6,7}
    b = zb2
    zb2,ok := b.(Zuobiao)     //ok本身是布尔类型,这里是重点
    if ok {                   //接下来条件判断,不会产生panic,而是根据判断继续流程控制
    	fmt.Println("OK,转换成功",zb2)
    } else {
    	fmt.Println("转换不成功,继续执行后续代码")
    }
    fmt.Println("断言已经判断,可以继续执行")
    

    //带检测类型的断言写法2

    var b A
    var zb2 = Zuobiao{6,7}
    b = zb2
      
    if zb2,ok := b.(Zuobiao); ok {       //使用了if并列条件方式,结合了两句话,开发中也常用。
    	fmt.Println("OK,转换成功",zb2)
    } else {
    	fmt.Println("转换不成功,继续执行后续代码")
    }
    fmt.Println("断言已经判断,可以继续执行")
    

案例1:类型断言经常使用的地方:共有接口的结构体变量中有自己独特方法,应该如何保证调用

package main

import "fmt"

type Usb interface {
	/*
		声明了两个没有实现的方法
	*/
	Start()                  //定义接口类型与结构体类似
	Stop()                   //接口类型中只能定义方法!不能定义属性!
}

type Phone struct {

}
func (p Phone) Start() {
	fmt.Println("手机开始工作")
}
func (p Phone) Stop() {
	fmt.Println("手机停止工作")
}
func (p Phone) Call() {
	fmt.Println("手机可以打电话")
}

type Camera struct {

}
func (c Camera) Start() {
	fmt.Println("相机开始工作")
}
func (c Camera) Stop() {
	fmt.Println("相机停止工作")
}

type Computer struct {

}
func (c1 Computer) Connect(usb Usb) {
	usb.Start()
	//如果usb指向是一个phone结构体变量的,还需要使用call()方法
	//这时候添加类型断言,这个在今后开发中很重要,常用到
	if phone,ok := usb.(Phone) ; ok {       //phone对应变量可以随意定义,保证变量与<变量>.Call()一致即可
		phone.Call()                    //手机可以打电话,这种方式可以使每个独特方法的结构体增加美观性。
	}
	usb.Stop()
}

func main() {
	/*
	解决共有接口类型的数组结构体又分别有自己独特方法的解决方案
	 */
	var arrUsb [2]Usb
	arrUsb[0] = Phone{}
	arrUsb[1] = Camera{}
	var c1 Computer
	for _ , v := range arrUsb {
		c1.Connect(v)
	}
}

案例2:写一个函数判定传入数据类型并输出结果,重点在于<数组元素>.(type)这种断言使用方式

package main
import "fmt"
func TypeJudge (items ...interface{}) {           //传入可变长度类型的空接口
	for i , v := range items {                    //遍历所有传入参数所在数组
		switch v.(type) {                         //switch case循环判定类型为哪些
		case bool:
			fmt.Println(i," ",v,"为布尔类型")
		case string:
			fmt.Println(i," ",v,"为字符串类型")
		}
	}
}

func main() {
	/*
	定义一个可变参数的空接口
	 */
	var name = "shit"           //字符串
	var buer = false            //布尔
	TypeJudge(name,buer)
}
发布了49 篇原创文章 · 获赞 18 · 访问量 4003

猜你喜欢

转载自blog.csdn.net/weixin_41047549/article/details/90236402