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的语言基础理解了,然后能够写出一些简单的例子了,这个时候基本上达到了一年工作经验的水平,而自己能够独立的写一些小功能,完成一些小效果,或者临摹修改一些比较复杂的插件的时候差不多就 ...
随机推荐
- 大话设计模式--职责连模式 Chain of Resposibility -- C++实现实例
1. 职责链模式: 使多个对象都有机会处理请求,从而避免请求发送者和接受者之间的耦合关系,将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它. 当客户提交一个请求时,请求是沿着链传递直 ...
- for循环中删除map中的元素,valgrind检测提示error:Invalid read of size 8
#include <iostream> #include <map> using namespace std; class A { public: typedef std::m ...
- android 网络编程--socket tcp/ip udp http之间的关系
网络七层由下往上分别为物理层.数据链路层.网络层.传输层.会话层.表示层和应用层,一般编程人员接触最多的就是应用层和运输层,再往下的就是所谓的媒体层了,不是我们研究的对象. 下面是应用层.运输层,网络 ...
- Uva10366
模拟乱搞 要说算法的话...乱搞算法? #include<iostream> #include<cstring> #include<algorithm> #incl ...
- GridView有用的小方法--2017年2月13日
原文:http://blog.csdn.net/21aspnet/article/category/285354更多:http://blog.csdn.net/21aspnet/article/cat ...
- 浅谈双流水线调度问题以及Jhonson算法
引入:何为流水线问题 有\(n\)个任务,对于每个任务有\(m\)道工序,每个任务的\(m\)道工序必须在不同的m台机器上依次完成才算把这个任务完成,在前\(i-1\)道工序完成后才能去完成第\(i\ ...
- log4net 使用
1. 代码中使用配置文件: log4net.Config.DOMConfigurator.Configure(new FileInfo("log4netConfig.xml")); ...
- sys.argv用法
argv是在脚本内部使用,旨在接受命令传参 比如,一个脚本argv.py,代码里面有,sys.argv[1],,sys.argv[2],那么运行这个脚本时,必须在后面跟两个参数,用空格隔开,如:pyt ...
- 问题:C# Dictionary嵌套使用;结果:嵌套Dictionary添加 , C#2.0泛型详细介绍
Dictionary<int, Dictionary<string, string>> dict1 = new Dictionary<int, Dictionary< ...
- hashcode和equals
Java中集合(Collection):一类是List另外一类是Set: 区别:list中元素有序,可重复 Set元素无序,不能重复 如何保证元素不重复呢?Object.Equals 但是当添加的元素 ...