go 面向对象编程-2

面向对象编程思想-抽象

定义一个结构体的时候,实际上就是把一类事物的共有的属性(字段)和行为(方法)提取出来,形成一个物理模型(结构体). 这种研究问题的方法称为抽象

在这里插入图片描述

package main

import (
	"fmt"
)

type Account struct {
	AccountNo string
	Pwd string
	Balance float64
}
//存款
func (account *Account) Deposite(money float64, pwd string) {
	if pwd != account.Pwd {
		fmt.Println("输入密码错误")
		return
	}
	if money <= 0 {
		fmt.Println("输入金额错误")
		return
	}
	account.Balance += money
	fmt.Println("存款成功")
}
//取款
func (account *Account) WithDraw(money float64, pwd string) {
	if pwd != account.Pwd {
		fmt.Println("输入密码错误")
		return
	}
	if money <= 0 {
		fmt.Println("输入金额错误")
		return
	}
	account.Balance -= money
	fmt.Println("取款成功")
}
//查询
func (account *Account) Query(pwd string) {
	if pwd != account.Pwd {
		fmt.Println("输入密码错误")
		return
	}
	fmt.Printf("你的账号为%v 余额=%v \n", account.AccountNo, account.Balance)
}
func main() {
	account := Account{
		AccountNo: "zhangsan",
		Pwd:       "111111",
		Balance:   100.00,
	}
	account.Query("111111")
	account.Deposite(1000, "111111")
	account.Query("111111")
	account.WithDraw(500, "111111")
	account.Query("111111")
}

面向对象编程三大特性-封装

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

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

封装的理解和好处

  1. 隐藏实现细节
  2. 可以对数据进行验证,保证安全合理

如何体现封装

  1. 对结构体中的属性进行封装
  2. 通过方法,包 实现封装

封装的实现步骤

  1. 将结构体、字段(属性)的首字母小写(不能导出了,其它包不能使用,类似private)
  2. 给结构体所在包提供一个工厂模式的函数,首字母大写。类似一个构造函数
  3. 提供一个首字母大写的Set方法(类似其它语言的public),用于对属性判断并赋值
func(var  结构体类型名)  SetXxx(参数列表)  (返回值类型列表) {
	var.字段 = 参数
}
  1. 提供一个首字母大写的Get方法(类似其它语言的public), 用于获取属性的值
func(var 结构体类型名) GetXxx() {
	return var.age
}

特别说明:在golang开发中并没有特别强调封装,这点并不像java,golang本身对面向对象的特性做了简化

案例
person.go 不能随便查看人的年龄,工资等隐私,并对输入的年龄进行合理的验证。设计: model包 (person.go) main包(main.go 调用 person结构体)

model/person.go

package model

import "fmt"

type person struct {
	Name string
	age int
	sal float64
}
//写一个工厂模式的函数, 相当于构造函数
func NewPerson(name string) *person {
	return &person{
		Name: name,
	}
}
//为了访问 age 和 sal 编写一个对SetXxx的方法和 GetXxx的方法
func (p *person) SetAge(age int) {
	if age > 0 && age < 150 {
		p.age = age
	} else {
		fmt.Println("年龄范围错误...")
	}
}
func (p *person) GetAge() int {
	return p.age
}
func (p *person) SetSal(sal float64) {
	if sal >= 3000 && sal <= 30000 {
		p.sal = sal
	} else {
		fmt.Println("薪水范围错误...")
	}
}
func (p *person) GetSal() float64 {
	return p.sal
}

main/main.go

package main

import (
	"fmt"
	"go_code/project01/model"
)

func main() {
	p := model.NewPerson("smith")
	p.SetAge(18)
	p.SetSal(5000)
	fmt.Println(p)
	fmt.Println(p.Name, "age=", p.GetAge(), "sal=", p.GetSal())
}

创建程序,在model包中定义account结构体
account结构体要求具有字段: 账号(长度在6-10位之间)、余额(必须>20)、密码(必须是6位)
通过SetXxx的方法给account的字段赋值
main函数中测试

model/account.go

package model

import "fmt"

type account struct {
	accountNo string
	pwd string
	balance float64
}

func NewAccount(accountNo string, pwd string, balance float64) *account {
	if len(accountNo) < 6 || len(accountNo) > 10 {
		fmt.Println("账号长度不对...")
		return nil
	}
	if len(pwd) != 6 {
		fmt.Println("密码长度不对...")
		return nil
	}
	if balance <= 20 {
		fmt.Println("余额不对...")
		return nil
	}
	return &account{
		accountNo: accountNo,
		pwd:       pwd,
		balance:   balance,
	}
}
//存款
func (account *account) Deposit(money float64, pwd string) {
	if pwd != account.pwd {
		fmt.Println("密码输入错误...")
		return
	}
	if money < 0 {
		fmt.Println("金额输入错误...")
		return
	}
	account.balance += money
	fmt.Println("存款成功")
}
//取款
func (account *account) WithDraw(money float64, pwd string) {
	if pwd != account.pwd {
		fmt.Println("密码输入错误")
		return
	}
	if money < 0 {
		fmt.Println("金额输入错误...")
		return
	}
	account.balance -= money
	fmt.Println("取款成功")
}
//查询
func (account *account) Query(pwd string) {
	if pwd != account.pwd {
		fmt.Println("密码输入错误")
		return
	}
	fmt.Printf("你的账号为%v 余额为%v \n", account.accountNo, account.balance)
}
func (account *account) GetAccountNo() string {
	return account.accountNo
}
func (account *account) SetAccountNo(accountNo string) {
	account.accountNo = accountNo
}

main/main.go

package main

import (
	"fmt"
	"go_code/project01/model"
)

func main() {
	account := model.NewAccount("zhangsan", "111111", 50)
	if account != nil {
		fmt.Println("创建成功 ", account)
		account.Query("111111")
		account.Deposit(1000, "111111")
		account.Query("111111")
		account.WithDraw(500, "111111")
		account.Query("111111")
		accountNo := account.GetAccountNo()
		fmt.Println("账户名 ", accountNo)
		account.SetAccountNo("zhangsan1")
		accountNo = account.GetAccountNo()
		fmt.Println("账户名改为 ", accountNo)
	} else {
		fmt.Println("创建失败")
	}
}

面向对象编程三大特性-继承

考试管理系统

package main

import "fmt"
//小学生
type Pupil struct {
	Name string
	Age int
	Score int
}

func (p *Pupil) ShowInfo() {
	fmt.Printf("姓名=%v 年龄=%v 成绩=%v\n", p.Name, p.Age, p.Score)
}
func (p *Pupil) SetScore(score int) {
	p.Score = score
}
func (p *Pupil) testing() {
	fmt.Println("小学生正在考试中...")
}
//大学生
type Graduate struct {
	Name string
	Age int
	Score int
}

func (p *Graduate) ShowInfo() {
	fmt.Printf("姓名=%v 年龄=%v 成绩=%v\n", p.Name, p.Age, p.Score)
}
func (p *Graduate) SetScore(score int) {
	p.Score = score
}
func (p *Graduate) testing() {
	fmt.Println("大学生正在考试中...")
}
func main() {
	var pupil = &Pupil{
		Name:  "tom",
		Age:   10,
	}
	pupil.testing()
	pupil.SetScore(90)
	pupil.ShowInfo()
	var graduate = &Graduate{
		Name:  "mary",
		Age:   20,
	}
	graduate.testing()
	graduate.SetScore(80)
	graduate.ShowInfo()
}
  1. Pupil 和 Graduate 两个结构体的字段和方法几乎一样,却写了相同的代码,代码复用性不强
  2. 出现代码冗余,而且代码不利于维护,同时也不利于功能的扩展
  3. 通过继承来解决

继承基本介绍

继承可以解决代码复用,让编程更加靠近人类思维

当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体(比如 Student),在该结构体中定义这些相同的属性和方法

其它的结构体不需要重新定义这些属性(字段)和方法,只需嵌套一个Student匿名结构体即可

在这里插入图片描述

在golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性

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

type Goods struct {
	Name string
	Price int
}
type Book struct {
	Goods  //这里就是嵌套匿名结构体 Goods
	Writer string
}

考试管理系统(使用继承)

package main

import "fmt"

type Student struct {
	Name string
	Age int
	Score int
}

func (stu *Student) ShowInfo() {
	fmt.Printf("姓名=%v 年龄=%v 成绩=%v\n", stu.Name, stu.Age, stu.Score)
}
func (stu *Student) SetScore(score int) {
	stu.Score = score
}
//小学生
type Pupil struct {
	Student //嵌入了Student匿名结构体
}
func (p *Pupil) testing() {
	fmt.Println("小学生正在考试中...")
}
//大学生
type Graduate struct {
	Student
}

func (p *Graduate) testing() {
	fmt.Println("大学生正在考试中...")
}
func main() {
	pupil := &Pupil{}
	pupil.Student.Name = "tom"
	pupil.Student.Age = 8
	pupil.testing()
	pupil.Student.SetScore(70)
	pupil.Student.ShowInfo()

	graduate := &Graduate{}
	graduate.Student.Name = "mary"
	graduate.Student.Age = 28
	graduate.testing()
	graduate.Student.SetScore(90)
	graduate.Student.ShowInfo()
}

继承给编程带来的便利

  1. 代码的复用性提高了
  2. 代码的扩展性和维护性提高了

继承的深入

  • 结构体可以使用嵌套匿名结构体所有的字段和方法,即: 首字母大写或者小写的字段、方法,都可以使用
package main

import "fmt"

type A struct {
	Name string
	age int
}

func (a *A) SayOk() {
	fmt.Println("A SayOk", a.Name)
}
func (a *A) hello() {
	fmt.Println("A hello,", a.Name)
}

type B struct {
	A
}
func main() {
	var b B
	b.A.Name = "tom"
	b.A.age = 19
	b.A.SayOk()
	b.A.hello()
}
  • 匿名结构体字段访问可以简化
package main

import "fmt"

type A struct {
	Name string
	age int
}

func (a *A) SayOk() {
	fmt.Println("A SayOk", a.Name)
}
func (a *A) hello() {
	fmt.Println("A hello,", a.Name)
}

type B struct {
	A
}
func main() {
	var b B
	/*
	b.A.Name = "tom"
	b.A.age = 19
	b.A.SayOk()
	b.A.hello()
	*/
	b.Name = "tom"
	b.age = 19
	b.SayOk()
	b.hello()
}

说明:
直接通过b访问字段或方法时,其执行流程 比如 b.Name
编译器会先看b对应的类型有没有Name,如果有,则直接调用B类型的Name字段
如果没有就去看B中嵌入的匿名结构体A 有没有声明Name字段,如果有就调用,如果没有继续查找 … 如果都找不到就报错

  • 当结构体和匿名结构体有相同的字段或方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分
package main

import "fmt"

type A struct {
	Name string
	age int
}

func (a *A) SayOk() {
	fmt.Println("A SayOk", a.Name)
}
func (a *A) hello() {
	fmt.Println("A hello,", a.Name)
}

type B struct {
	A
	Name string
	age int
}

func (b *B) SayOk() {
	fmt.Println("B SayOk,", b.Name)
}
func (b *B) hello() {
	fmt.Println("B hello,", b.Name)
}
func main() {
	var b B
	b.Name = "tom" //这时就近原则,会访问B结构体的name字段
	//b.A.Name 就明确指定访问A匿名结构体的字段Name
	b.A.Name = "jack"
	b.age = 78
	b.SayOk() //这时就近原则,会访问B结构体的SayOk函数
	b.hello()
	//b.A.hello()就明确指定访问A匿名结构体的方法hello()
	b.A.hello()
}

运行结果:

B SayOk, tom
B hello, tom
A hello, jack
  • 结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错
package main

import "fmt"

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.A.Name = "tom"
	fmt.Println(c)
}
  • 如果一个struct嵌套了一个有名结构体,这种模式就是组合, 如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字
package main

import "fmt"

type A struct {
	Name string
	age int
}
type B struct {
	Name string
	Score float64
}
type C struct {
	A
	B
}
type D struct {
	a A //有名结构体
}
func main() {
	var d D
	d.a.Name = "jack"
	fmt.Println(d)
}
  • 嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
package main

import "fmt"

type Goods struct {
	Name string
	Price float64
}
type Brand struct {
	Name string
	Address string
}
type TV struct {
	Goods
	Brand
}
type TV2 struct {
	*Goods
	*Brand
}
func main() {
	tv := TV{
		Goods: Goods{"电视机01", 5000.99},
		Brand: Brand{"海尔", "山东"},
	}
	tv2 := TV{
		Goods: Goods{
			"电视机02",
			5000.99,
		},
		Brand: Brand{
			"夏普",
			"北京",
		},
	}
	fmt.Println("tv", tv)
	fmt.Println("tv2", tv2)
	tv3 := TV2{&Goods{"电视机03", 7000.99}, &Brand{"创维", "河南"},}
	tv4 := TV2{
		&Goods{
			"电视机04",
			9000.99,
		},
		&Brand{
			"长虹",
			"四川",
		},
	}
	fmt.Println("tv3", *tv3.Goods, *tv3.Brand)
	fmt.Println("tv4", *tv4.Goods, *tv4.Brand)
}
  • 结构体的匿名字段是基本数据类型,如何访问
package main

import "fmt"

type Monster struct {
	Name string
	Age int
}
type E struct {
	Monster
	int
	n int
}
func main() {
	var e E
	e.Name = "狐狸精"
	e.Age = 300
	e.int = 20
	e.n = 40
	fmt.Println("e=", e)
}

运行结果:

e= {{狐狸精 300} 20 40}

说明:

  1. 如果一个结构体有int类型的匿名字段,就不能有第二个
  2. 如果需要有多个int的字段,则必须给int字段指定名字

面向对象编程-多重继承

如一个struct嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承

package main

import "fmt"

type Goods struct {
	Name string
	Price float64
}
type Brand struct {
	Name string
	Address string
}
type TV struct {
	Goods
	Brand
}
func main() {
	var tv TV
	tv.Goods.Name = "电视"
	tv.Price = 2000.99
	fmt.Println(tv.Goods.Name)
	fmt.Println(tv.Price)
}

说明:

  1. 如嵌入的匿名结构体有相同的字段名或方法名,则在访问时,需要通过匿名结构体类型名来区分
  2. 为了保证代码的简洁性,建议尽量不使用多重继承

接口(interface)

golang中多态特性主要是通过接口来体现的

package main

import "fmt"
//声明/定义一个接口
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接口类型变量
//只要是实现了Usb接口(所谓实现Usb接口,就是指实现了Usb接口声明所有方法)
func (c Computer) Working(usb Usb) {
	//通过usb接口变量来调用Start和Stop方法
	usb.Start()
	usb.Stop()
}
func main() {
	computer := Computer{}
	phone := Phone{}
	camera := Camera{}

	computer.Working(phone)
	computer.Working(camera)
}

接口概念

interface类型可以定义一组方法,但是这些不需要实现。并且interface不能包含任何变量。到某个自定义类型(比如结构体Phone)要使用的时候,再根据具体情况把这些方法写出来(实现)

基本语法

type 接口名 interface {
	method1(参数列表) 返回值类型列表
	method2(参数列表) 返回值类型列表
	...
}
//实现接口所有方法
func (t 自定义类型) method1(参数列表) 返回值类型列表 {
	//实现方法
}
func (t 自定义类型) method2(参数列表) 返回值类型列表 {
	//实现方法
}
...

说明:

  1. 接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的多态高内聚低耦合的思想
  2. golang中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。

一个项目经理,管理三个程序员,开发一个软件,为了控制和管理软件,项目经理可以定义一些接口,然后由程序员具体实现

在这里插入图片描述

注意事项

  • 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)
package main

import "fmt"

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

func (stu Stu) Say() {
	fmt.Println("Stu Say()")
}
func main() {
	var stu Stu //结构体变量,实现了Say() 实现了 AInterface
	var a AInterface = stu
	a.Say()
}
  • 接口中所有的方法都没有方法体,即都是没有实现的方法
  • 在golang中,一个自定义类型需要将某个接口的所有方法都实现,就说这个自定义类型实现了该接口
  • 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型
  • 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型
package main

import "fmt"

type AInterface interface {
	Say()
}
type integer int

func (i integer) Say() {
	fmt.Println("integer Say i =", i)
}
func main() {
	var i integer = 10
	var b AInterface = i
	b.Say()
}
  • 一个自定义类型可以实现多个接口
package main

import "fmt"

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 monster Monster
	var a2 AInterface = monster
	var b2 BInterface = monster
	a2.Say()
	b2.Hello()
}
  • golang接口中不能有任何变量
  • 一个接口(比如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 Stu struct {

}

func (stu Stu) test01() {
	fmt.Println("test01()...")
}
func (stu Stu) test02() {
	fmt.Println("test02()...")
}
func (stu Stu) test03() {
	fmt.Println("test03()...")
}
func main() {
	var stu Stu
	var a AInterface = stu
	a.test01()
}
  • interface类型默认是一个指针(引用类型),如果没有对interface初始化就使用,那么会输出nil
  • 空接口interface{} 没有任何方法,所以所有类型都实现了空接口,即可以把任何一个变量赋给空接口
package main

import "fmt"

type T interface {

}
type Stu struct {

}

func main() {
	var stu Stu
	var t T = stu
	fmt.Println(t)
	var t2 interface{} = stu
	var num1 float64 = 8.8
	t2 = num1
	t = num1
	fmt.Println(t2, t)
}
package main

import "fmt"

type Usb interface {
	Say()
}
type Stu struct {

}

func (this *Stu) Say() {
	fmt.Println("Say()...")
}
func main() {
	var stu Stu = Stu{}
	var u Usb = &stu  //注意这里
	u.Say()
	fmt.Println("here", u)
}

接口编程实战

import "sort"

func Sort

func Sort(data Interface)

Sort排序data。它调用1次data.Len确定长度,调用O(n*log(n))次data.Less和data.Swap。本函数不能保证排序的稳定性(即不保证相等元素的相对次序不变)

type Interface

type Interface interface {
    // Len方法返回集合中的元素个数
    Len() int
    // Less方法报告索引i的元素是否比索引j的元素小
    Less(i, j int) bool
    // Swap方法交换索引i和j的两个元素
    Swap(i, j int)
}

一个满足sort.Interface接口的(集合)类型可以被本包的函数进行排序。方法要求集合中的元素可以被整数索引。

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

package main

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

type Hero struct {
	Name string
	Age int
}
//声明一个Hero结构体切片类型
type HeroSlice []Hero
//实现Interface接口
func (hs HeroSlice) Len() int {
	return len(hs)
}
//Less方法就是决定你使用什么标准进行排序
//1. 按Hero的年龄从小到大排序
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) {
	//temp := hs[i]
	//hs[i] = hs[j]
	//hs[j] = temp
	//等价下面一句
	hs[i], hs[j] = hs[j], hs[i]
}
//声明Student结构体
type Student struct {
	Name string
	Age int
	Score float64
}
//将Student的切片,按Score从大到小排序
func main() {
	var intSlice = []int{0, -1, 10, 7, 90}
	sort.Ints(intSlice)
	fmt.Println(intSlice)

	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(heroes)
	fmt.Println("------排序后------")
	for _, v := range heroes {
		fmt.Println(v)
	}

	i := 10
	j := 20
	i, j = j, i
	fmt.Println("i=", i, "j=", j)
}

声明Student结构体

type Student struct {
	Name string
	Age int
	Score float64
}

将Student的切片,按Score从大到小排序

package main

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

//声明Student结构体
type Student struct {
	Name string
	Age int
	Score float64
}
type StudentSlice []Student
func (stu StudentSlice) Len() int {
	return len(stu)
}

func (stu StudentSlice) Less(i, j int) bool {
	return stu[i].Score < stu[j].Score
}
func (stu StudentSlice) Swap(i, j int) {
	stu[i], stu[j] = stu[j], stu[i]
}
//将Student的切片,按Score从大到小排序
func main() {
	var studentes StudentSlice
	for i := 0; i < 10; i++ {
		student := Student{
			Name:  fmt.Sprintf("stu-%d", rand.Intn(100)),
			Age:   10 + rand.Intn(20),
			Score: float64(rand.Intn(100)),
		}
		studentes = append(studentes, student)
	}
	for _, v := range studentes {
		fmt.Println(v)
	}
	sort.Sort(sort.Reverse(studentes))
	fmt.Println("------排序后------")
	for _, v := range studentes {
		fmt.Println(v)
	}
}

实现接口 vs 继承

package main

import "fmt"

type Monkey struct {
	Name string
}
type BirdAble interface {
	Flying()
}
type FishAble interface {
	Swimming()
}

func (this *Monkey) climbing() {
	fmt.Println(this.Name, "生来会爬树")
}

type LittleMonkey struct {
	Monkey
}

func (this *LittleMonkey) Flying() {
	fmt.Println(this.Name, "通过学习,会飞...")
}
func (this *LittleMonkey) Swimming() {
	fmt.Println(this.Name, "通过学习,会游泳...")
}
func main() {
	monkey := LittleMonkey{
		Monkey{
			"悟空",
		},
	}
	monkey.climbing()
	monkey.Flying()
	monkey.Swimming()
}

说明:

  1. 当A结构体继承了B结构体,那么A结构体就自动的继承了B结构体的字段和方法,并且可以直接使用
  2. 当A结构体需要扩展功能,同时不希望去破坏继承关系,则可以去实现某个接口即可
  3. 实现接口可以看作是对 继承的一种补充

在这里插入图片描述

  • 接口和继承解决的问题不同

    • 继承的价值主要在于: 解决代码的复用性和可维护性
    • 接口的价值在于: 设计,设计好各种规范(方法),让其它自定义类型去实现这些方法
  • 接口比继承更加灵活 继承是满足 is - a的关系,而接口只需要满足 like - a 的关系

  • 接口在一定程度上实现代码解耦

面向对象编程-多态

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

Usb接口,Usb usb , 既可以接收手机变量,有可以接收相机变量,就体现了 Usb接口 多态特性

package main

import "fmt"
//声明/定义一个接口
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接口类型变量
//只要是实现了Usb接口(所谓实现Usb接口,就是指实现了Usb接口声明所有方法)
func (c Computer) Working(usb Usb) {
	//usb变量会根据传入的实参,来判断到底是Phone还是Camera (usb接口变量就体现出多态的特点)
	//通过usb接口变量来调用Start和Stop方法
	usb.Start()
	usb.Stop()
}
func main() {
	computer := Computer{}
	phone := Phone{}
	camera := Camera{}

	computer.Working(phone)
	computer.Working(camera)
}

接口体现多态的两种形式

  • 多态参数
    Usb usb, 既可以接收手机变量,又可以接收相机变量,就体现了Usb接口多态
  • 多态数组
    给Usb数组中,存放Phone结构体和Camera结构体变量
package main

import "fmt"

type Usb interface {
	Start()
	Stop()
}
type Phone struct {
	name string
}

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

type Camera struct {
	name string
}

func (c Camera) Start() {
	fmt.Println("相机开始工作...")
}
func (c Camera) Stop() {
	fmt.Println("相机停止工作...")
}
func main() {
	//定义一个Usb接口数组,可以存放Phone和Camera的结构体变量
	//这里就体现出多态数组
	var usbArr [3]Usb
	usbArr[0] = Phone{"vivo"}
	usbArr[1] = Phone{"小米"}
	usbArr[2] = Camera{"尼康"}
	fmt.Println(usbArr)
}

类型断言

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
	b = a.(Point) //类型断言
	fmt.Println(b)
}

b = a.(Point) 就是类型断言,表示判断a是否指向Point类型的变量

基本介绍

类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言

package main

import "fmt"

func main() {
	var x interface{}
	var b2 float64 = 1.1
	x = b2 //空接口,可以接收任意类型
	y := x.(float64)
	fmt.Printf("y 的类型是 %T 值是 =%v", y, y)
}

说明:
在进行类型断言时,如果类型不匹配,就会包panic,因此进行类型断言时,要确保原来的空接口指向的就是断言的类型

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

package main

import "fmt"

func main() {
	var x interface{}
	var b2 float64 = 2.1
	x = b2
	//类型断言(带检测)
	if y, ok := x.(float64); ok {
		fmt.Println("convert success")
		fmt.Printf("y 的类型是 %T  值是 = %v", y, y)
	} else {
		fmt.Println("convert fail")
	}
	fmt.Println("继续执行...")
}

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("手机开始工作...")
}
func (p Phone) Stop() {
	fmt.Println("手机停止工作...")
}
func (p Phone) Call() {
	fmt.Println("手机在打电话...")
}

type Camera struct {
	name string
}

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

type Computer struct {

}

func (computer 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{"尼康"}
	var computer Computer
	for _, v := range usbArr {
		computer.Working(v)
		fmt.Println()
	}
	fmt.Println(usbArr)
}

写一个函数,循环判断传入参数类型

package main

import "fmt"

type Student struct {
	Name string
	Age int
}
func TypeJudge(items... interface{})  {
	for index, x := range items {
		switch x.(type) {
		case bool:
			fmt.Printf("第%v个参数是 bool 类型, 值是%v\n", index, x)
		case float32:
			fmt.Printf("第%v个参数是 float32 类型, 值是%v\n", index, x)
		case float64:
			fmt.Printf("第%v个参数是 float64 类型, 值是%v\n", index, x)
		case int, int32, int64:
			fmt.Printf("第%v个参数是 整数 类型, 值是%v\n", index, x)
		case string:
			fmt.Printf("第%v个参数是 string 类型, 值是%v\n", index, x)
		case Student:
			fmt.Printf("第%v个参数是 Student 类型, 值是%v\n", index, x)
		case *Student:
			fmt.Printf("第%v个参数是 *Student 类型, 值是%v\n", index, x)
		default:
			fmt.Printf("第%v个参数是 不确定 类型, 值是%v\n", index, x)
		}
	}
}
func main() {
	var n1 float32 = 1.1
	var n2 float64 = 2.3
	var n3 int32 = 30
	var name string = "tom"
	address := "北京"
	n4 := 300
	var stu1 Student
	var stu2 *Student
	TypeJudge(n1, n2, n3, name, address, n4, stu1, stu2)
}
发布了97 篇原创文章 · 获赞 25 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/wuxingge/article/details/104411985