Go 学习笔记(36)— 基于Go方法的面向对象(封装、继承、多态)
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
类型的零值为 false
,int
类型的零值为 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()
两个方法。接着定义了两种类型,分别是 dog
和 fish
,显然这两种类型的动物都拥有 animal
动物类的两个方法,因而各自实现了它们。最后一个带有 animal
参数的 behavior
函数的出现很好地诠释了面向对象中多态的构造形式。
Go 学习笔记(36)— 基于Go方法的面向对象(封装、继承、多态)相关推荐
- 大数据笔记8—java基础篇4(面向对象-封装-继承-多态)
面向对象 一.面向对象 1.面向过程 1.2.举例 1.3.总结 二.面向对象 1.简述 2.举例 3.思想特点 2.1.类的定义格式 2.1.1.简述 2.2.2.格式 2.3.3.示例 三.类的使 ...
- 面向对象封装继承多态五大基本原则魔法方法反射
目录 面向对象 三大基本特征 五大基本原则 魔法方法 反射 面向对象 什么是面向对象 使用模板的思想,将世界万事万物使用对象来表示一个类型 面向对象和面向过程的区别: 面向对象的不就是使用程序处理事情 ...
- 《Go语言圣经》学习笔记 第六章 方法
<Go语言圣经>学习笔记 第六章 方法 目录 方法声明 基于指针对象的方法 通过嵌入结构体来扩展类型 方法值和方法表达式 示例:Bit数组 封装 注:学习<Go语言圣经>笔记, ...
- 学习笔记之——基于深度学习的图像超分辨率重建
最近开展图像超分辨率( Image Super Resolution)方面的研究,做了一些列的调研,并结合本人的理解总结成本博文~(本博文仅用于本人的学习笔记,不做商业用途) 本博文涉及的paper已 ...
- C#入门学习笔记(基于刘铁锰老师C#入门2014教学视频)【1】
C#入门学习笔记(基于刘铁锰老师C#入门2014教学视频)[1] 前言: 本笔记作为记录我从零开始学习C#的记录,为了unity的兴趣爱好自学一门C#,也算是寒假为自己充个电,希望这个寒假可以坚持下去 ...
- C#入门学习笔记(基于刘铁锰老师C#入门2014教学视频)【2】
C#入门学习笔记(基于刘铁锰老师C#入门2014教学视频)[2] 初识类和名称空间 前言: 本笔记作为记录我从零开始学习C#的记录,为了unity的兴趣爱好自学一门C#,也算是寒假为自己充个电,希望这 ...
- 【图神经网络】图神经网络(GNN)学习笔记:基于GNN的图表示学习
图神经网络GNN学习笔记:基于GNN的图表示学习 1. 图表示学习 2. 基于GNN的图表示学习 2.1 基于重构损失的GNN 2.2 基于对比损失的GNN 参考资料 本文主要就基于GNN的无监督图表 ...
- 学习笔记之——基于matlab的数字通信系统(2)之离散信号的傅里叶分析
关于连续信号的傅里叶分析,可以参考博文<学习笔记之--基于matlab的数字通信系统(1)&连续信号的傅里叶分析> 目录 离散时间信号的傅里叶变换(DTFT) 连续时间信号的抽样- ...
- Unity游戏框架学习笔记——03基于观察者模式的事件中心
Unity游戏框架学习笔记--03基于观察者模式的事件中心 基于观察者模式的事件中心 一如既往指路牌:https://www.bilibili.com/video/BV1C441117wU?p=5. ...
- Ui学习笔记---EasyUI的使用方法,EasyLoader组件使用
Ui学习笔记---EasyUI的使用方法,EasyLoader组件使用 技术qq交流群:CreDream:251572072 1.使用之前导入文件: 这里用jquery-easyui-1.2.6 ...
最新文章
- Velocity配置优化
- innodb参数汇总
- 案例:验证用户名是否可用
- Python之sklearn:GridSearchCV()和fit()函数的简介、具体案例、使用方法之详细攻略
- Spring Boot 页面国际化
- linux有读EC RAM的工具吗,Step to UEFI (179)Shell下 EC Ram 读取工具
- cxf开发基于web的webservice项目(转载)
- ArcGIS Server 开发系列(一)--编程框架总览
- CENTOS6 X64 LAMP+GD SHELL脚本
- TypeScript学习(三):联合类型及推论
- restful 风格api
- Archlinux 在启动时出现Error file vmlinuz not found解决方法
- TCA笔记4:TCA代码笔记
- 10 款富有创意的博客名片设计
- Silicon单片机芯片解密
- 数绵羊(矩阵快速幂)
- 百度浏览器的隐私安全问题分析
- 郑州、昆明、韶关等多地全面推行商品房买卖合同电子签约
- 互联网+时代,智慧停车如何改变城市“停车难”现状?
- UVA 1218 完美的服务
热门文章
- 是否存在分布式的【大泥球】?
- php错误提示如何查询,php-如何显示我的MySQLi查询错误?
- 2022-2028年中国超声波探伤仪行业市场现状调研及发展前景分析报告
- etcd 笔记(01)— etcd 简介、特点、应用场景、常用术语、分布式 CAP 理论、分布式原理
- Go 学习笔记(60)— Go 第三方库之 go-redis(初始化 redis、操作 string、操作 list、操作 set、操作 hset)
- tf.get_variable
- html,xml_网页开发_爬虫_笔记
- dockerfile kafka
- Druid数据库连接池使用参考
- pass基础架构分析