浅谈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的资料请关注我们其它相关文章!

(0)

相关推荐

  • golang中struct和interface的基础使用教程

    前言 本文主要给大家介绍了关于golang中struct和interface的相关内容,是属于golang的基本知识,下面话不多说了,来一起看看详细的介绍吧. struct struct 用来自定义复杂数据结构,可以包含多个字段(属性),可以嵌套:go中的struct类型理解为类,可以定义方法,和函数定义有些许区别:struct类型是值类型. struct定义 type User struct { Name string Age int32 mess string } var user User

  • golang中interface接口的深度解析

    一 接口介绍 如果说gorountine和channel是支撑起Go语言的并发模型的基石,让Go语言在如今集群化与多核化的时代成为一道亮丽的风景,那么接口是Go语言整个类型系列的基石,让Go语言在基础编程哲学的探索上达到前所未有的高度.Go语言在编程哲学上是变革派,而不是改良派.这不是因为Go语言有gorountine和channel,而更重要的是因为Go语言的类型系统,更是因为Go语言的接口.Go语言的编程哲学因为有接口而趋于完美.C++,Java 使用"侵入式"接口,主要表现在实现

  • Go语言中你不知道的Interface详解

    前言 最近在看Go语言的面向对象的知识点时,发现它的面向对象能力全靠 interface 撑着,而且它的 interface 还与我们以前知道的 interface 完全不同.故而整个过程不断的思考为什么要如此设计?这样设计给我们带来了什么影响? interface 我不懂你 Rob Pike 曾说: 如果只能选择一个Go语言的特 性移植到其他语言中,他会选择接口 被Go语言设计者如此看重,想来 interface 一定是资质不凡,颜值爆表.但是说实话,当我第一次读这部分内容的时候,我产生了以下

  • golang语言如何将interface转为int, string,slice,struct等类型

    在golang中,interface{}允许接纳任意值,int,string,struct,slice等,因此我可以很简单的将值传递到interface{},例如: package main import ( "fmt" ) type User struct{ Name string } func main() { any := User{ Name: "fidding", } test(any) any2 := "fidding" test(a

  • Go之interface的具体使用

    浅显地了解了一下 Go,发现 Go 语法的设计非常简洁,易于理解.正应了 Go 语言之父 Rob Pike 说的那句"Less is more"-- 大道至简. 下面就具体的语法特性说说我自己的体会. interface 概览 与通常以类型层次与继承为根基的面向对象设计(OOP)语言(如C++.Java)不同,Go 的核心思想就是组合(composition).Go 进一步解耦了对象与操作,实现了真正的鸭子类型(Duck typing):一个对象如果能嘎嘎叫那就能当做鸭子,而不是像 C

  • golang基础之Interface接口的使用

    接口是一个或多个方法签名名的集合,定义方式如下 type Interface_Name interface { method_a() string method_b() int .... } 只要某个类型拥有该接口的所有方法签名,就算实现该接口,无需显示声明实现了那个接口,这称为structural Typing package main import "fmt" type USB interface { //定义一个接口:方法的集合 Name() string //Name方法,返回

  • Go语言实现类似c++中的多态功能实例

    前言 Go语言作为编程语言中的后起之秀,在博采众长的同时又不失个性,在注重运行效率的同时又重视开发效率,不失为一种好的开发语言.在go语言中,没有类的概念,但是仍然可以用struct+interface来实现类的功能,下面的这个简单的例子演示了如何用go来模拟c++中的多态的行为. 示例代码 package main import "os" import "fmt" type Human interface { sayHello() } type Chinese s

  • golang struct 实现 interface的方法

    golang中,一般strcut包含 interface类型后,struct类型都需要实现 interface导出的接口,从而成为相应的 interface接口类. 实际上,struct包含interface之后,并不需要实现interface的接口,也能成为 interface接口类. 代码如下: type newEr interface { New() } type testInterface interface { newEr Done() <-chan struct{} } type k

  • 使用go的interface案例实现多态范式操作

    看程序: package main import "fmt" type BaseIntf interface { Process() } type Msg1 struct { req int rsp int } func (p *Msg1) Process() { fmt.Println("process 1") } type Msg2 struct { req int rsp int } func (p *Msg2) Process() { fmt.Println

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

    目录 一.多态的含义 二.抽象类与接口 三.Golang中的接口 四.总结 一.多态的含义 对于Java或者是C++而言,我们在使用变量的时候,变量的类型是明确的.但是如果我们希望它可以宽松一点,比如说我们用父类指针或引用去调用方法,但是在执行的时候,能够根据子类的类型去执行子类当中的方法.也就是说实现我们用相同的调用方式调出不同结果或者是功能的情况,这种情况就叫做多态. 举个非常经典的例子,比如说猫.狗和人都是哺乳动物.这三个类都有一个say方法,大家都知道猫.狗以及人类的say是不一样的,猫

  • 浅谈Go语言中的结构体struct & 接口Interface & 反射

    结构体struct struct 用来自定义复杂数据结构,可以包含多个字段(属性),可以嵌套: go中的struct类型理解为类,可以定义方法,和函数定义有些许区别: struct类型是值类型. struct定义 type User struct { Name string Age int32 mess string } var user User var user1 *User = &User{} var user2 *User = new(User) struct使用 下面示例中user1和

  • 浅谈c语言中一种典型的排列组合算法

    c语言中的全排列算法和组合数算法在实际问题中应用非常之广,但算法有许许多多,而我个人认为方法不必记太多,最好只记熟一种即可,一招鲜亦可吃遍天 全排列: #include<stdio.h> void swap(int *p1,int *p2) { int t=*p1; *p1=*p2; *p2=t; } void permutation(int a[],int index,int size) { if(index==size) { for(int i=0;i<size;i++) print

  • 浅谈C语言共用体和与结构体的区别

    共用体与结构体的区别 共用体: 使用union 关键字 共用体内存长度是内部最长的数据类型的长度. 共用体的地址和内部各成员变量的地址都是同一个地址 结构体大小: 结构体内部的成员,大小等于最后一个成员的偏移量+最后一个成员大小+末尾的填充字节数. 结构体的偏移量:某一个成员的实际地址和结构体首地址之间的距离. 结构体字节对齐:每个成员相对于结构体首地址的偏移量都得是当前成员所占内存大小的整数倍,如果不是会在成员前面加填充字节.结构体的大小是内部最宽的成员的整数倍. 共用体 #include <

  • 浅谈C语言的字符串分割

    说起来很有意思,自认为对C语言理解得还是比较深刻的.但居然到今天才知道有个strtok函数,试用了一下突然感慨以前做了多少重复劳动.每次需要解析配置文件,每次需要分割字符串,居然都是自己去分割字符串,既累人又容易出错.感概技术学得不够全面啊!这里引用一段strtok用法: The strtok() function returns a pointer to the next "token" in str1, where str2 contains the delimiters that

  • 浅谈C语言之字符串处理函数

    下面介绍8种基本的常用的字符串处理函数,在数值数组中也常常用到(部分函数).所有的C语言编译系统中一般都提供这些函数. 1.puts函数--输出字符串的函数 一般的形式为puts(字符串组) 作用:将一个字符串输出到终端.如,char一个string,并赋予初值.调用puts(string);进行字符串的输出. 2.gets函数--输入字符串的函数 一般的形式:gets(字符数组) 作用:从终端输入一个字符串到字符数组,并且得到一个函数值成为字符数组的起始地址. gets(str); 键盘输入,

  • 浅谈C语言转义字符和格式控制符

    转义字符参考: \a:蜂鸣,响铃 \b:回退:向后退一格 \f:换页 \n:换行,光标到下行行首 \r:回车,光标到本行行首 \t:水平制表 \v:垂直制表 \\:反斜杠 \':单引号 \":双引号 \?:问号 \ddd:三位八进制 \xhh:二位十六进制 \0:空字符(NULL),什么都不做 注: 1,\v垂直制表和\f换页符对屏幕没有任何影响,但会影响打印机执行响应操作. 2,\n其实应该叫回车换行.换行只是换一行,不改变光标的横坐标:回车只是回到行首,不改变光标的纵坐标. 3,\t 光标向

  • 浅谈C语言函数调用参数压栈的相关问题

    参数入栈的顺序 以前在面试中被人问到这样的问题,函数调用的时候,参数入栈的顺序是从左向右,还是从右向左.参数的入栈顺序主要看调用方式,一般来说,__cdecl 和__stdcall 都是参数从右到左入栈. 看下面的代码: #include <stdio.h> int test(int a, int b) { printf("address of a %x.\n", &a); printf("address of b %x.\n", &b)

  • 浅谈c语言中类型隐性转换的坑

    谨记:在C语言中,当两种不同类型之间运算时,低字节长度类型会向高自己长度类型转换,有符号会向无符号类型转换. 举例子如下: #include <stdio.h> void func(void) { int i = 1; unsigned char c1 = 1; signed char c2 = -1; if (c2 > i){ printf("\r\n -1 > 1"); } else{ printf("\r\n -1 <= 1");

  • 浅谈c语言中转义字符的用法及注意事项

    c语言中的转义字符: \a 响铃符 \b 退格 \f 换页符 \n 换行符 \r 回车符(回到该行的首位置) \v 纵向制表符 \\ 反斜杠 \? 问号(?经vs10测试可以直接打印) \"(\') 双引号(单引号) \ooo 八进制数(ooo表示一个用8进制数表示出来的对应ANSII代码对应出字符,用此方法可以表示出所有ASCII字符.不过测试发现打不出%号,存疑!) \xhh 十六进制数(功能同八进制数,用hh表示一个十六进制数,如\x20表示空格) 注:使用转义字符的退格符,换行符,回车符

随机推荐