两分钟让你明白Go中如何继承

最近在重构代码的时候,抽象了大量的接口。也使用这些抽象的接口做了很多伪继承的操作,极大的减少了代码冗余,同时也增加了代码的可读性。

然后随便搜了一下关于Go继承的文章,发现有的文章的代码量过多,并且代码format极其粗糙,命名极其随意,类似于A、B这种,让人看着看着就忘了到底是谁继承谁,我又要回去看一遍逻辑。

虽然只是样例代码,我认为仍然需要做到简洁、清晰以及明了。这也是我为什么要写这篇博客的原因。接下里在这里简单分享一下在Go中如何实现继承。

1. 简单的组合

说到继承我们都知道,在Go中没有extends关键字,也就意味着Go并没有原生级别的继承支持。这也是为什么我在文章开头用了伪继承这个词。本质上,Go使用interface实现的功能叫组合,Go是使用组合来实现的继承,说的更精确一点,是使用组合来代替的继承,举个很简单的例子。

1.1 实现父类

我们用很容易理解的动物-来举例子,废话不多说,直接看代码。

type Animal struct {
    Name string
}

func (a *Animal) Eat() {
    fmt.Printf("%v is eating", a.Name)
    fmt.Println()
}

type Cat struct {
    *Animal
}

cat := &Cat{
    Animal: &Animal{
        Name: "cat",
    },
}
cat.Eat() // cat is eating

1.2 代码分析

首先,我们实现了一个Animal的结构体,代表动物类。并声明了Name字段,用于描述动物的名字。

然后,实现了一个以Animal为receiver的Eat方法,来描述动物进食的行为。

最后,声明了一个Cat结构体,组合了Cat字段。再实例化一个猫,调用Eat方法,可以看到会正常的输出。

可以看到,Cat结构体本身没有Name字段,也没有去实现Eat方法。唯一有的就是组合了Animal父类,至此,我们就证明了已经通过组合实现了继承。

2. 优雅的组合

熟悉Go的人看到上面的代码可能会发出如下感叹

这也太粗糙了吧 -- By 鲁迅:我没说过这句话

的确,上面的仅仅是为了给还没有了解过Go组合的人看的。作为一个简单的例子来理解Go的组合继承,这是完全没有问题的 。但如果要运用在真正的开发中,那还是远远不够的。

举个例子,我如果是这个抽象类的使用者,我拿到animal类不能一目了然的知道这个类干了什么,有哪些方法可以调用。以及,没有统一的初始化方式,这意味着凡是涉及到初始化的地方都会有重复代码。如果后期有初始化相关的修改,那么只有一个一个挨着改。所以接下来,我们对上述的代码做一些优化。

2.1 抽象接口

接口用于描述某个类的行为。例如,我们即将要抽象的动物接口就会描述作为一个动物,具有哪些行为。常识告诉我们,动物可以进食(Eat),可以发出声音(bark),可以移动(move)等等。这里有一个很有意思的类比。

接口就像是一个招牌,比如一家星巴克。星巴克就是一个招牌(接口)。

你看到这个招牌会想到什么?美式?星冰乐?抹茶拿铁?又或者是拿铁,甚至是店内的装修风格。

这就是一个好的接口应该达到的效果,同样这也是为什么我们需要抽象接口。

// 模拟动物行为的接口
type IAnimal interface {
    Eat() // 描述吃的行为
}

// 动物 所有动物的父类
type Animal struct {
    Name string
}

// 动物去实现IAnimal中描述的吃的接口
func (a *Animal) Eat() {
    fmt.Printf("%v is eating\n", a.Name)
}

// 动物的构造函数
func newAnimal(name string) *Animal {
    return &Animal{
        Name: name,
    }
}

// 猫的结构体 组合了animal
type Cat struct {
    *Animal
}

// 实现猫的构造函数 初始化animal结构体
func newCat(name string) *Cat {
    return &Cat{
        Animal: newAnimal(name),
    }
}

cat := newCat("cat")
cat.Eat() // cat is eating

在Go中其实没有关于构造函数的定义。例如我们在Java中可以使用构造函数来初始化变量,举个很简单的例子,Integer num = new Integer(1)。而在Go中就需要使用者自己通过结构体的初始化来模拟构造函数的实现。

然后在这里我们实现子类Cat,使用组合的方式代替继承,来调用Animal中的方法。运行之后我们可以看到,Cat结构体中并没有Name字段,也没有实现Eat方法,但是仍然可以正常运行。这证明我们已经通过组合的方式了实现了继承。

2.2 重载方法

// 猫结构体IAnimal的Eat方法
func (cat *Cat) Eat() {
    fmt.Printf("children %v is eating\n", cat.Name)
}

cat.Eat()
// children cat is eating

可以看到,Cat结构体已经重载了Animal中的Eat方法,这样就实现了重载。

2.3 参数多态

什么意思呢?举个例子,我们要如何在Java中解决函数的参数多态问题?熟悉Java的可能会想到一种解决方案,那就是通配符。用一句话概括,使用了通配符可以使该函数接收某个类的所有父类型或者某个类的所有子类型。但是我个人认为对于不熟悉Java的人来说,可读性不是特别友好。

而在Go中,就十分方便了。

func check(animal IAnimal) {
    animal.Eat()
}

在这个函数中就可以处理所有组合了Animal的单位类型,对应到Java中就是上界通配符,即一个可以处理任何特定类型以及是该特定类型的派生类的通配符,再换句人话,啥动物都能处理。

3. 总结

凡事都有两面性,做优化也不例外。大量的抽象接口的确可以精简代码,让代码看起来十分优雅、舒服。但是同样,这会给其他不熟悉的人review代码造成理解成本。想象你看某段代码,全是接口,点了好几层才能看到实现。更有的,往下找着找着突然就在另一个接口处断掉了,必须要手动的去另一个注册的地方去找。

这就是我认为优化的时候要面临的几个问题:

  • 优雅
  • 可读
  • 性能

有的时候我们很难做到三个方面都兼顾,例如这样写代码看起来很难受,但是性能要比优雅的代码好。再例如,这样写看起来很优雅,但是可读性很差等等。

还是引用我之前博客中经常写的一句话

适合自己的才是最好的

这种时候只能根据自己项目的特定情况,选择最适合你的解决方案。没有万能的解决方案。

分享一句最近弹吉他看到的毒鸡汤,学习也是一样的。

练琴的路上没有捷径,全是弯路

往期文章:

相关:

  • 微信公众号: SH的全栈笔记(或直接在添加公众号界面搜索微信号LunhaoHu)

原文地址:https://www.cnblogs.com/detectiveHLH/p/11738467.html

时间: 2024-10-10 04:46:14

两分钟让你明白Go中如何继承的相关文章

两分钟让你明白什么是ERP

把专业的问题通俗化——    ERP(Enterprise Resource Planning)企业资源计划系统,是指建立在信息技术基础上,以系统化的管理思想,为企业决策层及员工提供决策运行手段的管理平台.        不知各位听过这么一句话没有,“把简单的事情搞复杂了,太累:把复杂的事简单化了,贡献.”在许多企业者看来,ERP(Enterprise Resource Planning)是一个很复杂的庞大的系统,要想当一次吃螃蟹的人还真不容易,这也在一定程度上阻碍了中国企业的信息化进程.在当今

5.两分钟让你明白app后端有啥用

app后端,也称为app后台,称呼不一样,但指的是同一个东西. 我一直都以app后端有啥用这个问题不用解释.但在网络上,有准备进行app创业的网友(是从传统行业过来的)问过这个问题,我这里就以app后端的两个主要功能简单的介绍一下. 注意,app后端没有明确的定义,范围也很广,所以我解释的时候只抽取app后端两个主要的功能解析.为了易懂,会在一定程度下牺牲准确性.如果已经了解app后端有啥用的小伙伴,就不用再往下看了. 场景一: 用户a平时是很喜欢用qq音乐app听歌,他的qq音乐app保存了他

从头认识js-js中的继承

要彻底弄明白js中的继承,我们首先要弄清楚js中的一个很重要的概念那就是原型链. 1.什么是原型链? 我们知道每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针.如果,让原型对象等于另一个引用类型的实例,那么原型对象中将包含一个指向另一个原型的指针,相应地,另一个原型对象中包含着一个指向另一个构造函数的指针.假如另一个原型对象又是另一个引用类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型之间的链条.这就是所谓原型链的基本概念

两分钟彻底让你明白Android Activity生命周期(图文)!

首先看一下Android api中所提供的Activity生命周期图(不明白的,可以看完整篇文章,在回头看一下这个图,你会明白的): Activity其实是继承了ApplicationContext这个类,我们可以重写以下方法,如下代码: 1 public class Activity extends ApplicationContext { 2         protected void onCreate(Bundle savedInstanceState);        3       

Git 两分钟指南

转载: http://linux.cn/article-4704-1.html 受到Git五分钟指南的启发,我决定更进一步,写一篇更短时间内就能看完的Git教程.当然,这是指最简单的Git!但是对于新手个人开发者来说足够了,同时也给你提供一个可以继续深入的起点. 能够从本指南有所收获的例子也许是这样的,一个高中生正在进行他(或是她)的第一个项目,而且并不需要和别人分享代码.(具体讲其实是我儿子,他已 经写了很多代码,但是没有时间去学习一个版本控制工具.这份指南是为他写的,当然我觉得别人也能用的到

Visual C#两分钟搭建BHO IE钩子

微软在1997年正式推出Browser Helper Object (BHO), 使程序员能够更好的对IE进行二次开发和操作. 在通过编写BHO程序数月后, 我希望把我的一些经验告诉才开始的同志, 避免走一些弯路. 我本人是非常喜欢C++的. 因为C++对内存直接操作的方式可以节省非常多的内存损耗, 也更快一些. 但是在开发BHO的时候, 我的确也认识到, C#明显要比C++强大很多. 例如C#提供的FOREACH 循环就可以避免FOR循环产生的溢出. 另外C#的类型转换也明显要强于C++. 毕

两分钟学会Unity3D布娃娃的使用

在RPG游戏中,为了让人物的死亡更加真实,unity创建布娃娃系统,搞的跟真的一样,尼玛我差点就相信那是真的了. 1.首先打开unity,创建地形,导入已经准备好的人物模块. 2.project下选中该模型,再为该模型添加布娃娃属性:GameObject->Create Other->RagDoll 3.在弹出的框中将布娃娃的各个结点设置进去 4.一定要去掉该人物的Box Collider属性,运行程序,就可以看到人物的死亡效果啦. 两分钟学会Unity3D布娃娃的使用,布布扣,bubuko.

Git两分钟指南 | 程序师【转】

我们发布了很多Git相关的文章,有系列教程(<Pro Git系列>),也有各种独门绝技(<让你的Git水平更上一层楼的10个小贴士>).但这篇两分钟的Git指南算是最精简的.如果想了解更多Git相关的文章,请从这里开始. 以下是Git两分钟指南的正文内容. 受到Git五分钟指南的启发,我决定更进一步,写一篇更短时间内就能看完的Git教程.当然,这是指最简单的Git!但是对于新手个人开发者来说足够了,同时也给你提供一个可以继续深入的起点. 能够从本指南有所收获的例子也许是这样的,一个

Android零基础入门第30节:两分钟掌握FrameLayout帧布局

原文:Android零基础入门第30节:两分钟掌握FrameLayout帧布局 前面学习了线性布局.相对布局.表格布局,那么本期来学习第四种布局--FrameLayout帧布局. 一.认识FrameLayout 帧布局是Android布局中最简单的一种,使用FrameLayout标签. 帧布局为每个加入其中的控件创建一个空白区域(称为一帧,每个控件占据一 帧).釆用帧布局方式设计界面时,只能在屏幕左上角显示一个控件,如果添加多个控件,这些控件会按照顺序在屏幕的左上角重叠显示. 下表显示了 Fra