【翻譯】go是面嚮對象語言嗎?

原文:http://spf13.com/post/is-go-object-orientedhtml

前言

爲了真正理解面向對象的含義,咱們須要回顧一下這個概念的起源。第一個面嚮對象語言-simula問世於19世紀60年代。它引入了對象(object)、類(class)、繼承(inheritance)、子類(subclass)、虛方法(virtual method)、協程(coroutine)等概念。然而simula最重要的貢獻多是它引入顛覆性的思想——將數據和邏輯徹底分離。java

你可能不熟悉simula語言,但你確定熟悉Java, C++, C# & Smalltalk中的一種,這些語言深受simula的影響,固然這些語言又同時影響着現今幾乎全部的高級語言如:Objective C, Python, Ruby, Javascript, Scala, PHP, Perl… 大部分程序員都遵循着將數據和邏輯徹底分離的原則。程序員

因爲面向對象沒有標準的定義,爲了討論的方便,接下來咱們將提供一個標準的定義。golang

面向對象系統將數據和代碼經過「對象」集成到一塊兒,而不是將程序當作由分離的數據和代碼組成。對象是數據類型的抽象,它有狀態(數據)和行爲(代碼)編程

面向對象包括繼承、多態、虛派生等特性,接下來咱們將看看go語言是怎樣處理對象、多態、繼承,相信讀完接下來的介紹,您會對go是如何處理面向對象有本身的看法。session

go中的對象

go語言中沒有對象(object)這個關鍵詞。對象(object)僅僅是一個單詞,重要的是它所表示的含義。儘管go中沒有object這種類型,可是go中的struct有着跟object相同的特性。app

struct是一種包含了命名域和方法的類型ide

讓咱們從一個例子中來理解它:函數

type rect struct {
    width int
    height int
}

func (r *rect) area() int {
    return r.width * r.height
}

func main() {
    r := rect{width: 10, height: 5}
    fmt.Println("area: ", r.area())
}

咱們一行行來解釋一下上面的代碼。代碼的第一塊定義了一個叫作rect的struct類型,該struct含有兩個int類型的域;接下來定義了一個綁定在rect struct類型上的area方法。嚴格來講,area方法是綁定在指向rectct struct的指針上。若是方法綁定在rect type而非指針上,則在調用方法的時候須要使用該類型的值來調用,即便該值是空值,本例的空值實際是一個nil值;代碼的最後一塊是main函數,main函數第一行建立了一個rect類型的值,固然也有其餘的方法來建立一個類型的值,這裏給出的是一個地道的方法。main函數的最後一行是打印做用在r值上的area方法的返回結果。post

經過上面的描述,能夠看出這很像對象的行爲,咱們能夠建立一個結構化的數據類型,而後定義方法和這些數據進行交互。上述的簡單例子並無完成展現面向對象的全部特性,好比繼承和多態。須要說明的是go不只能夠在struct上定義方法,在任何命名的類型上一樣也能夠。好比,能夠定義一個名爲Counter的新類型,該類型是int型的別名,而後在Counter類型上定義方法。例子詳見:http://play.golang.org/p/LGB-2j707c

繼承和多態

定義對象間的關係的方法有以下幾種,它們之間都有一些差異,但目的都是同樣的:複用代碼。

  • 單繼承(Inheritance)
  • 多繼承(Multiple Inheritance)
  • 多態(Subtyping/Polymorphism)
  • 對象組合(Object composition)

繼承:一個對象基於另一個對象,使用其實現。有兩種不一樣的繼承實現:單繼承和多繼承。它們的不一樣在於對象是繼承自一個對象仍是多個對象。單繼承關係是一棵樹,而多繼承關係是一個格狀結構。單繼承語言包括PHP、C#、Java、Ruby等,多繼承語言包括Perl、Python、C++等

多態

多態是is-a的關係,繼承是實現的複用。多態定義了兩個對象的語義關係,繼承定義兩個對象的語法關係。

對象組合

對象組合是一個對象包含了其餘對象,而非繼承,它是has-a的關係,而非is-a。

go語言的繼承

go有意得被設計爲沒有繼承語法。但這並不意味go中的對象(struct value)之間沒有關係,只不過go的做者選擇了另一種機制來暗含這種特性。實際上go的這種設計是一種很是好的解決方法,它解決了圍繞着繼承的數十年的老問題和爭論。

最好不要繼承

下面引用的一段來自javaworld的一篇名爲《why extends is evil》的文章說明了這一點。

The Gang of Four Design Patterns book discusses at length replacing implementation inheritance (extends) with interface inheritance (implements).

I once attended a Java user group meeting where James Gosling (Java’s inventor) was the featured speaker. During the memorable Q&A session, someone asked him: 「If you could do Java over again, what would you change?」 「I’d leave out classes,」 he replied. After the laughter died down, he explained that the real problem wasn’t classes per se, but rather implementation inheritance (the extends relationship). Interface inheritance (the implements relationship) is preferable. You should avoid implementation inheritance whenever possible.

go語言中的多態和組合

go語言嚴格遵照composition over inheritance principle的原則。go經過在struct和interface上使用組合和多態來實現繼承關係。
Person和Address之間的關係是這種實現的一個很好的例子:http://play.golang.org/p/LigPIVT2mf

type Person struct {
   Name string
   Address Address
}

type Address struct {
   Number string
   Street string
   City   string
   State  string
   Zip    string
}

func (p *Person) Talk() {
    fmt.Println("Hi, my name is", p.Name)
}

func (p *Person) Location() {
    fmt.Println("I’m at", p.Address.Number, p.Address.Street, p.Address.City, p.Address.State, p.Address.Zip)
}

func main() {
    p := Person{
        Name: "Steve",
        Address: Address{
            Number: "13",
            Street: "Main",
            City:   "Gotham",
            State:  "NY",
            Zip:    "01313",
        },
    }

    p.Talk()
    p.Location()
}

Output

Hi, my name is Steve
I’m at 13 Main Gotham NY 01313

上面的例子須要注意的是, Address仍然是一個不一樣的對象,只不過存在於Person中。

go中的僞多態

AUTHORS NOTE:
In the first version of this post it made the incorrect claim that Go supports the is-a relationship via Anonymous fields. In reality Anonymous fields appear to be an is-a relationship by exposing embedded methods and properties as if they existed on the outer struct. This falls short of being an is-a relationship for reasons now provided below. Go does have support for is-a relationships via interfaces, covered below. The current version of this post refers to Anonymous fields as a pseudo is-a relationship because it looks and behaves in some ways like subtyping, but isn’t.

咱們經過擴展上面的例子來講明go中的僞多態。注意這裏「僞」字說明實際上go是沒有多態的概念的,只不過僞多態表現得像多態同樣。下面的例子中,Person能夠說話(Talk),一個Citizen也同時是一個Person,所以他也能說話(Talk)。在上面的例子中加入以下內容,完整代碼見:http://play.golang.org/p/eCEpLkQPR3

type Citizen struct {
   Country string
   Person
}

func (c *Citizen) Nationality() {
    fmt.Println(c.Name, "is a citizen of", c.Country)
}

func main() {
    c := Citizen{}
    c.Name = "Steve"
    c.Country = "America"
    c.Talk()
    c.Nationality()
}

上面的例子經過引入匿名域(Person)實現了is-a關係。Person是Citizen的一個匿名域(anonymous field),匿名域只給出了對象類型,而不給出類型的名字。經過匿名域,Citizen能夠訪問Person中的全部屬性(域)和方法。

匿名域方法提高

上述例子,Citizen能夠和Person執行同樣的Talk()方法。但若是想要Citizen的Talk()表現出不一樣的行爲該怎麼作呢?咱們只須要在Citizen上定義方法Talk()便可。當調用c.Talk()的時候,調用的則是Citizen的Talk()方法而非Person的Talk()方法,http://play.golang.org/p/jafbVPv5H9

func (c *Citizen) Talk() {
    fmt.Println("Hello, my name is", c.Name, "and I'm from", c.Country)
}

Output

Hello, my name is Steve and I'm from America
Steve is a citizen of America

爲什麼匿名域不是合適的多態實現

有兩個緣由:
1. 匿名域仍然能被訪問,就好像它們是被嵌入的對象同樣。
這並非一件壞事,多繼承存在的一個問題就是當多個父類具備相同的方法的時候,會產生歧義。然而go語言能夠經過訪問跟匿名類型同名的屬性來訪問嵌入的匿名對象。實際上當使用匿名域的時候,go會建立一個跟匿名類型同名的對象。上面的例子中,修改main方法以下,咱們能很清楚得看出這一點:

func main() {
//    c := Citizen{}
    c.Name = "Steve"
    c.Country = "America"
    c.Talk()         // <- Notice both are accessible
    c.Person.Talk()  // <- Notice both are accessible
    c.Nationality()
}

Output

Hello, my name is Steve and I'm from America
Hi, my name is Steve
Steve is a citizen of America

  1. 真正的多態,派生對象就是父對象
    若是匿名對象能實現多態,則外層對象應該等同於嵌入的對象,而實際上並不是如此,它們仍然是不一樣的存在。下面的例子印證了這一點:
package main

type A struct{
}

type B struct {
    A  //B is-a A
}

func save(A) {
    //do something
}

func main() {
    b := B
    save(&b);  //OOOPS! b IS NOT A
}

Output:

prog.go:17: cannot use b (type *B) as type A in function argument
[process exited with non-zero status]

go中的真正的多態實現

Go interfaces are pretty unique in how they work. This section focuses only on how they pertain to subtyping which will not do them proper justice. See the further reading section at the end of the post to learn more.

正如咱們上面提到的,多態是一種is-a的關係。在go語言中,每種類型(type)都是不一樣的,一種類型不能徹底等同於另一種類型,但它們能夠綁定到同一個接口(interface)上。接口能用於函數(方法)的輸入輸出中,於是能夠在類型之間創建起is-a的關係。

go語言定義一個接口並非使用using關鍵字,而是經過在對象上定義方法來實現。在Effective Go中指出,這種關係就像「若是某個東西能作這件事,那麼就把它應用到這裏」(無論黑貓白貓,只要能抓到老鼠,我就養這隻貓)。這一點很重要,由於這容許一個定義在package外的類型也能實現該接口。

咱們接着上面的例子,增長一個新函數SpeakTo,而後修改main函數,將該方法應用到Citizen和Person上,http://play.golang.org/p/lvEjaMQ25D

func SpeakTo(p *Person) {
    p.Talk()
}

func main() {
    p := Person{Name: "Dave"}
    c := Citizen{Person: Person{Name: "Steve"}, Country: "America"}

    SpeakTo(&p)
    SpeakTo(&c)
}

Output

Running it will result in
prog.go:48: cannot use c (type *Citizen) as type *Person in function argument
[process exited with non-zero status]

跟預期的結果同樣,編譯失敗。Citizen並非Person類型,儘管他們擁有一樣的屬性。然而咱們定義一個接口(interface)Human,而後將這個接口做爲SpeakTo函數的輸入參數,上面的例子就能夠正常運行了,http://play.golang.org/p/ifcP2mAOnf

type Human interface {
    Talk()
}

func SpeakTo(h Human) {
    h.Talk()
}

func main() {
    p := Person{Name: "Dave"}
    c := Citizen{Person: Person{Name: "Steve"}, Country: "America"}

    SpeakTo(&p)
    SpeakTo(&c)
}

Output

Hi, my name is Dave
Hi, my name is Steve

關於go語言中的多態,有以下兩點須要注意。
1. 能夠把匿名域綁定到一個接口,也能綁定到多個接口。接口和匿名域一塊兒使用,能夠起到和多態一樣的效果。
2. go提供了多態的能力。接口的使用能使得實現了該接口的不一樣對象都能做爲函數的輸入參數,甚至做爲返回結果,但它們仍然保持了它們本身的類型。這點從上面的例子能看出來,咱們不能直接在初始化Citizen對象的時候設置Name值,由於Name不是Citizen的屬性,而是Person的屬性,於是不能再初始化Citizen的時候設置Name值。

go,一個沒有object和inheritance的面向對象的語言

如上所述,面向對象的基本概念在go中被很好的實現了,雖然術語上存在差異。go把struct做爲數據和邏輯的結合。經過組合(composition),has-a關係來最小化代碼重用,而且避免了繼承的缺陷。go使用接口(interface)來創建類型(type)之間的is-a關係。

歡迎進入無對象的OO編程模型世界!

討論

Join the discussion on hacker news and Reddit - Golang

深刻閱讀

http://nathany.com/good/
http://www.artima.com/lejava/articles/designprinciples.html
http://www.goinggo.net/2014/05/methods-interfaces-and-embedded-types.html

相關文章
相關標籤/搜索