前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >22.Go面向对象-接口

22.Go面向对象-接口

作者头像
Devops海洋的渔夫
发布2022-01-17 10:31:29
2850
发布2022-01-17 10:31:29
举报
文章被收录于专栏:Devops专栏

22.Go面向对象-接口

6 接口

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

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

代码如下:

代码语言:javascript
复制
// 父类
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 接口定义

接口定义的语法如下:

代码语言:javascript
复制
// 定义接口类型
type Humaner interface {
   // 方法,只有声明,没有实现,由别的类型(自定义类型)实现
   sayhi()
}  

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

代码语言:javascript
复制
// 定义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)
}

具体的调用如下:

代码语言:javascript
复制
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 多态

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

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

img

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

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

代码语言:javascript
复制
// 定义接口类型
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:
代码语言:javascript
复制
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插到电脑上进行读写数据(分析类,接口,方法)

代码语言:javascript
复制
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:

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

代码语言:javascript
复制
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. 使用面向对象的思想实现一个加减功能的计算器,可能有同学感觉非常简单,代码如下:
代码语言:javascript
复制
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

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

具体实现如下:

代码语言:javascript
复制
// 定义操作父类
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:现在怎样调用呢?

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

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

代码语言:javascript
复制
// 创建工厂类
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:最后调用
代码语言:javascript
复制
func main() {
   var factory OperationFactory
   s := factory.CreateOption("-", 10, 12)
   fmt.Println(s)
} 

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

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

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

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

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

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

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

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

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

最后完整代码如下:

代码语言:javascript
复制
// 定义操作父类
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):创建汽车类(结构体)
代码语言:javascript
复制
// 汽车类
type Car struct {
   
}

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

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

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

(2):创建4S店类(结构体)
代码语言:javascript
复制
// 4s店类
type CarStore struct {
}

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

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

(3):下面进行调用
代码语言:javascript
复制
func main() {
   var carStore CarStore
   car := carStore.Order(80000)
   car.Move()
   car.Stop()
}
(4):如果在增加不同品牌的车,应该怎样处理呢?

代码如下:

代码语言:javascript
复制
// 汽车类
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类

代码语言:javascript
复制
// 定义奥迪车类
type AudiCar struct {
   Car
}

然后定义一个接口:

代码语言:javascript
复制
type CarTyper interface {
   GetCar()
} 

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

代码语言:javascript
复制
func (p *BMWCar) GetCar() {
   if p.money >= 60000 {
      p.Move()
      p.Stop()
   } else {
      fmt.Println("钱不够,无法买宝马!")
   }
}

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

代码语言:javascript
复制
func (p *AudiCar) GetCar() {
   if p.money >= 70000 {
      p.Move()
      p.Stop()
   } else {
      fmt.Println("钱不够,无法买奥迪!")
   }
}
(6):对Order()的改造
代码语言:javascript
复制
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()方法,该方法是一个多态的方法,定义如下:

代码语言:javascript
复制
func CarWho(i CarTyper)  {
   i.GetCar()
} 

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

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

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

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

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

(7) main( )函数的调用
代码语言:javascript
复制
func main() {
   var carStore CarStore
   carStore.Order(30000, "Audi")
}
(8) 完整代码如下:
代码语言:javascript
复制
// 汽车类
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")
}

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-08-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 海洋的渔夫 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 22.Go面向对象-接口
    • 6 接口
      • 6.1 什么是接口
      • 6.2 接口定义
      • 6.3 多态
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档