浅谈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 相关文章推荐
golang判断key是否在map中的代码
Apr 24 Golang
golang 如何通过反射创建新对象
Apr 28 Golang
golang 如何用反射reflect操作结构体
Apr 28 Golang
解决Golang time.Parse和time.Format的时区问题
Apr 29 Golang
Go标准容器之Ring的使用说明
May 05 Golang
Go 自定义package包设置与导入操作
May 06 Golang
Golang 获取文件md5校验的方法以及效率对比
May 08 Golang
go语言使用Casbin实现角色的权限控制
Jun 26 Golang
Go语言应该什么情况使用指针
Jul 25 Golang
Golang 1.18 多模块Multi-Module工作区模式的新特性
Apr 11 Golang
Go本地测试解耦任务拆解及沟通详解Go本地测试的思路沟通的重要性总结
Jun 21 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
php fputcsv命令 写csv文件遇到的小问题(多维数组连接符)
2011/05/24 PHP
不重新编译PHP为php增加openssl模块的方法
2011/06/14 PHP
kohana框架上传文件验证规则写法示例
2014/07/14 PHP
thinkphp使用phpmailer发送邮件的方法
2014/11/24 PHP
浅谈PHP中output_buffering
2015/07/13 PHP
Symfony学习十分钟入门经典教程
2016/02/03 PHP
JavaScript 判断判断某个对象是Object还是一个Array
2010/01/28 Javascript
javascript标签在页面中的位置探讨
2013/04/11 Javascript
jQuery统计指定子元素数量的方法
2015/03/17 Javascript
Javascript对象Clone实例分析
2015/06/09 Javascript
jQuery flip插件实现的翻牌效果示例【附demo源码下载】
2016/09/20 Javascript
微信小程序 实战小程序实例
2016/10/08 Javascript
基于Vue实例生命周期(全面解析)
2017/08/16 Javascript
解决在vue项目中webpack打包后字体不生效的问题
2018/09/01 Javascript
JS中验证整数和小数的正则表达式
2018/10/08 Javascript
从零开始用electron手撸一个截屏工具的示例代码
2018/10/10 Javascript
JavaScript实现与使用发布/订阅模式详解
2019/01/19 Javascript
如何通过setTimeout理解JS运行机制详解
2019/03/23 Javascript
vue draggable resizable 实现可拖拽缩放的组件功能
2019/07/15 Javascript
用JS实现选项卡
2020/03/23 Javascript
vue实现列表滚动的过渡动画
2020/06/29 Javascript
Python使用正则表达式抓取网页图片的方法示例
2017/04/21 Python
python http接口自动化脚本详解
2018/01/02 Python
浅谈python之新式类
2018/08/12 Python
基于Python中isfile函数和isdir函数使用详解
2019/11/29 Python
Python爬虫爬取杭州24时温度并展示操作示例
2020/03/27 Python
pycharm激活码2020最新分享适用pycharm2020最新版亲测可用
2020/11/22 Python
18-35岁旅游团的全球领导者:Contiki
2017/02/08 全球购物
GAP阿联酋官网:GAP UAE
2017/11/30 全球购物
党员承诺书范文
2014/05/19 职场文书
文秘专业应届生求职信
2014/05/26 职场文书
公司行政助理岗位职责
2015/04/11 职场文书
音乐会主持人开场白
2015/05/28 职场文书
基石观后感
2015/06/12 职场文书
2019年描写人生经典诗句大全
2019/07/08 职场文书
golang 实现Location跳转方式
2021/05/02 Golang