Java 面向對象核心特徵:封裝、繼承、多態,golang雖然可以通過各種方式達到類似的功能,但 golang 算是面向對象的語言嗎


這個咱就看go官網的說辭就好了,他們自己說是Yes and No。明顯go是允許OO的編程風格的,但又缺乏一些Java和C++中的常見類型繼承結構。Go的interface也和Java中的用法大相徑庭, 這也是我經常吹捧的隱式繼承。Go自己覺得這一套挺好的,更加的容易使用且通用性更強。很多時候我們用OO的思想來組織我們的項目,但需要注意的是java的繼承關係是一種非常強的耦合,有時候會給以後的升級帶來麻煩(這個可以看為什麼java8的升級帶來了interface的default method)。我覺得隱式繼承在超大型的monorepo項目中是非常有幫助的,當然小型的項目可能好處不是很明顯。

https://golang.org/doc/faq#Is_Go_an_object-oriented_language

Is Go an object-oriented language?

Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. The concept of 「interface」 in Go provides a different approach that we believe is easy to use and in some ways more general. There are also ways to embed types in other types to provide something analogous—but not identical—to subclassing. Moreover, methods in Go are more general than in C++ or Java: they can be defined for any sort of data, even built-in types such as plain, 「unboxed」 integers. They are not restricted to structs (classes).

Also, the lack of a type hierarchy makes 「objects」 in Go feel much more lightweight than in languages such as C++ or Java.

如果題主是從java過來,那麼完全照搬java的oo設計思想在go中會比較痛苦,你可能要大量的使用一些挺彆扭的方式來實現自己的應用。這個很多答主都給出了例子,是好是壞大家可以自己看。怎麼用好Go的OO特性其實是個挑戰,剛開始大家誰都不適應,有這一系列文章我早先讀過,我覺得對初學者很有幫助。

Methods, Interfaces and Embedded Types in Go?

www.ardanlabs.com圖標Structs Instead of Classes - Object Oriented Programming in Golang?

golangbot.com圖標

要想對Golang是否是面向對象語言做一個結論性的答案。我們首先說一下什麼是「面向對象語言」。如下是某百科對面向對象的描述。

面向對象語言是一類以對象作為基本程序結構單位的程序設計語言,指用於描述的設計是以對象為核心,而對象是程序設計時刻的基本成分。語言中提供了類、繼承等成分。

面向對象語言刻畫客觀系統較為自然,便於軟體擴充與復用。有四個主要特點:

  • 識認性:系統中的基本構件可識認為一組可識別的離散對象。
  • 類別性:系統具有相同數據結構與行為的所有對象可組成一類。
  • 多態性:對象具有唯一的靜態類型和多個可能的動態類型。
  • 繼承性:在基本層次關係的不同類中共享數據和操作。

其中,前三者為基礎,繼承是特色。四者結合使用,體現出面向對象語言的表達能力。

我們按照某百科的這個定義來對golang語言進行對號入座一下。我們可以發現,對於前三者,Golang語言中都是有體現的:結構體的封裝是識認和類別性;介面的使用是多態性的體現;在體現面向對象語言特色的繼承性這一點上,Golang明確不支持繼承,但是另一方面支持匿名欄位,這應該體現出組合的特點。

所以,如果嚴格就說一個結論,那Golang就不是一個面向對象語言。最後,在golang官網的回答中也有類似的描述:Yes and no。其實就是在說具備一些,但還不是,這是我的理解。

其實啊,沒有那麼死板和條條框框,掌握golang的優點和用法,用golang搬出好磚纔是最大的收穫。

———————————————於2019.4.28更————————————————

收到很多熱心知友的私信,表示對此問題還有很大的費解,那麼我們今天就再具體分析一波,希望對大家有所幫助!

首先直接上我的結論:

嚴格來講,Go不是OOP的語言,但是又允許有OOP的編程風格。

一、首先看看官網的說法:

Is Go an object oriented language?

Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. The concept of 「interface」 in Go provides a different approach that we believe is easy to use and in some ways more general. There are also ways to embed types in other types to provide something analogous?—?but not identical?—?to subclassing. Moreover, methods in Go are more general than in C++ or Java: they can be defined for any sort of data, even built-in types such as plain, 「unboxed」 integers. They are not restricted to structs (classes).

Also, the lack of a type hierarchy makes 「objects」 in Go feel much more lightweight than in languages such as C++ or Java.

翻譯過來就是:

Go是面向對象的語言嗎?

是也不是。儘管Go具有類型和方法,並且允許面向對象的編程風格,但是沒有類型層次結構。Go中「介面」的概念提供了一種我們認為易於使用且在某些方面更為通用的不同方法。還有一些方法可以在其他類型中嵌入類型,以便為子類化提供類似但不相同的東西。此外,Go中的方法比C ++或Java更通用:可以為任何類型的數據定義它們,甚至是內置類型,例如普通的「未裝箱」整數。它們不限於結構(類)。

此外,缺少類型層次結構使得Go中的「對象」比C ++或Java等語言更輕量級。

二、下面具體說說我的回答:

要真正理解「面向對象」意味著什麼,你需要回顧這個概念的起源。第一個面向對象的語言simula出現在20世紀60年代。它引入了對象,類,繼承和子類,虛擬方法,協同程序等等。也許最重要的是,它將數據和邏輯思維的範式轉換為完全獨立。

雖然你很多人不熟悉Simula,但你無疑熟悉那些將其稱為靈感的語言,包括Java,C ++,C#和Smalltalk,而這些語言又是Objective C,Python,Ruby,Javascript,Scala的靈感來源。 ,PHP,Perl ...今天使用的幾乎所有流行語言的名單。思維的這種轉變已經取代了,以至於今天活著的大多數程序員從未以任何其他方式編寫代碼。

由於不存在標準定義,為了我們的討論,我們將提供一個。

面向對象系統不是將程序結構化為代碼和數據,而是使用「對象」的概念將兩者集成在一起。對象是具有狀態(數據)和行為(代碼)的抽象數據類型。

也許作為具有繼承和多態性的初始實現的結果,幾乎所有衍生物也被採用的特徵,面向對象編程的定義通常還包括那些特徵作為要求。

我們將看看Go如何處理對象,多態和繼承,並允許你自己做出結論。

2.1 Go中的對象

Go沒有一個叫做「對象」的東西,但「對象」只是一個含義的詞。這意味著重要,而不是術語本身。

雖然Go沒有一個名為object的類型,但它的類型與匹配代碼和行為的數據結構的相同定義相匹配。在Go中,這被稱為struct。

struct是一種包含命名欄位和方法的類型。

讓我們用一個例子來說明這一點:

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有兩個欄位,兩個欄位都是int類型。

下一塊是定義綁定到此結構的方法。這是通過定義一個函數並將其綁定到rect來實現的。從技術上講,在我們的示例中,它實際上附加到指向rect的指針。雖然該方法綁定到類型,但Go要求我們使用該類型的值來進行調用,即使該值是該類型的零值(在結構的情況下,零值為nil)。

最後一塊是我們的主要功能。第一行創建一個rect類型的值。我們可以使用其他語法來做到這一點,但這是最慣用的方式。第二行在輸出中列印調用rectr上的區域函數的結果。

對我而言,這非常像一個物體。我能夠創建結構化數據類型,然後定義與特定數據交互的方法。

我們還沒做什麼?在大多數面向對象語言中,我們將使用class關鍵字來定義對象。使用繼承時,為這些類定義介面是一種很好的做法。在這樣做時,我們將定義一個繼承層次結構樹(在單繼承的情況下)。

值得注意的另一件事是,在Go中,任何命名類型都可以有方法,而不僅僅是結構。例如,我可以定義一個新類型Counter,它是int類型並在其上定義方法。

2.2 繼承和多態

有幾種不同的方法來定義對象之間的關係。雖然它們彼此之間存在很大差異,但它們作為代碼重用機制共同具有共同目的。

  • 繼承
  • 多重繼承
  • 子類
  • 對象組成

單一和多重繼承

繼承是指對象基於另一個對象,使用相同的實現。存在兩種不同的繼承實現。它們之間的根本區別在於對象是可以從單個對象還是從多個對象繼承。這是一個看似很小的區別,但具有很大的影響。單繼承中的層次結構是樹,而在多重繼承中它是一個點。單繼承語言包括PHP,C#,Java和Ruby。多種繼承語言包括Perl,Python和C ++。

子類型(多態)

在某些語言中,子類型和繼承是如此交織在一起,如果你的特定視角來自於它們緊密耦合的語言,那麼這對前一部分來說似乎是多餘的。子類型建立了一個is-a關係,而繼承只重用了實現。子類型定義了兩個(或更多)對象之間的語義關係。繼承只定義語法關係。

對象組合

對象組合是通過包含其他對象來定義一個對象的位置。該對象包含它們,而不是從它們繼承。與is-a子類型的關係不同,對象組合定義了has-a關係。

Go中的繼承

Go是有意設計的,沒有任何繼承。這並不意味著對象(結構值)沒有關係,而是Go作者選擇使用替代機制來表示關係。許多人第一次遇到Go這個決定可能看起來好像讓Go癱瘓了。實際上它是Go最好的屬性之一,它解決了十年前的遺留問題和爭論。

最好遺漏繼承

以下內容真正推動了這一點。它來自一篇JavaWorld文章,標題為什麼延伸是邪惡的 :

Gang of Four Design Patterns一書詳細討論了使用介面繼承(implements)替換實現繼承(擴展)。

在一次Java用戶組會議,其中James Gosling(Java的發明者)是特色演講者。在令人難忘的問答環節中,有人問他:「如果你能再次做Java,你會改變什麼?」「我會把課程留下來,」他回答道。在笑聲消失之後,他解釋說真正的問題不是類本身,而是實現繼承(擴展關係)。介面繼承(implements關係)是首選。您應該儘可能避免實現繼承。

2.3 Go中的多態性與構成

Go不是繼承,而是嚴格遵循繼承原則的構成。Go通過結構和介面之間的子類型(is-a)和對象組合(has-a)關係來實現這一點。

Go中的對象組合

Go用於實現對象組合原理的機制稱為嵌入式。Go允許您在結構中嵌入結構,為它們提供has-a關係。

一個很好的例子就是Person和Address之間的關係。

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()
}

輸出

&> Hi, my name is Steve
&> I』m at 13 Main Gotham NY 01313

從這個例子中要實現的重要事情是,Address仍然是一個獨特的實體,同時存在於Person中。在main函數中,我們演示了您可以將p.Address欄位設置為一個地址,或者只需通過點表示法訪問它們來設置欄位。

Go中的偽子類型

偽is-a關係以類似且直觀的方式工作。擴展上面的例子。我們使用以下語句。一個人可以說話。公民是一個人,因此公民可以說話。

此代碼依賴於並添加到上面示例中的代碼中。

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()
}

輸出

&> Hi, my name is Steve
&> Steve is a citizen of America

我們使用所謂的匿名欄位完成這個偽is-a關係。在我們的示例中,Person是Citizen的匿名欄位。僅給出類型,而不是欄位名稱。它假定了Person的所有屬性和方法,並且可以自由使用它們或者自己推廣它們。

2.4 推廣匿名欄位的方法

這方面的一個例子就是公民的談話就像人們一樣,但是以不同的方式。

為此,我們只需定義Talk for * Citizen,並運行與上面定義相同的主要功能。現在不是調用* Person.Talk(),而是調用* Citizen.Talk()。

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

輸出:

&> Hello, my name is Steve and Im from America
&> Steve is a citizen of America

為什麼匿名欄位不正確的子類型

有兩個不同的原因,為什麼這不能被認為是正確的子類型。

1.匿名欄位仍然可以像嵌入一樣訪問。

這不一定是壞事。多重繼承的問題之一是語言通常不明顯,有時甚至不明確在多個父類上存在相同方法時使用哪些方法。

使用Go,始終可以通過與該類型同名的屬性訪問各個方法。

實際上,當使用匿名欄位時,Go正在創建一個與您的類型同名的訪問者。

在上面的示例中,以下代碼可以使用:

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

輸出:

&> Hello, my name is Steve and Im from America
&> Hi, my name is Steve
&> Steve is a citizen of America

2.真正的子類型成為父級

如果這是真正的子類型,那麼匿名欄位將導致外部類型成為內部類型。在Go中,情況並非如此。這兩種類型仍然不同。以下示例說明瞭這一點:

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
}

輸出:

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

Go中的真正子類型

Go介面在工作方式上非常獨特。

如上所述,子類型是一種關係。在Go中,每種類型都是不同的,沒有任何東西可以作為另一種類型,但兩者都可以遵循相同的界面。介面可以用作函數(和方法)的輸入和輸出,從而在類型之間建立is-a關係。

Go中介面的遵守不是通過像using這樣的關鍵字來定義的,而是通過在類型上聲明的實際方法來定義的。在Efficient Go中它將這種關係稱為「如果有什麼東西可以做到這一點,那麼就可以在這裡使用它」。這非常重要,因為它可以創建一個外部包中定義的類型可以遵循的介面。

繼續上面的例子,我們添加一個新函數SpeakTo並修改main函數以嘗試SpeakTo a Citizen和Person。

func SpeakTo(p *Person) {
p.Talk()
}
?
func main() {
p := Person{Name: "Dave"}
c := Citizen{Person: Person{Name: "Steve"}, Country: "America"}
?
SpeakTo(p)
SpeakTo(c)
}

輸出:

&> 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,即使它們共享許多相同的屬性,它們也被視為不同的類型。

但是,如果我們添加一個名為Human的介面並將其用作SpeakTo函數的輸入,它將全部按預期工作。

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)
}

輸出:

&> Hi, my name is Dave
&> Hi, my name is Steve

Go中的子類型有兩個關鍵點:

  1. 我們可以使用匿名欄位來遵守介面。我們也可以堅持很多介面。通過使用匿名欄位和介面,我們非常接近真正的子類型。
  2. Go確實提供了正確的子類型功能,但僅限於使用類型。介面可用於確保各種不同類型都可以被接受為函數的輸入,或甚至作為函數的返回值,但實際上它們保留了它們的不同類型。這顯然顯示在主函數中,我們無法直接在Citizen上設置Name,因為Name實際上不是Citizen的屬性,它是Person的屬性,因此在Citizen初始化期間尚未出現。

Go,面向對象的編程,沒有對象或繼承

正如我們上面的代碼所演示,面向對象的基本概念在Go中仍然存在,儘管存在一些術語差異。術語差異是必不可少的,因為所使用的機制實際上與大多數面向對象的語言不同。

Go利用結構作為數據和邏輯的結合。通過組合,可以在Structs之間建立關係,以最大限度地減少代碼重複,同時避免遺漏的脆弱混亂。Go使用介面來建立類型之間的關係,而沒有不必要的和反作用的聲明。

歡迎使用新的「無對象」OO編程模型。


如果要嚴格按照面向對象特性來說,go不是。

之前寫過一個demo,希望能對你有用。

type person struct {
name string
age int

}

//如果一個struct嵌套了另一個匿名結構體,就可以直接訪問匿名結構體的欄位或方法,從而實現繼承
type student struct {
person //匿名欄位,struct
mobile string
}

//如果一個struct嵌套了另一個【有名】的結構體,叫做組合
type teacher struct {
p person //有名欄位,struct
mobile string
}

func (p *person) run(){
fmt.Println(p.name, " run")
}

func (p *person) reading(){
fmt.Println(p.name, " reading")
}

func (s *student) reading(){
fmt.Println(s.name, " reading")
}

func main(){
p := person{"zhangsan", 22}
s := student{person{"lisi", 20}, "000"}
t := teacher{person{"wangwu", 25}, "000"}

fmt.Println(s.name) //訪問【匿名】結構體的欄位
fmt.Println(t.p.name) //訪問【有名】結構體的欄位。不是繼承,需要指定結構體
p.run()
s.run()
}


封裝:首字母大小寫為區分,大寫的暴露出來,小寫的只在package內部用。

繼承:一個struct可以包含另外一個struct,同時擁有了它的methods以及屬性。

多態:介面的使用,注意不是struct,struct必須是相等的。

結論:有面向對象的意思,但又不全是。


面向對象是一種編程思想,我不認為有固定的實現方式,某答主以go沒有class關鍵字,沒有把main函數放到class中來斷定go不是面向對象語言的觀點真是令人捧腹。


推薦閱讀:
相關文章