Go语言——没有对象的面向对象编程
本文译自Steve Francia在OSCON 2014的一个PPT,原作请前往:https://spf13.com/presentation/go-for-object-oriented-programmers/
对我来说,最吸引我的不是Go拥有的特征,而是那些被故意遗漏的特征。 —— txxxxd
为什么你要创造一种从理论上来说,并不令人兴奋的语言?
因为它非常有用。 —— Rob Pike
Go中的“对象”
要探讨Go语言中的对象,我们先搞清楚一个问题:
Go语言有对象吗?
从语法上来说,
- Go中没有类(Classes)
- Go中没有“对象”(Objects)
到底什么是对象?
对象是一种抽象的数据类型,拥有状态(数据)和行为(代码)。 —— Steve Francia
在Go语言中,我们这样声明一个类型:
类型声明(Struct)
type Rect struct {
width int
height int
}
然后我们可以给这个Struct声明一个方法
func (r *Rect) Area() int {
return r.width * r.height
}
用起来就像这样
func main() {
r := Rect{width: 10, height: 5}
fmt.Println("area: ", r.Area())
}
我们不光可以声明结构体类型,我们可以声明任何类型。比如一个切片:
类型声明(Slice)
type Rects []*Rect
同样也可以给这个类型声明一个方法
func (rs Rects) Area() int {
var a int
for _, r := range rs {
a += r.Area()
}
return a
}
用起来
func main() {
r := &Rect{width: 10, height: 5}
x := &Rect{width: 7, height: 10}
rs := Rects{r, x}
fmt.Println("r's area: ", r.Area())
fmt.Println("x's area: ", x.Area())
fmt.Println("total area: ", rs.Area())
}
https://play.golang.org/p/G1OWXPGvc3
我们甚至可以声明一个函数类型
类型声明(Func)
type Foo func() int
同样的,给这个(函数)类型声明一个方法
func (f Foo) Add(x int) int {
return f() + x
}
然后用起来
func main() {
var x Foo
x = func() int { return 1 }
fmt.Println(x())
fmt.Println(x.Add(3))
}
https://play.golang.org/p/YGrdCG3SlI
通过上边的例子,这样看来,其实
Go有“对象”
那么我们来看看
“面向对象”的Go
如果一种语言包含对象的基本功能:标识、属性和特性,则通常认为它是基于对象的。
如果一种语言是基于对象的,并且具有多态性和继承性,那么它被认为是面向对象的。 —— Wikipedia
第一条,我们在上边的例子看到了,go中的type declaration其实满足了Go语言是基于对象的。那么,
Go是基于对象的,它是面向对象的吗?
我们来看看关于第二条,继承性和多态性
继承
- 提供对象的复用
- 类是按层级创建的
- 继承允许一个类中的结构和方法向下传递这种层级
Go中实现继承的方式
- Go明确地避免了继承
- Go严格地遵循了符合继承原则的组合方式
- Go中通过嵌入类型来实现组合
组合
- 提供对象的复用
- 通过包含其他的对象来声明一个对象
- 组合使一个类中的结构和方法被拉进其他类中
继承把“知识”向下传递,组合把“知识”向上拉升 —— Steve Francia
嵌入类型
type Person struct {
Name string
Address
}
type Address struct {
Number string
Street string
City string
State string
Zip string
}
给被嵌入的类型声明一个方法
func (a *Address) String() string {
return a.Number + " " + a.Street + "\n" + a.City + ", " + a.State + " " + a.Zip + "\n"
}
使用组合字面量声明一个Struct
func main() {
p := Person{
Name: "Steve",
Address: Address{
Number: "13",
Street: "Main",
City: "Gotham",
State: "NY",
Zip: "01313",
},
}
}
跑起来试试
func main() {
p := Person{
Name: "Steve",
Address: Address{
Number: "13",
Street: "Main",
City: "Gotham",
State: "NY",
Zip: "01313",
},
}
fmt.Println(p.String())
}
https://play.golang.org/p/9beVY9jNlW
升级
- 升级会检查一个内部类型是否能满足需要,并“升级”它
- 内嵌的数据域和方法会被“升级”
- 升级发生在运行时而不是声明时
- 被升级的方法被认为是符合接口的
升级不是重载
func (a *Address) String() string {
return a.Number + " " + a.Street + "\n" + a.City + ", " + a.State + " " + a.Zip + "\n"
}
func (p *Person) String() string {
return p.Name + "\n" + p.Address.String()
}
外部结构的方法和内部结构的方法都是可见的
func main() {
p := Person{
Name: "Steve",
Address: Address{
Number: "13",
Street: "Main",
City: "Gotham",
State: "NY",
Zip: "01313",
},
}
fmt.Println(p.String())
fmt.Println(p.Address.String())
}
https://play.golang.org/p/Aui0nGa5Xi
这两个类型仍然是两个不同的类型
func isValidAddress(a Address) bool {
return a.Street != ""
}
func main() {
p := Person{
Name: "Steve",
Address: Address{
Number: "13",
Street: "Main",
City: "Gotham",
State: "NY",
Zip: "01313",
},
}
// 这里不能用 p (Person类型) 作为 Address类型的IsValidAddress参数
// cannot use p (type Person) as type Address in argument to isValidAddress
fmt.Println(isValidAddress(p))
fmt.Println(isValidAddress(p.Address))
}
https://play.golang.org/p/KYjXZxNBcQ
升级不是子类型
多态
为不同类型的实体提供单一接口
通常通过泛型、重载和/或子类型实现
Go中实现多态的方式
- Go明确避免了子类型和重载
- Go尚未提供泛型
- Go的接口提供了多态功能
接口
- 接口就是(要实现某种功能所需要提供的)方法的列表
- 结构上的类型 vs 名义上的类型
- “如果什么东西能做这件事,那么就可以在这使用它”
- 惯例上就叫它 某种东西
Go语言采用了鸭式辩型,和JavaScript类似。鸭式辩型的思想是,只要一个动物走起路来像鸭子,叫起来像鸭子,那么就认为它是一只鸭子。
也就是说,只要一个对象提供了和某个接口同样(在Go中就是相同签名)的方法,那么这个对象就可以当做这个接口来用。并不需要像Java中一样显式的实现(implements)这个接口。
接口声明
type Shaper interface{
Area() int
}
然后把这个接口作为一个参数类型
func Describe(s Shaper) {
fmt.Println("Area is: ", s.Area())
}
这样用
func main() {
r := &Rect{width: 10, height: 5}
x := &Rect{width: 7, height: 10}
rs := &Rects{r, x}
Describe(r)
Describe(x)
Describe(rs)
}
https://play.golang.org/p/WL77LihUwi
“如果你可以重新做一次Java,你会改变什么?”
“我会去掉类class,” 他回答道。
在笑声消失后,他解释道,真正的问题不是类class本身,而是“实现”的继承(类之间extends的关系)。接口的继承(implements的关系)是更可取的方式。
只要有可能,你就应该尽可能避免“实现”的继承。
—— James Gosling(Java之父)
Go的接口是基于实现的,而不是基于声明的
这也就是上边所说的鸭式辩型
接口的力量
io.Reader
type Reader interface {
Read(p []byte) (n int, err error)
}
- Interface
- Read方法读取最多len(p) bytes的数据到字节数组p中
- 返回读取的字节数和遇到的任何error
- 并不规定Read()方法如何实现
- 被诸如 os.File, bytes.Buffer, net.Conn, http.Request.Body等等使用
io.Writer
type Writer interface {
Write(p []byte) (n int, err error)
}
- Interface
- Write方法写入最多len(p) bytes的数据到字节数组p中
- 返回写入的字节数和遇到的任何error
- 并不规定Write()方法如何实现
- 被诸如 os.File, bytes.Buffer, net.Conn, http.Request.Body等等使用
io.Reader 使用
func MarshalGzippedJSON(r io.Reader, v interface{}) error {
raw, err := gzip.NewReader(r)
if err != nil {
return err
}
return json.NewDecoder(raw).Decode(&v)
}
读取一个json.gz文件
func main() {
f, err := os.Open("myfile.json.gz")
if err != nil {
log.Fatalln(err)
}
defer f.Close()
m := make(map[string]interface{})
MarshalGzippedJSON(f, &m)
}
实用的交互性
- Gzip.NewReader(io.Reader) 只需要传入一个io.Reader接口类型即可
- 在files, http requests, byte buffers, network connections, ...任何你创建的东西里都能工作
- 在gzip包里不需要任何特殊处理。只要简单地调用Read(n),把抽象的部分留给实现者即可
将 http response 写入文件
func main() {
resp, err := http.Get("...")
if err != nil {
log.Fatalln(err)
}
defer resp.Body.Close()
out, err := os.Create("filename.ext")
if err != nil {
log.Fatalln(err)
}
defer out.Close()
io.Copy(out, resp.Body) // out io.Writer, resp.Body io.Reader
}
Go
简单比复杂更难:你必须努力使你的思维清晰,使之简单。但最终还是值得的,因为一旦你到了那里,你就可以移山。 —— Steve Jobs
Go简单,实用,绝妙
Go做了一些伟大的事情
Go语言——没有对象的面向对象编程的更多相关文章
- R语言基于S4的面向对象编程
前言 本文接上一篇文章 R语言基于S3的面向对象编程,本文继续介绍R语言基于S4的面向对象编程. S4对象系统具有明显的结构化特征,更适合面向对象的程序设计.Bioconductor社区,以S4对象系 ...
- 类和对象:面向对象编程 - 零基础入门学习Python037
类和对象:面向对象编程 让编程改变世界 Change the world by program 经过上节课的热身,相信大家对类和对象已经有了初步的认识,但似乎还是懵懵懂懂:好像面向对象编程很厉害,但不 ...
- go语言之进阶篇面向对象编程
1.面向对象编程 对于面向对象编程的支持Go 语言设计得非常简洁而优雅.因为, Go语言并没有沿袭传统面向对象编程中的诸多概念,比如继承(不支持继承,尽管匿名字段的内存布局和行为类似继承,但它并不是继 ...
- Go语言基础之接口(面向对象编程下)
1 接口 1.1 接口介绍 接口(interface)是Go语言中核心部分,Go语言提供面向接口编程,那么接口是什么? 现实生活中,有许多接口的例子,比如说电子设备上的充电接口,这个充电接口能干什么, ...
- java面向对象编程(类、对象)
一.面向对象编程概述面向对象编程(Object Oriented Programming,缩写为OOP)是当今最流行的程序设计技术,它具有代码易于维护.可扩展性好和代码可常用等优点.面向对象的设计 ...
- Python 第六篇(上):面向对象编程初级篇
面向:过程.函数.对象: 面向过程:根据业务逻辑从上到下写垒代码! 面向过程的编程弊:每次调用的时候都的重写,代码特别长,代码重用性没有,每次增加新功能所有的代码都的修改!那有什么办法解决上面出现的弊 ...
- java 面向对象编程。。。。
经过一周的学习(java),总结了许多,对java的理解,java的类型,运用,安装,基础语法,变量,常量,表达式,语句 java从C语言中继承了大量语言特性.java面向对象编程的基本特征,包括继承 ...
- C# 篇基础知识3——面向对象编程
面向过程的结构化编程,例如1972年美国贝尔研究所推出的C语言,这类编程方式重点放在在定函数上,将较大任务分解成若干小任务,每个小任务由函数实现,分而治之的思想,然而随着软件规模的不断扩张,软件的复杂 ...
- 体验javascript之美6:如果你觉得什么都会了或者不知道js学什么了看这里-面向对象编程
概述 当大家已经把js的语言基础理解了,然后能够写出一些简单的例子了,这个时候基本上达到了一年工作经验的水平,而自己能够独立的写一些小功能,完成一些小效果,或者临摹修改一些比较复杂的插件的时候差不多就 ...
随机推荐
- CSS: iPhone Custom CSS
1. [代码][CSS]代码 <style type="text/css" media="screen"> /* iPhone 4@media on ...
- 维度表, 事实表, 数据仓库, BI...
以前一直对维度表, 事实表, 数据分析, BI等概念等有一些模糊. 这几天的学习终于让这些有了一些眉目了: 维度表示你要对数据进行分析时所用的一个量, 比如你要分析产品销售情况, 你可以选择按类别来进 ...
- 分享知识-快乐自己:虚拟机 NET 网络配置
第一步: 第二步: 第三步: 第四步: 第五步: 第六步: 第七步: 第九步: 第十步: 第十一步: 第十二步: 第十三步: 成功. 第十四步:开启开机自启动网路连接 cd /etc/sy ...
- 一致性问题和Raft一致性算法——一致性问题是无法彻底解决的,可以说一个分布式系统可靠性达到99.99…%,但不能说它达到了100%
一致性问题 一致性算法是用来解决一致性问题的,那么什么是一致性问题呢? 在分布式系统中,一致性问题(consensus problem)是指对于一组服务器,给定一组操作,我们需要一个协议使得最后它们的 ...
- Java丨JDK与JRE
小编是以为热爱Java的程序员,可是在我身边的一部分人他们不知道JDK和JRE是什么,有什么区别! 今天小编以个人的理解来说一说,不足之处还望大家指证. 首先我们来看看JDK和JRE他们两个的英文意思 ...
- linux 在后台运行数据库导入导出命令
nohup imp dbusername/password@orcl file=/home/20170928.dmp ignore=y log=/home/oracle/20170928.log fu ...
- 2017-2018-1 20179215《Linux内核原理与分析》第三周作业
本次作业分为两部分:第一部分为实验.主要目的是进行基于MYKERNEL的一个简单的时间片轮转多道程序内核代码分析.第二部分为阅读教材,了解LINUX进程调度等. 一.实验部分 实验过程如过程所述:使用 ...
- maven的配置文件取不同版本
1. 两个地方需要配置首先是要定义配置源,dev/test/prod对应的配置源需要指定:这是灵活的,可变的:其次要定义配置文件,该配置文件里面的内容采用占位符的方式来编制,在编译打包的过程中要动态根 ...
- 杂项:TModJS
ylbtech-杂项:TModJS TmodJS(原名 atc)是一个简单易用的前端模板预编译工具.它通过预编译技术让前端模板突破浏览器限制,实现后端模板一样的同步“文件”加载能力.它采用目录来组织维 ...
- arm交叉编译 扫盲贴
ARM交叉编译工具链 为什么要用交叉编译器? 交叉编译通俗地讲就是在一种平台上编译出能运行在体系结构不同的另一种平台上的程序, 比如在PC平台(X86 CPU)上编译出能运行在以ARM为内核的CPU平 ...