前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go 面向对象编程篇(六):接口赋值

Go 面向对象编程篇(六):接口赋值

作者头像
学院君
发布2023-03-03 14:01:21
5000
发布2023-03-03 14:01:21
举报
文章被收录于专栏:学院君的专栏学院君的专栏

一、接口赋值概述

上篇教程中,学院君给大家介绍了 Go 接口的定义及实现,和其他编程语言一样,Go 接口不支持直接实例化,因为它只是一个契约而已,只能通过具体的类来实现接口声明的所有方法。不同之处在于,Go 接口支持赋值操作,从而快速实现接口与实现类的映射,与之相比,Java、PHP 要实现接口与实现类的映射,只能基于 IoC 容器通过依赖注入实现,要复杂的多。

接口赋值在 Go 语言中分为如下两种情况:

  • 将实现接口的类实例赋值给接口;
  • 将一个接口赋值给另一个接口。

下面我们通过代码实例逐个介绍对应的实现和注意事项。

二、将类实例赋值给接口

先看看将类实例赋值给接口,这要求该实例对应的类实现了接口声明的所有方法,这个是自然,否则也就不能算作实现该接口了。

只包含值方法

我们以之前为基本类型添加成员方法时定义过的 Integer 类型为例进行演示:

代码语言:javascript
复制
type Integer int

// 加法运算
func (a Integer) Add(b Integer) Integer {
    return a + b
}

// 乘法运算
func (a Integer) Multiply(b Integer) Integer {
    return a * b
}

type Math interface {
    Add(i Integer) Integer
    Multiply(i Integer) Integer
}

按照 Go 语言的约定,Integer 类型实现了 Math 接口。然后我们可以这样将 Integer 类型的实例 a 直接赋值给 Math 接口类型的变量 m

代码语言:javascript
复制
var a Integer = 1 
var m Math = a
fmt.Println(m.Add(1))

对于值方法而言,进行接口赋值时传递 a 实例的指针引用也是可以的:

代码语言:javascript
复制
var a Integer = 1 
var m Math = &a
fmt.Println(m.Add(1))

因为对于非指针方法,Go 底层会自动生成一个与之对应的指针成员方法:

代码语言:javascript
复制
func (a *Integer) Add(i Integer) Integer { 
    return (*a).Add(i) 
}

func (a *Integer) Multiply(i Integer) Integer { 
    return (*a).Multiply(i) 
}

包含指针方法

不过如果 Integer 类型中包含了归属于指针的实现方法:

代码语言:javascript
复制
type Integer int

func (a *Integer) Add(b Integer) {
    *a = (*a) + b
}

func (a Integer) Multiply(b Integer) Integer {
    return a * b
}

type Math interface {
    Add(i Integer)
    Multiply(i Integer) Integer
}

那么在做接口赋值时,就只能传递指针类型的变量了:

代码语言:javascript
复制
var a Integer = 1
var m Math = &a
m.Add(2)
fmt.Printf("1 + 2 = %d\n", a)

因为 Integer 类型不包含指针方法(参考前面介绍的值方法与指针方法区别),所以此时只有 *Integer 类型实现了 Math 接口,如果我们直接将 a 的值类型赋值给 m,编译时会报错:

代码语言:javascript
复制
cannot use a (type Integer) as type Math in assignment:
    Integer does not implement Math (Add method has pointer receiver)

综上所述,如果 Integer 类中实现接口的成员方法都是值方法,则进行接口赋值时,传递类实例的值类型或者指针类型均可,否则只能传递指针类型实例,从代码性能角度来说,值拷贝需要消耗更多的内存空间,统一使用指针类型代码性能会更好。

三、将接口赋值给接口

接下来,我们来看如何将一个接口赋值给另一个接口:在 Go 语言中,只要两个接口拥有相同的方法列表(与顺序无关),那么它们就是等同的,可以相互赋值。不过,这里有一个前提,那就是接口变量持有的是基于对应实现类的实例值,所以接口与接口间的赋值是基于类实例与接口间的赋值的。

完全对等

下面我们来编写对应的示例代码,这是第一个接口 Number1

代码语言:javascript
复制
type Number1 interface {
    Equal(i int) bool
    LessThan(i int) bool
    MoreThan(i int) bool
}

这是第二个接口 Number2

代码语言:javascript
复制
type Number2 interface {
    Equal(i int) bool
    MoreThan(i int) bool
    LessThan(i int) bool
}

这里我们定义了两个接口,一个叫 Number1,一个叫 Number2,两者都定义三个相同的方法,只是顺序不同而已。在 Go 语言中,这两个接口实际上并无区别,因为:

  • 任何实现了 Number1 接口的类,也实现了 Number2
  • 任何实现了 Number1 接口的类实例都可以赋值给 Number2,反之亦然;
  • 在任何地方使用 Number1 接口与使用 Number2 并无差异。

接下来我们定义一个实现了这两个接口的类 Number

代码语言:javascript
复制
type Number int

func (n Number) Equal(i int) bool {
    return int(n) == i
}

func (n Number) LessThan(i int) bool {
    return int(n) < i
}

func (n Number) MoreThan(i int) bool {
    return int(n) > i
}

那么下面这些赋值代码都是合法的,会编译通过:

代码语言:javascript
复制
var num1 Number = 1
var num2 Number1 = num1 
var num3 Number2 = num2

方法子集

此外,接口赋值并不要求两个接口完全等价(方法完全相同)。如果接口 A 的方法列表是接口 B 的方法列表的子集,那么接口 B 也可以赋值给接口 A。例如,假设 Number2 接口定义如下:

代码语言:javascript
复制
type Number2 interface {
    Equal(i int) bool
    MoreThan(i int) bool
    LessThan(i int) bool
    Add(i int)
}

要让 Number 类继续保持实现这两个接口,需要在 Number 类定义中新增一个 Add 方法实现(这里定义了一个指针方法):

代码语言:javascript
复制
func (n *Number) Add(i int) {
    *n = *n + Number(i)
}

接下来,将上面的接口赋值语句改写如下即可:

代码语言:javascript
复制
var num1 Number = 1
var num2 Number2 = &num1
var num3 Number1 = num2 

这样一来,就实现了接口赋值,但是反过来不行:

代码语言:javascript
复制
var num1 Number = 1
var num2 Number1 = &num1
var num3 Number2 = num2   // 这一段编译出错

因为 Number1 接口中没有声明 Add 方法,或者换句话说,实现了 Number2 接口的类肯定实现了 Number1,但是实现了 Number1 接口的类不一定实现了 Number2。这句话是不是似曾相识?没错,这一点和 Java、PHP 中子类实例可以直接赋值给父类变量,而父类实例不能直接赋值给子类变量有异曲同工之妙,我们在学习新知识时要善于通过这种类比来降低学习成本,提高学习效率。

(本文完)

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

本文分享自 极客书房 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、接口赋值概述
  • 二、将类实例赋值给接口
    • 只包含值方法
      • 包含指针方法
      • 三、将接口赋值给接口
        • 完全对等
          • 方法子集
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档