0
点赞
收藏
分享

微信扫一扫

22.Go面向对象-接口

22.Go面向对象-接口

6 接口

在讲解具体的接口之前,先看如下问题。

使用面向对象的方式,设计一个加减的计算器

代码如下:

// 父类
type ObjectOperate struct {
num1 int
num2 int
}

// 加法类
type AddOperate struct {
ObjectOperate
}

// 加法类的方法
func (p *AddOperate) Operate(a, b int) int {
p.num1 = a
p.num2 = b
return p.num1 + p.num2
}

// 减法类
type SubOperate struct {
ObjectOperate
}

// 减法类方法
func (p *SubOperate) Operate(a, b int) int {
p.num1 = a
p.num2 = b
return p.num1 - p.num2
}

func main() {
// 创建减法类
var sub SubOperate
i := sub.Operate(5, 2) // 执行减法
fmt.Println(i)
}

以上实现非常简单,但是有个问题,在​​main()​​函数中,当我们想使用减法操作时,创建减法类的对象,调用其对应的减法的方法。

但是,有一天,系统需求发生了变化,要求使用加法,不再使用减法,那么需要对​​main()​​函数中的代码,做大量的修改。

将原有的代码注释掉,创建加法的类对象,调用其对应的加法的方法。

有没有一种方法,让​​main()​​函数,只修改很少的代码就可以解决该问题呢?

有,要用到接下来给大家讲解的接口的知识点。

6.1 什么是接口

接口就是一种规范与标准,在生活中经常见接口,例如:笔记本电脑的USB接口,可以将任何厂商生产的鼠标与键盘,与电脑进行链接。

为什么呢?原因就是,USB接口将规范和标准制定好后,各个生产厂商可以按照该标准生产鼠标和键盘就可以了。

在程序开发中,接口只是规定了要做哪些事情,干什么。具体怎么做,接口是不管的。这和生活中接口的案例也很相似,例如:USB接口,只是规定了标准,但是不关心具体鼠标与键盘是怎样按照标准生产的.

在企业开发中,如果一个项目比较庞大,那么就需要一个能理清所有业务的架构师来定义一些主要的接口,这些接口告诉开发人员你需要实现那些功能。

6.2 接口定义

接口定义的语法如下:

// 定义接口类型
type Humaner interface {
// 方法,只有声明,没有实现,由别的类型(自定义类型)实现
sayhi()
}

怎样具体实现接口中定义的方法呢?

// 定义Student类
type Student struct {
name string
id int
}

// Student实现了此接口方法
func (tmp *Student) sayhi() {
fmt.Printf("Student[%s, %d] sayhi\n", tmp.name, tmp.id)
}

// 定义Teacher类
type Teacher struct {
addr string
group string
}

// Teacher实现了此方法
func (tmp *Teacher) sayhi() {
fmt.Printf("Teacher[%s, %s] sayhi\n", tmp.addr, tmp.group)
}

具体的调用如下:

func main() {
// 定义接口类型的变量
var i Humaner
// 只是实现了此接口方法的类型,那么这个类型的变量(接收者类型)就可以给i赋值
s := &Student{"mike", 666}
i = s
i.sayhi()

t := &Teacher{"beijing", "go"}
i = t
i.sayhi()
}

//执行
Student[mike, 666] sayhi
Teacher[beijing, go] sayhi

只要类(结构体)实现对应的接口,那么根据该类创建的对象,可以赋值给对应的接口类型。

接口的命名习惯以​​er​​结尾。

6.3 多态

接口有什么好处呢?实现多态。

所谓多态指的是多种表现形式,如下图所示:


22.Go面向对象-接口_go语言

img

该拖拉机既可以扫地又可以当风扇。功能非常强大。

使用接口实现多态的方式如下:

// 定义接口类型
type Humaner interface {
// 方法,只有声明,没有实现,由别的类型(自定义类型)实现
sayhi()
}

// 定义Student类
type Student struct {
name string
id int
}

// Student实现了此接口方法
func (tmp *Student) sayhi() {
fmt.Printf("Student[%s, %d] sayhi\n", tmp.name, tmp.id)
}

// 定义Teacher类
type Teacher struct {
addr string
group string
}

// Teacher实现了此方法
func (tmp *Teacher) sayhi() {
fmt.Printf("Teacher[%s, %s] sayhi\n", tmp.addr, tmp.group)
}

// 定义一个普通函数,函数的参数为接口类型
// 只有一个函数,可以有不同的表现,实现了多态
func WhoSayHi(i Humaner) {
i.sayhi()
}

func main() {
// 定义接口类型的变量
var i Humaner
// 只是实现了此接口方法的类型,那么这个类型的变量(接收者类型)就可以给i赋值
s := &Student{"mike", 666}
t := &Teacher{"beijing", "go"}

// 调用同一个函数,传递不同的类对象,不同的表现,实现了多态
WhoSayHi(s)
WhoSayHi(t)
}

练习:

练习1:

type Person struct{
name string
}

func (p *Person) SayHello(){
fmt.Println("我是人类,我叫:"+p.name)
}

type Chinese struct{
Person
}

func (c *Chinese) SayHello(){
fmt.Println("我是中国人,我叫:"+c.name)
}

type English struct{
Person
}

func (e *English) SayHello(){
fmt.Println("我是英国人,我叫:"+e.name)
}

type Personer interface{
SayHello()
}

func main(){
c := &Chinese{Person{"zhangsan"}}
e := &English{Person{"MrZhang"}}
x := make([]Personer,2)
x[0] = c
x[1] = e
for i:=0;i<len(x);i++{
x[i].SayHello()
}
}

上面的案例与第一个案例,基本上是一样的,不同之处是在输出方面,通过一个循环获取切片中存储的所有对象,然后分别调用​​SayHello()​​方法。

如果没有接口,那么只能一个一个的调用其方法。

练习2:

用多态来实现 将 移动硬盘或者U盘或者MP3插到电脑上进行读写数据(分析类,接口,方法)

type MobileStorager interface {
Read()
Write()
}

type MobileDisk struct { //移动硬盘
}

func (m *MobileDisk) Read() {
fmt.Println("移动硬盘在读取数据")
}
func (m *MobileDisk) Write() {
fmt.Println("移动硬盘在写入数据")
}

type UDisk struct {
}

func (u *UDisk) Read() {
fmt.Println("U盘在读取数据")
}
func (u *UDisk) Write() {
fmt.Println("U盘在写入数据")
}

type Computer struct {
}

func (c *Computer) CpuRead(i MobileStorager) {
i.Read()
}
func (c *Computer) CpuWrite(i MobileStorager) {
i.Write()
}

func main() {
m := &MobileDisk{}
c := &Computer{}
c.CpuRead(m)
c.CpuWrite(m)
}

// 执行:
移动硬盘在读取数据
移动硬盘在写入数据

练习3:

麻雀会飞 鹦鹉会飞,直升飞机会飞

type Flyabler interface {
Fly()
}

type Bird struct {
}

func (b *Bird) EatAndDrink() { //为Bird定义方法
fmt.Println("鸟儿吃喝")
}

type MaQue struct { //麻雀
Bird
}

func (m *MaQue) Fly() {
fmt.Println("麻雀会飞")
}

type YingWu struct {
Bird
}

func (y *YingWu) Fly() {
fmt.Println("鹦鹉飞")
}

type Plane struct {
}

func (p *Plane) Fly() {
fmt.Println("飞机飞")
}

func WhoFly(i Flyabler) {
i.Fly()
}

func main() {
m := &MaQue{}
m.EatAndDrink()
WhoFly(m)
plane := &Plane{}
WhoFly(plane)
}

// 执行:
鸟儿吃喝
麻雀会飞
飞机飞

计算器案例

关于接口的定义,以及使用接口实现多态,大家都比较熟悉了,但是多态有什么好处呢?

现在还是以开始提出的计算器案例给大家讲解一下,在开始我们已经实现了一个加减功能的计算器,但是有同学感觉太麻烦了,因为实现加法,就要定义加法操作的类(结构体),实现减法就要定义减法的类(结构体),所以该同学实现了一个比较简单的加减法的计算器,如下所示:

1. 使用面向对象的思想实现一个加减功能的计算器,可能有同学感觉非常简单,代码如下:

type Operation struct {
}

func (p *Operation) GetResult(numA float64, numB float64, operate string) float64 {
var result float64
switch operate {
case "+":
result = numA + numB
case "-":
result = numA - numB
}
return result
}

func main() {
var operation Operation
sum := operation.GetResult(10, 13, "+")
fmt.Println(sum)
}

我们定义了一个类(结构体),然后为该类创建了一个方法,封装了整个计算器功能,以后要使用直接使用该类(结构体)创建对象就可以了。

这就是面向对象的封装性。

也就是说,当你写完这个计算器后,交给你的同事,你的同事要用,直接创建对象,然后调用​​GetResult()​​方法就可以, 根本不需要关心该方法是怎样实现的.

这不是我们前面在讲解面向对象概念时说到的,找个对象来干活吗?不需要自己去实现该功能。

2.大家仔细观察上面的代码,有什么问题吗?

现在让你在改计算器中,再增加一个功能,例如乘法,应该怎么办呢?你可能会说很简单啊,直接在​​GetResult()​​​方法的​​switch​​​中添加一个​​case​​分支就可以了。

问题是:在这个过程中,如果你不小心将加法修改成了减法怎么办?或者说,对加法运算的规则做了修改怎么办?

举例子说明:

你可以把该程序方法想象成公司中的薪资管理系统。如果公司决定对薪资的运算规则做修改,由于所有的运算规则都在Operation类中的​​GetResult()​​方法中,所以公司只能将该类的代码全部给你,你才能进行修改。

这时,你一看自己作为开发人员工资这么低,心想“TMD,老子累死累活才给这么点工资,这下有机会了”。直接在自己工资后面加了3000

​numA+numB+3000​

所以说,我们应该将 加减等运算分开,不应该全部糅合在一起,这样你修改加的时候,不会影响其它的运算规则:

具体实现如下:

// 定义操作父类
type Operation struct {
numA float64
numB float64
}

// 定义结果接口
type GetResulter interface {
GetResult() float64 // 方法有返回值
}

// 加法
type OperationAdd struct {
Operation
}

func (a *OperationAdd) GetResult() float64 {
return a.numA + a.numB
}

// 减法
type OperationSub struct {
Operation
}

func (s *OperationSub) GetResult() float64 {
return s.numA - s.numB
}

22.Go面向对象-接口_编程语言_02


img

现在已经将各个操作分开了,并且这里我们还定义了一个父类(结构体),将公共的成员放在该父类中。如果现在要修改某项运算规则,只需将对应的类和方法发给你,进行修改就可以了。

这里的实现虽然将各个运算分开了,但是与我们第一次实现的还是有点区别。我们第一次实现的加减计算器也是将各个运算分开了,但是没有定义接口。那么该接口的意义是什么呢?继续看下面的问题。

3:现在怎样调用呢?

这就是我们一开始给大家提出的问题,如果调用的时候,直接创建加法操作的对象,调用对应的方法,那么后期要改成减法呢?

需要做大量的修改,所以问题解决的方法如下:

// 创建工厂类
type OperationFactory struct {

}

func (f *OperationFactory) CreateOption(option string, numA float64, numB float64) float64 {
var result float64
switch option {
case "+":
add := &OperationAdd{Operation{numA, numB}}
result = OperationWho(add)
case "-":
sub := &OperationSub{Operation{numA, numB}}
result = OperationWho(sub)
}
return result
}

// 使用接口实现多态
func OperationWho(i GetResulter) float64 {
return i.GetResult()
}

创建了一个类​​OperationFactory​​​,在改类中添加了一个方法​​CreateOption()​​​负责创建对象,如果输入的是​​“+”​​,创建

​OperationAdd​​​的对象,然后调用​​OperationWho()​​​方法,将对象的地址传递到该方法中,所以变量i指的就是​​OperationAdd​​​,接下来在调用​​GetResult()​​​方法,实际上调用的是​​OperationAdd​​​类实现的​​GetResult()​​方法。

同理如果传递过来的是​​“-”​​,流程也是一样的。

所以,通过该程序,大家能够体会出多态带来的好处。

4:最后调用

func main() {
var factory OperationFactory
s := factory.CreateOption("-", 10, 12)
fmt.Println(s)
}

这时会发现调用,非常简单,如果现在想计算加法,只要将​​”-”​​​,修改成​​”+”​​就可以。

也就是说,除去了main( )函数与具体运算类的依赖。

当然程序经过这样设计以后:

如果现在修改加法的运算规则,只需要修改OperationAdd类中对应的方法,不需要关心其它的类,如果现在要增加“乘法” 功能,应该怎样进行修改呢?

第一:定义乘法的类,完成乘法运算。

第二:在​​OperationFactory​​​类中​​CrateOption()​​方法中添加相应的分支。

但是这样做并不会影响到其它的任何运算。

大家可以自己尝试实现“乘法”与“除法”的运算。

在使用面向对象思想解决问题时,一定要先分析,定义哪些类,哪些接口,哪些方法。把这些分析定义出来,然后在考虑具体实现。

最后完整代码如下:

// 定义操作父类
type Operation struct {
numA float64
numB float64
}

// 定义结果接口
type GetResulter interface {
GetResult() float64 // 方法有返回值
}

// 加法
type OperationAdd struct {
Operation
}

func (a *OperationAdd) GetResult() float64 {
return a.numA + a.numB
}

// 减法
type OperationSub struct {
Operation
}

func (s *OperationSub) GetResult() float64 {
return s.numA - s.numB
}

// 创建工厂类
type OperationFactory struct {

}

func (f *OperationFactory) CreateOption(option string, numA float64, numB float64) float64 {
var result float64
switch option {
case "+":
add := &OperationAdd{Operation{numA, numB}}
result = OperationWho(add)
case "-":
sub := &OperationSub{Operation{numA, numB}}
result = OperationWho(sub)
}
return result
}

// 使用接口实现多态
func OperationWho(i GetResulter) float64 {
return i.GetResult()
}


func main() {
var factory OperationFactory
s := factory.CreateOption("-", 10, 12)
fmt.Println(s)
}

通过以上案例,大家应该能够体会出多态的好处。

下面我们再通过一个练习,体会一下接口和多态的应用。

练习:设计一个4s店卖车的程序。(分析要设计多个类,多个方法,接口)

首先我们思考一下,该程序需要设计几个类(结构体)

大家想到的有汽车类,还有4s店类。

所以基本设计的代码:

(1):创建汽车类(结构体)

// 汽车类
type Car struct {

}

func (p *Car) Move() {
fmt.Println("汽车移动")
}

func (p *Car) Stop() {
fmt.Println("汽车停止")
}

并在改结构体中定义了两个方法。

(2):创建4S店类(结构体)

// 4s店类
type CarStore struct {
}

func (c *CarStore) Order(money float64) *Car {
if money > 50000 {
return &Car{}
} else {
return nil
}
}

为该类(结构体)添加​​Order()​​​方法,该方法的作用就是卖车,所以需要给该方法传递“钱”,然后进行判断,如果条件满足,就返回Car地址,所以返回类型为​​*Car​

(3):下面进行调用

func main() {
var carStore CarStore
car := carStore.Order(80000)
car.Move()
car.Stop()
}

(4):如果在增加不同品牌的车,应该怎样处理呢?

代码如下:

// 汽车类
type Car struct {
catType string
money float64
}

func (p *Car) Move() {
fmt.Println(p.catType + "汽车移动")
}

func (p *Car) Stop() {
fmt.Println(p.catType + "汽车停止")
}

// 定义BMW车
type BMWCar struct {
Car
}

在以上的代码中,定义了“宝马车”类,让其继承Car类,并且在Car类中定义了两个成员 。

(5) 定义接口

在定义接口前,又定义了“奥迪车”类,也让其继承Car类

// 定义奥迪车类
type AudiCar struct {
Car
}

然后定义一个接口:

type CarTyper interface {
GetCar()
}

下面实现该接口中定义的方法,

func (p *BMWCar) GetCar() {
if p.money >= 60000 {
p.Move()
p.Stop()
} else {
fmt.Println("钱不够,无法买宝马!")
}
}

该方法的作用就是判断钱是否够了,如果钱够了,就可以调用车具有的方法。

func (p *AudiCar) GetCar() {
if p.money >= 70000 {
p.Move()
p.Stop()
} else {
fmt.Println("钱不够,无法买奥迪!")
}
}

(6):对​​Order()​​的改造

func (c *CarStore) Order(money float64, carType string) {
switch carType {
case "BMW":
CarWho(&BMWCar{Car{carType, money}})
case "Audi":
CarWho(&AudiCar{Car{carType, money}})
}

}

根据传递过来的车的类型,进行判断,然后调用​​CarWho()​​方法,该方法是一个多态的方法,定义如下:

func CarWho(i CarTyper)  {
i.GetCar()
}

如果传递过来的类型是​​”BMW”​​​,在调用​​CarWho()​​​方法时,将​​BMWCar{}​​类(结构体)的地址传递到该方法中(同时完成了父类Car中两个属性的赋值)。

由于​​CarWho()​​​方法参数的类型是一个接口,但是​​BMWCar{}​​​类(结构体)实现了该接口,所以是完全可以​​BMWCar{}​​类的地址传递过来。

这时参数​​i​​​指的就是​​BMWCar{}​​​,调用​​GetCar()​​​方法,指的就是​​BMWCar{}​​实现的方法。

在该方法中完成钱数的判断。

同理如果传递的过来的类型是“Audi”,那么过程也是一样的。

(7) main( )函数的调用

func main() {
var carStore CarStore
carStore.Order(30000, "Audi")
}

(8) 完整代码如下:

// 汽车类
type Car struct {
catType string
money float64
}

func (p *Car) Move() {
fmt.Println(p.catType + "汽车移动")
}

func (p *Car) Stop() {
fmt.Println(p.catType + "汽车停止")
}

// 定义BMW车
type BMWCar struct {
Car
}

func (p *BMWCar) GetCar() {
if p.money >= 60000 {
p.Move()
p.Stop()
} else {
fmt.Println("钱不够,无法买宝马!")
}
}

// 定义奥迪车类
type AudiCar struct {
Car
}

func (p *AudiCar) GetCar() {
if p.money >= 70000 {
p.Move()
p.Stop()
} else {
fmt.Println("钱不够,无法买奥迪!")
}
}

type CarTyper interface {
GetCar()
}

// 4s店类
type CarStore struct {
}

func (c *CarStore) Order(money float64, carType string) {
switch carType {
case "BMW":
CarWho(&BMWCar{Car{carType, money}})
case "Audi":
CarWho(&AudiCar{Car{carType, money}})
}

}

func CarWho(i CarTyper) {
i.GetCar()
}

func main() {
var carStore CarStore
carStore.Order(30000, "Audi")
}

下一章节,我们将接口其它的知识点再给大家说一下。


举报

相关推荐

0 条评论