本文译自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语言——没有对象的面向对象编程的更多相关文章

  1. R语言基于S4的面向对象编程

    前言 本文接上一篇文章 R语言基于S3的面向对象编程,本文继续介绍R语言基于S4的面向对象编程. S4对象系统具有明显的结构化特征,更适合面向对象的程序设计.Bioconductor社区,以S4对象系 ...

  2. 类和对象:面向对象编程 - 零基础入门学习Python037

    类和对象:面向对象编程 让编程改变世界 Change the world by program 经过上节课的热身,相信大家对类和对象已经有了初步的认识,但似乎还是懵懵懂懂:好像面向对象编程很厉害,但不 ...

  3. go语言之进阶篇面向对象编程

    1.面向对象编程 对于面向对象编程的支持Go 语言设计得非常简洁而优雅.因为, Go语言并没有沿袭传统面向对象编程中的诸多概念,比如继承(不支持继承,尽管匿名字段的内存布局和行为类似继承,但它并不是继 ...

  4. Go语言基础之接口(面向对象编程下)

    1 接口 1.1 接口介绍 接口(interface)是Go语言中核心部分,Go语言提供面向接口编程,那么接口是什么? 现实生活中,有许多接口的例子,比如说电子设备上的充电接口,这个充电接口能干什么, ...

  5. java面向对象编程(类、对象)

    一.面向对象编程概述面向对象编程(Object  Oriented  Programming,缩写为OOP)是当今最流行的程序设计技术,它具有代码易于维护.可扩展性好和代码可常用等优点.面向对象的设计 ...

  6. Python 第六篇(上):面向对象编程初级篇

    面向:过程.函数.对象: 面向过程:根据业务逻辑从上到下写垒代码! 面向过程的编程弊:每次调用的时候都的重写,代码特别长,代码重用性没有,每次增加新功能所有的代码都的修改!那有什么办法解决上面出现的弊 ...

  7. java 面向对象编程。。。。

    经过一周的学习(java),总结了许多,对java的理解,java的类型,运用,安装,基础语法,变量,常量,表达式,语句 java从C语言中继承了大量语言特性.java面向对象编程的基本特征,包括继承 ...

  8. C# 篇基础知识3——面向对象编程

    面向过程的结构化编程,例如1972年美国贝尔研究所推出的C语言,这类编程方式重点放在在定函数上,将较大任务分解成若干小任务,每个小任务由函数实现,分而治之的思想,然而随着软件规模的不断扩张,软件的复杂 ...

  9. 体验javascript之美6:如果你觉得什么都会了或者不知道js学什么了看这里-面向对象编程

    概述 当大家已经把js的语言基础理解了,然后能够写出一些简单的例子了,这个时候基本上达到了一年工作经验的水平,而自己能够独立的写一些小功能,完成一些小效果,或者临摹修改一些比较复杂的插件的时候差不多就 ...

随机推荐

  1. C/C++ 库函数 是否调用 WinAPI

    1. 跟了一个函数 fopen,简单测试代码为: #include<stdio.h> #define F_PATH "e:\\Z.txt" int main(void) ...

  2. Codeforces 571B Minimization:dp + 贪心【前后相消】

    题目链接:http://codeforces.com/problemset/problem/571/B 题意: 给你一个长度为n的数列a[i]. 现在你可以随意改变数字的位置,问你 ∑| a[i] - ...

  3. 手把手编写PHP框架 深入了解MVC运行流程

    1 什么是MVC MVC模式(Model-View-Controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model).视图(View)和控制器(Controller ...

  4. Selenium-几种等待方式

    强制等待 一直使用的time.sleep(5),可以放在任意地方,不好的地方,不太准确确定时间 隐形等待 driver.implicitly_wait(5) 设置了一个最长等待时间,如果在规定时间内网 ...

  5. informix 把数据从一个表倒到另外一个表中

    drop table zrjReinUnClaimTmpT; create table zrjReinUnClaimTmpT ( mainid SERIAL not null, RepayNo var ...

  6. freeMarker(二)——模板开发指南之入门

    学习笔记,选自freeMarker中文文档,译自 Email: ddekany at users.sourceforge.net 模板开发指南-入门  1.模板+数据模型=输出 假设在一个在线商店的应 ...

  7. RTP 打包H264与AAC

    static int h264_parse(Track *tr, uint8_t *data, size_t len) { h264_priv *priv = tr->private_data; ...

  8. box head上身旋转问题

    现有资源: 1.可旋转上身的动画,旋转角度左右各90度. 2.下身没有旋转动画(腿部左转右转动画) 使用场景: 1.整个模型随鼠标位置旋转,不使用上身旋转动画. 缺点:人物转向动画僵硬.  解决方案: ...

  9. css--offsetParent

    offsetParent属性返回一个对象的引用,这个对象是距离调用offsetParent的元素最近的(在包含层次中最靠近的),并且是已进行过CSS定位的容器元素. 如果这个容器元素未进行CSS定位, ...

  10. 一次spark卡顿分析

    在104上面执行,经常会发生卡到了如下一句话: storage.BlockManagerInfo: Added broadcast_8_piece0 当再次卡顿的时候,我直接退出,然后通过yarn看后 ...