重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
本篇内容主要讲解“怎么理解Go语言多态与interface”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么理解Go语言多态与interface”吧!
专注于为中小企业提供网站设计制作、成都网站建设服务,电脑端+手机端+微信端的三站合一,更高效的管理,为中小企业柳城免费做网站提供优质的服务。我们立足成都,凝聚了一批互联网行业人才,有力地推动了成百上千企业的稳健成长,帮助中小企业通过网站建设实现规模扩充和转变。
对于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) { Listmammals = 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当中也有接口,但是它的理念和使用方法和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”有了更深的了解,不妨来实际操作一番吧!这里是创新互联网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!