Go面向对象编程的三大特性

Go面向对象编程的三大特性

1.基本介绍

Golang仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它OOP语言不一样。

2.面向对象编程思想-抽象

抽象:
在定义一个结构体类型时,实际上就是把一类事物共有的属性(字段)行为(方法)提取出来形成一个物理模型

在这里插入图片描述
案例演示:

type Account struct {
	AccountNo string
	Pwd       string
	Balance   float64
}

//方法
func (account *Account) Withdraw(money float64) {
	if money <= 0 || money > account.Balance {
		fmt.Println("输入的金额数不正确")
	}
	account.Balance -= money
	fmt.Println("取款成功")

}

func (account *Account) Deposite(money float64) {

	if money <= 0 {
		fmt.Println("输入的金额数不正确")
	}
	account.Balance += money
	fmt.Println("存款成功")

}

func (account *Account) Query() {

	fmt.Printf("账号:%v的余额为%v\n", account.AccountNo, account.Balance)
}
func main() {

	account := Account{
		AccountNo: "0123x",
		Pwd:       "888888",
		Balance:   100.0,
	}

	var (
		accountno string
		pwd       string
		money     float64
		mode      int
	)
label:
	for {
		fmt.Println("请输入账号:")
		fmt.Scanln(&accountno)
		if accountno == "n" {
			fmt.Println("退出程序...")
			break
		}

		for i := 0; i < 3; i++ {
			fmt.Println("请输入密码:")
			fmt.Scanln(&pwd)
			if pwd != account.Pwd {
				fmt.Println("密码输入错误")
			} else {
				break
			}
			if i == 2 {
				fmt.Println("密码输错三次今日机会已用完...")
				break label
			}

		}

	here:
		for {
			fmt.Println("输入办理义务选项:0查询,1存款,2取款")
			fmt.Scanln(&mode)
			switch mode {
			case 0:
				account.Query()
				break here
			case 1:
				fmt.Println("请输入金额:")
				fmt.Scanln(&money)
				account.Deposite(money)
				break here
			case 2:
				fmt.Println("请输入取出金额:")
				fmt.Scanln(&money)
				account.Withdraw(money)
				break here
			default:
				fmt.Println("模式选择有误,请重新输入")
			}
		}

	}

}

3.封装

封装(encapsulation)就是把抽象出的字段和对字段操作封装在一起,数据被保护在内部,程序的其它包只有通过被授权的操作(方法),才能对字段进行操作。

封装实现的具体步骤:
1)将结构体、字段/属性的首字母小写(不能导出,其它包不能使用,类似private但本包能还是能使用的)
2)给结构体所在包提供一个工厂模式函数,首字母大写。类似一个构造函数
3)提供一个首字母大写的Set方法(类似其它语言的public),用于对属性判定并赋值

func (结构体名 结构体类型)SetXxx(参数列表)(返回值列表){
	//加入数据验证的业务逻辑
	结构体名.字段 = 参数
}

4)提供一个首字母大写的Get方法(类似其它语言的public),用于获取字段/属性的值

func (结构体名 结构体类型)GetXxx(参数列表)(返回值列表){
	return 结构体名.字段 
}

案例演示:
要求不能随便查看人的年龄和工资等隐私,并对输入的年龄进行合理的验证。采用封装的思想完成设计,设计要求:model包中建立一个person.go,在main包中main.go能调用person.go中的person结构体。

model包/person.go程序:

package model

import "fmt"

type person struct {
	Name   string
	age    int //字段名首字母小写不能被其他包直接访问
	salary float64
}

//  构造一个工厂模式函数,类似构造函数,访问person结构体
func NewPerson(name string) *person {
	return &person{
		Name: name,
	}
}

// 为了能在其它包访问age 和salary 编写一对SetXXX()和GetXxx()方法
func (p *person) SetAge(age int) {
	if age > 0 && age < 150 {
		p.age = age
	} else {
		fmt.Println("输入年龄超过范围")
		p.age = -1
	}
}

func (p *person) GetAge() int {
	return p.age
}

func (p *person) SetSalary(salary float64) {
	if salary > 3000 && salary < 30000 {
		p.salary = salary
	} else {
		fmt.Println("薪水超范围不正确")
		p.salary = -1
	}
}

func (p *person) GetSalary() float64 {
	return p.salary
}

main包main.go程序:

package main
import (
	"fmt"
	"go_code/chapter11/encapsulate02/model"
)

func main() {

	p := model.NewPerson("tom")
	p.SetAge(21)
	p.SetSalary(15000)

	fmt.Println(p)  //&{tom 21 15000}
	fmt.Println(*p) //{tom 21 15000}
	fmt.Println(p.Name, "age=", p.GetAge(), "salary=", p.GetSalary())
	//tom age= 21 salary= 15000

}

4.继承

为防止代码冗余\复 用,更利于代码的维护和功能拓展,需要继承方式。

1.基本介绍和示意图:
1)当多个结构体存在相同的属性/字段和 方法时,可以从这些结构体中抽象出新的结构体,该结构体中定义这些相同的字段和方法。
2)其他结构体不再需要重新定义这些相同的字段和方法,只需要嵌套一个包含这些相同字段和方法的匿名结构体即可。
3)即在Golang中,如果一个结构体嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现继承特性
4)与其他OOP语言不同的是,在Golang中弱化了继承的概念。例如在Java中继承使用关键字extend实现,划分父类、子类的概念。在Golang中没有这些概念,简单的通过结构体后跟匿名结构的方式完成继承的特性。

图例说明在这里插入图片描述


2.嵌套匿名结构体的基本语法

type Goods struct{
	Name string
	Price float64
}
type Book struct{
	Goods //此处就是嵌套匿名结构体Goods
	Writer string
}

案例演示:

//共有属性结构体
type Student struct {
	Name  string
	Age   int
	Score float64
}

func (stu *Student) ShowInfo() {
	fmt.Printf("学生名:%v,年龄为:%v,成绩:%v\n", stu.Name, stu.Age, stu.Score)

}

func (stu *Student) SetAge(age int) {
	if age < 8 || age > 35 {
		fmt.Println("年龄超过范围")
		return
	}
	stu.Age = age

}

func (stu *Student) SetScore(score float64) {
	if score < 0 || score > 100 {
		fmt.Println("分数超过范围")
		return
	}
	stu.Score = score

}

type Pupil struct {
	Student
}
//特有方法
func (p *Pupil) testing() {
	fmt.Println("小学生正在考试中")

}

type Undergraduate struct {
	Student
	Married bool //特有属性/字段
}
//特有方法
func (Undergraduate *Undergraduate) SetMarried(judge bool) {
	if judge {
		Undergraduate.Married = judge
	}

}

func main() {

	pupil := &Pupil{}
	pupil.Student.Name = "tom"
	pupil.Student.Age = 9
	pupil.testing() //小学生正在考试中
	pupil.Student.SetScore(91)
	pupil.Student.SetAge(10)
	pupil.Student.ShowInfo() //学生名:tom,年龄为:10,成绩:91
	fmt.Println()

	undergraduate := &Undergraduate{}
	undergraduate.Student.Name = "marry"
	undergraduate.Student.Age = 20
	undergraduate.Student.SetScore(92)
	undergraduate.Student.SetAge(21)
	undergraduate.Student.ShowInfo() //学生名:marry,年龄为:21,成绩:92

	undergraduate.SetMarried(true)
	fmt.Printf("%v是否结婚:%v\n", undergraduate.Name, undergraduate.Married)
	// marry是否结婚:true
}


3.继承的细节
1)结构体可以使用嵌套匿名结构体所有的字段和方法,即无论首字母是否大写的字段和方法都可以使用。
案例演示:

type A struct {
	Name string
	age  int
}

func (a *A) Sayok() { //类似public
	fmt.Println("A Sayok,", a.Name)
}
func (a *A) hello() { //类似private
	fmt.Println("A hello,", a.Name)
}

type B struct {
	A
}

func main() {

	var b B
	b.A.Name = "marry"
	b.A.age = 18
	b.A.Sayok()    //A Sayok, marry
	b.A.hello()    // A hello, marry
	fmt.Println(b) //{{marry 18}}

}

2)匿名结构体字段访问可以简化,省略匿名结构体进行访问。

var b B
	b.A.Name = "marry"
	b.A.age = 18
	b.A.Sayok()    //A Sayok, marry
	b.A.hello()    // A hello, marry
	fmt.Println(b) //{{marry 18}}

	// 写法等价于
	b.Name = "tom"
	b.age = 19
	b.Sayok()      //A Sayok, tom
	b.hello()      // A hello, tom
	fmt.Println(b) //{{tom 19}}

结合上述案例,结构体含有匿名结构体的访问机制:
(1)当直接通过结构体变量b访问字段或方法时,例如b.Name其流程如下:
(2)编译器先在结构体变量b中查询有无对应的Name字段,如果有则直接访B类型的Name字段
(3)如果在B类型中没有Name字段,则访问B类型中嵌入的匿名结构体A有没有声明Name字段,若有就调用,若没有就继续查找,如果最后都没有找到Name字段就报错。


3)当结构体和匿名结构体有相同的字段、方法时,编译器采用就近原则访问,即优先先访问结构体自身的字段和方法,再访问匿名结构体内的字段和方法。若指定访问匿名结构体的字段和方法,可以通过匿名结构体名来区分。

type A struct {
	Name string
	age  int
}

func (a *A) Sayok() { //类似public
	fmt.Println("A Sayok,", a.Name)
}
func (a *A) hello() { //类似private
	fmt.Printf("A hello,%q\n", a.Name)
}

type B struct {
	A
	Name string
}

func (b *B) Sayok() {
	fmt.Println("B Sayok,", b.Name)

}

func main() {
	var b B
	b.Name = "Alex"
	b.age = 20
	b.Sayok()      //B Sayok, Alex
	b.hello()      //A hello,""
	fmt.Println(b) //{{ 20} Alex}
	//访问匿名结构体的字段 
	var b1 B
	b1.A.Name = "Scoot"
	b1.age = 21
	b1.hello()      //A hello,"Scoot"
	fmt.Println(b1) // {{Scoot 21} }

4)当结构体嵌入 多个匿名结构体,这些匿名结构体含有相同的字段或方法(同时结构体本身不存在这些字段或方法时),在访问这些字段或方法时,就必须明确指定匿名结构体的名字,否则编译报错。

type A struct {
	Name string
	age  int
}
type B struct {
	Name string
	Score float64
}
type C struct{
	A
	B
}
func main() {
	var c C
	//c.Name = "tom" //报错 指代模糊
	c.A.Name = "tom" //须明确匿名结构
}

5)如果一个结构体嵌套了一个有变量名的结构体,这种模式就是组合。如果是组合关系,那么在访问组合结构体的的字段或方法时,必须带上结构体的名字。

type A struct {
	Name string
	age  int
}
type D struct{
	a A
	age int
}
func main() {
	var d D
	//d.Name = "tom" //报错 D内没有相关字段或方法
	d.a.Name = "tom" 
	//存在有名结构体时,访问有名结构体字段或方法时,须明确匿名结构的名字 例如 d.a.Name
	d.age = 18 //操作允许,其访问机制还是先访问结构体本身
}

6)嵌套匿名结构体后,也可以在创建结构体变量(实例)时直接指定各个匿名结构体字段的值。

type Goods struct {
	Name  string
	Price float64
}
type Brand struct {
	Name    string
	Address string
}
type Tv struct {
	Goods
	Brand
}
type Tv1 struct {
	*Goods
	*Brand
}

func main() {
	tv1 := Tv{Goods{"电视机类型1", 1600}, Brand{"夏普", "东京"}}

	tv2 := Tv{
		Goods{
			Name:  "电视机类型2",
			Price: 1799,
		},
		Brand{
			Name:    "长虹",
			Address: "长安",
		},
	}
	fmt.Println(tv1) //{{电视机类型1 1600} {夏普 东京}}
	fmt.Println(tv2) //{{电视机类型2 1799} {长虹 长安}}

	tv3 := Tv1{&Goods{"电视机类型3", 1888}, &Brand{"海尔", "北京"}} //地址符不可省略
	tv4 := Tv1{
		&Goods{
			Name:  "电视机类型4",
			Price: 1999,
		},
		&Brand{
			Name:    "Htc",
			Address: "首尔",
		},
	}
	fmt.Printf("%v\n", tv3)                    //{0xc000004078 0xc0000503e0}
	fmt.Printf("%v\n", tv4)                    //{0xc000004090 0xc000050400}
	fmt.Println("tv3", *tv3.Goods, *tv3.Brand) //tv3 {电视机类型3 1888} {海尔 北京}
	fmt.Println("tv4", tv4.Goods, *tv4.Brand)  //tv4 &{电视机类型4 1999} {Htc 首尔}
}

7)结构体内也允许匿名字段是基本数据类型,但为了不是逻辑紊乱不能重复使用同一类型的基本数据匿名字段。

type Monster struct {
	Name string
	Age  int
}
type E struct {
	Monster
	int
	n int
}

func main() {
	//结构体内也允许匿名字段是基本数据类型,
	// 但为了不是逻辑紊乱不能重复使用同一类型的基本数据匿名字段。
	// 若需要多个int的字段,则必须给int字段指定名字。
	e := E{Monster{"牛魔王", 188}, 20, 22}
	fmt.Println("e=", e) //e= {{牛魔王 188} 20 22}
}

小结:
结构体内包含匿名结构体实现继承特性;包含带名结构体则形成组合(调用带名结构体的字段和方法时须明确结构体名);结构体内含有匿名基本类型字段时,本质上就是其本身的一种属性/字段,但需要注意的是,同一类型的匿名字段不可多次使用,若需要多次定义相同类型的字段,则直接使用基础的定义字段方式即可(例如 n1 int )。


4.多重继承
一个结构体内含有多个匿名结构体,那么这个结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现多重继承。
1)如果嵌入的结构体们含有相同的字段名或者方法名,则在访问时需要通过匿名结构体类型名来区分。(参考上述3.继承继承细节第四点)
2)为了保证代码的简洁性,建议不要使用多重继承,防止逻辑紊乱,继承关系复杂化。


5.接口

在介绍多态前需要;了解接口(interface),在GOlang中多态特性主要是通过接口来实现的,并且在Go语言面向对象编程中面向接口编程占据着重要地位。


入门案例:

// 声明/定义一个借口
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("相机停止工作")
}

type Computer struct {
}

// 编写一个方法Working,接收一个Usb接口类型
func (c Computer) Working(usb Usb) {
	usb.Start()
	usb.stop()
}

func main() {
	// 测试
	phone := Phone{}
	camera := Camera{}
	computer := Computer{}

	//关键点
	//Phone和Camera结构体都具有Start()和stop()方法,故两者是满足Usb接口类型的接口
	computer.Working(phone)
	computer.Working(camera)

}

Output:
手机开始工作
手机停止工作
相机开始工作
相机停止工作


1.接口基本介绍
interface类型可以定义一组方法,但这些方法不需要实现。并且在golang中接口interface不能包含任何变量。到某个自定义类型(比如上述结构体Phone)要使用的时候,再根据结构体具体情况详细写出全部的方法(只有实现了全部的方法的变量才能实现接口)。
2.接口基本语法

type 接口名 interface{
	method1(参数列表)返回值列表
	method2(参数列表)返回值列表
}

实现接口所有方法的变量类型才具备该接口特性,即变量需要具有:

func(t 自定义类型) method1(参数列表)返回值列表{
	//方法实现
}
func(t 自定义类型) method2(参数列表)返回值列表{
	//方法实现
 }

小结说明:
1)接口里的所有方法都不允许有方法体,即接口的方法是没有实现的方法,最多只有方法名(参数列表)返回值列表。interface接口体现了程序设计的多态和高内聚低耦合的思想
2)Golang中的接口,相比其他OOP语言不需要显示实现,因此Golang中没有implement这样的关键字。Golang的接口的实现依赖于方法的实现;即:只要一个变量含有一种接口的所有方法,那么这个变量就实现了这种接口。


3.接口注意事项和细节
1)接口本身不能创建实例,但是可以指向实现了该接口的自定义类型的变量(实例);即一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型。

type Ainterface interface {
	say()
}

type Stu struct {
	Name string
	Age  int
}

func (stu Stu) say() {
	fmt.Println("stu Say() ok")

}

func main() {
	var (
		a   Ainterface
		stu Stu //结构体变量,实现了say()方法,实现了Ainterface
	)
	a = stu
	a.say() //stu Say() ok

}

2)接口中所有的方法都没有方法体,即都是没有实现的方法。
3)在Golang中,一个自定义类型需要将某个接口的所有方法都实现了,才能称该自定义类型实现了该接口。
4)只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。

type integer int

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

func main() {
	var (
		b Ainterface
		//自定义变量i,实现了say()方法,实现了Ainterface
		i    integer		
	)
	b = i
	b.say()//integer say i =  0

}

5)一个自定义类型可以实现多个接口

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 sau() ~~")
}
func main() {
	// 自定义结构体Moster 实现了say()、 Hello()方法 
	// Monster变量m实现了Ainterface 和 Binterface 
	var (
		a1 Ainterface
		b1 Binterface
		m  Monster
	)
	a1 = m
	b1 = m
	a1.say()
	b1.Hello()
}

6)与其他OOP语言不同之处,Golang接口中不能定义任何变量

type Ainterface interface {
	Name string //报错
	Test01()
	Test02()
}

7)一个接口(比如A接口)可以继承多个别的接口(比如B、C接口),这时如果要实现A接口,也必须将B,C接口也全部实现。

type Binterface interface {
	test01()
}
type Cinterface interface {
	test02()
}
type Ainterface interface {
	Binterface
	Cinterface
	test03()
}

// 若需要实现Ainterface接口,则需将Binterface和Cinterface所以方法都实现
type Stu struct {
	Name string
	Age  int
}

func (stu Stu) test01() {
	fmt.Println("test01()....")
}
func (stu Stu) test02() {
	fmt.Println("test02()....")
}
func (stu Stu) test03() {
	fmt.Println("test03()....")
}
func main() {
	stu := Stu{"tom", 18}
	stu.test01() //test01()....
	stu.test02() //test02()....
	stu.test03() //test03()....

}

8)interface类型默认是一个指针(** ),如果没有对interface初始化就是使用,那么会输出nil
9)空接口interface{}没有任何方法,所以
所用类型都实现了空接口**,即可以将任何一个变量都赋给空接口。

// 空接口
type T interface {
}
func main() {
	f1 := 8.8
	n1 := 10
	// 空接口使用方法1:
	var t1 T = f1
	fmt.Println(t1) //8.8
	// 空接口使用方法2:
	var t2 interface{} = n1
	fmt.Println(t2) //10
}

4.接口实践案例
使用系统自带的sort.Sort(data interface)进行排序,要求data interface满足:

这里是引用

案例演示

// 实现对Hero结构体切片的排序:sort.Sort(data Interface)

type Hero struct {
	Name string
	Age  int
}

type HeroSlice []Hero

func (hs HeroSlice) Len() int {
	return len(hs)

}

// Less方法决定使用什么标准进行排序
func (hs HeroSlice) Less(i, j int) bool {
	return hs[i].Age < hs[j].Age //按年龄升序
	// 修改成对Name排序
	// return hs[i].Name < hs[j].Name

}
func (hs HeroSlice) Swap(i, j int) {
	hs[i], hs[j] = hs[j], hs[i]
	/*等价于
	temp := hs[i]
	hs[i] = hs[j]
	hs[j] = temp*/

}

func main() {
	heroslice := make(HeroSlice, 0)
	for i := 0; i < 10; i++ {
		hero := Hero{
			Name: fmt.Sprintf("英雄%d", rand.Intn(100)),
			Age:  rand.Intn(100) + 10,
		}
		heroslice = append(heroslice, hero)
	}
	// 未排序前
	for i, v := range heroslice {
		fmt.Printf("no.%d,%v\n", i, v)
	}
	// fmt.Println(heroslice)

	sort.Sort(heroslice)
	fmt.Println("------------")
	// 排序后
	// fmt.Println(heroslice)
	for i, v := range heroslice {
		fmt.Printf("no.%d,%v\n", i, v)
	}

	var intslice = []int{7, -1, 3, 4}
	sort.Ints(intslice)
	fmt.Println(intslice) //[-1 3 4 7]

}

5.接口VS继承
接口,可以在不破坏继承特性的基础上扩展功能
1)当A结构体继承了B结构体,那么A结构体就自动继承了B结构体的字段和方法,并且可以直接使用
2)当A结构体需要扩展功能,同时不希望破坏原有的继承特性,则可以通过实现某个接口的即可。因此实现接口是对继承机制的补充。
案例演示:

type Monkey struct {
	Name string
}

func (m *Monkey) Clambing() {
	fmt.Println(m.Name, "天生会爬树")
}

// 声明一个接口 接口是对继承的补充
type BirdAble interface {
	Flying()
}
type FishAble interface {
	Swimming()
}

type LittelMonkey struct {
	Monkey //继承
}

func (LM *LittelMonkey) Flying() {
	fmt.Println(LM.Name, "通过修行掌握了风行术")
}
func (LM *LittelMonkey) Swimming() {
	fmt.Println(LM.Name, "通过修行掌握了水遁术")
}

type GetAbility struct {
}

// 编写一个方法study,接收一个BirdAble接口类型
func (gb GetAbility) Study(ba BirdAble) {
	ba.Flying()
}

// 编写一个方法study1,接收一个FishAble接口类型
func (gb GetAbility) Study1(fa FishAble) {
	fa.Swimming()
}

func main() {
	monkey := LittelMonkey{
		Monkey{
			Name: "悟空",
		},
	}
	// 方法
	//标准写法:(&monkey).Monkey.Clambing()等价于(&monkey).Clambing()
	// (&monkey).Clambing() 等价于下者
	monkey.Clambing() //悟空 天生会爬树
	monkey.Flying()   //悟空 通过修行掌握了风行术
	monkey.Swimming() //悟空 通过修行掌握了水遁术

	// 接口的应用
	// 实际上实现接口的是*LittleMonkey类型
	getAbility := GetAbility{}
	getAbility.Study(&monkey)  //悟空 通过修行掌握了风行术
	getAbility.Study1(&monkey) //悟空 通过修行掌握了水遁术
}

3)接口与继承解决问题的侧重点:
(1)继承的价值主要在于:解决代码复用性和可维护性
(2)接口的价值主要在于:设计,设计好各种规范(方法),让其他自定义类型去实现这些方法
(3)接口比继承更灵活,继承是满足is-a的关系,即需要明确继承对象;而接口只需满足like-a的关系,即只需实现了所有相关方法就实现了相应的接口。
(4)接口在一定程度上实现了代码的解耦。

6.多态

多态:变量(实例)具有多种形态。在Go语言中,多态特征是通过接口实现的。可以按照统一的接口来调用不同的实现,这时接口变量就呈现不同的形态。

1.快速入门案例
在“5.接口”章节的快速入门案例(Usb接口案例)中,func (c Computer) Working(usb Usb) {} 方法的参数列表usb Usb既可以接收手机变量phone,又可以接收相机变量camera,就体现了Usb接口的多态特性。

2.接口体现多态
1)多态参数
在前面Usb接口案例中,参数列表(usb Usb)可以接收多种不同类型的变量(Phone 、Camera两种结构体的变量),就体现了Usb接口的多态。
2)多态数组
在基础数组里只允许存放同一类型的数据;但在使用接口后,可以存放实现了该接口的任何自定义类型变量。
案例说明:

// 声明/定义一个借口
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("相机停止工作")
}

type Computer struct {
}

// 编写一个方法Working,接收一个Usb接口类型
func (c Computer) Working(usb Usb) { //usb 既能接收phone也能接收camera 多态特性
	usb.Start()
	usb.stop()
}

func main() {
	// 多态参数
	phone := Phone{}
	camera := Camera{}
	computer := Computer{}
	//关键点
	//Phone和Camera结构体都具有Start()和stop()方法,故两者是满足Usb接口类型的接口
	computer.Working(phone)
	computer.Working(camera)
	
	// 多态数组
	var usbArr [3]Usb
	fmt.Println(usbArr) //[<nil> <nil> <nil>]
	usbArr[0] = Phone{"小米"}
	usbArr[1] = Phone{"华为"}
	usbArr[2] = Phone{"尼康"}
	fmt.Println(usbArr) //[{小米} {华为} {尼康}]
}

3.类型断言
assertion类型断言:将一个接口变量赋给自定义类型变量的操作。
入门案例演示:

在这里插入代码片type Point struct {
	x int
	y int
}

func main() {
	var a interface{}
	var point Point = Point{1, 2}
	a = point //将Point变量传入给空接口,空接口能接收任何变量
	// 如何将变量a赋给一个Point变量?
	var b Point
	// b = a//报错,类型不一致,需要断言assertion操作
	b = a.(Point)
	fmt.Println(b) //{1 2}
}

案例中b = a.(Point)就是一个类型断言操作,表示判断a接口变量是否指向Point类型的变量,如果是a就转成Point类型(返回一个Point类型的变量)并赋值给b变量,否则就报错。

1)基本介绍
类型断言作用在于:由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言指向具体的转换类型。
案例2:

	var f float32 = 3.3
	var c interface{} = f
	c1 := c.(float32)
	fmt.Printf("c1的类型是%T,值是%v", c1, c1) // c1的类型是float32,值是3.3

注意:在进行类型断言时要确保类型匹配,即确保原来的空接口指向的就是断言的类型;若类型不匹配则会报panic。

基于上述,引出在进行断言时,带上检测机制。即断言成功就ok,断言不匹配也不要报panic,程序能继续执行。
案例:

	var x float32 = 3.3
	var d interface{} = x
	y, flag := d.(float64)
	if flag { //简洁写法 if y,flag:=d.(float64);flag
		fmt.Println("convert success")
		fmt.Printf("y的类型是%T,值是%v\n", y, y)
	} else {
		fmt.Println("convert fail")
	}
	fmt.Println("程序继续执行")

Output:
convert fail
程序继续执行

2)类型断言的实践案例
案例一:
在前面Usb接口案例改进:给Pheon结构体增加一个特有的方法Call(),当Usb接口接收的是Phone变量时,还需要调用Call()方法。

// 声明/定义一个借口
type Usb interface {
	// 声明了两个没有实现的方法
	Start()
	stop()
}
type Phone struct {
	Name string
}

// 让phone实现Usb接口的方法
func (p Phone) Start() {
	fmt.Println("手机开始工作")
}
func (p Phone) stop() {
	fmt.Println("手机停止工作")
}

// Phone结构体独有的方法
func (p Phone) Call() {
	fmt.Println("手机 打电话功能")
}

type Camera struct {
	Name string
}

// 让camera实现Usb接口的方法
func (c Camera) Start() {
	fmt.Println("相机开始工作")
}
func (c Camera) stop() {
	fmt.Println("相机停止工作")
}

type Computer struct {
}

// 编写一个方法Working,接收一个Usb接口类型
func (c Computer) Working(usb Usb) { //usb 既能接收phone也能接收camera 多态特性
	usb.Start()
	usb.stop()
	// 写法二:将类型断言放入方法内(不需重新声明一个接口变量,有利于代码统一管理)
	if phone, flag := usb.(Phone); flag {
		phone.Call()
	}//简写可为:
	//if _, flag := usb.(Phone); flag {
	//	usb.(Phone).Call()
	//}
	
}

func main() {
	// 多态参数
	phone := Phone{}
	camera := Camera{}
	computer := Computer{}
	//关键点
	//Phone和Camera结构体都具有Start()和stop()方法,故两者是满足Usb接口类型的接口
	computer.Working(phone)
	computer.Working(camera)

	// 多态数组
	var usbArr [3]Usb
	fmt.Println(usbArr) //[<nil> <nil> <nil>]
	usbArr[0] = Phone{"小米"}
	usbArr[1] = Phone{"华为"}
	usbArr[2] = Camera{"尼康"}
	fmt.Println(usbArr) //[{小米} {华为} {尼康}]

	// 遍历USBArr
	//如果是Phone变量除了调用接口声明的方法,还需调用Phone特有的方法Call()
	// 使用类型断言实现
	// var a Usb
	for _, v := range usbArr {
		computer.Working(v)
		// // 写法一:定义一个新的接口变量用于执行类型断言
		// a = v
		// if v1, flag := a.(Phone); flag {
		// 	v1.Call()
		// }
		fmt.Println()
	}
}

案例二:
能够实现循环判断传入参数的类型

type Student struct {
	Name string
	Age  int
	Id   string
}

// 编写一个函数,可以判断输入的参数是什么类型
func TypeJudge(items ...interface{}) {
	for index, x := range items {
		switch x.(type) {//这里type是关键字,固定写法
		case bool:
			fmt.Printf("第%v个参数是bool类型,值是%v\n", index+1, x)
		case int, int32, int64:
			fmt.Printf("第%v个参数是整数类型,值是%v\n", index+1, x)
		case float32, float64:
			fmt.Printf("第%v个参数是浮点数类型,值是%v\n", index+1, x)
		case string:
			fmt.Printf("第%v个参数是string类型,值是%v\n", index+1, x)
		case nil:
			fmt.Printf("第%v个参数是nil类型,值是%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.Printf("第%v个参数是类型不确定,值是%v\n", index+1, x)
		}
	}

}
func main() {
	var n1 int = 1
	var n2 int64 = 64
	var n3 float64 = 15.5
	var name string = "tom"
	address := "上海"
	var nil1 interface{}
	stu1 := Student{
		Name: "marry",
		Age:  18,
		Id:   "0123x",
	}
	stu2 := &Student{"jim", 19, "0124x"}
	TypeJudge(n1, n2, n3, name, address, nil1, stu1, stu2)

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值