Go语言——没有对象的面向对象编程

in 编程
关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9

本文译自Steve Francia在OSCON 2014的一个PPT,原作请前往:https://spf13.com/presentation/go-for-object-oriented-programmers/


对我来说,最吸引我的不是Go拥有的特征,而是那些被故意遗漏的特征。 —— txxxxd

为什么你要创造一种从理论上来说,并不令人兴奋的语言?
因为它非常有用。 —— Rob Pike

Go中的“对象”

要探讨Go语言中的对象,我们先搞清楚一个问题:

Go语言有对象吗?

从语法上来说,

到底什么是对象?

对象是一种抽象的数据类型,拥有状态(数据)和行为(代码)。 —— 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中实现继承的方式

组合

继承把“知识”向下传递,组合把“知识”向上拉升 —— 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语言采用了鸭式辩型,和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)
}
io.Writer
type Writer interface {
	Write(p []byte) (n int, err error)
}
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)
}
实用的交互性
将 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做了一些伟大的事情

关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9
扫一扫关注公众号添加购物返利助手,领红包
Comments are closed.

推荐使用阿里云服务器

超多优惠券

服务器最低一折,一年不到100!

朕已阅去看看