浅谈Go语言多态的实现与interface使用


Posted in Golang onJune 16, 2021

一、多态的含义

对于Java或者是C++而言,我们在使用变量的时候,变量的类型是明确的。但是如果我们希望它可以宽松一点,比如说我们用父类指针或引用去调用方法,但是在执行的时候,能够根据子类的类型去执行子类当中的方法。也就是说实现我们用相同的调用方式调出不同结果或者是功能的情况,这种情况就叫做多态。

举个非常经典的例子,比如说猫、狗和人都是哺乳动物。这三个类都有一个say方法,大家都知道猫、狗以及人类的say是不一样的,猫可能是喵喵叫,狗是汪汪叫,人类则是说话。

class Mammal {
    public void say() {
    System.out.println("do nothing")
    }
}


class Cat extends Mammal{
    public void say() {
    System.out.println("meow");
    }
}


class Dog extends Mammal{
    public void say() {
    System.out.println("woof");
    }
}

class Human extends Mammal{
    public void say() {
    System.out.println("speak");
    }
}

这段代码大家应该都不难看懂,这三个类都是Mammal的子类,假设这个时候我们有一系列实例,它们都是Mammal的子类的实例,但是这三种类型都有,我们希望用一个循环来一起全都调用了。虽然我们接收变量的时候是用的Mammal的父类类型去接收的,但是我们调用的时候却会获得各个子类的运行结果。

比如这样:

class Main {
    public static void main(String[] args) {
        List<Mammal> mammals = new ArrayList<>();
        mammals.add(new Human());
        mammals.add(new Dog());
        mammals.add(new Cat());
        
        for (Mammal mammal : mammals) {
            mammal.say();
        }
    }
}

不知道大家有没有get到精髓,我们创建了一个父类的List,将它各个子类的实例放入了其中。然后通过了一个循环用父类对象来接收,并且调用了say方法。我们希望虽然我们用的是父类的引用来调用的方法,但是它可以自动根据子类的类型调用对应不同子类当中的方法。

也就是说我们得到的结果应该是:

speak

woof

meow

这种功能就是多态,说白了我们可以在父类当中定义方法,在子类当中创建不同的实现。但是在调用的时候依然还是用父类的引用去调用,编译器会自动替我们做好内部的映射和转化。

二、抽象类与接口

这样实现当然是可行的,但其实有一个小小的问题,就是Mammal类当中的say方法多余了。因为我们使用的只会是它的子类,并不会用到Mammal这个父类。所以我们没必要实现父类Mammal中的say方法,做一个标记,表示有这么一个方法,子类实现的时候需要实现它就可以了。

这就是抽象类和抽象方法的来源,我们可以把Mammal做成一个抽象类,声明say是一个抽象方法。抽象类是不能直接创建实例的,只能创建子类的实例,并且抽象方法也不用实现,只需要标记好参数和返回就行了。具体的实现都在子类当中进行。说白了抽象方法就是一个标记,告诉编译器凡是继承了这个类的子类必须要实现抽象方法,父类当中的方法不能调用。那抽象类就是含有抽象方法的类。

我们写出Mammal变成抽象类之后的代码:

abstract class Mammal {
    abstract void say();
}

很简单,因为我们只需要定义方法的参数就可以了,不需要实现方法的功能,方法的功能在子类当中实现。由于我们标记了say这个方法是一个抽象方法,凡是继承了Mammal的子类都必须要实现这个方法,否则一定会报错。

抽象类其实是一个擦边球,我们可以在抽象类中定义抽象的方法也就是只声明不实现,也可以在抽象类中实现具体的方法。在抽象类当中非抽象的方法子类的实例是可以直接调用的,和子类调用父类的普通方法一样。但假如我们不需要父类实现方法,我们提出提取出来的父类中的所有方法都是抽象的呢?针对这一种情况,Java当中还有一个概念叫做接口,也就是interface,本质上来说interface就是抽象类,只不过是只有抽象方法的抽象类。

所以刚才的Mammal也可以写成:

interface Mammal {
    void say();
}

把Mammal变成了interface之后,子类的实现没什么太大的差别,只不过将extends关键字换成了implements。另外,子类只能继承一个抽象类,但是可以实现多个接口。早先的Java版本当中,interface只能够定义方法和常量,在Java8以后的版本当中,我们也可以在接口当中实现一些默认方法和静态方法。

接口的好处是很明显的,我们可以用接口的实例来调用所有实现了这个接口的类。也就是说接口和它的实现是一种要宽泛许多的继承关系,大大增加了灵活性。

以上虽然全是Java的内容,但是讲的其实是面向对象的内容,如果没有学过Java的小伙伴可能看起来稍稍有一点点吃力,但总体来说问题不大,没必要细扣当中的语法细节,get到核心精髓就可以了。

讲这么一大段的目的是为了厘清面向对象当中的一些概念,以及接口的使用方法和理念,后面才是本文的重头戏,也就是Go语言当中接口的使用以及理念。

三、Golang中的接口

Golang当中也有接口,但是它的理念和使用方法和Java稍稍有所不同,它们的使用场景以及实现的目的是类似的,本质上都是为了抽象。通过接口提取出了一些方法,所有继承了这个接口的类都必然带有这些方法,那么我们通过接口获取这些类的实例就可以使用了,大大增加了灵活性。

但是Java当中的接口有一个很大的问题就是侵入性,说白了就是会颠倒供需关系。举个简单的例子,假设你写了一个爬虫从各个网页上爬取内容。爬虫爬到的内容的类别是很多的,有图片、有文本还有视频。假设你想要抽象出一个接口来,在这个接口当中定义你规定的一些提取数据的方法。这样不论获取到的数据的格式是什么,你都可以用这个接口来调用。这本身也是接口的使用场景,但问题是处理图片、文本以及视频的组件可能是开源或者是第三方的,并不是你开发的。你定义接口并没有什么卵用,别人的代码可不会继承这个接口。

当然这也是可以解决的, 比如你可以在这些第三方工具库外面自己封装一层,实现你定义的接口。这样当然是OK的,但是显然比较麻烦。

Golang当中的接口解决了这个问题,也就是说它完全拿掉了原本弱化的继承关系,只要接口中定义的方法能对应的上,那么就可以认为这个类实现了这个接口。

我们先来创建一个interface,当然也是通过type关键字:

type Mammal interface {
 Say()
}

我们定义了一个Mammal的接口,当中声明了一个Say函数。也就是说只要是拥有这个函数的结构体就可以用这个接口来接收,我们和刚才一样,定义Cat、Dog和Human三个结构体,分别实现各自的Say方法:

type Dog struct{}

type Cat struct{}

type Human struct{}

func (d Dog) Say() {
 fmt.Println("woof")
}

func (c Cat) Say() {
 fmt.Println("meow")
}

func (h Human) Say() {
 fmt.Println("speak")
}

之后,我们尝试使用这个接口来接收各种结构体的对象,然后调用它们的Say方法:

func main() {
    var m Mammal
    m = Dog{}
    m.Say()
    m = Cat{}
    m.Say()
    m = Human{}
    m.Say()
}

出来的结果当然和我们预想的一样:

浅谈Go语言多态的实现与interface使用

四、总结

今天我们一起聊了面向对象中多态以及接口的概念,借此进一步了解了为什么golang中的接口设计非常出色,因为它解耦了接口和实现类之间的联系,使得进一步增加了我们编码的灵活度,解决了供需关系颠倒的问题。但是世上没有绝对的好坏,golang中的接口在方便了我们编码的同时也带来了一些问题,比如说由于没了接口和实现类的强绑定,其实也一定程度上增加了开发和维护的成本。

总体来说这是一个仁者见仁的改动,有些写惯了Java的同学可能会觉得没有必要,这是过度解绑,有些人之前深受其害的同学可能觉得这个进步非常关键。但不论你怎么看,这都不影响我们学习它,毕竟学习本身是不带立场的。今天的内容当中包含一些Java和面向对象的概念,只是用来引出后面golang的内容,如果存在部分不理解的地方,希望大家抓大放小,理解核心关键就好了,不需要细扣每一个细节。

以上就是浅谈Go语言多态的实现与interface使用的详细内容,更多关于Go 多态与interface的资料请关注三水点靠木其它相关文章!

Golang 相关文章推荐
Go语言中的UTF-8实现
Apr 26 Golang
golang 实现对Map进行键值自定义排序
Apr 28 Golang
golang import自定义包方式
Apr 29 Golang
解决go在函数退出后子协程的退出问题
Apr 30 Golang
golang 定时任务方面time.Sleep和time.Tick的优劣对比分析
May 05 Golang
Golang 实现获取当前函数名称和文件行号等操作
May 08 Golang
Golang 1.18 多模块Multi-Module工作区模式的新特性
Apr 11 Golang
golang连接MySQl使用sqlx库
Apr 14 Golang
详解Go语言中Get/Post请求测试
Jun 01 Golang
Go Grpc Gateway兼容HTTP协议文档自动生成网关
Jun 16 Golang
Go语言怎么使用变长参数函数
Jul 15 Golang
Go语言编译原理之源码调试
Aug 05 Golang
再次探讨go实现无限 buffer 的 channel方法
Jun 13 #Golang
Go遍历struct,map,slice的实现
Jun 13 #Golang
go web 预防跨站脚本的实现方式
Jun 11 #Golang
Golang生成Excel文档的方法步骤
Go timer如何调度
浅谈Golang 切片(slice)扩容机制的原理
Jun 09 #Golang
Golang中异常处理机制详解
You might like
PHP4实际应用经验篇(8)
2006/10/09 PHP
PHP学习之正则表达式
2011/04/17 PHP
PHP文件管理之实现网盘及压缩包的功能操作
2017/09/20 PHP
JavaScript中this关键字使用方法详解
2007/03/08 Javascript
Ext JS Grid在IE6 下宽度的问题解决方法
2009/02/15 Javascript
JQuery验证工具类搜集整理
2013/01/16 Javascript
js实现网页多级级联菜单代码
2015/08/20 Javascript
分享15个大家都熟知的jquery小技巧
2015/12/02 Javascript
jQuery实现布局高宽自适应的简单实例
2016/05/28 Javascript
详解微信小程序 相对定位和绝对定位
2017/05/11 Javascript
理解javascript async的用法
2017/08/22 Javascript
Vue.js 2.5新特性介绍(推荐)
2017/10/24 Javascript
D3.js实现拓扑图的示例代码
2018/06/30 Javascript
详解node.js创建一个web服务器(Server)的详细步骤
2021/01/15 Javascript
[38:39]完美世界DOTA2联赛循环赛 IO vs GXR BO2第二场 11.04
2020/11/05 DOTA
[38:38]完美世界DOTA2联赛PWL S3 access vs Rebirth 第二场 12.17
2020/12/18 DOTA
python使用nntp读取新闻组内容的方法
2015/05/08 Python
python命令行解析之parse_known_args()函数和parse_args()使用区别介绍
2018/01/24 Python
python中yaml配置文件模块的使用详解
2018/04/27 Python
python浪漫表白源码
2019/04/05 Python
Python实现Mysql数据统计及numpy统计函数
2019/07/15 Python
Python使用贪婪算法解决问题
2019/10/22 Python
python利用paramiko实现交换机巡检的示例
2020/09/22 Python
HTML5边玩边学(3)像素和颜色
2010/09/21 HTML / CSS
全球知名的珠宝首饰品牌:Kay Jewelers
2018/02/11 全球购物
文明礼仪小标兵事迹
2014/01/12 职场文书
党校培训自我鉴定范文
2014/03/20 职场文书
工程售后服务方案
2014/06/08 职场文书
市场营销专业应届生自荐信
2014/06/19 职场文书
社区活动策划方案
2014/08/21 职场文书
战略性融资合作协议书范本
2014/10/17 职场文书
给客户的检讨书
2014/12/21 职场文书
SqlServer 垂直分表(减少程序改动)
2021/04/16 SQL Server
css3中2D转换之有趣的transform形变效果
2022/02/24 HTML / CSS
css3应用示例:新增的选择器
2022/03/16 HTML / CSS
关于MySQL临时表为什么可以重名的问题
2022/03/22 MySQL