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做了一些伟大的事情

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Rancher Server单容器部署使用外部(宿主)数据库
Rancher除了使用内部的数据库,你可以启动一个Rancher Server并使用一个外部的数据库。启动命令与之前一样,但添加了一些额外的参数去说明如何连接你的外部数据库。 注意:在你的外部数据库中,只需要提前创建数据库名和数据库用户。Rancher会自动创建Rancher所需要的数据库表。 我们需要提前创建好数据名、为rancher专门创建一个登陆用户。 创建数据库 CREATE DATABASE IF NOT EXISTS rancher COLLATE = 'utf8_general_ci' CHARACTER SET = 'utf8'; 通rancher数据库创建专用用户并赋予权限 GRANT ALL ON rancher.* TO 'rancher'@'%' IDENTIFIED BY 'rancher126.128' GRANT ALL ON rancher.* TO 'rancher'@'localhost' IDENTIFIED BY 'rancher126.128'; 启动一个Rancher连接一个外部数据库,你需要在启动容器的命令中添加额外参数。 docke...
- 下一篇
iOS 瘦身之道
App 的包大小做优化的目的就是为了节省用户流量,提高用户的下载速度,也是为了用户手机节省更多的空间。另外 App Store 官方规定 App 安装包如果超过 150MB,那么不可以使 OTA(over-the-air)环境下载,也就是只可以在 WiFi 环境下载,企业或者独立开发者万万不想看到这一点。免得失去大量的用户。 同时如果你的 App 需要适配 iOS7、iOS8 那么官方规定主二进制 text 段的大小不能超过 60MB。如果不能满足这个标准,则无法上架 App Store。 另一种情况是 App 包体积过大,对用户更新升级率也会有很大影响。 所以应用包的瘦身迫在眉睫。 1. App Thinning App Thinning 是指 iOS9 以后引入的一项优化,官方描述如下 The App Store and operating system optimize the installation of iOS, tvOS, and watchOS apps by tailoring app delivery to the capabilities of the user’...
相关文章
文章评论
共有0条评论来说两句吧...