Go 面向对象编程的三大特性:封装、继承和多态。

  • 封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式
  • 继承:使得子类具有父类的属性和方法或者重新定义、追加属性和方法等
  • 多态:不同对象中同种行为的不同实现方式

Go 语言的结构体(struct)和其他语言的类(class)有同等的地位,但 Go 语言放弃了包括继
承在内的大量面向对象特性,只保留了组合(composition)这个最基础的特性。

例如,我们要定义一个矩形类型

type Rect struct {x, y          float64width, height float64
}

然后我们定义成员方法 Area() 来计算矩形的面积:

func (r *Rect) Area() float64 {return r.width * r.height
}

在定义了 Rect 类型后,该如何创建并初始化 Rect 类型的对象实例呢?这可以通过如下几种方法实现:

rect1 := new(Rect)
rect2 := &Rect{}
rect3 := &Rect{0, 0, 100, 200}
rect4 := &Rect{width: 100, height: 200}

Go 语言中,未进行显式初始化的变量都会被初始化为该类型的零值,例如 bool 类型的零值为 falseint 类型的零值为 0,string 类型的零值为空字符串。

Go 语言中没有构造函数的概念,对象的创建通常交由一个全局的创建函数来完成,以 NewXXX来命名,表示“构造函数”:

func NewRect(x, y, width, height float64) *Rect {return &Rect{x, y, width, height}
}

详细参见:https://blog.csdn.net/wohu1104/article/details/106202892 中的结构体初始化章节。

1. 封装

package mainimport "fmt"type data struct {val int
}func (p_data *data) set(num int) {p_data.val = num
}func (p_data *data) show() {fmt.Println(p_data.val)
}func main() {p := &data{4}p.set(10)p.show()
}

或者

package mainimport ("fmt"
)// 矩形结构体
type Rectangle struct {Length intWidth  int
}// 计算矩形面积
func (r *Rectangle) Area() int {return r.Length * r.Width
}func main() {r := Rectangle{4, 2}// 调用 Area() 方法,计算面积fmt.Println(r.Area())
}

2. 继承

确切地说,Go 语言也提供了继承,但是采用了组合的文法,所以我们将其称为匿名组合,Go 语言的继承方式采用的是匿名组合的方式。

package maintype Base struct {Name string
}func (base *Base) Foo() { ... }
func (base *Base) Bar() { ... }type Foo struct {Base...
}func (foo *Foo) Bar() {foo.Base.Bar()...
}

以上代码定义了一个 Base 类(实现了 Foo()Bar() 两个成员方法),然后定义了一个 Foo 类,该类从Base 类“继承”并改写了 Bar() 方法(该方法实现时先调用了基类的 Bar() 方法)。

在“派生类” Foo 没有改写“基类” Base 的成员方法时,相应的方法就被“继承”,例如在上面的例子中,调用foo.Foo() 和调用 foo.Base.Foo() 效果一致。

此外,在 Go 语言中你还可以随心所欲地修改内存布局,如:

type Foo struct {... // 其他成员Base
}

这段代码从语义上来说,和上面给的例子并无不同,但内存布局发生了改变。“基类” Base 的数据放在了“派生类” Foo 的最后。

另外,在 Go 语言中,你还可以以指针方式从一个类型“派生”

type Foo struct {*Base...
}

这段 Go 代码仍然有“派生”的效果,只是 Foo 创建实例的时候,需要外部提供一个 Base 类实例的指针。

另外,我们必须关注一下接口组合中的名字冲突问题,比如如下的组合:

type X struct {Name string
}type Y struct {XName string
}

组合的类型和被组合的类型都包含一个 Name 成员,会不会有问题呢?

答案是否定的。所有的 Y 类型的 Name 成员的访问都只会访问到最外层的那个 Name 变量,X.Name 变量相当于被隐藏起来了。

那么下面这样的场景呢:

type Logger struct {Level int
}type Y struct {*LoggerName string*log.Logger
}

显然这里会有问题。因为之前已经提到过,匿名组合类型相当于以其类型名称(去掉包名部分)作为成员变量的名字。按此规则,Y 类型中就相当于存在两个名为 Logger 的成员,虽然类型不同。

因此,我们预期会收到编译错误。有意思的是,这个编译错误并不是一定会发生的。假如这两个 Logger在定义后再也没有被用过,那么编译器将直接忽略掉这个冲突问题,直至开发者开始使用其中的某个 Logger

Woman结构体中包含匿名字段 Person,那么 Person 中的属性也就属于 Woman 对象。

package mainimport "fmt"type Person struct {name string
}type Man struct {Personsex string
}func main() {man := Man{Person{"wohu"}, "男"}fmt.Println(man.name) // wohufmt.Println(man.sex)  // 男
}
package mainimport "fmt"type parent struct {val int
}type child struct {parentnum int
}func main() {c := child{parent{1}, 2}fmt.Println(c.num)fmt.Println(c.val)
}

3. 多态

在面向对象中,多态的特征为:不同对象中同种行为的不同实现方式。在 Go 语言中可以使用接口实现这一特征。

package mainimport ("fmt"
)// 正方形
type Square struct {side float32
}// 长方形
type Rectangle struct {length, width float32
}// 接口 Shaper
type Shaper interface {Area() float32
}// 计算正方形的面积
func (sq *Square) Area() float32 {return sq.side * sq.side
}// 计算长方形的面积
func (r *Rectangle) Area() float32 {return r.length * r.width
}func main() {// 创建并初始化 Rectangle 和 Square 的实例,由于这两个实例都实现了接口中的方法,
//所以这两个实例,都可以赋值给接口 Shaper r := &Rectangle{10, 2}q := &Square{10}// 创建一个 Shaper 类型的数组shapes := []Shaper{r, q}// 迭代数组上的每一个元素并调用 Area() 方法for n, _ := range shapes {fmt.Println("矩形数据: ", shapes[n])fmt.Println("它的面积是: ", shapes[n].Area())}
}/*
矩形数据:  &{10 2}
它的面积是:  20
图形数据:  &{10}
它的面积是:  100
*/
package mainimport "fmt"type act interface {write()
}type xiaoming struct {}type xiaobai struct {}func (xm *xiaoming) write() {fmt.Println("xiaoming write")
}func (xf *xiaobai) write() {fmt.Println("xiaobai write")
}func main() {/*
> 接口特点:
> + 接口只有方法声明、没有实现,没有数据字段
> + 接口可以匿名嵌入其它接口,或者嵌入到结构中> 接口是用来定义行为的类型,这些被定义的行为不由接口直接实现,
> 而是由用户定义的类型实现,**一个实现了这些方法的具体类型是这个接口类型的实例。****如果用户定义的类型实现了某个接口类型声明的一组方法,
那么这个用户定义的类型的值就可以赋给这个接口类型的值。
这个赋值会把用户定义的类型存入接口类型的值。**
*/var w actxm := xiaoming{}xb := xiaobai{}w = &xmw.write()w = &xbw.write()
}

输出结果:

xiaoming write
xiaobai write

或者以下代码,将结构体初始化封装为函数。

// 创建初始化函数,初始化结构体对象,返回为接口对象
func NewXiaoming(xm xiaoming) act {return &xm
}// 创建初始化函数,初始化结构体对象,返回为接口对象
func NewXiaobai(xb xiaobai) act {return &xb
}func main() {m := NewXiaoming(xiaoming{})m.write()b := NewXiaobai(xiaobai{})b.write()
}

Go 中的接口可以说是方法特征的集合表达。要实现其接口,只需要实现接口中的所有方法即可。

package mainimport ("fmt"
)type animal interface {run()breath()
}type dog struct {legs intnose string
}type fish struct {fin  stringgill string
}func (d dog) run() {fmt.Printf("Dog runs with %d legs\n", d.legs)
}func (d dog) breath() {fmt.Printf("Dog breath with %s\n", d.nose)
}func (f fish) run() {fmt.Printf("Fish runs with %s\n", f.fin)
}func (f fish) breath() {fmt.Printf("Fisn breath with %s\n", f.gill)
}func behavior(an animal) {an.run()an.breath()
}func main() {d := dog{legs: 4,nose: "nose",}f := fish{fin:  "fin",gill: "gill",}behavior(d)behavior(f)
}

输出结果:

Dog runs with 4 legs
Dog breath with nose
Fish runs with fin
Fisn breath with gill

程序首先定义了一个 animal 接口,它有 run()breath() 两个方法。接着定义了两种类型,分别是 dogfish,显然这两种类型的动物都拥有 animal 动物类的两个方法,因而各自实现了它们。最后一个带有 animal 参数的 behavior 函数的出现很好地诠释了面向对象中多态的构造形式。

Go 学习笔记(36)— 基于Go方法的面向对象(封装、继承、多态)相关推荐

  1. 大数据笔记8—java基础篇4(面向对象-封装-继承-多态)

    面向对象 一.面向对象 1.面向过程 1.2.举例 1.3.总结 二.面向对象 1.简述 2.举例 3.思想特点 2.1.类的定义格式 2.1.1.简述 2.2.2.格式 2.3.3.示例 三.类的使 ...

  2. 面向对象封装继承多态五大基本原则魔法方法反射

    目录 面向对象 三大基本特征 五大基本原则 魔法方法 反射 面向对象 什么是面向对象 使用模板的思想,将世界万事万物使用对象来表示一个类型 面向对象和面向过程的区别: 面向对象的不就是使用程序处理事情 ...

  3. 《Go语言圣经》学习笔记 第六章 方法

    <Go语言圣经>学习笔记 第六章 方法 目录 方法声明 基于指针对象的方法 通过嵌入结构体来扩展类型 方法值和方法表达式 示例:Bit数组 封装 注:学习<Go语言圣经>笔记, ...

  4. 学习笔记之——基于深度学习的图像超分辨率重建

    最近开展图像超分辨率( Image Super Resolution)方面的研究,做了一些列的调研,并结合本人的理解总结成本博文~(本博文仅用于本人的学习笔记,不做商业用途) 本博文涉及的paper已 ...

  5. C#入门学习笔记(基于刘铁锰老师C#入门2014教学视频)【1】

    C#入门学习笔记(基于刘铁锰老师C#入门2014教学视频)[1] 前言: 本笔记作为记录我从零开始学习C#的记录,为了unity的兴趣爱好自学一门C#,也算是寒假为自己充个电,希望这个寒假可以坚持下去 ...

  6. C#入门学习笔记(基于刘铁锰老师C#入门2014教学视频)【2】

    C#入门学习笔记(基于刘铁锰老师C#入门2014教学视频)[2] 初识类和名称空间 前言: 本笔记作为记录我从零开始学习C#的记录,为了unity的兴趣爱好自学一门C#,也算是寒假为自己充个电,希望这 ...

  7. 【图神经网络】图神经网络(GNN)学习笔记:基于GNN的图表示学习

    图神经网络GNN学习笔记:基于GNN的图表示学习 1. 图表示学习 2. 基于GNN的图表示学习 2.1 基于重构损失的GNN 2.2 基于对比损失的GNN 参考资料 本文主要就基于GNN的无监督图表 ...

  8. 学习笔记之——基于matlab的数字通信系统(2)之离散信号的傅里叶分析

    关于连续信号的傅里叶分析,可以参考博文<学习笔记之--基于matlab的数字通信系统(1)&连续信号的傅里叶分析> 目录 离散时间信号的傅里叶变换(DTFT) 连续时间信号的抽样- ...

  9. Unity游戏框架学习笔记——03基于观察者模式的事件中心

    Unity游戏框架学习笔记--03基于观察者模式的事件中心 基于观察者模式的事件中心 一如既往指路牌:https://www.bilibili.com/video/BV1C441117wU?p=5. ...

  10. Ui学习笔记---EasyUI的使用方法,EasyLoader组件使用

    Ui学习笔记---EasyUI的使用方法,EasyLoader组件使用 技术qq交流群:CreDream:251572072 1.使用之前导入文件:   这里用jquery-easyui-1.2.6 ...

最新文章

  1. Velocity配置优化
  2. innodb参数汇总
  3. 案例:验证用户名是否可用
  4. Python之sklearn:GridSearchCV()和fit()函数的简介、具体案例、使用方法之详细攻略
  5. Spring Boot 页面国际化
  6. linux有读EC RAM的工具吗,Step to UEFI (179)Shell下 EC Ram 读取工具
  7. cxf开发基于web的webservice项目(转载)
  8. ArcGIS Server 开发系列(一)--编程框架总览
  9. CENTOS6 X64 LAMP+GD SHELL脚本
  10. TypeScript学习(三):联合类型及推论
  11. restful 风格api
  12. Archlinux 在启动时出现Error file vmlinuz not found解决方法
  13. TCA笔记4:TCA代码笔记
  14. 10 款富有创意的博客名片设计
  15. Silicon单片机芯片解密
  16. 数绵羊(矩阵快速幂)
  17. 百度浏览器的隐私安全问题分析
  18. 郑州、昆明、韶关等多地全面推行商品房买卖合同电子签约
  19. 互联网+时代,智慧停车如何改变城市“停车难”现状?
  20. UVA 1218 完美的服务

热门文章

  1. 是否存在分布式的【大泥球】?
  2. php错误提示如何查询,php-如何显示我的MySQLi查询错误?
  3. 2022-2028年中国超声波探伤仪行业市场现状调研及发展前景分析报告
  4. etcd 笔记(01)— etcd 简介、特点、应用场景、常用术语、分布式 CAP 理论、分布式原理
  5. Go 学习笔记(60)— Go 第三方库之 go-redis(初始化 redis、操作 string、操作 list、操作 set、操作 hset)
  6. tf.get_variable
  7. html,xml_网页开发_爬虫_笔记
  8. dockerfile kafka
  9. Druid数据库连接池使用参考
  10. pass基础架构分析