浅谈Go语⾔多态的实现与interface使⽤
⽬录
⼀、多态的含义
⼆、抽象类与接⼝
三、Golang中的接⼝
四、总结
⼀、多态的含义
对于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()
}
出来的结果当然和我们预想的⼀样:
四、总结
今天我们⼀起聊了⾯向对象中多态以及接⼝的概念,借此进⼀步了解了为什么golang中的接⼝设计⾮常出⾊,因为它解耦了接⼝和实现类之间的联系,使得进⼀步增加了我们编码的灵活度,解决了供需关系颠倒的问题。但是世上没有绝对的好
坏,golang中的接⼝在⽅便了我们编码的同时也带来了⼀些问题,⽐如说由于没了接⼝和实现类的强绑定,其实也⼀定程度上增加了开发和维护的成本。
总体来说这是⼀个仁者见仁的改动,有些写惯了Java的同学可能会觉得没有必要,这是过度解绑,有些⼈之前深受其害的同学可能觉得这个进步⾮常关键。但不论你怎么看,这都不影响我们学习它,毕竟学习本⾝是不带⽴场的。今天的内容当中包含⼀些Java和⾯向对象的概念,只是⽤来引出后⾯golang的内容,如果存在部分不理解的地⽅,希望⼤家抓⼤放⼩,理解核⼼关键就好了,不需要细扣每⼀个细节。
以上就是浅谈Go语⾔多态的实现与interface使⽤的详细内容,更多关于Go 多态与interface的资料请关注其它相关⽂章!

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。