22.Go面向对象-接口

网友投稿 903 2022-09-10

22.Go面向对象-接口

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 接口定义

接口定义的语法如下:

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

// 定义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] sayhiTeacher[beijing, go] sayhi

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

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

6.3 多态

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

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

img

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

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

练习:

练习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

上面的案例与第一个案例,基本上是一样的,不同之处是在输出方面,通过一个循环获取切片中存储的所有对象,然后分别调用​​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}

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")}

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

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Python 爬取猫眼电影《无名之辈》并对其进行数据分析(python培训)
下一篇:2. 第三方连接池
相关文章

 发表评论

暂时没有评论,来抢沙发吧~