本文译自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语言中的位操作(16)--计算二进制数字尾部连续0的数目

    本篇文章介绍计算二进制数字尾部连续0的数目的相关算法,例如:v=(1101000)2,该数尾部连续0的数目=3 方法1:线性时间算法 unsigned int v; // 需要计算的目标整数 int ...

  2. tkinter模块中常用的参数

    以下内容来自于:http://www.cnblogs.com/aland-1415/p/6849193.html(个别内容掺入了自己的重新整理) cnf={}与**kw: cnf={}这是一个默认参数 ...

  3. ajax如何处理返回的数据格式是xml的情况

    <!DOCTYPE html> <html> <head> <title>用户注册</title> <meta charset=&qu ...

  4. javascript中的this值

    如何确定this的值  this值会被传递给所有函数,this的值是基于运行时调用函数的上下文. 例如:从全局作用域调用sayFoo函数时,this引用window对象, 当它作为myObject的一 ...

  5. JavaWeb中文件的上传和下载

    JavaWeb中文件的上传和下载 转自: JavaWeb学习总结(五十)——文件上传和下载 - 孤傲苍狼 - 博客园https://www.cnblogs.com/xdp-gacl/p/4200090 ...

  6. Cuckoo hash算法分析——其根本思想和bloom filter一致 增加hash函数来解决碰撞 节省了空间但代价是查找次数增加

    基本思想: cuckoo hash是一种解决hash冲突的方法,其目的是使用简单的hash 函数来提高hash table的利用率,同时保证O(1)的查询时间 基本思想是使用2个hash函数来处理碰撞 ...

  7. loj516dp一般看规律

    STL 这...我只能说是...考得是... STL的正确用法? #include<iostream> #include<cstdio> #include<cstdlib ...

  8. luogu1353 Running

    dp[i][j]表示走i分钟疲劳值为j时的最远距离 然后搞一下就好啦 #include <iostream> #include <cstdio> #include <al ...

  9. ACM学习历程—广东工业大学2016校赛决赛-网络赛C wintermelon的魔界寻路之旅(最短路 && 递推)

    题目链接:http://gdutcode.sinaapp.com/problem.php?cid=1031&pid=2 题目由于要找对称的路径,那么狠明显可以把右下角的每一块加到左上角对应的每 ...

  10. bzoj 2342: 双倍回文 回文自动机

    题目大意: 定义双倍回文串的左一半和右一半均是回文串的长度为4的倍数的回文串 求一个给定字符串中最长的双倍回文串的长度 题解: 我们知道可以简单地判定以某一点结尾的最长回文串 我们知道可以简单地判定以 ...